mirror of https://github.com/halo-dev/halo-admin
perf: auto focus when opening editing forms
parent
daaf5bcf7a
commit
ce93d9813b
|
@ -0,0 +1,9 @@
|
||||||
|
export function setFocus(id: string) {
|
||||||
|
const inputElement = document.getElementById(id);
|
||||||
|
if (inputElement instanceof HTMLInputElement) {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
inputElement?.focus();
|
||||||
|
clearTimeout(timer);
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ import cloneDeep from "lodash.clonedeep";
|
||||||
import { apiClient } from "@halo-dev/admin-shared";
|
import { apiClient } from "@halo-dev/admin-shared";
|
||||||
import { reset, submitForm } from "@formkit/core";
|
import { reset, submitForm } from "@formkit/core";
|
||||||
import { useMagicKeys } from "@vueuse/core";
|
import { useMagicKeys } from "@vueuse/core";
|
||||||
|
import { setFocus } from "@/formkit/utils/focus";
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
@ -95,7 +96,9 @@ watchEffect(() => {
|
||||||
watch(
|
watch(
|
||||||
() => props.visible,
|
() => props.visible,
|
||||||
(visible) => {
|
(visible) => {
|
||||||
if (!visible) {
|
if (visible) {
|
||||||
|
setFocus("displayNameInput");
|
||||||
|
} else {
|
||||||
handleResetForm();
|
handleResetForm();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -119,8 +122,14 @@ watch(
|
||||||
:width="500"
|
:width="500"
|
||||||
@update:visible="onVisibleChange"
|
@update:visible="onVisibleChange"
|
||||||
>
|
>
|
||||||
<FormKit id="attachment-group-form" type="form" @submit="handleSave">
|
<FormKit
|
||||||
|
id="attachment-group-form"
|
||||||
|
:config="{ validationVisibility: 'submit' }"
|
||||||
|
type="form"
|
||||||
|
@submit="handleSave"
|
||||||
|
>
|
||||||
<FormKit
|
<FormKit
|
||||||
|
id="displayNameInput"
|
||||||
v-model="formState.spec.displayName"
|
v-model="formState.spec.displayName"
|
||||||
label="名称"
|
label="名称"
|
||||||
type="text"
|
type="text"
|
||||||
|
|
|
@ -133,7 +133,7 @@ onMounted(async () => {
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<FloatingDropdown v-if="!readonly">
|
<FloatingDropdown v-if="!readonly">
|
||||||
<IconMore />
|
<IconMore @click.stop />
|
||||||
<template #popper>
|
<template #popper>
|
||||||
<div class="w-48 p-2">
|
<div class="w-48 p-2">
|
||||||
<VSpace class="w-full" direction="column">
|
<VSpace class="w-full" direction="column">
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { apiClient, useSettingForm } from "@halo-dev/admin-shared";
|
||||||
import { v4 as uuid } from "uuid";
|
import { v4 as uuid } from "uuid";
|
||||||
import { reset, submitForm } from "@formkit/core";
|
import { reset, submitForm } from "@formkit/core";
|
||||||
import { useMagicKeys } from "@vueuse/core";
|
import { useMagicKeys } from "@vueuse/core";
|
||||||
|
import { setFocus } from "@/formkit/utils/focus";
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
@ -135,11 +136,14 @@ watchEffect(() => {
|
||||||
watch(
|
watch(
|
||||||
() => props.visible,
|
() => props.visible,
|
||||||
(visible) => {
|
(visible) => {
|
||||||
if (!visible) {
|
if (visible) {
|
||||||
setTimeout(() => {
|
setFocus("displayNameInput");
|
||||||
|
} else {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
policyTemplate.value = undefined;
|
policyTemplate.value = undefined;
|
||||||
handleResetForm();
|
handleResetForm();
|
||||||
handleResetSettingForm();
|
handleResetSettingForm();
|
||||||
|
clearTimeout(timer);
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -192,9 +196,11 @@ const onVisibleChange = (visible: boolean) => {
|
||||||
:actions="false"
|
:actions="false"
|
||||||
:preserve="true"
|
:preserve="true"
|
||||||
type="form"
|
type="form"
|
||||||
|
:config="{ validationVisibility: 'submit' }"
|
||||||
@submit="handleSave"
|
@submit="handleSave"
|
||||||
>
|
>
|
||||||
<FormKit
|
<FormKit
|
||||||
|
id="displayNameInput"
|
||||||
v-model="formState.spec.displayName"
|
v-model="formState.spec.displayName"
|
||||||
label="名称"
|
label="名称"
|
||||||
type="text"
|
type="text"
|
||||||
|
|
|
@ -12,6 +12,7 @@ import type { Category } from "@halo-dev/api-client";
|
||||||
// libs
|
// libs
|
||||||
import cloneDeep from "lodash.clonedeep";
|
import cloneDeep from "lodash.clonedeep";
|
||||||
import { reset, submitForm } from "@formkit/core";
|
import { reset, submitForm } from "@formkit/core";
|
||||||
|
import { setFocus } from "@/formkit/utils/focus";
|
||||||
import { useMagicKeys } from "@vueuse/core";
|
import { useMagicKeys } from "@vueuse/core";
|
||||||
import { v4 as uuid } from "uuid";
|
import { v4 as uuid } from "uuid";
|
||||||
|
|
||||||
|
@ -105,7 +106,9 @@ watchEffect(() => {
|
||||||
watch(
|
watch(
|
||||||
() => props.visible,
|
() => props.visible,
|
||||||
(visible) => {
|
(visible) => {
|
||||||
if (!visible) {
|
if (visible) {
|
||||||
|
setFocus("displayNameInput");
|
||||||
|
} else {
|
||||||
handleResetForm();
|
handleResetForm();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -129,8 +132,14 @@ watch(
|
||||||
:width="600"
|
:width="600"
|
||||||
@update:visible="onVisibleChange"
|
@update:visible="onVisibleChange"
|
||||||
>
|
>
|
||||||
<FormKit id="category-form" type="form" @submit="handleSaveCategory">
|
<FormKit
|
||||||
|
id="category-form"
|
||||||
|
type="form"
|
||||||
|
:config="{ validationVisibility: 'submit' }"
|
||||||
|
@submit="handleSaveCategory"
|
||||||
|
>
|
||||||
<FormKit
|
<FormKit
|
||||||
|
id="displayNameInput"
|
||||||
v-model="formState.spec.displayName"
|
v-model="formState.spec.displayName"
|
||||||
label="名称"
|
label="名称"
|
||||||
type="text"
|
type="text"
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { useRouter } from "vue-router";
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
tag: Tag;
|
tag: Tag;
|
||||||
route: boolean;
|
route?: boolean;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
route: false,
|
route: false,
|
||||||
|
|
|
@ -18,6 +18,7 @@ import type { Tag } from "@halo-dev/api-client";
|
||||||
// libs
|
// libs
|
||||||
import cloneDeep from "lodash.clonedeep";
|
import cloneDeep from "lodash.clonedeep";
|
||||||
import { reset, submitForm } from "@formkit/core";
|
import { reset, submitForm } from "@formkit/core";
|
||||||
|
import { setFocus } from "@/formkit/utils/focus";
|
||||||
import { useMagicKeys } from "@vueuse/core";
|
import { useMagicKeys } from "@vueuse/core";
|
||||||
import { v4 as uuid } from "uuid";
|
import { v4 as uuid } from "uuid";
|
||||||
|
|
||||||
|
@ -109,7 +110,9 @@ watchEffect(() => {
|
||||||
watch(
|
watch(
|
||||||
() => props.visible,
|
() => props.visible,
|
||||||
(visible) => {
|
(visible) => {
|
||||||
if (!visible) {
|
if (visible) {
|
||||||
|
setFocus("displayNameInput");
|
||||||
|
} else {
|
||||||
handleResetForm();
|
handleResetForm();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -141,8 +144,14 @@ watch(
|
||||||
<IconArrowRight />
|
<IconArrowRight />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<FormKit id="tag-form" type="form" @submit="handleSaveTag">
|
<FormKit
|
||||||
|
id="tag-form"
|
||||||
|
:config="{ validationVisibility: 'submit' }"
|
||||||
|
type="form"
|
||||||
|
@submit="handleSaveTag"
|
||||||
|
>
|
||||||
<FormKit
|
<FormKit
|
||||||
|
id="displayNameInput"
|
||||||
v-model="formState.spec.displayName"
|
v-model="formState.spec.displayName"
|
||||||
label="名称"
|
label="名称"
|
||||||
type="text"
|
type="text"
|
||||||
|
|
|
@ -2,11 +2,12 @@
|
||||||
import { VButton, VModal, VSpace } from "@halo-dev/components";
|
import { VButton, VModal, VSpace } from "@halo-dev/components";
|
||||||
import type { Menu } from "@halo-dev/api-client";
|
import type { Menu } from "@halo-dev/api-client";
|
||||||
import { v4 as uuid } from "uuid";
|
import { v4 as uuid } from "uuid";
|
||||||
import { computed, ref, watch } from "vue";
|
import { computed, ref, watch, watchEffect } from "vue";
|
||||||
import { apiClient } from "@halo-dev/admin-shared";
|
import { apiClient } from "@halo-dev/admin-shared";
|
||||||
import { reset, submitForm } from "@formkit/core";
|
import { reset, submitForm } from "@formkit/core";
|
||||||
import cloneDeep from "lodash.clonedeep";
|
import cloneDeep from "lodash.clonedeep";
|
||||||
import { useMagicKeys } from "@vueuse/core";
|
import { useMagicKeys } from "@vueuse/core";
|
||||||
|
import { setFocus } from "@/formkit/utils/focus";
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
@ -73,27 +74,41 @@ const onVisibleChange = (visible: boolean) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
watch(props, (newVal) => {
|
const handleResetForm = () => {
|
||||||
const { Command_Enter } = useMagicKeys();
|
|
||||||
let keyboardWatcher;
|
|
||||||
if (newVal.visible) {
|
|
||||||
keyboardWatcher = watch(Command_Enter, (v) => {
|
|
||||||
if (v) {
|
|
||||||
submitForm("menu-form");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
keyboardWatcher?.unwatch();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newVal.visible && props.menu) {
|
|
||||||
formState.value = cloneDeep(props.menu);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
formState.value = cloneDeep(initialFormState);
|
formState.value = cloneDeep(initialFormState);
|
||||||
formState.value.metadata.name = uuid();
|
formState.value.metadata.name = uuid();
|
||||||
reset("menu-form");
|
reset("menu-form");
|
||||||
|
};
|
||||||
|
|
||||||
|
const { Command_Enter } = useMagicKeys();
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
if (Command_Enter.value && props.visible) {
|
||||||
|
submitForm("menu-form");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.visible,
|
||||||
|
(visible) => {
|
||||||
|
if (visible) {
|
||||||
|
setFocus("displayNameInput");
|
||||||
|
} else {
|
||||||
|
handleResetForm();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.menu,
|
||||||
|
(menu) => {
|
||||||
|
if (menu) {
|
||||||
|
formState.value = cloneDeep(menu);
|
||||||
|
} else {
|
||||||
|
handleResetForm();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<VModal
|
<VModal
|
||||||
|
@ -106,9 +121,11 @@ watch(props, (newVal) => {
|
||||||
id="menu-form"
|
id="menu-form"
|
||||||
:classes="{ form: 'w-full' }"
|
:classes="{ form: 'w-full' }"
|
||||||
type="form"
|
type="form"
|
||||||
|
:config="{ validationVisibility: 'submit' }"
|
||||||
@submit="handleCreateMenu"
|
@submit="handleCreateMenu"
|
||||||
>
|
>
|
||||||
<FormKit
|
<FormKit
|
||||||
|
id="displayNameInput"
|
||||||
v-model="formState.spec.displayName"
|
v-model="formState.spec.displayName"
|
||||||
help="可根据此名称查询菜单项"
|
help="可根据此名称查询菜单项"
|
||||||
label="菜单名称"
|
label="菜单名称"
|
||||||
|
|
|
@ -9,6 +9,7 @@ import cloneDeep from "lodash.clonedeep";
|
||||||
import { useMagicKeys } from "@vueuse/core";
|
import { useMagicKeys } from "@vueuse/core";
|
||||||
import { usePostCategory } from "@/modules/contents/posts/categories/composables/use-post-category";
|
import { usePostCategory } from "@/modules/contents/posts/categories/composables/use-post-category";
|
||||||
import { usePostTag } from "@/modules/contents/posts/tags/composables/use-post-tag";
|
import { usePostTag } from "@/modules/contents/posts/tags/composables/use-post-tag";
|
||||||
|
import { setFocus } from "@/formkit/utils/focus";
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
@ -114,7 +115,9 @@ watchEffect(() => {
|
||||||
watch(
|
watch(
|
||||||
() => props.visible,
|
() => props.visible,
|
||||||
(visible) => {
|
(visible) => {
|
||||||
if (!visible) {
|
if (visible) {
|
||||||
|
setFocus("displayNameInput");
|
||||||
|
} else {
|
||||||
handleResetForm();
|
handleResetForm();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -301,7 +304,12 @@ watch(
|
||||||
title="编辑菜单项"
|
title="编辑菜单项"
|
||||||
@update:visible="onVisibleChange"
|
@update:visible="onVisibleChange"
|
||||||
>
|
>
|
||||||
<FormKit id="menuitem-form" type="form" @submit="handleSaveMenuItem">
|
<FormKit
|
||||||
|
id="menuitem-form"
|
||||||
|
type="form"
|
||||||
|
:config="{ validationVisibility: 'submit' }"
|
||||||
|
@submit="handleSaveMenuItem"
|
||||||
|
>
|
||||||
<FormKit
|
<FormKit
|
||||||
v-model="selectedMenuItemSource"
|
v-model="selectedMenuItemSource"
|
||||||
:options="menuItemSources"
|
:options="menuItemSources"
|
||||||
|
@ -314,6 +322,7 @@ watch(
|
||||||
|
|
||||||
<FormKit
|
<FormKit
|
||||||
v-if="selectedMenuItemSource === 'custom'"
|
v-if="selectedMenuItemSource === 'custom'"
|
||||||
|
id="displayNameInput"
|
||||||
v-model="formState.spec.displayName"
|
v-model="formState.spec.displayName"
|
||||||
label="名称"
|
label="名称"
|
||||||
type="text"
|
type="text"
|
||||||
|
|
|
@ -11,6 +11,7 @@ import cloneDeep from "lodash.clonedeep";
|
||||||
import { reset, submitForm } from "@formkit/core";
|
import { reset, submitForm } from "@formkit/core";
|
||||||
import { useMagicKeys } from "@vueuse/core";
|
import { useMagicKeys } from "@vueuse/core";
|
||||||
import { v4 as uuid } from "uuid";
|
import { v4 as uuid } from "uuid";
|
||||||
|
import { setFocus } from "@/formkit/utils/focus";
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
@ -60,7 +61,9 @@ watchEffect(() => {
|
||||||
watch(
|
watch(
|
||||||
() => props.visible,
|
() => props.visible,
|
||||||
(visible) => {
|
(visible) => {
|
||||||
if (!visible) {
|
if (visible) {
|
||||||
|
setFocus("displayNameInput");
|
||||||
|
} else {
|
||||||
handleResetForm();
|
handleResetForm();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -124,9 +127,11 @@ const handleResetForm = () => {
|
||||||
id="role-form"
|
id="role-form"
|
||||||
:actions="false"
|
:actions="false"
|
||||||
type="form"
|
type="form"
|
||||||
|
:config="{ validationVisibility: 'submit' }"
|
||||||
@submit="handleCreateOrUpdateRole"
|
@submit="handleCreateOrUpdateRole"
|
||||||
>
|
>
|
||||||
<FormKit
|
<FormKit
|
||||||
|
id="displayNameInput"
|
||||||
v-model="
|
v-model="
|
||||||
formState.metadata.annotations[rbacAnnotations.DISPLAY_NAME]
|
formState.metadata.annotations[rbacAnnotations.DISPLAY_NAME]
|
||||||
"
|
"
|
||||||
|
@ -139,6 +144,7 @@ const handleResetForm = () => {
|
||||||
help="角色别名,用于区分角色,不能重复,创建之后不能修改"
|
help="角色别名,用于区分角色,不能重复,创建之后不能修改"
|
||||||
label="别名"
|
label="别名"
|
||||||
type="text"
|
type="text"
|
||||||
|
:disabled="isUpdateMode"
|
||||||
validation="required"
|
validation="required"
|
||||||
></FormKit>
|
></FormKit>
|
||||||
</FormKit>
|
</FormKit>
|
||||||
|
|
|
@ -10,6 +10,7 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
path: "/settings",
|
path: "/settings",
|
||||||
component: SystemSettingsLayout,
|
component: SystemSettingsLayout,
|
||||||
|
redirect: "/settings/basic",
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: ":group",
|
path: ":group",
|
||||||
|
@ -25,7 +26,7 @@ export default definePlugin({
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
name: "设置",
|
name: "设置",
|
||||||
path: "/settings/basic",
|
path: "/settings",
|
||||||
icon: IconSettings,
|
icon: IconSettings,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -102,14 +102,12 @@ onMounted(() => {
|
||||||
<template>
|
<template>
|
||||||
<UserEditingModal
|
<UserEditingModal
|
||||||
v-model:visible="editingModal"
|
v-model:visible="editingModal"
|
||||||
v-permission="['system:users:manage']"
|
|
||||||
:user="selectedUser"
|
:user="selectedUser"
|
||||||
@close="onEditingModalClose"
|
@close="onEditingModalClose"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<UserPasswordChangeModal
|
<UserPasswordChangeModal
|
||||||
v-model:visible="passwordChangeModal"
|
v-model:visible="passwordChangeModal"
|
||||||
v-permission="['system:users:manage']"
|
|
||||||
:user="selectedUser"
|
:user="selectedUser"
|
||||||
@close="handleFetchUsers"
|
@close="handleFetchUsers"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -15,7 +15,6 @@ import {
|
||||||
} from "@halo-dev/components";
|
} from "@halo-dev/components";
|
||||||
|
|
||||||
// libs
|
// libs
|
||||||
import { v4 as uuid } from "uuid";
|
|
||||||
import YAML from "yaml";
|
import YAML from "yaml";
|
||||||
import cloneDeep from "lodash.clonedeep";
|
import cloneDeep from "lodash.clonedeep";
|
||||||
import { useMagicKeys } from "@vueuse/core";
|
import { useMagicKeys } from "@vueuse/core";
|
||||||
|
@ -27,6 +26,7 @@ import { rbacAnnotations } from "@/constants/annotations";
|
||||||
// hooks
|
// hooks
|
||||||
import { useFetchRole } from "@/modules/system/roles/composables/use-role";
|
import { useFetchRole } from "@/modules/system/roles/composables/use-role";
|
||||||
import type { FormKitOptionsList } from "@formkit/inputs";
|
import type { FormKitOptionsList } from "@formkit/inputs";
|
||||||
|
import { setFocus } from "@/formkit/utils/focus";
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
@ -44,41 +44,32 @@ const emit = defineEmits<{
|
||||||
(event: "close"): void;
|
(event: "close"): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
interface FormState {
|
const initialFormState: User = {
|
||||||
user: User;
|
spec: {
|
||||||
saving: boolean;
|
displayName: "",
|
||||||
rawMode: boolean;
|
avatar: "",
|
||||||
raw: string;
|
email: "",
|
||||||
}
|
phone: "",
|
||||||
|
password: "",
|
||||||
const initialFormState: FormState = {
|
bio: "",
|
||||||
user: {
|
disabled: false,
|
||||||
spec: {
|
loginHistoryLimit: 0,
|
||||||
displayName: "",
|
},
|
||||||
avatar: "",
|
apiVersion: "v1alpha1",
|
||||||
email: "",
|
kind: "User",
|
||||||
phone: "",
|
metadata: {
|
||||||
password: "",
|
name: "",
|
||||||
bio: "",
|
|
||||||
disabled: false,
|
|
||||||
loginHistoryLimit: 0,
|
|
||||||
},
|
|
||||||
apiVersion: "v1alpha1",
|
|
||||||
kind: "User",
|
|
||||||
metadata: {
|
|
||||||
name: uuid(),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
saving: false,
|
|
||||||
rawMode: false,
|
|
||||||
raw: "",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const formState = ref<FormState>(cloneDeep(initialFormState));
|
const formState = ref<User>(cloneDeep(initialFormState));
|
||||||
|
const saving = ref(false);
|
||||||
|
const rawMode = ref(false);
|
||||||
|
const raw = ref("");
|
||||||
const selectedRole = ref("");
|
const selectedRole = ref("");
|
||||||
|
|
||||||
const isUpdateMode = computed(() => {
|
const isUpdateMode = computed(() => {
|
||||||
return !!formState.value.user.metadata.creationTimestamp;
|
return !!formState.value.metadata.creationTimestamp;
|
||||||
});
|
});
|
||||||
|
|
||||||
const creationModalTitle = computed(() => {
|
const creationModalTitle = computed(() => {
|
||||||
|
@ -86,7 +77,7 @@ const creationModalTitle = computed(() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const modalWidth = computed(() => {
|
const modalWidth = computed(() => {
|
||||||
return formState.value.rawMode ? 800 : 700;
|
return rawMode.value ? 800 : 700;
|
||||||
});
|
});
|
||||||
|
|
||||||
const { roles } = useFetchRole();
|
const { roles } = useFetchRole();
|
||||||
|
@ -112,7 +103,9 @@ watchEffect(() => {
|
||||||
watch(
|
watch(
|
||||||
() => props.visible,
|
() => props.visible,
|
||||||
(visible) => {
|
(visible) => {
|
||||||
if (!visible) {
|
if (visible) {
|
||||||
|
setFocus(isUpdateMode.value ? "displayNameInput" : "userNameInput");
|
||||||
|
} else {
|
||||||
handleResetForm();
|
handleResetForm();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,7 +115,7 @@ watch(
|
||||||
() => props.user,
|
() => props.user,
|
||||||
(user) => {
|
(user) => {
|
||||||
if (user) {
|
if (user) {
|
||||||
formState.value.user = cloneDeep(user);
|
formState.value = cloneDeep(user);
|
||||||
} else {
|
} else {
|
||||||
handleResetForm();
|
handleResetForm();
|
||||||
}
|
}
|
||||||
|
@ -138,24 +131,23 @@ const onVisibleChange = (visible: boolean) => {
|
||||||
|
|
||||||
const handleResetForm = () => {
|
const handleResetForm = () => {
|
||||||
formState.value = cloneDeep(initialFormState);
|
formState.value = cloneDeep(initialFormState);
|
||||||
formState.value.user.metadata.name = uuid();
|
|
||||||
reset("user-form");
|
reset("user-form");
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCreateUser = async () => {
|
const handleCreateUser = async () => {
|
||||||
try {
|
try {
|
||||||
formState.value.saving = true;
|
saving.value = true;
|
||||||
let user: User;
|
let user: User;
|
||||||
|
|
||||||
if (isUpdateMode.value) {
|
if (isUpdateMode.value) {
|
||||||
const response = await apiClient.extension.user.updatev1alpha1User({
|
const response = await apiClient.extension.user.updatev1alpha1User({
|
||||||
name: formState.value.user.metadata.name,
|
name: formState.value.metadata.name,
|
||||||
user: formState.value.user,
|
user: formState.value,
|
||||||
});
|
});
|
||||||
user = response.data;
|
user = response.data;
|
||||||
} else {
|
} else {
|
||||||
const response = await apiClient.extension.user.createv1alpha1User({
|
const response = await apiClient.extension.user.createv1alpha1User({
|
||||||
user: formState.value.user,
|
user: formState.value,
|
||||||
});
|
});
|
||||||
user = response.data;
|
user = response.data;
|
||||||
}
|
}
|
||||||
|
@ -173,17 +165,17 @@ const handleCreateUser = async () => {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
} finally {
|
} finally {
|
||||||
formState.value.saving = false;
|
saving.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRawModeChange = () => {
|
const handleRawModeChange = () => {
|
||||||
formState.value.rawMode = !formState.value.rawMode;
|
rawMode.value = !rawMode.value;
|
||||||
|
|
||||||
if (formState.value.rawMode) {
|
if (rawMode.value) {
|
||||||
formState.value.raw = YAML.stringify(formState.value.user);
|
raw.value = YAML.stringify(formState.value);
|
||||||
} else {
|
} else {
|
||||||
formState.value.user = YAML.parse(formState.value.raw);
|
formState.value = YAML.parse(raw.value);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -196,35 +188,37 @@ const handleRawModeChange = () => {
|
||||||
>
|
>
|
||||||
<template #actions>
|
<template #actions>
|
||||||
<div class="modal-header-action" @click="handleRawModeChange">
|
<div class="modal-header-action" @click="handleRawModeChange">
|
||||||
<IconCodeBoxLine v-if="!formState.rawMode" />
|
<IconCodeBoxLine v-if="!rawMode" />
|
||||||
<IconEye v-else />
|
<IconEye v-else />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<VCodemirror
|
<VCodemirror v-show="rawMode" v-model="raw" height="50vh" language="yaml" />
|
||||||
v-show="formState.rawMode"
|
|
||||||
v-model="formState.raw"
|
|
||||||
height="50vh"
|
|
||||||
language="yaml"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div v-show="!formState.rawMode">
|
<div v-show="!rawMode">
|
||||||
<FormKit id="user-form" type="form" @submit="handleCreateUser">
|
<FormKit
|
||||||
|
id="user-form"
|
||||||
|
:config="{ validationVisibility: 'submit' }"
|
||||||
|
type="form"
|
||||||
|
@submit="handleCreateUser"
|
||||||
|
>
|
||||||
<FormKit
|
<FormKit
|
||||||
v-model="formState.user.metadata.name"
|
id="userNameInput"
|
||||||
|
v-model="formState.metadata.name"
|
||||||
:disabled="isUpdateMode"
|
:disabled="isUpdateMode"
|
||||||
label="用户名"
|
label="用户名"
|
||||||
type="text"
|
type="text"
|
||||||
validation="required"
|
validation="required"
|
||||||
></FormKit>
|
></FormKit>
|
||||||
<FormKit
|
<FormKit
|
||||||
v-model="formState.user.spec.displayName"
|
id="displayNameInput"
|
||||||
|
v-model="formState.spec.displayName"
|
||||||
label="显示名称"
|
label="显示名称"
|
||||||
type="text"
|
type="text"
|
||||||
validation="required"
|
validation="required"
|
||||||
></FormKit>
|
></FormKit>
|
||||||
<FormKit
|
<FormKit
|
||||||
v-model="formState.user.spec.email"
|
v-model="formState.spec.email"
|
||||||
label="电子邮箱"
|
label="电子邮箱"
|
||||||
type="email"
|
type="email"
|
||||||
validation="required"
|
validation="required"
|
||||||
|
@ -236,17 +230,17 @@ const handleRawModeChange = () => {
|
||||||
type="select"
|
type="select"
|
||||||
></FormKit>
|
></FormKit>
|
||||||
<FormKit
|
<FormKit
|
||||||
v-model="formState.user.spec.phone"
|
v-model="formState.spec.phone"
|
||||||
label="手机号"
|
label="手机号"
|
||||||
type="text"
|
type="text"
|
||||||
></FormKit>
|
></FormKit>
|
||||||
<FormKit
|
<FormKit
|
||||||
v-model="formState.user.spec.avatar"
|
v-model="formState.spec.avatar"
|
||||||
label="头像"
|
label="头像"
|
||||||
type="text"
|
type="text"
|
||||||
></FormKit>
|
></FormKit>
|
||||||
<FormKit
|
<FormKit
|
||||||
v-model="formState.user.spec.bio"
|
v-model="formState.spec.bio"
|
||||||
label="描述"
|
label="描述"
|
||||||
type="textarea"
|
type="textarea"
|
||||||
></FormKit>
|
></FormKit>
|
||||||
|
@ -255,7 +249,7 @@ const handleRawModeChange = () => {
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<VSpace>
|
<VSpace>
|
||||||
<VButton
|
<VButton
|
||||||
:loading="formState.saving"
|
:loading="saving"
|
||||||
type="secondary"
|
type="secondary"
|
||||||
@click="$formkit.submit('user-form')"
|
@click="$formkit.submit('user-form')"
|
||||||
>
|
>
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { IconSave, VButton, VModal } from "@halo-dev/components";
|
import { VButton, VModal, VSpace } from "@halo-dev/components";
|
||||||
import { inject, ref } from "vue";
|
import { inject, ref, watch, watchEffect } from "vue";
|
||||||
import type { User } from "@halo-dev/api-client";
|
import type { User } from "@halo-dev/api-client";
|
||||||
import { apiClient } from "@halo-dev/admin-shared";
|
import { apiClient } from "@halo-dev/admin-shared";
|
||||||
import cloneDeep from "lodash.clonedeep";
|
import cloneDeep from "lodash.clonedeep";
|
||||||
|
import { reset, submitForm } from "@formkit/core";
|
||||||
|
import { useMagicKeys } from "@vueuse/core";
|
||||||
|
import { setFocus } from "@/formkit/utils/focus";
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
@ -24,35 +27,54 @@ const emit = defineEmits<{
|
||||||
const currentUser = inject<User>("currentUser");
|
const currentUser = inject<User>("currentUser");
|
||||||
|
|
||||||
interface PasswordChangeFormState {
|
interface PasswordChangeFormState {
|
||||||
state: {
|
password: string;
|
||||||
password: string;
|
password_confirm?: string;
|
||||||
password_confirm?: string;
|
|
||||||
};
|
|
||||||
submitting: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const formState = ref<PasswordChangeFormState>({
|
const initialFormState: PasswordChangeFormState = {
|
||||||
state: {
|
password: "",
|
||||||
password: "",
|
password_confirm: "",
|
||||||
password_confirm: "",
|
};
|
||||||
},
|
|
||||||
submitting: false,
|
const formState = ref<PasswordChangeFormState>(cloneDeep(initialFormState));
|
||||||
|
const saving = ref(false);
|
||||||
|
|
||||||
|
const { Command_Enter } = useMagicKeys();
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
if (Command_Enter.value && props.visible) {
|
||||||
|
submitForm("password-form");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleVisibleChange = (visible: boolean) => {
|
watch(
|
||||||
|
() => props.visible,
|
||||||
|
(visible) => {
|
||||||
|
if (visible) {
|
||||||
|
setFocus("passwordInput");
|
||||||
|
} else {
|
||||||
|
handleResetForm();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const onVisibleChange = (visible: boolean) => {
|
||||||
emit("update:visible", visible);
|
emit("update:visible", visible);
|
||||||
if (!visible) {
|
if (!visible) {
|
||||||
formState.value.state.password = "";
|
|
||||||
formState.value.state.password_confirm = "";
|
|
||||||
emit("close");
|
emit("close");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleResetForm = () => {
|
||||||
|
formState.value = cloneDeep(initialFormState);
|
||||||
|
reset("password-form");
|
||||||
|
};
|
||||||
|
|
||||||
const handleChangePassword = async () => {
|
const handleChangePassword = async () => {
|
||||||
try {
|
try {
|
||||||
formState.value.submitting = true;
|
saving.value = true;
|
||||||
|
|
||||||
const changePasswordRequest = cloneDeep(formState.value.state);
|
const changePasswordRequest = cloneDeep(formState.value);
|
||||||
delete changePasswordRequest.password_confirm;
|
delete changePasswordRequest.password_confirm;
|
||||||
|
|
||||||
if (props.user?.metadata.name === currentUser?.metadata.name) {
|
if (props.user?.metadata.name === currentUser?.metadata.name) {
|
||||||
|
@ -67,11 +89,11 @@ const handleChangePassword = async () => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleVisibleChange(false);
|
onVisibleChange(false);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
} finally {
|
} finally {
|
||||||
formState.value.submitting = false;
|
saving.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -81,16 +103,18 @@ const handleChangePassword = async () => {
|
||||||
:visible="visible"
|
:visible="visible"
|
||||||
:width="500"
|
:width="500"
|
||||||
title="密码修改"
|
title="密码修改"
|
||||||
@update:visible="handleVisibleChange"
|
@update:visible="onVisibleChange"
|
||||||
>
|
>
|
||||||
<FormKit
|
<FormKit
|
||||||
id="password-form"
|
id="password-form"
|
||||||
v-model="formState.state"
|
v-model="formState"
|
||||||
:actions="false"
|
:actions="false"
|
||||||
type="form"
|
type="form"
|
||||||
|
:config="{ validationVisibility: 'submit' }"
|
||||||
@submit="handleChangePassword"
|
@submit="handleChangePassword"
|
||||||
>
|
>
|
||||||
<FormKit
|
<FormKit
|
||||||
|
id="passwordInput"
|
||||||
label="新密码"
|
label="新密码"
|
||||||
name="password"
|
name="password"
|
||||||
type="password"
|
type="password"
|
||||||
|
@ -104,16 +128,16 @@ const handleChangePassword = async () => {
|
||||||
></FormKit>
|
></FormKit>
|
||||||
</FormKit>
|
</FormKit>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<VButton
|
<VSpace>
|
||||||
:loading="formState.submitting"
|
<VButton
|
||||||
type="secondary"
|
:loading="saving"
|
||||||
@click="$formkit.submit('password-form')"
|
type="secondary"
|
||||||
>
|
@click="$formkit.submit('password-form')"
|
||||||
<template #icon>
|
>
|
||||||
<IconSave class="h-full w-full" />
|
提交 ⌘ + ↵
|
||||||
</template>
|
</VButton>
|
||||||
保存
|
<VButton @click="onVisibleChange(false)">取消 Esc</VButton>
|
||||||
</VButton>
|
</VSpace>
|
||||||
</template>
|
</template>
|
||||||
</VModal>
|
</VModal>
|
||||||
</template>
|
</template>
|
||||||
|
|
Loading…
Reference in New Issue