From 4ca171da6f9f14bb3e49977058b6c3c80957d6ef Mon Sep 17 00:00:00 2001
From: npmrun <1549469775@qq.com>
Date: Mon, 20 Apr 2026 21:15:39 +0800
Subject: [PATCH] feat(profile): add email field to user profile and validation
Introduce an email field in the user profile, allowing users to input their email address. Implement email format validation on the server side to ensure proper formatting before saving. Update the profile form to include the email input, enhancing user experience and notification capabilities.
Made-with: Cursor
---
app/pages/me/profile/index.vue | 11 +++++++++++
server/api/me/profile.put.ts | 17 +++++++++++++++++
server/service/profile/index.ts | 4 ++++
3 files changed, 32 insertions(+)
diff --git a/app/pages/me/profile/index.vue b/app/pages/me/profile/index.vue
index 0803354..3a41df7 100644
--- a/app/pages/me/profile/index.vue
+++ b/app/pages/me/profile/index.vue
@@ -9,6 +9,7 @@ const toast = useToast()
type ProfileGet = {
profile: {
+ email: string | null
nickname: string | null
avatar: string | null
avatarVisibility: string
@@ -27,6 +28,7 @@ type MeConfigGet = {
}
const state = reactive({
+ email: '',
nickname: '',
avatar: '',
avatarVisibility: 'private',
@@ -184,6 +186,7 @@ async function load() {
const p = profilePayload.profile
const cfg = meCfgPayload.config
state.nickname = p.nickname ?? ''
+ state.email = p.email ?? ''
state.avatar = p.avatar ?? ''
state.avatarVisibility = p.avatarVisibility
state.bioMarkdown = p.bioMarkdown ?? ''
@@ -219,6 +222,7 @@ async function save() {
method: 'PUT',
notify: false,
body: {
+ email: state.email.trim() || null,
nickname: state.nickname || null,
avatar: state.avatar || null,
avatarVisibility: state.avatarVisibility,
@@ -327,6 +331,13 @@ async function save() {
+
+
+ {
const user = await event.context.auth.requireUser();
const body = await readBody<{
+ email?: string | null;
nickname?: string | null;
avatar?: string | null;
avatarVisibility?: string;
@@ -19,6 +32,7 @@ export default defineWrappedResponseHandler(async (event) => {
try {
const row = await updateProfile(user.id, {
+ email: normalizeEmailInput(body.email),
nickname: body.nickname,
avatar: body.avatar,
avatarVisibility:
@@ -37,6 +51,9 @@ export default defineWrappedResponseHandler(async (event) => {
}
return R.success({ ok: true });
} catch (e) {
+ if (e instanceof ZodError) {
+ throw createError({ statusCode: 400, statusMessage: e.issues[0]?.message ?? "参数不合法" });
+ }
if (isUniqueConstraintViolation(e)) {
throw createError({ statusCode: 409, statusMessage: "公开链接 slug 已被占用" });
}
diff --git a/server/service/profile/index.ts b/server/service/profile/index.ts
index ad35279..8b3c952 100644
--- a/server/service/profile/index.ts
+++ b/server/service/profile/index.ts
@@ -52,6 +52,7 @@ export async function getProfileRow(userId: number) {
export async function updateProfile(
userId: number,
patch: {
+ email?: string | null;
nickname?: string | null;
avatar?: string | null;
avatarVisibility?: Visibility;
@@ -66,6 +67,9 @@ export async function updateProfile(
) {
const updates: Record = {};
+ if (patch.email !== undefined) {
+ updates.email = patch.email?.trim() || null;
+ }
if (patch.nickname !== undefined) {
updates.nickname = patch.nickname?.trim() || null;
}