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 logo from "@/assets/logo.svg";
|
||||
import { RouterView, useRoute, useRouter } from "vue-router";
|
||||
import { inject, ref } from "vue";
|
||||
import { computed, inject, ref } from "vue";
|
||||
|
||||
const menus = inject<MenuGroupType>("menus");
|
||||
const minimenus = inject<MenuItemType>("minimenus");
|
||||
|
@ -28,6 +28,14 @@ const currentUser = inject<User>("currentUser");
|
|||
const handleRouteToProfile = () => {
|
||||
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>
|
||||
|
||||
<template>
|
||||
|
@ -62,7 +70,7 @@ const handleRouteToProfile = () => {
|
|||
<template #leftIcon>
|
||||
<IconUserSettings />
|
||||
</template>
|
||||
管理员
|
||||
{{ currentRole }}
|
||||
</VTag>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -32,7 +32,7 @@ const loginForm = ref<LoginFormState>({
|
|||
state: {
|
||||
_csrf: "",
|
||||
username: "admin",
|
||||
password: "1A3DTGDF7yeGsZaR",
|
||||
password: "12345678",
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -1,16 +1,72 @@
|
|||
<script lang="ts" setup>
|
||||
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>
|
||||
<template>
|
||||
<FormKit id="password-form" :actions="false" type="form">
|
||||
<FormKit label="原密码" type="password"></FormKit>
|
||||
<FormKit label="新密码" type="password"></FormKit>
|
||||
<FormKit label="确认密码" type="password"></FormKit>
|
||||
<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>
|
||||
|
||||
<div class="pt-5">
|
||||
<div class="flex justify-start">
|
||||
<VButton type="secondary">修改密码</VButton>
|
||||
<VButton
|
||||
:loading="formState.submitting"
|
||||
type="secondary"
|
||||
@click="$formkit.submit('password-form')"
|
||||
>修改密码
|
||||
</VButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
<script lang="ts" setup>
|
||||
import { VButton } from "@halo-dev/components";
|
||||
import { inject } from "vue";
|
||||
import type { Ref } from "vue";
|
||||
import type { User } from "@halo-dev/api-client";
|
||||
|
||||
const user = inject<User>("user");
|
||||
const user = inject<Ref<User>>("user");
|
||||
</script>
|
||||
<template>
|
||||
<FormKit v-if="user" id="user-form" :actions="false" type="form">
|
||||
|
|
|
@ -12,17 +12,19 @@ import {
|
|||
VSpace,
|
||||
VTag,
|
||||
} 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 { apiClient } from "@halo-dev/admin-shared";
|
||||
import type { User } from "@halo-dev/api-client";
|
||||
import type { UserList } from "@halo-dev/api-client";
|
||||
|
||||
const checkAll = ref(false);
|
||||
const creationModal = ref<boolean>(false);
|
||||
const editingModal = ref<boolean>(false);
|
||||
const passwordChangeModal = ref<boolean>(false);
|
||||
const users = ref<UserList>({
|
||||
page: 1,
|
||||
size: 5,
|
||||
size: 10,
|
||||
total: 0,
|
||||
items: [],
|
||||
first: true,
|
||||
|
@ -41,6 +43,8 @@ const handleFetchUsers = async () => {
|
|||
users.value = data;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
} finally {
|
||||
selectedUser.value = null;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -58,7 +62,12 @@ const handlePaginationChange = async ({
|
|||
|
||||
const handleOpenCreateModal = (user: User) => {
|
||||
selectedUser.value = user;
|
||||
creationModal.value = true;
|
||||
editingModal.value = true;
|
||||
};
|
||||
|
||||
const handleOpenPasswordChangeModal = (user: User) => {
|
||||
selectedUser.value = user;
|
||||
passwordChangeModal.value = true;
|
||||
};
|
||||
|
||||
const getRoles = (user: User) => {
|
||||
|
@ -73,8 +82,14 @@ onMounted(() => {
|
|||
});
|
||||
</script>
|
||||
<template>
|
||||
<UserCreationModal
|
||||
v-model:visible="creationModal"
|
||||
<UserEditingModal
|
||||
v-model:visible="editingModal"
|
||||
:user="selectedUser"
|
||||
@close="handleFetchUsers"
|
||||
/>
|
||||
|
||||
<UserPasswordChangeModal
|
||||
v-model:visible="passwordChangeModal"
|
||||
:user="selectedUser"
|
||||
@close="handleFetchUsers"
|
||||
/>
|
||||
|
@ -91,7 +106,7 @@ onMounted(() => {
|
|||
</template>
|
||||
角色管理
|
||||
</VButton>
|
||||
<VButton type="secondary" @click="creationModal = true">
|
||||
<VButton type="secondary" @click="editingModal = true">
|
||||
<template #icon>
|
||||
<IconAddCircle class="h-full w-full" />
|
||||
</template>
|
||||
|
@ -290,7 +305,30 @@ onMounted(() => {
|
|||
{{ user.metadata.creationTimestamp }}
|
||||
</time>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -19,13 +19,13 @@ const props = defineProps({
|
|||
|
||||
const emit = defineEmits(["update:visible", "close"]);
|
||||
|
||||
interface creationFormState {
|
||||
interface EditingFormState {
|
||||
user: User;
|
||||
saving: boolean;
|
||||
}
|
||||
|
||||
const roles = ref<Role[]>([]);
|
||||
const creationForm = ref<creationFormState>({
|
||||
const editingFormState = ref<EditingFormState>({
|
||||
user: {
|
||||
spec: {
|
||||
displayName: "",
|
||||
|
@ -48,7 +48,7 @@ const creationForm = ref<creationFormState>({
|
|||
const selectedRole = ref("");
|
||||
|
||||
const isUpdateMode = computed(() => {
|
||||
return !!creationForm.value.user.metadata.creationTimestamp;
|
||||
return !!editingFormState.value.user.metadata.creationTimestamp;
|
||||
});
|
||||
|
||||
const creationModalTitle = computed(() => {
|
||||
|
@ -64,7 +64,7 @@ const basicRoles = computed(() => {
|
|||
|
||||
watch(props, (newVal) => {
|
||||
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 () => {
|
||||
try {
|
||||
creationForm.value.saving = true;
|
||||
editingFormState.value.saving = true;
|
||||
let user: User;
|
||||
|
||||
if (isUpdateMode.value) {
|
||||
const response = await apiClient.extension.user.updatev1alpha1User(
|
||||
creationForm.value.user.metadata.name,
|
||||
creationForm.value.user
|
||||
editingFormState.value.user.metadata.name,
|
||||
editingFormState.value.user
|
||||
);
|
||||
user = response.data;
|
||||
} else {
|
||||
const response = await apiClient.extension.user.createv1alpha1User(
|
||||
creationForm.value.user
|
||||
editingFormState.value.user
|
||||
);
|
||||
user = response.data;
|
||||
}
|
||||
|
@ -113,7 +113,7 @@ const handleCreateUser = async () => {
|
|||
} catch (e) {
|
||||
console.error(e);
|
||||
} finally {
|
||||
creationForm.value.saving = false;
|
||||
editingFormState.value.saving = false;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -128,19 +128,20 @@ onMounted(handleFetchRoles);
|
|||
>
|
||||
<FormKit id="user-form" type="form" @submit="handleCreateUser">
|
||||
<FormKit
|
||||
v-model="creationForm.user.metadata.name"
|
||||
v-model="editingFormState.user.metadata.name"
|
||||
:disabled="true"
|
||||
label="用户名"
|
||||
type="text"
|
||||
validation="required"
|
||||
></FormKit>
|
||||
<FormKit
|
||||
v-model="creationForm.user.spec.displayName"
|
||||
v-model="editingFormState.user.spec.displayName"
|
||||
label="显示名称"
|
||||
type="text"
|
||||
validation="required"
|
||||
></FormKit>
|
||||
<FormKit
|
||||
v-model="creationForm.user.spec.email"
|
||||
v-model="editingFormState.user.spec.email"
|
||||
label="电子邮箱"
|
||||
type="email"
|
||||
validation="required"
|
||||
|
@ -160,24 +161,24 @@ onMounted(handleFetchRoles);
|
|||
validation="required"
|
||||
></FormKit>
|
||||
<FormKit
|
||||
v-model="creationForm.user.spec.phone"
|
||||
v-model="editingFormState.user.spec.phone"
|
||||
label="手机号"
|
||||
type="text"
|
||||
></FormKit>
|
||||
<FormKit
|
||||
v-model="creationForm.user.spec.avatar"
|
||||
v-model="editingFormState.user.spec.avatar"
|
||||
label="头像"
|
||||
type="text"
|
||||
></FormKit>
|
||||
<FormKit
|
||||
v-model="creationForm.user.spec.bio"
|
||||
v-model="editingFormState.user.spec.bio"
|
||||
label="描述"
|
||||
type="textarea"
|
||||
></FormKit>
|
||||
</FormKit>
|
||||
<template #footer>
|
||||
<VButton
|
||||
:loading="creationForm.saving"
|
||||
:loading="editingFormState.saving"
|
||||
type="secondary"
|
||||
@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