feat: 优化管理后台页面UI、修复TS编译错误、新增人生事件模块

- 优化 AI 配置列表页面:重构统计卡片、搜索表单、表格列展示
- 修复 3 处 TypeScript TS6133 编译错误,恢复构建
- 新增管理员修改密码和重置密码功能
- 优化小程序多个页面样式和交互
- 人生事件模块完善

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-10 23:23:09 +08:00
parent 60c63850ee
commit 755059807a
62 changed files with 4661 additions and 3019 deletions
@@ -1,5 +1,6 @@
package com.emotion.service.impl;
import com.emotion.dto.request.AdminChangePasswordRequest;
import com.emotion.dto.request.AdminLoginRequest;
import com.emotion.dto.response.AdminAuthResponse;
import com.emotion.dto.response.AdminInfoResponse;
@@ -201,4 +202,25 @@ public class AdminAuthServiceImpl implements AdminAuthService {
BeanUtils.copyProperties(admin, response);
return response;
}
@Override
public void changePassword(String adminId, AdminChangePasswordRequest request) {
Admin admin = adminService.getById(adminId);
if (admin == null) {
throw new AuthException("管理员不存在");
}
if (!passwordEncoder.matches(request.getOldPassword(), admin.getPassword())) {
throw new AuthException("原密码不正确");
}
admin.setPassword(passwordEncoder.encode(request.getNewPassword()));
adminService.updateById(admin);
// 清除该管理员的Redis token,强制重新登录
redisTemplate.delete(ADMIN_TOKEN_PREFIX + adminId);
redisTemplate.delete(ADMIN_REFRESH_TOKEN_PREFIX + adminId);
log.info("管理员修改密码成功: adminId={}", adminId);
}
}
@@ -13,7 +13,9 @@ import com.emotion.entity.Admin;
import com.emotion.exception.BusinessException;
import com.emotion.mapper.AdminMapper;
import com.emotion.service.AdminService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
@@ -29,11 +31,18 @@ import java.util.stream.Collectors;
* @date 2025-10-27
*/
@Service
@Slf4j
public class AdminServiceImpl extends ServiceImpl<AdminMapper, Admin> implements AdminService {
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
private final RedisTemplate<String, Object> redisTemplate;
public AdminServiceImpl(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
@Override
public PageResult<AdminResponse> getPageWithResponse(AdminPageRequest request) {
Page<Admin> page = new Page<>(request.getCurrent(), request.getSize());
@@ -238,6 +247,23 @@ public class AdminServiceImpl extends ServiceImpl<AdminMapper, Admin> implements
return this.getOne(wrapper);
}
@Override
public void resetPassword(String adminId, String newPassword) {
Admin admin = this.getById(adminId);
if (admin == null) {
throw new BusinessException("管理员不存在");
}
admin.setPassword(passwordEncoder.encode(newPassword));
this.updateById(admin);
// 清除该管理员的Redis token,强制重新登录
redisTemplate.delete("admin_token:" + adminId);
redisTemplate.delete("admin_refresh_token:" + adminId);
log.info("管理员重置密码成功: adminId={}", adminId);
}
private AdminResponse convertToResponse(Admin admin) {
AdminResponse response = new AdminResponse();
BeanUtils.copyProperties(admin, response);
@@ -21,7 +21,9 @@ import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.stream.Collectors;
@@ -40,6 +42,7 @@ public class LifeEventServiceImpl extends ServiceImpl<LifeEventMapper, LifeEvent
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private static final DateTimeFormatter ISO_FORMATTER = DateTimeFormatter.ISO_DATE_TIME;
private static final DateTimeFormatter DATE_ONLY_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
private static final DateTimeFormatter YEAR_MONTH_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM");
/**
* Coze工作流配置键 - AI疗愈
@@ -143,9 +146,12 @@ public class LifeEventServiceImpl extends ServiceImpl<LifeEventMapper, LifeEvent
event.setAiReply(request.getAiReply());
event.setEmotionType(request.getEmotionType());
event.setTags(request.getTags());
event.setTimeMode(StringUtils.hasText(request.getTimeMode()) ? request.getTimeMode() : "date");
event.setEventDateText(StringUtils.hasText(request.getEventDateText()) ? request.getEventDateText() : request.getEventDate());
// 解析事件日期,支持多种格式
event.setEventDate(parseEventDate(request.getEventDate()));
event.setEventDate(parseEventDate(request.getEventDate(), event.getTimeMode(), event.getEventDateText()));
event.setEventEndDate(parseEventEndDate(request.getEventEndDate(), event.getTimeMode()));
// 情绪评分
if (request.getEmotionScore() != null) {
@@ -251,8 +257,17 @@ public class LifeEventServiceImpl extends ServiceImpl<LifeEventMapper, LifeEvent
if (request.getTags() != null) {
event.setTags(request.getTags());
}
if (StringUtils.hasText(request.getTimeMode())) {
event.setTimeMode(request.getTimeMode());
}
if (request.getEventDateText() != null) {
event.setEventDateText(request.getEventDateText());
}
if (StringUtils.hasText(request.getEventDate())) {
event.setEventDate(parseEventDate(request.getEventDate()));
event.setEventDate(parseEventDate(request.getEventDate(), event.getTimeMode(), event.getEventDateText()));
}
if (request.getEventEndDate() != null) {
event.setEventEndDate(parseEventEndDate(request.getEventEndDate(), event.getTimeMode()));
}
if (request.getEmotionScore() != null) {
event.setEmotionScore(BigDecimal.valueOf(request.getEmotionScore()));
@@ -295,6 +310,9 @@ public class LifeEventServiceImpl extends ServiceImpl<LifeEventMapper, LifeEvent
if (event.getEventDate() != null) {
response.setEventDate(event.getEventDate().format(ISO_FORMATTER));
}
if (event.getEventEndDate() != null) {
response.setEventEndDate(event.getEventEndDate().format(ISO_FORMATTER));
}
if (event.getEmotionScore() != null) {
response.setEmotionScore(event.getEmotionScore().doubleValue());
}
@@ -314,30 +332,79 @@ public class LifeEventServiceImpl extends ServiceImpl<LifeEventMapper, LifeEvent
* @param dateStr 日期字符串
* @return 解析后的LocalDateTime,解析失败返回当前时间
*/
private LocalDateTime parseEventDate(String dateStr) {
if (!StringUtils.hasText(dateStr)) {
private LocalDateTime parseEventDate(String dateStr, String timeMode, String eventDateText) {
String source = StringUtils.hasText(dateStr) ? dateStr : eventDateText;
if (!StringUtils.hasText(source)) {
return LocalDateTime.now();
}
if ("month".equals(timeMode)) {
try {
return YearMonth.parse(source.substring(0, 7), YEAR_MONTH_FORMATTER).atDay(1).atStartOfDay();
} catch (Exception ignored) {
}
}
if ("season".equals(timeMode)) {
LocalDate seasonDate = parseSeasonStart(source);
if (seasonDate != null) {
return seasonDate.atStartOfDay();
}
}
// 尝试ISO格式 (yyyy-MM-ddTHH:mm:ss.SSSZ)
try {
return LocalDateTime.parse(dateStr, ISO_FORMATTER);
return LocalDateTime.parse(source, ISO_FORMATTER);
} catch (Exception ignored) {
}
// 尝试日期时间格式 (yyyy-MM-dd HH:mm:ss)
try {
return LocalDateTime.parse(dateStr, DATE_TIME_FORMATTER);
return LocalDateTime.parse(source, DATE_TIME_FORMATTER);
} catch (Exception ignored) {
}
// 尝试纯日期格式 (yyyy-MM-dd),时间设为当天开始
try {
return java.time.LocalDate.parse(dateStr, DATE_ONLY_FORMATTER).atStartOfDay();
return LocalDate.parse(source, DATE_ONLY_FORMATTER).atStartOfDay();
} catch (Exception ignored) {
}
// 所有格式都失败,返回当前时间
return LocalDateTime.now();
}
private LocalDateTime parseEventEndDate(String dateStr, String timeMode) {
if (!"range".equals(timeMode) || !StringUtils.hasText(dateStr)) {
return null;
}
return parseEventDate(dateStr, "date", dateStr);
}
private LocalDate parseSeasonStart(String value) {
try {
String[] parts = value.split("-");
int year = Integer.parseInt(parts[0]);
String season = parts.length > 1 ? parts[1] : "spring";
int month;
switch (season) {
case "summer":
month = 6;
break;
case "autumn":
month = 9;
break;
case "winter":
month = 12;
break;
case "spring":
default:
month = 3;
break;
}
return LocalDate.of(year, month, 1);
} catch (Exception ignored) {
return null;
}
}
}