267 lines
6.3 KiB
Vue
267 lines
6.3 KiB
Vue
<template>
|
|
<div class="profile-page">
|
|
<t-form class="form" label-align="left" v-entrance-transition>
|
|
<t-form-item label="头像">
|
|
<photo-field
|
|
v-model="formModel.avatar"
|
|
add-text="更换"
|
|
alt-text="用户头像"
|
|
size="compact"
|
|
:loading="isAvatarUploading"
|
|
:preview="false"
|
|
:show-remove="false"
|
|
@add="changeAvatar"
|
|
/>
|
|
</t-form-item>
|
|
<t-form-item label="用户名">
|
|
<t-input v-model.trim="formModel.name" placeholder="用户名" borderless />
|
|
</t-form-item>
|
|
<t-form-item label="昵称">
|
|
<t-input v-model.trim="formModel.nickname" placeholder="昵称" borderless />
|
|
</t-form-item>
|
|
<t-form-item label="性别">
|
|
<t-radio-group class="sex" v-model="formModel.gender" variant="default-filled">
|
|
<t-radio :block="false" borderless value="MALE">男</t-radio>
|
|
<t-radio :block="false" borderless value="FEMALE">女</t-radio>
|
|
<t-radio :block="false" borderless value="">未知</t-radio>
|
|
</t-radio-group>
|
|
</t-form-item>
|
|
<t-form-item label="手机号">
|
|
<div class="mobile-value">
|
|
<div v-if="formModel.mobile" v-text="formModel.mobile" />
|
|
<span v-else v-text="'未填写'" />
|
|
</div>
|
|
</t-form-item>
|
|
</t-form>
|
|
<t-cell-group theme="card" v-entrance-transition="70">
|
|
<t-cell title="修改密码" arrow hover @click="toPassword" />
|
|
</t-cell-group>
|
|
<t-button
|
|
class="red bg-white cell-button"
|
|
variant="text"
|
|
v-entrance-transition="140"
|
|
@click="confirmLogout"
|
|
>退出登录</t-button>
|
|
<input
|
|
ref="avatarInputRef"
|
|
class="avatar-input"
|
|
type="file"
|
|
accept="image/*"
|
|
@change="handleAvatarSelected"
|
|
>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { DialogPlugin } from "tdesign-mobile-vue";
|
|
import { AttachmentAPI, type Gender, UserAPI, useUserStore } from "timi-web-api";
|
|
import PhotoField from "@/components/PhotoField.vue";
|
|
import { useNavBarStore } from "@/store/navBarStore";
|
|
import { buildFallbackAvatar } from "@/utils/avatar";
|
|
import { compressUploadImage } from "@/utils/imageCompression";
|
|
import { Message } from "@/utils/message";
|
|
|
|
type UserAvatarSource = NonNullable<Parameters<typeof UserAPI.getAvatarURL>[0]> & {
|
|
name?: string;
|
|
nick?: string;
|
|
};
|
|
type ProfileGender = Gender | "";
|
|
defineOptions({
|
|
name: "UserProfile"
|
|
});
|
|
|
|
const router = useRouter();
|
|
const navBarStore = useNavBarStore();
|
|
const userStore = useUserStore();
|
|
|
|
// ---------- 头像 ----------
|
|
|
|
const avatarInputRef = ref<HTMLInputElement>();
|
|
const avatarTempFileId = ref("");
|
|
const isAvatarUploading = ref(false);
|
|
|
|
// ---------- 表单 ----------
|
|
|
|
const isSaving = ref(false);
|
|
|
|
const formModel = reactive({
|
|
id: "",
|
|
name: "",
|
|
avatar: "",
|
|
nickname: "",
|
|
role: "",
|
|
gender: "" as ProfileGender,
|
|
mobile: "",
|
|
password: "",
|
|
office: "",
|
|
scope: "",
|
|
status: "",
|
|
lastLoginAt: ""
|
|
});
|
|
|
|
// ---------- 页面数据 ----------
|
|
|
|
function fillForm(): void {
|
|
const user = userStore.loginUser.user;
|
|
Object.assign(formModel, {
|
|
id: user?.id || "",
|
|
name: user?.name || "",
|
|
avatar: buildUserAvatar(user),
|
|
nickname: user?.nick || "",
|
|
role: formatRoleCode(userStore.getRoleList()[0]),
|
|
gender: user?.gender || "",
|
|
mobile: user?.phoneNo || "",
|
|
password: "",
|
|
office: "",
|
|
scope: "",
|
|
status: "",
|
|
lastLoginAt: user?.lastLoginAt ? new Date(user.lastLoginAt).toLocaleString() : ""
|
|
});
|
|
}
|
|
|
|
// ---------- 头像 ----------
|
|
|
|
function changeAvatar(): void {
|
|
avatarInputRef.value?.click();
|
|
}
|
|
|
|
async function handleAvatarSelected(event: Event): Promise<void> {
|
|
const input = event.target as HTMLInputElement;
|
|
const file = input.files?.[0];
|
|
if (!file) {
|
|
return;
|
|
}
|
|
isAvatarUploading.value = true;
|
|
try {
|
|
const compressedFile = await compressUploadImage(file);
|
|
const [tempFile] = await AttachmentAPI.upload(compressedFile);
|
|
if (!tempFile?.id) {
|
|
Message.error("头像上传失败");
|
|
return;
|
|
}
|
|
avatarTempFileId.value = tempFile.id;
|
|
formModel.avatar = AttachmentAPI.getTempReadURL(tempFile.id);
|
|
} finally {
|
|
isAvatarUploading.value = false;
|
|
input.value = "";
|
|
}
|
|
}
|
|
|
|
// ---------- 导航 ----------
|
|
|
|
async function toPassword(): Promise<void> {
|
|
await router.push("/management/profile/password");
|
|
}
|
|
|
|
// ---------- 保存 ----------
|
|
|
|
async function saveProfile(): Promise<void> {
|
|
if (!formModel.name) {
|
|
Message.warning("请填写用户名");
|
|
return;
|
|
}
|
|
isSaving.value = true;
|
|
try {
|
|
const result = await UserAPI.updateCurrent({
|
|
id: formModel.id,
|
|
name: formModel.name,
|
|
nick: formModel.nickname,
|
|
gender: formModel.gender,
|
|
tempFileIdList: avatarTempFileId.value ? [avatarTempFileId.value] : undefined
|
|
});
|
|
await userStore.updateLoginUser(result);
|
|
Message.success("资料已保存");
|
|
await router.back();
|
|
} finally {
|
|
isSaving.value = false;
|
|
}
|
|
}
|
|
|
|
// ---------- 退出登录 ----------
|
|
|
|
async function confirmLogout(): Promise<void> {
|
|
const dialog = DialogPlugin.confirm({
|
|
title: "退出登录",
|
|
content: "确定要退出当前账号吗?",
|
|
cancelBtn: "取消",
|
|
confirmBtn: "退出",
|
|
onCancel() {
|
|
dialog.destroy();
|
|
},
|
|
async onConfirm() {
|
|
dialog.destroy();
|
|
await logout();
|
|
}
|
|
});
|
|
}
|
|
|
|
async function logout(): Promise<void> {
|
|
await userStore.logout();
|
|
await router.replace("/login");
|
|
}
|
|
|
|
// ---------- 格式化 ----------
|
|
|
|
function formatRoleCode(roleCode?: string): string {
|
|
if (!roleCode) {
|
|
return "";
|
|
}
|
|
|
|
const codePartList = roleCode.split(":");
|
|
return codePartList[codePartList.length - 1] || roleCode;
|
|
}
|
|
|
|
function buildUserAvatar(user?: UserAvatarSource): string {
|
|
const name = user?.nick || user?.name || "用户";
|
|
return UserAPI.getAvatarURL(user) || buildFallbackAvatar(name, "#0f766e");
|
|
}
|
|
|
|
onMounted(() => {
|
|
navBarStore.setRightText("保存", "var(--td-brand-color)", saveProfile);
|
|
fillForm();
|
|
});
|
|
|
|
onUnmounted(() => {
|
|
navBarStore.clearRight();
|
|
});
|
|
</script>
|
|
|
|
<style scoped lang="less">
|
|
.profile-page {
|
|
gap: 1rem;
|
|
padding: calc(var(--app-nav-height) + 1rem) 0 calc(1rem + env(safe-area-inset-bottom, 0)) 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
|
|
.form {
|
|
padding: .5rem 0;
|
|
background: rgba(255, 255, 255, .92);
|
|
box-shadow: var(--td-shadow-1);
|
|
border-radius: 1.4rem;
|
|
|
|
.sex {
|
|
gap: .5rem;
|
|
display: flex;
|
|
}
|
|
}
|
|
|
|
.avatar-input {
|
|
display: none;
|
|
}
|
|
|
|
.mobile-value {
|
|
color: var(--td-text-color-primary);
|
|
line-height: 1.5rem;
|
|
}
|
|
}
|
|
|
|
:global(html.theme-dark) {
|
|
.profile-page {
|
|
.form {
|
|
background: rgba(15, 23, 42, .88);
|
|
box-shadow: 0 .75rem 2rem rgba(2, 6, 23, .32);
|
|
}
|
|
}
|
|
}
|
|
</style>
|