refactor: 前端 SSE 鉴权改造与公共逻辑抽取,修复统计分组空指针
- SSE 由 EventSource 改为 fetch 流式读取(新增 utils/sse.ts),token 走 Authorization 请求头,避免令牌经 URL 泄露到访问日志 - 抽取 useKeywordHistory,统一备注关键词与 AI 查询历史的读写、下拉与清空 - 抽取 renderCategoryLabel 至 utils/render.ts,消除多处重复实现 - 导出改用统一 request 实例(自动携带鉴权头),并将 blob 错误体解析为业务异常 - 新增 untagged 筛选参数;AI 智能查询改为弹窗入口并补充三语文案 - 分类/标签/邮箱/交易表单补充提交 loading,避免重复提交 - 交易时间改用本地格式化,修复 toISOString 造成的时区偏移 - 移除 Naive UI 全局注册;修正 NLog 的 rows 属性为 lines - 修复未分类记录分组时 categoryMap.get(null) 抛出的 NPE
This commit is contained in:
@@ -10,6 +10,7 @@ import type { DataTableColumns } from 'naive-ui'
|
||||
import type { ParsedBillRow, BillImportItem } from '../api/transaction'
|
||||
import { getCategoryList } from '../api/category'
|
||||
import { getTagList } from '../api/tag'
|
||||
import { renderCategoryLabel } from '../utils/render'
|
||||
|
||||
const props = defineProps<{
|
||||
/** 解析结果行 */
|
||||
@@ -34,6 +35,14 @@ const { t } = useI18n()
|
||||
// 勾选状态
|
||||
const checkedRowNos = ref<number[]>([])
|
||||
|
||||
// 用户对「计入统计」的临时覆盖(按行号),避免直接修改父组件传入的 props.rows
|
||||
const includeOverrides = ref<Record<number, boolean>>({})
|
||||
|
||||
/** 读取某行是否计入统计(优先取用户覆盖值) */
|
||||
function rowIncludeInStats(row: ParsedBillRow): boolean {
|
||||
return includeOverrides.value[row.rowNo] ?? row.includeInStats
|
||||
}
|
||||
|
||||
// 筛选条件
|
||||
const filterKeyword = ref('')
|
||||
const filterType = ref<string | null>(null)
|
||||
@@ -83,7 +92,7 @@ const filteredRows = computed(() => {
|
||||
if (!text.includes(kw)) return false
|
||||
}
|
||||
if (filterType.value === 'NEUTRAL') {
|
||||
if (r.includeInStats) return false
|
||||
if (rowIncludeInStats(r)) return false
|
||||
} else if (filterType.value && r.type !== filterType.value) {
|
||||
return false
|
||||
}
|
||||
@@ -143,7 +152,7 @@ const columns = computed<DataTableColumns<ParsedBillRow>>(() => [
|
||||
if (!row.importable) {
|
||||
return h(NTag, { type: 'error', size: 'small' }, { default: () => t('import.notImportable') })
|
||||
}
|
||||
if (!row.includeInStats) {
|
||||
if (!rowIncludeInStats(row)) {
|
||||
return h(NTag, { type: 'default', size: 'small' }, { default: () => t('import.notIncluded') })
|
||||
}
|
||||
return h(
|
||||
@@ -160,7 +169,7 @@ const columns = computed<DataTableColumns<ParsedBillRow>>(() => [
|
||||
align: 'center',
|
||||
render: (row) =>
|
||||
h(NCheckbox, {
|
||||
checked: !!row.includeInStats,
|
||||
checked: rowIncludeInStats(row),
|
||||
disabled: !row.importable,
|
||||
onUpdateChecked: (v: boolean) => toggleIncludeInStats(row, v),
|
||||
}),
|
||||
@@ -236,14 +245,6 @@ async function loadCategories() {
|
||||
}
|
||||
}
|
||||
|
||||
/** 分类下拉项渲染:图标 + 名称 */
|
||||
function renderCategoryLabel(option: any) {
|
||||
return h('span', null, [
|
||||
option.icon ? h('span', { style: { marginRight: '6px' } }, option.icon) : null,
|
||||
option.label,
|
||||
])
|
||||
}
|
||||
|
||||
/** 加载标签选项 */
|
||||
async function loadTags() {
|
||||
try {
|
||||
@@ -272,7 +273,7 @@ function toggleAll(checked: boolean) {
|
||||
|
||||
/** 切换单行是否计入统计 */
|
||||
function toggleIncludeInStats(row: ParsedBillRow, value: boolean) {
|
||||
row.includeInStats = value
|
||||
includeOverrides.value = { ...includeOverrides.value, [row.rowNo]: value }
|
||||
}
|
||||
|
||||
/** 批量设置已勾选行的计入统计标记 */
|
||||
@@ -281,9 +282,11 @@ function setCheckedIncludeInStats(value: boolean) {
|
||||
message.warning(t('import.selectAtLeastOne'))
|
||||
return
|
||||
}
|
||||
const next = { ...includeOverrides.value }
|
||||
checkedRows.value.forEach((row) => {
|
||||
row.includeInStats = value
|
||||
next[row.rowNo] = value
|
||||
})
|
||||
includeOverrides.value = next
|
||||
}
|
||||
|
||||
/** 确认导入:向父组件抛出勾选行与默认归类 */
|
||||
@@ -299,7 +302,7 @@ function handleConfirm() {
|
||||
amount: r.amount,
|
||||
counterparty: r.counterparty,
|
||||
product: r.product,
|
||||
includeInStats: r.includeInStats,
|
||||
includeInStats: rowIncludeInStats(r),
|
||||
rawData: r.rawData,
|
||||
}))
|
||||
emit('confirm', {
|
||||
@@ -310,9 +313,10 @@ function handleConfirm() {
|
||||
})
|
||||
}
|
||||
|
||||
// 解析结果变化时重置勾选(默认选中解析器建议行)与筛选条件
|
||||
// 解析结果变化时重置勾选(默认选中解析器建议行)、计入统计覆盖与筛选条件
|
||||
watch(() => props.rows, (rows) => {
|
||||
checkedRowNos.value = rows.filter((r) => r.selected).map((r) => r.rowNo)
|
||||
includeOverrides.value = {}
|
||||
resetFilters()
|
||||
}, { immediate: true })
|
||||
|
||||
|
||||
Reference in New Issue
Block a user