feat: 外观设置分页与展示样式改为即时保存
- 分页每页条数候选与默认值变更后防抖 500ms 自动保存,移除分组内独立「保存」按钮 - 「不计入统计」展示样式切换即保存,失败回滚到上一值,移除面板底部「保存」按钮 - 自动保存增加初始化与回填保护、过期响应丢弃,候选删空时不提交以免覆盖后端值 - 更新中英日三语提示文案,清理不再使用的保存相关文案
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
// 外观设置面板:配置「不计入统计」记录在收支记录列表中的展示样式,以及是否显示已隐藏备注的记录
|
||||
import { ref, onMounted, computed, watch } from 'vue'
|
||||
import { ref, onMounted, computed, watch, nextTick } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import {
|
||||
useMessage, NCard, NSpace, NButton, NIcon, NText, NRadioGroup, NRadio, NSwitch, NDynamicTags, NSelect,
|
||||
useMessage, NCard, NSpace, NIcon, NText, NRadioGroup, NRadio, NSwitch, NDynamicTags, NSelect,
|
||||
} from 'naive-ui'
|
||||
import { ColorPaletteOutline, SaveOutline } from '@vicons/ionicons5'
|
||||
import { ColorPaletteOutline } from '@vicons/ionicons5'
|
||||
import {
|
||||
getExcludedDisplayStyle, updateExcludedDisplayStyle, getShowHiddenData, updateShowHiddenData,
|
||||
getPaginationPrefs, updatePaginationPrefs,
|
||||
@@ -18,18 +18,25 @@ const { t } = useI18n()
|
||||
/** 当前展示样式:strikethrough-删除线(默认)、muted-灰色标签 */
|
||||
const style = ref<string>('strikethrough')
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
|
||||
/** 是否显示已隐藏备注的记录,默认不显示 */
|
||||
const showHiddenData = ref(false)
|
||||
const loadingHidden = ref(false)
|
||||
const savingHidden = ref(false)
|
||||
|
||||
/** 表格分页每页条数候选列表(字符串标签形式,保存时解析为数字) */
|
||||
/** 表格分页每页条数候选列表(字符串标签形式,提交时解析为数字) */
|
||||
const pageSizeTags = ref<string[]>([])
|
||||
/** 默认每页条数 */
|
||||
const pageSizeDefault = ref<number>(DEFAULT_PAGE_SIZE)
|
||||
const savingPageSizes = ref(false)
|
||||
|
||||
/** 初次加载完成后才允许自动保存,避免加载时误提交 */
|
||||
let paginationReady = false
|
||||
/** 回填后端响应期间跳过自动保存,避免循环提交 */
|
||||
let syncingFromServer = false
|
||||
/** 自动保存防抖计时器 */
|
||||
let pageSizeSaveTimer: ReturnType<typeof setTimeout> | null = null
|
||||
/** 保存请求序号,用于丢弃过期响应 */
|
||||
let pageSizeSaveSeq = 0
|
||||
|
||||
/** 候选列表解析为合法正整数(升序去重),用于默认值下拉 */
|
||||
const parsedPageSizes = computed(() =>
|
||||
@@ -41,11 +48,13 @@ const parsedPageSizes = computed(() =>
|
||||
/** 默认每页条数下拉选项(来自候选列表) */
|
||||
const defaultSizeOptions = computed(() => parsedPageSizes.value.map((v) => ({ label: String(v), value: v })))
|
||||
|
||||
// 候选列表变更后,若默认值不在其中则回退为首项
|
||||
watch(parsedPageSizes, (list) => {
|
||||
// 候选列表或默认值变更后:先校正失效的默认值,再防抖自动保存
|
||||
watch([parsedPageSizes, pageSizeDefault], ([list]) => {
|
||||
if (syncingFromServer) return
|
||||
if (list.length && !list.includes(pageSizeDefault.value)) {
|
||||
pageSizeDefault.value = list[0]
|
||||
}
|
||||
schedulePageSizeSave()
|
||||
})
|
||||
|
||||
/** 展示样式选项(随语言切换响应式更新) */
|
||||
@@ -67,17 +76,17 @@ async function loadStyle() {
|
||||
}
|
||||
}
|
||||
|
||||
/** 保存展示样式 */
|
||||
async function handleSave() {
|
||||
saving.value = true
|
||||
/** 切换展示样式并即时保存,失败时回滚到上一值 */
|
||||
async function handleStyleChange(value: string | number) {
|
||||
const next = String(value)
|
||||
const previous = style.value
|
||||
style.value = next
|
||||
try {
|
||||
const res: any = await updateExcludedDisplayStyle(style.value)
|
||||
style.value = res.data || style.value
|
||||
message.success(t('display.saveSuccess'))
|
||||
const res: any = await updateExcludedDisplayStyle(next)
|
||||
style.value = res.data || next
|
||||
} catch (e: any) {
|
||||
style.value = previous
|
||||
message.error(e.message || t('display.saveFailed'))
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,12 +121,16 @@ async function handleHiddenChange(value: string | number | boolean) {
|
||||
}
|
||||
}
|
||||
|
||||
/** 应用后端返回的分页偏好到表单 */
|
||||
/** 应用后端返回的分页偏好到表单,回填期间跳过自动保存,避免循环提交 */
|
||||
function applyPaginationPrefs(data: any, fallbackOptions: number[], fallbackDefault: number) {
|
||||
syncingFromServer = true
|
||||
const list: number[] = Array.isArray(data?.options) ? data.options : fallbackOptions
|
||||
pageSizeTags.value = list.map((v) => String(v))
|
||||
const def = Number(data?.defaultSize)
|
||||
pageSizeDefault.value = list.includes(def) ? def : (list[0] ?? fallbackDefault)
|
||||
void nextTick(() => {
|
||||
syncingFromServer = false
|
||||
})
|
||||
}
|
||||
|
||||
/** 加载表格分页偏好,失败时回退默认值 */
|
||||
@@ -127,27 +140,40 @@ async function loadPageSizes() {
|
||||
applyPaginationPrefs(res?.data, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PAGE_SIZE)
|
||||
} catch (e: any) {
|
||||
applyPaginationPrefs(null, DEFAULT_PAGE_SIZE_OPTIONS, DEFAULT_PAGE_SIZE)
|
||||
} finally {
|
||||
paginationReady = true
|
||||
}
|
||||
}
|
||||
|
||||
/** 保存表格分页偏好(候选列表 + 默认每页条数),成功后刷新全局共享配置 */
|
||||
/** 防抖调度分页偏好自动保存,加载完成前或回填响应期间不触发 */
|
||||
function schedulePageSizeSave() {
|
||||
if (!paginationReady || syncingFromServer) return
|
||||
if (pageSizeSaveTimer) clearTimeout(pageSizeSaveTimer)
|
||||
pageSizeSaveTimer = setTimeout(() => {
|
||||
pageSizeSaveTimer = null
|
||||
void handleSavePageSizes()
|
||||
}, 500)
|
||||
}
|
||||
|
||||
/** 自动保存分页偏好(候选列表 + 默认每页条数),成功后刷新全局共享配置 */
|
||||
async function handleSavePageSizes() {
|
||||
const options = parsedPageSizes.value
|
||||
// 候选被删空时保持后端原值,避免回退系统默认覆盖用户配置
|
||||
if (!options.length) {
|
||||
message.error(t('display.pageSizeEmpty'))
|
||||
return
|
||||
}
|
||||
const defaultSize = options.includes(pageSizeDefault.value) ? pageSizeDefault.value : options[0]
|
||||
savingPageSizes.value = true
|
||||
const seq = ++pageSizeSaveSeq
|
||||
try {
|
||||
const res: any = await updatePaginationPrefs({ options, defaultSize })
|
||||
// 已有更新的保存请求,丢弃本次过期响应
|
||||
if (seq !== pageSizeSaveSeq) return
|
||||
applyPaginationPrefs(res?.data, options, defaultSize)
|
||||
await loadPageSizeOptions(true)
|
||||
message.success(t('display.pageSizeSaved'))
|
||||
} catch (e: any) {
|
||||
if (seq !== pageSizeSaveSeq) return
|
||||
message.error(e.message || t('display.pageSizeFailed'))
|
||||
} finally {
|
||||
savingPageSizes.value = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,7 +196,7 @@ onMounted(() => {
|
||||
<div class="config-group">
|
||||
<div class="group-title">{{ t('display.label') }}</div>
|
||||
<NText depth="3" style="font-size: 12px; display: block; margin-bottom: 8px;">{{ t('display.hint') }}</NText>
|
||||
<NRadioGroup v-model:value="style" :disabled="loading">
|
||||
<NRadioGroup :value="style" :disabled="loading" @update:value="handleStyleChange">
|
||||
<NRadio v-for="option in styleOptions" :key="option.value" :value="option.value">
|
||||
{{ option.label }}
|
||||
</NRadio>
|
||||
@@ -223,20 +249,8 @@ onMounted(() => {
|
||||
<NSpace align="center" :size="12" style="margin-top: 8px;">
|
||||
<NText depth="3" style="font-size: 12px;">{{ t('display.defaultPageSizeLabel') }}</NText>
|
||||
<NSelect v-model:value="pageSizeDefault" :options="defaultSizeOptions" style="width: 120px;" />
|
||||
<NButton size="small" :loading="savingPageSizes" @click="handleSavePageSizes">
|
||||
{{ t('display.pageSizeSave') }}
|
||||
</NButton>
|
||||
</NSpace>
|
||||
</div>
|
||||
|
||||
<NSpace justify="end" style="margin-top: 16px; width: 100%;">
|
||||
<NButton type="primary" :loading="saving" @click="handleSave">
|
||||
<template #icon>
|
||||
<NIcon :component="SaveOutline" />
|
||||
</template>
|
||||
{{ t('display.save') }}
|
||||
</NButton>
|
||||
</NSpace>
|
||||
</NCard>
|
||||
</template>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user