feat: add change user password support

pull/588/head
Ryan Wang 2022-07-18 11:52:13 +08:00
parent 31dbe5a0eb
commit 56b9dcaa61
7 changed files with 250 additions and 33 deletions

View File

@ -12,7 +12,7 @@ import type { MenuGroupType, MenuItemType } from "@/types/menus";
import type { User } from "../../../../src/types/extension"; import type { User } from "../../../../src/types/extension";
import logo from "@/assets/logo.svg"; import logo from "@/assets/logo.svg";
import { RouterView, useRoute, useRouter } from "vue-router"; import { RouterView, useRoute, useRouter } from "vue-router";
import { inject, ref } from "vue"; import { computed, inject, ref } from "vue";
const menus = inject<MenuGroupType>("menus"); const menus = inject<MenuGroupType>("menus");
const minimenus = inject<MenuItemType>("minimenus"); const minimenus = inject<MenuItemType>("minimenus");
@ -28,6 +28,14 @@ const currentUser = inject<User>("currentUser");
const handleRouteToProfile = () => { const handleRouteToProfile = () => {
router.push({ path: `/users/${currentUser?.metadata.name}/detail` }); router.push({ path: `/users/${currentUser?.metadata.name}/detail` });
}; };
const currentRole = computed(() => {
return JSON.parse(
currentUser?.metadata.annotations?.[
"rbac.authorization.halo.run/role-names"
] || "[]"
)[0];
});
</script> </script>
<template> <template>
@ -62,7 +70,7 @@ const handleRouteToProfile = () => {
<template #leftIcon> <template #leftIcon>
<IconUserSettings /> <IconUserSettings />
</template> </template>
管理员 {{ currentRole }}
</VTag> </VTag>
</div> </div>
</div> </div>

View File

@ -32,7 +32,7 @@ const loginForm = ref<LoginFormState>({
state: { state: {
_csrf: "", _csrf: "",
username: "admin", username: "admin",
password: "1A3DTGDF7yeGsZaR", password: "12345678",
}, },
}); });

View File

@ -1,16 +1,72 @@
<script lang="ts" setup> <script lang="ts" setup>
import { VButton } from "@halo-dev/components"; import { VButton } from "@halo-dev/components";
import { inject, ref } from "vue";
import type { Ref } from "vue";
import { apiClient } from "@halo-dev/admin-shared";
import type { User } from "@halo-dev/api-client";
interface FormState {
state: {
password: string;
password_confirm?: string;
};
submitting: boolean;
}
const user = inject<Ref<User>>("user");
const currentUser = inject<User>("currentUser");
const formState = ref<FormState>({
state: {
password: "",
password_confirm: "",
},
submitting: false,
});
const handleChangePassword = async () => {
try {
formState.value.submitting = true;
if (user?.value.metadata.name === currentUser?.metadata.name) {
await apiClient.user.changePassword("-", formState.value.state);
}
} catch (e) {
console.error(e);
} finally {
formState.value.submitting = false;
}
};
</script> </script>
<template> <template>
<FormKit id="password-form" :actions="false" type="form"> <FormKit
<FormKit label="原密码" type="password"></FormKit> id="password-form"
<FormKit label="新密码" type="password"></FormKit> v-model="formState.state"
<FormKit label="确认密码" type="password"></FormKit> :actions="false"
type="form"
@submit="handleChangePassword"
>
<FormKit
label="新密码"
name="password"
type="password"
validation="required"
></FormKit>
<FormKit
label="确认密码"
name="password_confirm"
type="password"
validation="required|confirm"
></FormKit>
</FormKit> </FormKit>
<div class="pt-5"> <div class="pt-5">
<div class="flex justify-start"> <div class="flex justify-start">
<VButton type="secondary">修改密码</VButton> <VButton
:loading="formState.submitting"
type="secondary"
@click="$formkit.submit('password-form')"
>修改密码
</VButton>
</div> </div>
</div> </div>
</template> </template>

View File

@ -1,9 +1,10 @@
<script lang="ts" setup> <script lang="ts" setup>
import { VButton } from "@halo-dev/components"; import { VButton } from "@halo-dev/components";
import { inject } from "vue"; import { inject } from "vue";
import type { Ref } from "vue";
import type { User } from "@halo-dev/api-client"; import type { User } from "@halo-dev/api-client";
const user = inject<User>("user"); const user = inject<Ref<User>>("user");
</script> </script>
<template> <template>
<FormKit v-if="user" id="user-form" :actions="false" type="form"> <FormKit v-if="user" id="user-form" :actions="false" type="form">

View File

@ -12,17 +12,19 @@ import {
VSpace, VSpace,
VTag, VTag,
} from "@halo-dev/components"; } from "@halo-dev/components";
import UserCreationModal from "./components/UserCreationModal.vue"; import UserEditingModal from "./components/UserEditingModal.vue";
import UserPasswordChangeModal from "./components/UserPasswordChangeModal.vue";
import { onMounted, ref } from "vue"; import { onMounted, ref } from "vue";
import { apiClient } from "@halo-dev/admin-shared"; import { apiClient } from "@halo-dev/admin-shared";
import type { User } from "@halo-dev/api-client"; import type { User } from "@halo-dev/api-client";
import type { UserList } from "@halo-dev/api-client"; import type { UserList } from "@halo-dev/api-client";
const checkAll = ref(false); const checkAll = ref(false);
const creationModal = ref<boolean>(false); const editingModal = ref<boolean>(false);
const passwordChangeModal = ref<boolean>(false);
const users = ref<UserList>({ const users = ref<UserList>({
page: 1, page: 1,
size: 5, size: 10,
total: 0, total: 0,
items: [], items: [],
first: true, first: true,
@ -41,6 +43,8 @@ const handleFetchUsers = async () => {
users.value = data; users.value = data;
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} finally {
selectedUser.value = null;
} }
}; };
@ -58,7 +62,12 @@ const handlePaginationChange = async ({
const handleOpenCreateModal = (user: User) => { const handleOpenCreateModal = (user: User) => {
selectedUser.value = user; selectedUser.value = user;
creationModal.value = true; editingModal.value = true;
};
const handleOpenPasswordChangeModal = (user: User) => {
selectedUser.value = user;
passwordChangeModal.value = true;
}; };
const getRoles = (user: User) => { const getRoles = (user: User) => {
@ -73,8 +82,14 @@ onMounted(() => {
}); });
</script> </script>
<template> <template>
<UserCreationModal <UserEditingModal
v-model:visible="creationModal" v-model:visible="editingModal"
:user="selectedUser"
@close="handleFetchUsers"
/>
<UserPasswordChangeModal
v-model:visible="passwordChangeModal"
:user="selectedUser" :user="selectedUser"
@close="handleFetchUsers" @close="handleFetchUsers"
/> />
@ -91,7 +106,7 @@ onMounted(() => {
</template> </template>
角色管理 角色管理
</VButton> </VButton>
<VButton type="secondary" @click="creationModal = true"> <VButton type="secondary" @click="editingModal = true">
<template #icon> <template #icon>
<IconAddCircle class="h-full w-full" /> <IconAddCircle class="h-full w-full" />
</template> </template>
@ -290,7 +305,30 @@ onMounted(() => {
{{ user.metadata.creationTimestamp }} {{ user.metadata.creationTimestamp }}
</time> </time>
<span class="cursor-pointer"> <span class="cursor-pointer">
<IconSettings @click="handleOpenCreateModal(user)" /> <FloatingDropdown>
<IconSettings />
<template #popper>
<div class="links-w-48 links-p-2">
<VSpace class="links-w-full" direction="column">
<VButton
block
type="secondary"
@click="handleOpenCreateModal(user)"
>
修改资料
</VButton>
<VButton
v-permission="['system:users:manage']"
block
@click="handleOpenPasswordChangeModal(user)"
>
修改密码
</VButton>
</VSpace>
</div>
</template>
</FloatingDropdown>
</span> </span>
</div> </div>
</div> </div>

View File

@ -19,13 +19,13 @@ const props = defineProps({
const emit = defineEmits(["update:visible", "close"]); const emit = defineEmits(["update:visible", "close"]);
interface creationFormState { interface EditingFormState {
user: User; user: User;
saving: boolean; saving: boolean;
} }
const roles = ref<Role[]>([]); const roles = ref<Role[]>([]);
const creationForm = ref<creationFormState>({ const editingFormState = ref<EditingFormState>({
user: { user: {
spec: { spec: {
displayName: "", displayName: "",
@ -48,7 +48,7 @@ const creationForm = ref<creationFormState>({
const selectedRole = ref(""); const selectedRole = ref("");
const isUpdateMode = computed(() => { const isUpdateMode = computed(() => {
return !!creationForm.value.user.metadata.creationTimestamp; return !!editingFormState.value.user.metadata.creationTimestamp;
}); });
const creationModalTitle = computed(() => { const creationModalTitle = computed(() => {
@ -64,7 +64,7 @@ const basicRoles = computed(() => {
watch(props, (newVal) => { watch(props, (newVal) => {
if (newVal.visible && props.user) { if (newVal.visible && props.user) {
creationForm.value.user = props.user; editingFormState.value.user = props.user;
} }
}); });
@ -86,18 +86,18 @@ const handleVisibleChange = (visible: boolean) => {
const handleCreateUser = async () => { const handleCreateUser = async () => {
try { try {
creationForm.value.saving = true; editingFormState.value.saving = 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(
creationForm.value.user.metadata.name, editingFormState.value.user.metadata.name,
creationForm.value.user editingFormState.value.user
); );
user = response.data; user = response.data;
} else { } else {
const response = await apiClient.extension.user.createv1alpha1User( const response = await apiClient.extension.user.createv1alpha1User(
creationForm.value.user editingFormState.value.user
); );
user = response.data; user = response.data;
} }
@ -113,7 +113,7 @@ const handleCreateUser = async () => {
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} finally { } finally {
creationForm.value.saving = false; editingFormState.value.saving = false;
} }
}; };
@ -128,19 +128,20 @@ onMounted(handleFetchRoles);
> >
<FormKit id="user-form" type="form" @submit="handleCreateUser"> <FormKit id="user-form" type="form" @submit="handleCreateUser">
<FormKit <FormKit
v-model="creationForm.user.metadata.name" v-model="editingFormState.user.metadata.name"
:disabled="true"
label="用户名" label="用户名"
type="text" type="text"
validation="required" validation="required"
></FormKit> ></FormKit>
<FormKit <FormKit
v-model="creationForm.user.spec.displayName" v-model="editingFormState.user.spec.displayName"
label="显示名称" label="显示名称"
type="text" type="text"
validation="required" validation="required"
></FormKit> ></FormKit>
<FormKit <FormKit
v-model="creationForm.user.spec.email" v-model="editingFormState.user.spec.email"
label="电子邮箱" label="电子邮箱"
type="email" type="email"
validation="required" validation="required"
@ -160,24 +161,24 @@ onMounted(handleFetchRoles);
validation="required" validation="required"
></FormKit> ></FormKit>
<FormKit <FormKit
v-model="creationForm.user.spec.phone" v-model="editingFormState.user.spec.phone"
label="手机号" label="手机号"
type="text" type="text"
></FormKit> ></FormKit>
<FormKit <FormKit
v-model="creationForm.user.spec.avatar" v-model="editingFormState.user.spec.avatar"
label="头像" label="头像"
type="text" type="text"
></FormKit> ></FormKit>
<FormKit <FormKit
v-model="creationForm.user.spec.bio" v-model="editingFormState.user.spec.bio"
label="描述" label="描述"
type="textarea" type="textarea"
></FormKit> ></FormKit>
</FormKit> </FormKit>
<template #footer> <template #footer>
<VButton <VButton
:loading="creationForm.saving" :loading="editingFormState.saving"
type="secondary" type="secondary"
@click="$formkit.submit('user-form')" @click="$formkit.submit('user-form')"
> >

View File

@ -0,0 +1,113 @@
<script lang="ts" setup>
import { VModal, VButton, IconSave } from "@halo-dev/components";
import { inject, ref } from "vue";
import type { User } from "@halo-dev/api-client";
import type { PropType } from "vue";
import { apiClient } from "@halo-dev/admin-shared";
import cloneDeep from "lodash.clonedeep";
const props = defineProps({
visible: {
type: Boolean,
default: false,
},
user: {
type: Object as PropType<User | null>,
default: null,
},
});
const emit = defineEmits(["update:visible", "close"]);
const currentUser = inject<User>("currentUser");
interface PasswordChangeFormState {
state: {
password: string;
password_confirm?: string;
};
submitting: boolean;
}
const formState = ref<PasswordChangeFormState>({
state: {
password: "",
password_confirm: "",
},
submitting: false,
});
const handleVisibleChange = (visible: boolean) => {
emit("update:visible", visible);
if (!visible) {
formState.value.state.password = "";
formState.value.state.password_confirm = "";
emit("close");
}
};
const handleChangePassword = async () => {
try {
formState.value.submitting = true;
const changePasswordRequest = cloneDeep(formState.value.state);
delete changePasswordRequest.password_confirm;
if (props.user?.metadata.name === currentUser?.metadata.name) {
await apiClient.user.changePassword("-", changePasswordRequest);
} else {
await apiClient.user.changePassword(
props.user?.metadata.name || "",
changePasswordRequest
);
}
handleVisibleChange(false);
} catch (e) {
console.error(e);
} finally {
formState.value.submitting = false;
}
};
</script>
<template>
<VModal
:visible="visible"
:width="500"
title="密码修改"
@update:visible="handleVisibleChange"
>
<FormKit
id="password-form"
v-model="formState.state"
:actions="false"
type="form"
@submit="handleChangePassword"
>
<FormKit
label="新密码"
name="password"
type="password"
validation="required"
></FormKit>
<FormKit
label="确认密码"
name="password_confirm"
type="password"
validation="required|confirm"
></FormKit>
</FormKit>
<template #footer>
<VButton
:loading="formState.submitting"
type="secondary"
@click="$formkit.submit('password-form')"
>
<template #icon>
<IconSave class="h-full w-full" />
</template>
保存
</VButton>
</template>
</VModal>
</template>