新增用户注册功能

This commit is contained in:
Cheng Zhou
2025-12-22 21:19:48 +08:00
parent 03fddba406
commit 6ddf9901a0
33 changed files with 1389 additions and 138 deletions
+185
View File
@@ -0,0 +1,185 @@
<template>
<div class="page">
<el-card>
<h3 class="title">个人设置</h3>
<p class="subtitle">更新个人信息或修改登录密码</p>
<el-form ref="formRef" :model="form" :rules="rules" label-width="120px" class="form">
<el-form-item label="头像">
<div class="avatar-row">
<el-avatar :size="64" :src="avatarPreview" :alt="form.full_name || form.email">
{{ form.full_name?.charAt(0)?.toUpperCase() || form.email?.charAt(0)?.toUpperCase() }}
</el-avatar>
<el-upload
class="avatar-uploader"
:show-file-list="false"
action="/api/v1/auth/me/avatar"
name="file"
:headers="uploadHeaders"
:on-success="onAvatarUploaded"
:on-error="onAvatarError"
>
<el-button>上传头像</el-button>
</el-upload>
</div>
</el-form-item>
<el-form-item label="邮箱">
<el-input v-model="form.email" disabled />
</el-form-item>
<el-form-item label="姓名" prop="full_name">
<el-input v-model="form.full_name" placeholder="请输入姓名" />
</el-form-item>
<el-form-item label="部门" prop="department">
<el-input v-model="form.department" placeholder="请输入部门" />
</el-form-item>
<el-divider />
<el-form-item label="当前密码" prop="current_password">
<el-input v-model="form.current_password" type="password" show-password placeholder="修改密码时必填" />
</el-form-item>
<el-form-item label="新密码" prop="password">
<el-input v-model="form.password" type="password" show-password placeholder="不修改请留空" />
</el-form-item>
<el-form-item label="确认新密码" prop="confirmPassword">
<el-input v-model="form.confirmPassword" type="password" show-password placeholder="再次输入新密码" />
</el-form-item>
<div class="actions">
<el-button type="primary" :loading="submitting" @click="onSubmit">保存</el-button>
</div>
</el-form>
</el-card>
</div>
</template>
<script setup lang="ts">
import { computed, onMounted, reactive, ref } from "vue";
import type { FormInstance, FormRules } from "element-plus";
import { ElMessage } from "element-plus";
import { updateProfile, fetchMe } from "../api/auth";
import { useAuthStore } from "../store/auth";
import { getToken } from "../utils/auth";
const auth = useAuthStore();
const formRef = ref<FormInstance>();
const submitting = ref(false);
const form = reactive({
email: "",
full_name: "",
department: "",
current_password: "",
password: "",
confirmPassword: "",
});
const avatarPreview = ref<string | undefined>();
const uploadHeaders = computed<Record<string, string>>(() => ({
Authorization: `Bearer ${getToken() || ""}`,
}));
const rules: FormRules<typeof form> = {
full_name: [{ required: true, message: "请输入姓名", trigger: "blur" }],
department: [{ required: true, message: "请输入部门", trigger: "blur" }],
current_password: [
{
validator: (_r, value, cb) => {
if (form.password && !value) {
cb(new Error("修改密码需输入当前密码"));
return;
}
cb();
},
trigger: "blur",
},
],
password: [
{
validator: (_r, value, cb) => {
if (!value) return cb();
if (value.length < 8 || !/[A-Za-z]/.test(value) || !/[0-9]/.test(value)) {
cb(new Error("至少 8 位且包含字母与数字"));
return;
}
cb();
},
trigger: "blur",
},
],
confirmPassword: [
{
validator: (_r, value, cb) => {
if (form.password && value !== form.password) {
cb(new Error("两次输入的密码不一致"));
return;
}
cb();
},
trigger: "blur",
},
],
};
const loadProfile = async () => {
const { data } = await fetchMe();
form.email = data.email;
form.full_name = data.full_name;
form.department = data.department;
avatarPreview.value = data.avatar_url || undefined;
};
const onSubmit = async () => {
if (!formRef.value) return;
await formRef.value.validate(async (valid) => {
if (!valid) return;
submitting.value = true;
try {
await updateProfile({
full_name: form.full_name,
department: form.department,
current_password: form.password ? form.current_password : undefined,
password: form.password || undefined,
});
await auth.fetchMe();
ElMessage.success("已更新");
form.current_password = "";
form.password = "";
form.confirmPassword = "";
avatarPreview.value = auth.user?.avatar_url || avatarPreview.value;
} catch (error: any) {
ElMessage.error(error?.response?.data?.message || "更新失败");
} finally {
submitting.value = false;
}
});
};
const onAvatarUploaded = async () => {
await auth.fetchMe();
avatarPreview.value = auth.user?.avatar_url || undefined;
ElMessage.success("头像已更新");
};
const onAvatarError = (err: any) => {
ElMessage.error(err?.response?.data?.message || "头像上传失败");
};
onMounted(() => {
loadProfile();
});
</script>
<style scoped>
.page {
padding: 16px;
}
.title {
margin: 0;
}
.subtitle {
margin: 6px 0 16px;
color: #666;
}
.form {
max-width: 520px;
}
.actions {
margin-top: 12px;
text-align: right;
}
</style>