feat: 表格分页支持统一配置并记忆各表格每页条数
- 后端新增聚合用户偏好 paginationPrefs(每页条数候选列表与默认每页条数),提供 GET/PUT /api/setting/pagination-prefs,默认候选 [10,20,50,100]、默认每页 20,非法值自动过滤并回退 - 后端新增 /category/page、/tag/page、/channel/page、/email-bill/configs/page、/email-bill/sender-rules/page 分页接口,原全量接口保持不变以免影响下拉、筛选、AI 与导出 - 前端新增共享 composables/usePageSizeOptions.ts 与 useTablePageSize.ts,所有表格统一取用候选列表并记住各自上次选择的每页条数,优先级为表格记忆 > 全局默认 > 20 - 各表格上次选择以聚合偏好 finance.pagination.tablePageSizes 存于本机 localStorage,收支记录页旧的 filterPrefs.pageSize 自动迁移 - 设置页外观设置新增「表格每页条数选项」分组,可增删候选值并配置默认每页条数,保存后全局即时生效 - 路由守卫在已登录时预加载分页偏好,保证表格页首次渲染即使用配置值 - 前端分类、标签、渠道、邮箱配置、发件人规则表格接入后端分页 - 补充中文、英文、日文三语文案
This commit is contained in:
@@ -16,6 +16,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import cn.rosercode.finance.common.PageResult;
|
||||
import cn.rosercode.finance.common.Result;
|
||||
import cn.rosercode.finance.dto.CategoryRequest;
|
||||
import cn.rosercode.finance.service.CategoryService;
|
||||
@@ -54,6 +55,24 @@ public class CategoryController {
|
||||
return Result.success(categoryService.list(userId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页获取分类列表
|
||||
*
|
||||
* @param type 分类类型(可选),用于按类型筛选
|
||||
* @param page 页码
|
||||
* @param size 每页大小
|
||||
* @return 分页结果
|
||||
*/
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "分页获取分类列表", description = "分页查询当前用户的分类,支持按类型筛选")
|
||||
public Result<PageResult<CategoryVO>> page(
|
||||
@Parameter(description = "分类类型,可选值:income/expense") @RequestParam(required = false) String type,
|
||||
@Parameter(description = "页码") @RequestParam(defaultValue = "1") int page,
|
||||
@Parameter(description = "每页大小") @RequestParam(defaultValue = "20") int size) {
|
||||
Long userId = UserContext.getUserId();
|
||||
return Result.success(categoryService.page(userId, type, page, size));
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建分类
|
||||
*
|
||||
|
||||
@@ -12,8 +12,10 @@ import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import cn.rosercode.finance.common.PageResult;
|
||||
import cn.rosercode.finance.common.Result;
|
||||
import cn.rosercode.finance.dto.ChannelRequest;
|
||||
import cn.rosercode.finance.service.ChannelService;
|
||||
@@ -49,6 +51,21 @@ public class ChannelController {
|
||||
return Result.success(channelService.list(UserContext.getUserId()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询当前用户的自定义渠道列表
|
||||
*
|
||||
* @param page 页码
|
||||
* @param size 每页大小
|
||||
* @return 分页结果(含使用账单数)
|
||||
*/
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "分页渠道列表", description = "分页查询当前用户的自定义交易渠道,含各自被使用的账单数")
|
||||
public Result<PageResult<ChannelVO>> page(
|
||||
@RequestParam(defaultValue = "1") int page,
|
||||
@RequestParam(defaultValue = "20") int size) {
|
||||
return Result.success(channelService.page(UserContext.getUserId(), page, size));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新建自定义渠道
|
||||
*
|
||||
|
||||
@@ -79,6 +79,22 @@ public class EmailBillController {
|
||||
return Result.success(configService.list(userId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询邮箱配置列表
|
||||
*
|
||||
* @param page 页码
|
||||
* @param size 每页大小
|
||||
* @return 分页结果
|
||||
*/
|
||||
@GetMapping("/configs/page")
|
||||
@Operation(summary = "分页查询邮箱配置列表", description = "分页获取当前用户的 IMAP 邮箱配置,授权码已脱敏")
|
||||
public Result<PageResult<EmailBillConfigVO>> pageConfigs(
|
||||
@Parameter(description = "页码") @RequestParam(defaultValue = "1") int page,
|
||||
@Parameter(description = "每页大小") @RequestParam(defaultValue = "20") int size) {
|
||||
Long userId = UserContext.getUserId();
|
||||
return Result.success(configService.page(userId, page, size));
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建邮箱配置
|
||||
*
|
||||
@@ -164,6 +180,22 @@ public class EmailBillController {
|
||||
return Result.success(senderRuleService.list(userId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询发件人规则列表
|
||||
*
|
||||
* @param page 页码
|
||||
* @param size 每页大小
|
||||
* @return 分页结果
|
||||
*/
|
||||
@GetMapping("/sender-rules/page")
|
||||
@Operation(summary = "分页查询发件人规则列表", description = "分页获取当前用户的发件人识别规则,按优先级排序")
|
||||
public Result<PageResult<EmailBillSenderRuleVO>> pageSenderRules(
|
||||
@Parameter(description = "页码") @RequestParam(defaultValue = "1") int page,
|
||||
@Parameter(description = "每页大小") @RequestParam(defaultValue = "20") int size) {
|
||||
Long userId = UserContext.getUserId();
|
||||
return Result.success(senderRuleService.page(userId, page, size));
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建发件人规则
|
||||
*
|
||||
|
||||
@@ -13,8 +13,10 @@ import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import cn.rosercode.finance.common.PageResult;
|
||||
import cn.rosercode.finance.common.Result;
|
||||
import cn.rosercode.finance.dto.TagRequest;
|
||||
import cn.rosercode.finance.service.TagService;
|
||||
@@ -51,6 +53,22 @@ public class TagController {
|
||||
return Result.success(tagService.list(userId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页获取标签列表
|
||||
*
|
||||
* @param page 页码
|
||||
* @param size 每页大小
|
||||
* @return 分页结果
|
||||
*/
|
||||
@GetMapping("/page")
|
||||
@Operation(summary = "分页获取标签列表", description = "分页查询当前用户的标签")
|
||||
public Result<PageResult<TagVO>> page(
|
||||
@Parameter(description = "页码") @RequestParam(defaultValue = "1") int page,
|
||||
@Parameter(description = "每页大小") @RequestParam(defaultValue = "20") int size) {
|
||||
Long userId = UserContext.getUserId();
|
||||
return Result.success(tagService.page(userId, page, size));
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建标签
|
||||
*
|
||||
|
||||
@@ -13,15 +13,17 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
import cn.rosercode.finance.common.Result;
|
||||
import cn.rosercode.finance.dto.BillSourceStatsRequest;
|
||||
import cn.rosercode.finance.dto.ExcludedDisplayStyleRequest;
|
||||
import cn.rosercode.finance.dto.PaginationPrefsRequest;
|
||||
import cn.rosercode.finance.dto.ShowHiddenDataRequest;
|
||||
import cn.rosercode.finance.service.UserPreferenceService;
|
||||
import cn.rosercode.finance.util.UserContext;
|
||||
import cn.rosercode.finance.vo.PaginationPrefsVO;
|
||||
|
||||
/**
|
||||
* 用户设置控制器
|
||||
*
|
||||
* <p>提供按当前登录用户存储的偏好配置读写接口,包含账单来源统计默认值与
|
||||
* 「不计入统计」记录的展示样式。</p>
|
||||
* <p>提供按当前登录用户存储的偏好配置读写接口,包含账单来源统计默认值、
|
||||
* 「不计入统计」记录的展示样式与表格分页偏好。</p>
|
||||
*
|
||||
* @date 2026-09-21 10:14:12
|
||||
*/
|
||||
@@ -111,4 +113,32 @@ public class UserSettingController {
|
||||
userPreferenceService.saveShowHiddenData(userId, show);
|
||||
return Result.success(userPreferenceService.getShowHiddenData(userId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询当前用户配置的表格分页偏好。
|
||||
*
|
||||
* @return 分页偏好(每页条数候选列表与默认每页条数),未配置时返回系统默认
|
||||
*/
|
||||
@GetMapping("/pagination-prefs")
|
||||
@Operation(summary = "查询分页偏好", description = "读取当前用户所有表格分页的每页条数候选列表与默认每页条数")
|
||||
public Result<PaginationPrefsVO> getPaginationPrefs() {
|
||||
return Result.success(userPreferenceService.getPaginationPrefs(UserContext.getUserId()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新当前用户配置的表格分页偏好。
|
||||
*
|
||||
* @param request 分页偏好更新请求
|
||||
* @return 更新后的分页偏好,空或非法时回退系统默认
|
||||
*/
|
||||
@PutMapping("/pagination-prefs")
|
||||
@Operation(summary = "更新分页偏好", description = "设置当前用户所有表格分页的每页条数候选列表与默认每页条数")
|
||||
public Result<PaginationPrefsVO> updatePaginationPrefs(@RequestBody PaginationPrefsRequest request) {
|
||||
Long userId = UserContext.getUserId();
|
||||
PaginationPrefsVO prefs = new PaginationPrefsVO();
|
||||
prefs.setOptions(request.getOptions());
|
||||
prefs.setDefaultSize(request.getDefaultSize());
|
||||
userPreferenceService.savePaginationPrefs(userId, prefs);
|
||||
return Result.success(userPreferenceService.getPaginationPrefs(userId));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package cn.rosercode.finance.dto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 表格分页偏好更新请求对象。
|
||||
*
|
||||
* @date 2026-09-23 11:05:25
|
||||
*/
|
||||
@Data
|
||||
public class PaginationPrefsRequest {
|
||||
|
||||
/** 每页条数候选列表 */
|
||||
private List<Integer> options;
|
||||
|
||||
/** 默认每页条数 */
|
||||
private Integer defaultSize;
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package cn.rosercode.finance.service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import cn.rosercode.finance.common.PageResult;
|
||||
import cn.rosercode.finance.dto.CategoryRequest;
|
||||
import cn.rosercode.finance.vo.CategoryVO;
|
||||
|
||||
@@ -27,6 +28,17 @@ public interface CategoryService {
|
||||
*/
|
||||
List<CategoryVO> listByType(Long userId, String type);
|
||||
|
||||
/**
|
||||
* 分页查询用户的分类,可按类型筛选。
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param type 分类类型(如 INCOME、EXPENSE),为空时查询全部
|
||||
* @param page 页码,从 1 开始
|
||||
* @param size 每页大小
|
||||
* @return 分页结果
|
||||
*/
|
||||
PageResult<CategoryVO> page(Long userId, String type, int page, int size);
|
||||
|
||||
/**
|
||||
* 创建新分类
|
||||
*
|
||||
|
||||
@@ -4,6 +4,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import cn.rosercode.finance.common.PageResult;
|
||||
import cn.rosercode.finance.dto.ChannelRequest;
|
||||
import cn.rosercode.finance.vo.ChannelOptionVO;
|
||||
import cn.rosercode.finance.vo.ChannelVO;
|
||||
@@ -23,6 +24,16 @@ public interface ChannelService {
|
||||
*/
|
||||
List<ChannelVO> list(Long userId);
|
||||
|
||||
/**
|
||||
* 分页查询当前用户的自定义渠道列表(含各自使用账单数)。
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param page 页码,从 1 开始
|
||||
* @param size 每页大小
|
||||
* @return 分页结果
|
||||
*/
|
||||
PageResult<ChannelVO> page(Long userId, int page, int size);
|
||||
|
||||
/**
|
||||
* 新建自定义渠道。
|
||||
*
|
||||
|
||||
@@ -2,6 +2,7 @@ package cn.rosercode.finance.service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import cn.rosercode.finance.common.PageResult;
|
||||
import cn.rosercode.finance.dto.EmailBillConfigRequest;
|
||||
import cn.rosercode.finance.entity.EmailBillConfig;
|
||||
import cn.rosercode.finance.vo.EmailBillConfigVO;
|
||||
@@ -21,6 +22,16 @@ public interface EmailBillConfigService {
|
||||
*/
|
||||
List<EmailBillConfigVO> list(Long userId);
|
||||
|
||||
/**
|
||||
* 分页查询当前用户的邮箱配置。
|
||||
*
|
||||
* @param userId 当前用户ID
|
||||
* @param page 页码,从 1 开始
|
||||
* @param size 每页大小
|
||||
* @return 分页结果
|
||||
*/
|
||||
PageResult<EmailBillConfigVO> page(Long userId, int page, int size);
|
||||
|
||||
/**
|
||||
* 新增邮箱配置。
|
||||
*
|
||||
|
||||
@@ -2,6 +2,7 @@ package cn.rosercode.finance.service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import cn.rosercode.finance.common.PageResult;
|
||||
import cn.rosercode.finance.dto.EmailBillSenderRuleRequest;
|
||||
import cn.rosercode.finance.entity.EmailBillSenderRule;
|
||||
import cn.rosercode.finance.vo.EmailBillSenderRuleVO;
|
||||
@@ -21,6 +22,16 @@ public interface EmailBillSenderRuleService {
|
||||
*/
|
||||
List<EmailBillSenderRuleVO> list(Long userId);
|
||||
|
||||
/**
|
||||
* 分页查询当前用户的发件人规则。
|
||||
*
|
||||
* @param userId 当前用户ID
|
||||
* @param page 页码,从 1 开始
|
||||
* @param size 每页大小
|
||||
* @return 分页结果,按优先级升序
|
||||
*/
|
||||
PageResult<EmailBillSenderRuleVO> page(Long userId, int page, int size);
|
||||
|
||||
/**
|
||||
* 新增发件人规则。
|
||||
*
|
||||
|
||||
@@ -2,6 +2,7 @@ package cn.rosercode.finance.service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import cn.rosercode.finance.common.PageResult;
|
||||
import cn.rosercode.finance.dto.TagRequest;
|
||||
import cn.rosercode.finance.vo.TagVO;
|
||||
|
||||
@@ -20,6 +21,16 @@ public interface TagService {
|
||||
*/
|
||||
List<TagVO> list(Long userId);
|
||||
|
||||
/**
|
||||
* 分页查询用户的标签列表。
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param page 页码,从 1 开始
|
||||
* @param size 每页大小
|
||||
* @return 分页结果
|
||||
*/
|
||||
PageResult<TagVO> page(Long userId, int page, int size);
|
||||
|
||||
/**
|
||||
* 创建新标签
|
||||
*
|
||||
|
||||
@@ -2,6 +2,8 @@ package cn.rosercode.finance.service;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import cn.rosercode.finance.vo.PaginationPrefsVO;
|
||||
|
||||
/**
|
||||
* 用户偏好服务接口,提供按用户存储的偏好配置读写。
|
||||
*
|
||||
@@ -56,4 +58,20 @@ public interface UserPreferenceService {
|
||||
* @param show 是否显示
|
||||
*/
|
||||
void saveShowHiddenData(Long userId, boolean show);
|
||||
|
||||
/**
|
||||
* 获取当前用户配置的表格分页偏好(每页条数候选列表与默认每页条数),未配置或非法时回退默认值。
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @return 分页偏好
|
||||
*/
|
||||
PaginationPrefsVO getPaginationPrefs(Long userId);
|
||||
|
||||
/**
|
||||
* 保存当前用户配置的表格分页偏好,空或非法时回退默认值。
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param prefs 分页偏好
|
||||
*/
|
||||
void savePaginationPrefs(Long userId, PaginationPrefsVO prefs);
|
||||
}
|
||||
|
||||
@@ -4,11 +4,13 @@ import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import cn.rosercode.finance.common.BusinessException;
|
||||
import cn.rosercode.finance.common.PageResult;
|
||||
import cn.rosercode.finance.converter.VoConverter;
|
||||
import cn.rosercode.finance.dto.CategoryRequest;
|
||||
import cn.rosercode.finance.entity.Category;
|
||||
@@ -60,6 +62,28 @@ public class CategoryServiceImpl implements CategoryService {
|
||||
return categories.stream().map(voConverter::toCategoryVO).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* 按排序字段和ID升序分页,type 为空时查询全部类型。
|
||||
*/
|
||||
@Override
|
||||
public PageResult<CategoryVO> page(Long userId, String type, int page, int size) {
|
||||
LambdaQueryWrapper<Category> wrapper = new LambdaQueryWrapper<Category>()
|
||||
.eq(Category::getUserId, userId);
|
||||
if (type != null && !type.isEmpty()) {
|
||||
wrapper.eq(Category::getType, type);
|
||||
}
|
||||
wrapper.orderByAsc(Category::getSortOrder).orderByAsc(Category::getId);
|
||||
int pageNo = page < 1 ? 1 : page;
|
||||
int pageSize = size < 1 ? 20 : size;
|
||||
Page<Category> result = categoryMapper.selectPage(new Page<>(pageNo, pageSize), wrapper);
|
||||
List<CategoryVO> records = result.getRecords().stream()
|
||||
.map(voConverter::toCategoryVO)
|
||||
.collect(Collectors.toList());
|
||||
return new PageResult<>(records, result.getTotal(), result.getCurrent(), result.getSize());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
|
||||
@@ -9,11 +9,13 @@ import java.util.Set;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import cn.rosercode.finance.common.BusinessException;
|
||||
import cn.rosercode.finance.common.PageResult;
|
||||
import cn.rosercode.finance.dto.ChannelRequest;
|
||||
import cn.rosercode.finance.entity.Channel;
|
||||
import cn.rosercode.finance.entity.Transaction;
|
||||
@@ -69,16 +71,48 @@ public class ChannelServiceImpl implements ChannelService {
|
||||
.orderByAsc(Channel::getId));
|
||||
List<ChannelVO> result = new ArrayList<>();
|
||||
for (Channel channel : channels) {
|
||||
ChannelVO vo = new ChannelVO();
|
||||
vo.setId(channel.getId());
|
||||
vo.setCode(channel.getCode());
|
||||
vo.setName(channel.getName());
|
||||
vo.setTransactionCount(countTransactions(userId, channel.getCode()));
|
||||
result.add(vo);
|
||||
result.add(toChannelVO(userId, channel));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* 按排序字段和ID升序分页,并对当前页渠道逐条统计使用账单数。
|
||||
*/
|
||||
@Override
|
||||
public PageResult<ChannelVO> page(Long userId, int page, int size) {
|
||||
LambdaQueryWrapper<Channel> wrapper = new LambdaQueryWrapper<Channel>()
|
||||
.eq(Channel::getUserId, userId)
|
||||
.orderByAsc(Channel::getSortOrder)
|
||||
.orderByAsc(Channel::getId);
|
||||
int pageNo = page < 1 ? 1 : page;
|
||||
int pageSize = size < 1 ? 20 : size;
|
||||
Page<Channel> result = channelMapper.selectPage(new Page<>(pageNo, pageSize), wrapper);
|
||||
List<ChannelVO> records = new ArrayList<>();
|
||||
for (Channel channel : result.getRecords()) {
|
||||
records.add(toChannelVO(userId, channel));
|
||||
}
|
||||
return new PageResult<>(records, result.getTotal(), result.getCurrent(), result.getSize());
|
||||
}
|
||||
|
||||
/**
|
||||
* 将渠道实体转换为视图对象,并填充使用账单数。
|
||||
*
|
||||
* @param userId 用户ID
|
||||
* @param channel 渠道实体
|
||||
* @return 渠道视图对象
|
||||
*/
|
||||
private ChannelVO toChannelVO(Long userId, Channel channel) {
|
||||
ChannelVO vo = new ChannelVO();
|
||||
vo.setId(channel.getId());
|
||||
vo.setCode(channel.getCode());
|
||||
vo.setName(channel.getName());
|
||||
vo.setTransactionCount(countTransactions(userId, channel.getCode()));
|
||||
return vo;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
|
||||
@@ -5,6 +5,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@@ -17,6 +18,7 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import cn.rosercode.finance.common.BusinessException;
|
||||
import cn.rosercode.finance.common.PageResult;
|
||||
import cn.rosercode.finance.converter.VoConverter;
|
||||
import cn.rosercode.finance.dto.EmailBillConfigRequest;
|
||||
import cn.rosercode.finance.email.EmailBillMonitorManager;
|
||||
@@ -105,6 +107,26 @@ public class EmailBillConfigServiceImpl implements EmailBillConfigService {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* 按配置ID升序分页。
|
||||
*/
|
||||
@Override
|
||||
public PageResult<EmailBillConfigVO> page(Long userId, int page, int size) {
|
||||
LambdaQueryWrapper<EmailBillConfig> wrapper = new LambdaQueryWrapper<EmailBillConfig>()
|
||||
.eq(EmailBillConfig::getUserId, userId)
|
||||
.orderByAsc(EmailBillConfig::getId);
|
||||
int pageNo = page < 1 ? 1 : page;
|
||||
int pageSize = size < 1 ? 20 : size;
|
||||
Page<EmailBillConfig> result = configMapper.selectPage(new Page<>(pageNo, pageSize), wrapper);
|
||||
List<EmailBillConfigVO> records = new ArrayList<>();
|
||||
for (EmailBillConfig config : result.getRecords()) {
|
||||
records.add(toVO(config));
|
||||
}
|
||||
return new PageResult<>(records, result.getTotal(), result.getCurrent(), result.getSize());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
|
||||
@@ -4,6 +4,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@@ -13,6 +14,7 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import cn.rosercode.finance.common.BusinessException;
|
||||
import cn.rosercode.finance.common.PageResult;
|
||||
import cn.rosercode.finance.converter.VoConverter;
|
||||
import cn.rosercode.finance.dto.EmailBillSenderRuleRequest;
|
||||
import cn.rosercode.finance.entity.Category;
|
||||
@@ -72,6 +74,27 @@ public class EmailBillSenderRuleServiceImpl implements EmailBillSenderRuleServic
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* 按优先级和ID升序分页。
|
||||
*/
|
||||
@Override
|
||||
public PageResult<EmailBillSenderRuleVO> page(Long userId, int page, int size) {
|
||||
LambdaQueryWrapper<EmailBillSenderRule> wrapper = new LambdaQueryWrapper<EmailBillSenderRule>()
|
||||
.eq(EmailBillSenderRule::getUserId, userId)
|
||||
.orderByAsc(EmailBillSenderRule::getSortOrder)
|
||||
.orderByAsc(EmailBillSenderRule::getId);
|
||||
int pageNo = page < 1 ? 1 : page;
|
||||
int pageSize = size < 1 ? 20 : size;
|
||||
Page<EmailBillSenderRule> result = senderRuleMapper.selectPage(new Page<>(pageNo, pageSize), wrapper);
|
||||
List<EmailBillSenderRuleVO> records = new ArrayList<>();
|
||||
for (EmailBillSenderRule rule : result.getRecords()) {
|
||||
records.add(toVO(rule));
|
||||
}
|
||||
return new PageResult<>(records, result.getTotal(), result.getCurrent(), result.getSize());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
||||
@@ -4,11 +4,13 @@ import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import cn.rosercode.finance.common.BusinessException;
|
||||
import cn.rosercode.finance.common.PageResult;
|
||||
import cn.rosercode.finance.converter.VoConverter;
|
||||
import cn.rosercode.finance.dto.TagRequest;
|
||||
import cn.rosercode.finance.entity.RuleTag;
|
||||
@@ -56,6 +58,26 @@ public class TagServiceImpl implements TagService {
|
||||
return tags.stream().map(voConverter::toTagVO).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* 按排序字段和ID升序分页。
|
||||
*/
|
||||
@Override
|
||||
public PageResult<TagVO> page(Long userId, int page, int size) {
|
||||
LambdaQueryWrapper<Tag> wrapper = new LambdaQueryWrapper<Tag>()
|
||||
.eq(Tag::getUserId, userId)
|
||||
.orderByAsc(Tag::getSortOrder)
|
||||
.orderByAsc(Tag::getId);
|
||||
int pageNo = page < 1 ? 1 : page;
|
||||
int pageSize = size < 1 ? 20 : size;
|
||||
Page<Tag> result = tagMapper.selectPage(new Page<>(pageNo, pageSize), wrapper);
|
||||
List<TagVO> records = result.getRecords().stream()
|
||||
.map(voConverter::toTagVO)
|
||||
.collect(Collectors.toList());
|
||||
return new PageResult<>(records, result.getTotal(), result.getCurrent(), result.getSize());
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
|
||||
@@ -2,8 +2,10 @@ package cn.rosercode.finance.service.impl;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
@@ -19,6 +21,7 @@ import cn.rosercode.finance.entity.UserPreference;
|
||||
import cn.rosercode.finance.enums.BillType;
|
||||
import cn.rosercode.finance.mapper.UserPreferenceMapper;
|
||||
import cn.rosercode.finance.service.UserPreferenceService;
|
||||
import cn.rosercode.finance.vo.PaginationPrefsVO;
|
||||
|
||||
/**
|
||||
* 用户偏好服务实现类,以键值形式读写每个用户的偏好配置。
|
||||
@@ -40,6 +43,24 @@ public class UserPreferenceServiceImpl implements UserPreferenceService {
|
||||
/** 偏好键:是否显示已隐藏备注的记录 */
|
||||
private static final String KEY_SHOW_HIDDEN_DATA = "showHiddenData";
|
||||
|
||||
/** 偏好键:表格分页偏好(每页条数候选列表与默认每页条数) */
|
||||
private static final String KEY_PAGINATION_PREFS = "paginationPrefs";
|
||||
|
||||
/** 默认的表格分页每页条数候选列表 */
|
||||
private static final List<Integer> DEFAULT_PAGE_SIZE_OPTIONS = List.of(10, 20, 50, 100);
|
||||
|
||||
/** 默认每页条数 */
|
||||
private static final int DEFAULT_PAGE_SIZE = 20;
|
||||
|
||||
/** 每页条数下限 */
|
||||
private static final int MIN_PAGE_SIZE = 1;
|
||||
|
||||
/** 每页条数上限 */
|
||||
private static final int MAX_PAGE_SIZE = 1000;
|
||||
|
||||
/** 每页条数候选值最大数量 */
|
||||
private static final int MAX_PAGE_SIZE_OPTIONS = 10;
|
||||
|
||||
/** 默认计入统计的账单来源,其余来源默认不计入 */
|
||||
private static final Set<String> DEFAULT_INCLUDED_SOURCES = Set.of("WECHAT", "ALIPAY");
|
||||
|
||||
@@ -146,6 +167,59 @@ public class UserPreferenceServiceImpl implements UserPreferenceService {
|
||||
log.info("保存隐藏备注记录显示偏好: userId={}, show={}", userId, show);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* 结果按用户缓存,未配置或取值非法时回退默认值。
|
||||
*/
|
||||
@Override
|
||||
@Cacheable(cacheNames = CacheConfig.USER_PREFERENCE_CACHE, key = "'paginationPrefs:' + #userId")
|
||||
public PaginationPrefsVO getPaginationPrefs(Long userId) {
|
||||
return normalizePaginationPrefs(parsePaginationPrefs(findValue(userId, KEY_PAGINATION_PREFS)));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* 保存后失效该用户的分页偏好缓存。
|
||||
*/
|
||||
@Override
|
||||
@CacheEvict(cacheNames = CacheConfig.USER_PREFERENCE_CACHE, key = "'paginationPrefs:' + #userId")
|
||||
public void savePaginationPrefs(Long userId, PaginationPrefsVO prefs) {
|
||||
PaginationPrefsVO normalized = normalizePaginationPrefs(prefs);
|
||||
upsert(userId, KEY_PAGINATION_PREFS, toJson(normalized));
|
||||
log.info("保存表格分页偏好: userId={}, options={}, defaultSize={}",
|
||||
userId, normalized.getOptions(), normalized.getDefaultSize());
|
||||
}
|
||||
|
||||
/**
|
||||
* 规范化分页偏好:候选列表过滤非正整数与越界值、去重升序并限制数量(为空回退默认);
|
||||
* 默认每页条数须为候选列表内的正整数,否则回退候选列表首项。
|
||||
*
|
||||
* @param prefs 原始分页偏好
|
||||
* @return 规范化后的分页偏好
|
||||
*/
|
||||
private PaginationPrefsVO normalizePaginationPrefs(PaginationPrefsVO prefs) {
|
||||
List<Integer> options = prefs != null ? prefs.getOptions() : null;
|
||||
List<Integer> normalizedOptions = options == null ? List.of() : options.stream()
|
||||
.filter(value -> value != null && value >= MIN_PAGE_SIZE && value <= MAX_PAGE_SIZE)
|
||||
.distinct()
|
||||
.sorted()
|
||||
.limit(MAX_PAGE_SIZE_OPTIONS)
|
||||
.collect(Collectors.toList());
|
||||
if (normalizedOptions.isEmpty()) {
|
||||
normalizedOptions = DEFAULT_PAGE_SIZE_OPTIONS;
|
||||
}
|
||||
// 未配置或未传默认值时回退系统默认每页条数,再校验其是否在候选列表内
|
||||
Integer rawDefault = prefs != null ? prefs.getDefaultSize() : null;
|
||||
int defaultSize = rawDefault != null ? rawDefault : DEFAULT_PAGE_SIZE;
|
||||
int resolvedDefault = normalizedOptions.contains(defaultSize) ? defaultSize : normalizedOptions.get(0);
|
||||
PaginationPrefsVO result = new PaginationPrefsVO();
|
||||
result.setOptions(List.copyOf(normalizedOptions));
|
||||
result.setDefaultSize(resolvedDefault);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询指定用户的某个偏好值。
|
||||
*
|
||||
@@ -202,6 +276,24 @@ public class UserPreferenceServiceImpl implements UserPreferenceService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 JSON 字符串解析为分页偏好,解析失败或为空时返回 null。
|
||||
*
|
||||
* @param json JSON 字符串
|
||||
* @return 分页偏好,解析失败时为 null
|
||||
*/
|
||||
private PaginationPrefsVO parsePaginationPrefs(String json) {
|
||||
if (json == null || json.isBlank()) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return objectMapper.readValue(json, PaginationPrefsVO.class);
|
||||
} catch (JsonProcessingException e) {
|
||||
log.warn("解析表格分页偏好失败:{}", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将对象序列化为 JSON 字符串。
|
||||
*
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package cn.rosercode.finance.vo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 表格分页用户偏好视图对象,包含每页条数候选列表与默认每页条数。
|
||||
*
|
||||
* @date 2026-09-23 11:05:25
|
||||
*/
|
||||
@Data
|
||||
public class PaginationPrefsVO {
|
||||
|
||||
/** 每页条数候选列表,升序去重 */
|
||||
private List<Integer> options;
|
||||
|
||||
/** 默认每页条数,必为候选列表中的值 */
|
||||
private Integer defaultSize;
|
||||
}
|
||||
Reference in New Issue
Block a user