From 92f7bf54198bafa92420f8fd33cc8098f0e27b09 Mon Sep 17 00:00:00 2001 From: zhonghua Date: Thu, 16 Apr 2026 17:41:54 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=85=AC=E4=BC=97=E7=AB=AF=E4=B8=AA?= =?UTF-8?q?=E4=BA=BA=E8=B5=84=E6=96=99=E6=9B=B4=E6=96=B0=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=20DTO=20=E6=A0=A1=E9=AA=8C=E4=B8=8E=E5=89=8D=E7=AB=AF=E5=AF=B9?= =?UTF-8?q?=E9=BD=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made-with: Cursor --- .../controller/PublicProfileController.java | 9 +-- .../pub/dto/PublicProfileUpdateDto.java | 30 +++++++++ .../pub/service/PublicProfileService.java | 22 ++++-- lesingle-creation-frontend/src/api/public.ts | 18 +++-- .../src/views/public/mine/Index.vue | 67 +++++++++++++++---- 5 files changed, 117 insertions(+), 29 deletions(-) create mode 100644 lesingle-creation-backend/src/main/java/com/lesingle/modules/pub/dto/PublicProfileUpdateDto.java diff --git a/lesingle-creation-backend/src/main/java/com/lesingle/modules/pub/controller/PublicProfileController.java b/lesingle-creation-backend/src/main/java/com/lesingle/modules/pub/controller/PublicProfileController.java index b3d8727..55aac78 100644 --- a/lesingle-creation-backend/src/main/java/com/lesingle/modules/pub/controller/PublicProfileController.java +++ b/lesingle-creation-backend/src/main/java/com/lesingle/modules/pub/controller/PublicProfileController.java @@ -6,11 +6,13 @@ import com.lesingle.common.util.SecurityUtil; import com.lesingle.modules.biz.contest.entity.BizContestRegistration; import com.lesingle.modules.pub.service.PublicActivityService; import com.lesingle.modules.pub.service.PublicInteractionService; +import com.lesingle.modules.pub.dto.PublicProfileUpdateDto; import com.lesingle.modules.pub.service.PublicProfileService; import com.lesingle.modules.pub.service.PublicUserWorkService; import com.lesingle.modules.ugc.entity.UgcWork; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; @@ -34,11 +36,10 @@ public class PublicProfileController { } @PutMapping("/profile") - @Operation(summary = "更新个人资料") - public Result updateProfile(@RequestBody Map body) { + @Operation(summary = "更新个人资料", description = "请求体 JSON:nickname(必填)、city、gender(male/female)、avatar(可选,不传则不改头像)") + public Result updateProfile(@Valid @RequestBody PublicProfileUpdateDto dto) { Long userId = SecurityUtil.getCurrentUserId(); - publicProfileService.updateProfile(userId, - body.get("nickname"), body.get("city"), body.get("avatar"), body.get("gender")); + publicProfileService.updateProfile(userId, dto); return Result.success(); } diff --git a/lesingle-creation-backend/src/main/java/com/lesingle/modules/pub/dto/PublicProfileUpdateDto.java b/lesingle-creation-backend/src/main/java/com/lesingle/modules/pub/dto/PublicProfileUpdateDto.java new file mode 100644 index 0000000..13e944a --- /dev/null +++ b/lesingle-creation-backend/src/main/java/com/lesingle/modules/pub/dto/PublicProfileUpdateDto.java @@ -0,0 +1,30 @@ +package com.lesingle.modules.pub.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.Data; + +/** + * 公众端「我的」个人资料更新,与前端 PUT /public/mine/profile 请求体一致(camelCase) + */ +@Data +@Schema(description = "公众端更新个人资料") +public class PublicProfileUpdateDto { + + @NotBlank(message = "昵称不能为空") + @Size(min = 1, max = 20, message = "昵称长度为1-20个字符") + @Schema(description = "昵称") + private String nickname; + + @Size(max = 50, message = "城市长度不超过50个字符") + @Schema(description = "城市,可空表示清空") + private String city; + + @Schema(description = "性别:male / female,可空表示清空") + private String gender; + + @Size(max = 512, message = "头像地址过长") + @Schema(description = "头像 URL,不传则不修改") + private String avatar; +} diff --git a/lesingle-creation-backend/src/main/java/com/lesingle/modules/pub/service/PublicProfileService.java b/lesingle-creation-backend/src/main/java/com/lesingle/modules/pub/service/PublicProfileService.java index 9b1a7b8..c25877c 100644 --- a/lesingle-creation-backend/src/main/java/com/lesingle/modules/pub/service/PublicProfileService.java +++ b/lesingle-creation-backend/src/main/java/com/lesingle/modules/pub/service/PublicProfileService.java @@ -4,10 +4,12 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.lesingle.common.constants.TenantConstants; import com.lesingle.common.enums.CommonStatus; +import com.lesingle.common.enums.ErrorCode; import com.lesingle.common.enums.UserSource; import com.lesingle.common.enums.UserType; import com.lesingle.common.exception.BusinessException; import com.lesingle.modules.pub.dto.CreateChildDto; +import com.lesingle.modules.pub.dto.PublicProfileUpdateDto; import com.lesingle.modules.sys.entity.SysUser; import com.lesingle.modules.sys.entity.SysTenant; import com.lesingle.modules.sys.mapper.SysUserMapper; @@ -21,6 +23,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.StringUtils; import java.time.LocalDate; import java.time.LocalDateTime; @@ -62,15 +65,22 @@ public class PublicProfileService { } /** - * 更新个人资料 + * 更新个人资料(与 {@link PublicProfileUpdateDto} 字段一致) */ - public void updateProfile(Long userId, String nickname, String city, String avatar, String gender) { + public void updateProfile(Long userId, PublicProfileUpdateDto dto) { + String g = dto.getGender(); + if (StringUtils.hasText(g) && !"male".equals(g) && !"female".equals(g)) { + throw BusinessException.of(ErrorCode.BAD_REQUEST, "性别须为 male 或 female"); + } + LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>(); wrapper.eq(SysUser::getId, userId); - if (nickname != null) wrapper.set(SysUser::getNickname, nickname); - if (city != null) wrapper.set(SysUser::getCity, city); - if (avatar != null) wrapper.set(SysUser::getAvatar, avatar); - if (gender != null) wrapper.set(SysUser::getGender, gender); + wrapper.set(SysUser::getNickname, dto.getNickname().trim()); + wrapper.set(SysUser::getCity, StringUtils.hasText(dto.getCity()) ? dto.getCity().trim() : null); + wrapper.set(SysUser::getGender, StringUtils.hasText(g) ? g.trim() : null); + if (dto.getAvatar() != null) { + wrapper.set(SysUser::getAvatar, StringUtils.hasText(dto.getAvatar()) ? dto.getAvatar().trim() : null); + } sysUserMapper.update(null, wrapper); } diff --git a/lesingle-creation-frontend/src/api/public.ts b/lesingle-creation-frontend/src/api/public.ts index 2b8c281..67a3ff4 100644 --- a/lesingle-creation-frontend/src/api/public.ts +++ b/lesingle-creation-frontend/src/api/public.ts @@ -107,6 +107,8 @@ export interface PublicUser { phone: string | null; city: string | null; avatar: string | null; + /** 与后端 SysUser.gender 一致:male / female */ + gender?: string | null; tenantId: number; tenantCode: string; userSource: string; @@ -118,6 +120,14 @@ export interface PublicUser { childrenCount?: number; } +/** PUT /public/mine/profile 请求体,与后端 PublicProfileUpdateDto 对齐 */ +export interface PublicProfileUpdatePayload { + nickname: string; + city?: string; + gender?: string; + avatar?: string; +} + export interface LoginResponse { token: string; user: PublicUser; @@ -144,12 +154,8 @@ export const publicAuthApi = { export const publicProfileApi = { getProfile: (): Promise => publicApi.get("/public/mine/profile"), - updateProfile: (data: { - nickname?: string; - city?: string; - avatar?: string; - gender?: string; - }) => publicApi.put("/public/mine/profile", data), + updateProfile: (data: PublicProfileUpdatePayload) => + publicApi.put("/public/mine/profile", data), }; // ==================== 子女管理 ==================== diff --git a/lesingle-creation-frontend/src/views/public/mine/Index.vue b/lesingle-creation-frontend/src/views/public/mine/Index.vue index de47be0..ab43ef5 100644 --- a/lesingle-creation-frontend/src/views/public/mine/Index.vue +++ b/lesingle-creation-frontend/src/views/public/mine/Index.vue @@ -70,17 +70,27 @@ - - + + - + - + { return count > 0 ? `已创建 ${count} 个子女账号` : "为子女创建独立账号" }) +/** 与登录态 localStorage `public_user` 合并,避免改资料后其它页仍显示旧昵称 */ +const syncPublicUserCache = (patch: { nickname?: string; city?: string | null; gender?: string | null; avatar?: string | null }) => { + try { + const raw = localStorage.getItem("public_user") + if (!raw) return + const prev = JSON.parse(raw) as Record + localStorage.setItem( + "public_user", + JSON.stringify({ + ...prev, + ...(patch.nickname !== undefined && { nickname: patch.nickname }), + ...(patch.city !== undefined && { city: patch.city }), + ...(patch.gender !== undefined && { gender: patch.gender }), + ...(patch.avatar !== undefined && { avatar: patch.avatar }), + }), + ) + } catch { + /* ignore */ + } +} + const fetchProfile = async () => { try { user.value = await publicProfileApi.getProfile() editForm.nickname = user.value?.nickname || "" editForm.city = user.value?.city || "" editForm.gender = user.value?.gender || "" + syncPublicUserCache({ + nickname: user.value?.nickname, + city: user.value?.city ?? null, + gender: user.value?.gender ?? null, + avatar: user.value?.avatar ?? null, + }) } catch { message.error("获取个人信息失败,请重新登录") handleLogout() @@ -172,17 +209,21 @@ const fetchCounts = async () => { const handleSaveProfile = async () => { editLoading.value = true + const nickname = editForm.nickname.trim() + const payload: PublicProfileUpdatePayload = { + nickname, + ...(editForm.city?.trim() ? { city: editForm.city.trim() } : { city: "" }), + ...(editForm.gender ? { gender: editForm.gender } : { gender: "" }), + } try { - await publicProfileApi.updateProfile({ - nickname: editForm.nickname, - city: editForm.city || undefined, - gender: editForm.gender || undefined, - }) + await publicProfileApi.updateProfile(payload) message.success("保存成功") showEditModal.value = false - fetchProfile() - } catch { - message.error("保存失败") + await fetchProfile() + } catch (e: unknown) { + const msg = + (e as { response?: { data?: { message?: string } } })?.response?.data?.message || "保存失败" + message.error(msg) } finally { editLoading.value = false }