feat: 新增隐藏备注记录显示开关并简化设置与日志菜单文案
- 设置页「外观设置」新增「显示已隐藏备注的记录」开关,默认关闭,切换即时保存 - 新增 GET/PUT /api/setting/show-hidden-data 接口,用户级偏好持久化于 t_user_preference - 收支记录列表与 Excel 导出按该偏好过滤,关闭时排除已隐藏备注的记录 - 移除「备注关键字搜索时总是跳过隐藏记录」逻辑,改由该偏好统一控制,统计备注搜索同步放开 - 列表内隐藏备注后,偏好关闭时该行即时移除,当前页清空自动回退上一页 - 外观设置面板拆分为两个浅灰底分组,两个配置项区分更清晰 - 顶部用户菜单与设置/日志页标题由「系统设置/系统日志」简化为「设置/日志」,同步中英日三语
This commit is contained in:
@@ -20,3 +20,13 @@ export function getExcludedDisplayStyle() {
|
|||||||
export function updateExcludedDisplayStyle(style: string) {
|
export function updateExcludedDisplayStyle(style: string) {
|
||||||
return request.put('/setting/excluded-display-style', { style })
|
return request.put('/setting/excluded-display-style', { style })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 查询当前用户是否在收支记录列表与导出中显示已隐藏备注的记录 */
|
||||||
|
export function getShowHiddenData() {
|
||||||
|
return request.get('/setting/show-hidden-data')
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 更新当前用户是否在收支记录列表与导出中显示已隐藏备注的记录 */
|
||||||
|
export function updateShowHiddenData(show: boolean) {
|
||||||
|
return request.put('/setting/show-hidden-data', { show })
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
// 外观设置面板:配置「不计入统计」记录在收支记录列表中的展示样式
|
// 外观设置面板:配置「不计入统计」记录在收支记录列表中的展示样式,以及是否显示已隐藏备注的记录
|
||||||
import { ref, onMounted, computed } from 'vue'
|
import { ref, onMounted, computed } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import {
|
import {
|
||||||
useMessage, NCard, NSpace, NButton, NIcon, NText, NRadioGroup, NRadio,
|
useMessage, NCard, NSpace, NButton, NIcon, NText, NRadioGroup, NRadio, NSwitch,
|
||||||
} from 'naive-ui'
|
} from 'naive-ui'
|
||||||
import { ColorPaletteOutline, SaveOutline } from '@vicons/ionicons5'
|
import { ColorPaletteOutline, SaveOutline } from '@vicons/ionicons5'
|
||||||
import { getExcludedDisplayStyle, updateExcludedDisplayStyle } from '../api/setting'
|
import { getExcludedDisplayStyle, updateExcludedDisplayStyle, getShowHiddenData, updateShowHiddenData } from '../api/setting'
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
@@ -16,6 +16,11 @@ const style = ref<string>('strikethrough')
|
|||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const saving = ref(false)
|
const saving = ref(false)
|
||||||
|
|
||||||
|
/** 是否显示已隐藏备注的记录,默认不显示 */
|
||||||
|
const showHiddenData = ref(false)
|
||||||
|
const loadingHidden = ref(false)
|
||||||
|
const savingHidden = ref(false)
|
||||||
|
|
||||||
/** 展示样式选项(随语言切换响应式更新) */
|
/** 展示样式选项(随语言切换响应式更新) */
|
||||||
const styleOptions = computed(() => [
|
const styleOptions = computed(() => [
|
||||||
{ label: t('display.strikethrough'), value: 'strikethrough' },
|
{ label: t('display.strikethrough'), value: 'strikethrough' },
|
||||||
@@ -49,7 +54,41 @@ async function handleSave() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(loadStyle)
|
/** 加载「显示已隐藏备注记录」偏好,失败时保持默认不显示 */
|
||||||
|
async function loadHiddenData() {
|
||||||
|
loadingHidden.value = true
|
||||||
|
try {
|
||||||
|
const res: any = await getShowHiddenData()
|
||||||
|
showHiddenData.value = !!res.data
|
||||||
|
} catch (e: any) {
|
||||||
|
// 静默处理,保持默认不显示
|
||||||
|
} finally {
|
||||||
|
loadingHidden.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 切换「显示已隐藏备注记录」偏好并即时保存,失败时回滚 */
|
||||||
|
async function handleHiddenChange(value: string | number | boolean) {
|
||||||
|
const next = value === true
|
||||||
|
const previous = showHiddenData.value
|
||||||
|
showHiddenData.value = next
|
||||||
|
savingHidden.value = true
|
||||||
|
try {
|
||||||
|
const res: any = await updateShowHiddenData(next)
|
||||||
|
showHiddenData.value = !!res.data
|
||||||
|
message.success(t('display.hiddenDataSaved'))
|
||||||
|
} catch (e: any) {
|
||||||
|
showHiddenData.value = previous
|
||||||
|
message.error(e.message || t('display.hiddenDataFailed'))
|
||||||
|
} finally {
|
||||||
|
savingHidden.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadStyle()
|
||||||
|
loadHiddenData()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -61,16 +100,14 @@ onMounted(loadStyle)
|
|||||||
</NSpace>
|
</NSpace>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<NText depth="3" style="font-size: 12px;">{{ t('display.hint') }}</NText>
|
<div class="config-group">
|
||||||
|
<div class="group-title">{{ t('display.label') }}</div>
|
||||||
<div class="style-row">
|
<NText depth="3" style="font-size: 12px; display: block; margin-bottom: 8px;">{{ t('display.hint') }}</NText>
|
||||||
<div class="filter-label">{{ t('display.label') }}</div>
|
|
||||||
<NRadioGroup v-model:value="style" :disabled="loading">
|
<NRadioGroup v-model:value="style" :disabled="loading">
|
||||||
<NRadio v-for="option in styleOptions" :key="option.value" :value="option.value">
|
<NRadio v-for="option in styleOptions" :key="option.value" :value="option.value">
|
||||||
{{ option.label }}
|
{{ option.label }}
|
||||||
</NRadio>
|
</NRadio>
|
||||||
</NRadioGroup>
|
</NRadioGroup>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 预览:按当前选择实时展示「不计入统计」记录的样式 -->
|
<!-- 预览:按当前选择实时展示「不计入统计」记录的样式 -->
|
||||||
<div class="preview">
|
<div class="preview">
|
||||||
@@ -95,6 +132,20 @@ onMounted(loadStyle)
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="config-group">
|
||||||
|
<div class="group-title">{{ t('display.hiddenDataLabel') }}</div>
|
||||||
|
<NSpace align="center" :size="12">
|
||||||
|
<NSwitch
|
||||||
|
:value="showHiddenData"
|
||||||
|
:disabled="loadingHidden"
|
||||||
|
:loading="savingHidden"
|
||||||
|
@update:value="handleHiddenChange"
|
||||||
|
/>
|
||||||
|
<NText depth="3" style="font-size: 12px;">{{ t('display.hiddenDataHint') }}</NText>
|
||||||
|
</NSpace>
|
||||||
|
</div>
|
||||||
|
|
||||||
<NSpace justify="end" style="margin-top: 16px; width: 100%;">
|
<NSpace justify="end" style="margin-top: 16px; width: 100%;">
|
||||||
<NButton type="primary" :loading="saving" @click="handleSave">
|
<NButton type="primary" :loading="saving" @click="handleSave">
|
||||||
@@ -108,9 +159,20 @@ onMounted(loadStyle)
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.style-row {
|
.config-group {
|
||||||
|
padding: 12px 14px;
|
||||||
|
background: #fafafc;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
.config-group + .config-group {
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
}
|
}
|
||||||
|
.group-title {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: #333;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
.filter-label {
|
.filter-label {
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
color: #999;
|
color: #999;
|
||||||
@@ -123,6 +185,7 @@ onMounted(loadStyle)
|
|||||||
border: 1px solid #efeff5;
|
border: 1px solid #efeff5;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
background: #fff;
|
||||||
}
|
}
|
||||||
.preview-head,
|
.preview-head,
|
||||||
.preview-row {
|
.preview-row {
|
||||||
|
|||||||
@@ -93,8 +93,8 @@ export default {
|
|||||||
tags: { zh: '标签管理', en: 'Tags', ja: 'タグ管理' },
|
tags: { zh: '标签管理', en: 'Tags', ja: 'タグ管理' },
|
||||||
rules: { zh: '规则管理', en: 'Rules', ja: 'ルール管理' },
|
rules: { zh: '规则管理', en: 'Rules', ja: 'ルール管理' },
|
||||||
emailBills: { zh: '邮箱账单', en: 'Email Bills', ja: 'メール請求' },
|
emailBills: { zh: '邮箱账单', en: 'Email Bills', ja: 'メール請求' },
|
||||||
logs: { zh: '系统日志', en: 'Logs', ja: 'システムログ' },
|
logs: { zh: '日志', en: 'Logs', ja: 'ログ' },
|
||||||
settings: { zh: '系统设置', en: 'Settings', ja: 'システム設定' },
|
settings: { zh: '设置', en: 'Settings', ja: '設定' },
|
||||||
user: { zh: '用户', en: 'User', ja: 'ユーザー' },
|
user: { zh: '用户', en: 'User', ja: 'ユーザー' },
|
||||||
logout: { zh: '退出', en: 'Sign Out', ja: 'ログアウト' },
|
logout: { zh: '退出', en: 'Sign Out', ja: 'ログアウト' },
|
||||||
},
|
},
|
||||||
@@ -135,7 +135,7 @@ export default {
|
|||||||
|
|
||||||
// 系统设置
|
// 系统设置
|
||||||
settings: {
|
settings: {
|
||||||
title: { zh: '系统设置', en: 'Settings', ja: 'システム設定' },
|
title: { zh: '设置', en: 'Settings', ja: '設定' },
|
||||||
accountSection: { zh: '账号安全', en: 'Account Security', ja: 'アカウントセキュリティ' },
|
accountSection: { zh: '账号安全', en: 'Account Security', ja: 'アカウントセキュリティ' },
|
||||||
databaseSection: { zh: '数据库配置', en: 'Database', ja: 'データベース' },
|
databaseSection: { zh: '数据库配置', en: 'Database', ja: 'データベース' },
|
||||||
aiSection: { zh: 'AI 模型配置', en: 'AI Model', ja: 'AI モデル' },
|
aiSection: { zh: 'AI 模型配置', en: 'AI Model', ja: 'AI モデル' },
|
||||||
@@ -192,6 +192,10 @@ export default {
|
|||||||
saveSuccess: { zh: '外观设置已保存', en: 'Appearance settings saved', ja: '外観設定を保存しました' },
|
saveSuccess: { zh: '外观设置已保存', en: 'Appearance settings saved', ja: '外観設定を保存しました' },
|
||||||
saveFailed: { zh: '保存外观设置失败', en: 'Failed to save appearance settings', ja: '外観設定の保存に失敗しました' },
|
saveFailed: { zh: '保存外观设置失败', en: 'Failed to save appearance settings', ja: '外観設定の保存に失敗しました' },
|
||||||
loadFailed: { zh: '加载外观设置失败', en: 'Failed to load appearance settings', ja: '外観設定の読み込みに失敗しました' },
|
loadFailed: { zh: '加载外观设置失败', en: 'Failed to load appearance settings', ja: '外観設定の読み込みに失敗しました' },
|
||||||
|
hiddenDataLabel: { zh: '显示已隐藏备注的记录', en: 'Show records with hidden remarks', ja: '備考を非表示にした記録を表示' },
|
||||||
|
hiddenDataHint: { zh: '关闭时,已隐藏备注的记录不会出现在收支记录列表与导出中', en: 'When off, records with hidden remarks are excluded from the transaction list and export', ja: 'オフの場合、備考を非表示にした記録は収支リストとエクスポートに表示されません' },
|
||||||
|
hiddenDataSaved: { zh: '已更新隐藏备注记录的显示设置', en: 'Hidden remark record display updated', ja: '非表示備考の記録表示を更新しました' },
|
||||||
|
hiddenDataFailed: { zh: '更新隐藏备注记录的显示设置失败', en: 'Failed to update hidden remark record display', ja: '非表示備考の記録表示の更新に失敗しました' },
|
||||||
},
|
},
|
||||||
|
|
||||||
// 注册设置
|
// 注册设置
|
||||||
@@ -281,7 +285,7 @@ export default {
|
|||||||
|
|
||||||
// 系统日志
|
// 系统日志
|
||||||
log: {
|
log: {
|
||||||
title: { zh: '系统日志', en: 'System Logs', ja: 'システムログ' },
|
title: { zh: '日志', en: 'Logs', ja: 'ログ' },
|
||||||
httpTab: { zh: '访问日志', en: 'Access Logs', ja: 'アクセスログ' },
|
httpTab: { zh: '访问日志', en: 'Access Logs', ja: 'アクセスログ' },
|
||||||
loginTab: { zh: '登录日志', en: 'Login Logs', ja: 'ログインログ' },
|
loginTab: { zh: '登录日志', en: 'Login Logs', ja: 'ログインログ' },
|
||||||
appTab: { zh: '运行日志', en: 'Application Logs', ja: 'アプリログ' },
|
appTab: { zh: '运行日志', en: 'Application Logs', ja: 'アプリログ' },
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { getTransactionPage, createTransaction, updateTransaction, deleteTransac
|
|||||||
import { getAiConfig, suggestByAi, suggestOneByAi, applyAiSuggestions, queryByAi, parseRecordByAi, parseCategoryByAi, parseTagByAi, parseChannelByAi } from '../api/ai'
|
import { getAiConfig, suggestByAi, suggestOneByAi, applyAiSuggestions, queryByAi, parseRecordByAi, parseCategoryByAi, parseTagByAi, parseChannelByAi } from '../api/ai'
|
||||||
import { createChannel } from '../api/channel'
|
import { createChannel } from '../api/channel'
|
||||||
import type { AiSuggestion, AiQueryResult, AiRecordResult, AiRecordImageResult } from '../api/ai'
|
import type { AiSuggestion, AiQueryResult, AiRecordResult, AiRecordImageResult } from '../api/ai'
|
||||||
import { getExcludedDisplayStyle } from '../api/setting'
|
import { getExcludedDisplayStyle, getShowHiddenData } from '../api/setting'
|
||||||
import { getCategoryList, createCategory } from '../api/category'
|
import { getCategoryList, createCategory } from '../api/category'
|
||||||
import { getTagList, createTag } from '../api/tag'
|
import { getTagList, createTag } from '../api/tag'
|
||||||
import { getChannelLabel } from '../utils/channel'
|
import { getChannelLabel } from '../utils/channel'
|
||||||
@@ -129,6 +129,8 @@ const pageExcludedSummary = computed(() => {
|
|||||||
const loading = ref(false) // 列表加载状态
|
const loading = ref(false) // 列表加载状态
|
||||||
// 「不计入统计」记录的展示样式:strikethrough-整行删除线(默认)、muted-灰色标签
|
// 「不计入统计」记录的展示样式:strikethrough-整行删除线(默认)、muted-灰色标签
|
||||||
const excludedDisplayStyle = ref<'strikethrough' | 'muted'>('strikethrough')
|
const excludedDisplayStyle = ref<'strikethrough' | 'muted'>('strikethrough')
|
||||||
|
// 是否显示已隐藏备注的记录:默认不显示,隐藏后从当前列表移除
|
||||||
|
const showHiddenData = ref(false)
|
||||||
const submitting = ref(false) // 表单提交状态
|
const submitting = ref(false) // 表单提交状态
|
||||||
const showModal = ref(false) // 新增/编辑弹窗显示状态
|
const showModal = ref(false) // 新增/编辑弹窗显示状态
|
||||||
const showImportModal = ref(false) // 账单导入弹窗显示状态
|
const showImportModal = ref(false) // 账单导入弹窗显示状态
|
||||||
@@ -492,6 +494,18 @@ async function setRemarkHidden(row: TransactionItem, hidden: boolean) {
|
|||||||
row.remarkHidden = hidden
|
row.remarkHidden = hidden
|
||||||
concealRemark(row.id)
|
concealRemark(row.id)
|
||||||
message.success(hidden ? t('transaction.remarkHiddenSuccess') : t('transaction.remarkShownSuccess'))
|
message.success(hidden ? t('transaction.remarkHiddenSuccess') : t('transaction.remarkShownSuccess'))
|
||||||
|
// 偏好为不显示时,隐藏后将该行从列表移除;当前页清空则回退一页
|
||||||
|
if (hidden && !showHiddenData.value) {
|
||||||
|
const index = transactions.value.findIndex((item) => item.id === row.id)
|
||||||
|
if (index >= 0) {
|
||||||
|
transactions.value.splice(index, 1)
|
||||||
|
total.value = Math.max(0, total.value - 1)
|
||||||
|
}
|
||||||
|
if (transactions.value.length === 0 && page.value > 1) {
|
||||||
|
page.value -= 1
|
||||||
|
fetchData()
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
message.error(e.message || t('common.operationFailed'))
|
message.error(e.message || t('common.operationFailed'))
|
||||||
}
|
}
|
||||||
@@ -647,6 +661,16 @@ async function loadExcludedDisplayStyle() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 加载「显示已隐藏备注记录」偏好,失败时保持默认不显示 */
|
||||||
|
async function loadShowHiddenData() {
|
||||||
|
try {
|
||||||
|
const res: any = await getShowHiddenData()
|
||||||
|
showHiddenData.value = !!res.data
|
||||||
|
} catch (e) {
|
||||||
|
// 静默处理,保持默认不显示
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** 加载分类列表 */
|
/** 加载分类列表 */
|
||||||
async function fetchCategories() {
|
async function fetchCategories() {
|
||||||
try {
|
try {
|
||||||
@@ -1999,6 +2023,7 @@ onMounted(() => {
|
|||||||
remarkHistory.load()
|
remarkHistory.load()
|
||||||
loadAiConfig()
|
loadAiConfig()
|
||||||
loadExcludedDisplayStyle()
|
loadExcludedDisplayStyle()
|
||||||
|
loadShowHiddenData()
|
||||||
fetchCategories()
|
fetchCategories()
|
||||||
fetchTags()
|
fetchTags()
|
||||||
loadChannels()
|
loadChannels()
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import org.springframework.web.bind.annotation.RestController;
|
|||||||
import cn.rosercode.finance.common.Result;
|
import cn.rosercode.finance.common.Result;
|
||||||
import cn.rosercode.finance.dto.BillSourceStatsRequest;
|
import cn.rosercode.finance.dto.BillSourceStatsRequest;
|
||||||
import cn.rosercode.finance.dto.ExcludedDisplayStyleRequest;
|
import cn.rosercode.finance.dto.ExcludedDisplayStyleRequest;
|
||||||
|
import cn.rosercode.finance.dto.ShowHiddenDataRequest;
|
||||||
import cn.rosercode.finance.service.UserPreferenceService;
|
import cn.rosercode.finance.service.UserPreferenceService;
|
||||||
import cn.rosercode.finance.util.UserContext;
|
import cn.rosercode.finance.util.UserContext;
|
||||||
|
|
||||||
@@ -84,4 +85,30 @@ public class UserSettingController {
|
|||||||
userPreferenceService.saveExcludedDisplayStyle(userId, request.getStyle());
|
userPreferenceService.saveExcludedDisplayStyle(userId, request.getStyle());
|
||||||
return Result.success(userPreferenceService.getExcludedDisplayStyle(userId));
|
return Result.success(userPreferenceService.getExcludedDisplayStyle(userId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询当前用户是否在收支记录列表与导出中显示已隐藏备注的记录。
|
||||||
|
*
|
||||||
|
* @return true-显示,false-不显示
|
||||||
|
*/
|
||||||
|
@GetMapping("/show-hidden-data")
|
||||||
|
@Operation(summary = "查询隐藏备注记录显示偏好", description = "读取当前用户是否在收支记录列表与导出中显示已隐藏备注的记录")
|
||||||
|
public Result<Boolean> getShowHiddenData() {
|
||||||
|
return Result.success(userPreferenceService.getShowHiddenData(UserContext.getUserId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新当前用户是否在收支记录列表与导出中显示已隐藏备注的记录。
|
||||||
|
*
|
||||||
|
* @param request 显示偏好更新请求
|
||||||
|
* @return 更新后的显示偏好
|
||||||
|
*/
|
||||||
|
@PutMapping("/show-hidden-data")
|
||||||
|
@Operation(summary = "更新隐藏备注记录显示偏好", description = "设置当前用户是否在收支记录列表与导出中显示已隐藏备注的记录")
|
||||||
|
public Result<Boolean> updateShowHiddenData(@RequestBody ShowHiddenDataRequest request) {
|
||||||
|
Long userId = UserContext.getUserId();
|
||||||
|
boolean show = Boolean.TRUE.equals(request.getShow());
|
||||||
|
userPreferenceService.saveShowHiddenData(userId, show);
|
||||||
|
return Result.success(userPreferenceService.getShowHiddenData(userId));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package cn.rosercode.finance.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 「显示已隐藏备注记录」偏好更新请求对象。
|
||||||
|
*
|
||||||
|
* @date 2026-09-23 09:18:55
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ShowHiddenDataRequest {
|
||||||
|
|
||||||
|
/** 是否在收支记录列表与导出中显示已隐藏备注的记录 */
|
||||||
|
private Boolean show;
|
||||||
|
}
|
||||||
@@ -40,4 +40,20 @@ public interface UserPreferenceService {
|
|||||||
* @param style 展示样式
|
* @param style 展示样式
|
||||||
*/
|
*/
|
||||||
void saveExcludedDisplayStyle(Long userId, String style);
|
void saveExcludedDisplayStyle(Long userId, String style);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取当前用户是否在收支记录列表与导出中显示已隐藏备注的记录,未配置时回退默认值(不显示)。
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @return true-显示,false-不显示
|
||||||
|
*/
|
||||||
|
boolean getShowHiddenData(Long userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存当前用户是否在收支记录列表与导出中显示已隐藏备注的记录。
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
* @param show 是否显示
|
||||||
|
*/
|
||||||
|
void saveShowHiddenData(Long userId, boolean show);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -285,10 +285,9 @@ public class StatisticsServiceImpl implements StatisticsService {
|
|||||||
if (query.getMaxAmount() != null) {
|
if (query.getMaxAmount() != null) {
|
||||||
wrapper.le("amount", query.getMaxAmount());
|
wrapper.le("amount", query.getMaxAmount());
|
||||||
}
|
}
|
||||||
// 备注关键字模糊匹配;备注已隐藏的记录不参与搜索
|
// 备注关键字模糊匹配
|
||||||
if (StringUtils.hasText(query.getRemark())) {
|
if (StringUtils.hasText(query.getRemark())) {
|
||||||
wrapper.like("remark", query.getRemark());
|
wrapper.like("remark", query.getRemark());
|
||||||
wrapper.and(w -> w.isNull("remark_hidden").or().eq("remark_hidden", false));
|
|
||||||
}
|
}
|
||||||
// 标签筛选(多选);同时勾选「未打标签」时与其取并集
|
// 标签筛选(多选);同时勾选「未打标签」时与其取并集
|
||||||
boolean hasTagIds = query.getTagIds() != null && !query.getTagIds().isEmpty();
|
boolean hasTagIds = query.getTagIds() != null && !query.getTagIds().isEmpty();
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import cn.rosercode.finance.mapper.TransactionTagMapper;
|
|||||||
import cn.rosercode.finance.service.ChannelService;
|
import cn.rosercode.finance.service.ChannelService;
|
||||||
import cn.rosercode.finance.service.TransactionGroupService;
|
import cn.rosercode.finance.service.TransactionGroupService;
|
||||||
import cn.rosercode.finance.service.TransactionService;
|
import cn.rosercode.finance.service.TransactionService;
|
||||||
|
import cn.rosercode.finance.service.UserPreferenceService;
|
||||||
import cn.rosercode.finance.util.TransactionDedupUtils;
|
import cn.rosercode.finance.util.TransactionDedupUtils;
|
||||||
import cn.rosercode.finance.vo.TagVO;
|
import cn.rosercode.finance.vo.TagVO;
|
||||||
import cn.rosercode.finance.vo.TransactionExportVO;
|
import cn.rosercode.finance.vo.TransactionExportVO;
|
||||||
@@ -70,6 +71,7 @@ public class TransactionServiceImpl implements TransactionService {
|
|||||||
private final TransactionGroupItemMapper transactionGroupItemMapper;
|
private final TransactionGroupItemMapper transactionGroupItemMapper;
|
||||||
private final ChannelService channelService;
|
private final ChannelService channelService;
|
||||||
private final TransactionGroupService transactionGroupService;
|
private final TransactionGroupService transactionGroupService;
|
||||||
|
private final UserPreferenceService userPreferenceService;
|
||||||
private final VoConverter voConverter;
|
private final VoConverter voConverter;
|
||||||
|
|
||||||
public TransactionServiceImpl(TransactionMapper transactionMapper,
|
public TransactionServiceImpl(TransactionMapper transactionMapper,
|
||||||
@@ -81,6 +83,7 @@ public class TransactionServiceImpl implements TransactionService {
|
|||||||
TransactionGroupItemMapper transactionGroupItemMapper,
|
TransactionGroupItemMapper transactionGroupItemMapper,
|
||||||
ChannelService channelService,
|
ChannelService channelService,
|
||||||
TransactionGroupService transactionGroupService,
|
TransactionGroupService transactionGroupService,
|
||||||
|
UserPreferenceService userPreferenceService,
|
||||||
VoConverter voConverter) {
|
VoConverter voConverter) {
|
||||||
this.transactionMapper = transactionMapper;
|
this.transactionMapper = transactionMapper;
|
||||||
this.categoryMapper = categoryMapper;
|
this.categoryMapper = categoryMapper;
|
||||||
@@ -91,6 +94,7 @@ public class TransactionServiceImpl implements TransactionService {
|
|||||||
this.transactionGroupItemMapper = transactionGroupItemMapper;
|
this.transactionGroupItemMapper = transactionGroupItemMapper;
|
||||||
this.channelService = channelService;
|
this.channelService = channelService;
|
||||||
this.transactionGroupService = transactionGroupService;
|
this.transactionGroupService = transactionGroupService;
|
||||||
|
this.userPreferenceService = userPreferenceService;
|
||||||
this.voConverter = voConverter;
|
this.voConverter = voConverter;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,10 +149,13 @@ public class TransactionServiceImpl implements TransactionService {
|
|||||||
if (query.getMaxAmount() != null) {
|
if (query.getMaxAmount() != null) {
|
||||||
wrapper.le(Transaction::getAmount, query.getMaxAmount());
|
wrapper.le(Transaction::getAmount, query.getMaxAmount());
|
||||||
}
|
}
|
||||||
// 按备注关键字模糊匹配;备注已隐藏的记录不参与搜索
|
// 按备注关键字模糊匹配
|
||||||
if (StringUtils.hasText(query.getRemark())) {
|
if (StringUtils.hasText(query.getRemark())) {
|
||||||
wrapper.like(Transaction::getRemark, query.getRemark())
|
wrapper.like(Transaction::getRemark, query.getRemark());
|
||||||
.and(w -> w.isNull(Transaction::getRemarkHidden)
|
}
|
||||||
|
// 是否显示已隐藏备注的记录由用户偏好控制,默认不显示
|
||||||
|
if (!userPreferenceService.getShowHiddenData(userId)) {
|
||||||
|
wrapper.and(w -> w.isNull(Transaction::getRemarkHidden)
|
||||||
.or().eq(Transaction::getRemarkHidden, false));
|
.or().eq(Transaction::getRemarkHidden, false));
|
||||||
}
|
}
|
||||||
// 按标签筛选:命中任一选中标签、有任意标签,或筛选无任何标签的记录(多条件取并集)
|
// 按标签筛选:命中任一选中标签、有任意标签,或筛选无任何标签的记录(多条件取并集)
|
||||||
@@ -802,10 +809,13 @@ public class TransactionServiceImpl implements TransactionService {
|
|||||||
if (query.getMaxAmount() != null) {
|
if (query.getMaxAmount() != null) {
|
||||||
wrapper.le(Transaction::getAmount, query.getMaxAmount());
|
wrapper.le(Transaction::getAmount, query.getMaxAmount());
|
||||||
}
|
}
|
||||||
// 按备注关键字模糊匹配;备注已隐藏的记录不参与搜索
|
// 按备注关键字模糊匹配
|
||||||
if (StringUtils.hasText(query.getRemark())) {
|
if (StringUtils.hasText(query.getRemark())) {
|
||||||
wrapper.like(Transaction::getRemark, query.getRemark())
|
wrapper.like(Transaction::getRemark, query.getRemark());
|
||||||
.and(w -> w.isNull(Transaction::getRemarkHidden)
|
}
|
||||||
|
// 是否显示已隐藏备注的记录由用户偏好控制,默认不显示
|
||||||
|
if (!userPreferenceService.getShowHiddenData(userId)) {
|
||||||
|
wrapper.and(w -> w.isNull(Transaction::getRemarkHidden)
|
||||||
.or().eq(Transaction::getRemarkHidden, false));
|
.or().eq(Transaction::getRemarkHidden, false));
|
||||||
}
|
}
|
||||||
// 按标签筛选:命中任一选中标签、有任意标签,或筛选无任何标签的记录(多条件取并集)
|
// 按标签筛选:命中任一选中标签、有任意标签,或筛选无任何标签的记录(多条件取并集)
|
||||||
|
|||||||
@@ -37,6 +37,9 @@ public class UserPreferenceServiceImpl implements UserPreferenceService {
|
|||||||
/** 偏好键:「不计入统计」记录的展示样式 */
|
/** 偏好键:「不计入统计」记录的展示样式 */
|
||||||
private static final String KEY_EXCLUDED_DISPLAY_STYLE = "excludedDisplayStyle";
|
private static final String KEY_EXCLUDED_DISPLAY_STYLE = "excludedDisplayStyle";
|
||||||
|
|
||||||
|
/** 偏好键:是否显示已隐藏备注的记录 */
|
||||||
|
private static final String KEY_SHOW_HIDDEN_DATA = "showHiddenData";
|
||||||
|
|
||||||
/** 默认计入统计的账单来源,其余来源默认不计入 */
|
/** 默认计入统计的账单来源,其余来源默认不计入 */
|
||||||
private static final Set<String> DEFAULT_INCLUDED_SOURCES = Set.of("WECHAT", "ALIPAY");
|
private static final Set<String> DEFAULT_INCLUDED_SOURCES = Set.of("WECHAT", "ALIPAY");
|
||||||
|
|
||||||
@@ -120,6 +123,29 @@ public class UserPreferenceServiceImpl implements UserPreferenceService {
|
|||||||
log.info("保存不计入统计展示样式: userId={}, style={}", userId, resolved);
|
log.info("保存不计入统计展示样式: userId={}, style={}", userId, resolved);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*
|
||||||
|
* 结果按用户缓存,未配置时默认不显示。
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Cacheable(cacheNames = CacheConfig.USER_PREFERENCE_CACHE, key = "'showHiddenData:' + #userId")
|
||||||
|
public boolean getShowHiddenData(Long userId) {
|
||||||
|
return Boolean.parseBoolean(findValue(userId, KEY_SHOW_HIDDEN_DATA));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*
|
||||||
|
* 保存后失效该用户的显示偏好缓存。
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@CacheEvict(cacheNames = CacheConfig.USER_PREFERENCE_CACHE, key = "'showHiddenData:' + #userId")
|
||||||
|
public void saveShowHiddenData(Long userId, boolean show) {
|
||||||
|
upsert(userId, KEY_SHOW_HIDDEN_DATA, Boolean.toString(show));
|
||||||
|
log.info("保存隐藏备注记录显示偏好: userId={}, show={}", userId, show);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询指定用户的某个偏好值。
|
* 查询指定用户的某个偏好值。
|
||||||
*
|
*
|
||||||
|
|||||||
Reference in New Issue
Block a user