mirror of https://github.com/halo-dev/halo-admin
feat: add change user password support
parent
31dbe5a0eb
commit
56b9dcaa61
|
@ -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>
|
||||||
|
|
|
@ -32,7 +32,7 @@ const loginForm = ref<LoginFormState>({
|
||||||
state: {
|
state: {
|
||||||
_csrf: "",
|
_csrf: "",
|
||||||
username: "admin",
|
username: "admin",
|
||||||
password: "1A3DTGDF7yeGsZaR",
|
password: "12345678",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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')"
|
||||||
>
|
>
|
|
@ -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>
|
Loading…
Reference in New Issue