From 83cee61476b31cbd44aa7a582a78ac25a7faf628 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E7=BB=9D=E5=B0=98?= <237809796@qq.com>
Date: Mon, 22 Jun 2026 01:24:50 +0800
Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E:=20=E5=88=97=E8=A1=A8?=
=?UTF-8?q?=E5=88=86=E9=A1=B5UI(Paginator=E7=BB=84=E4=BB=B6+=E4=B8=89?=
=?UTF-8?q?=E8=A7=86=E5=9B=BE=E6=8E=A5=E5=85=A5)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Paginator.vue复用组件(客户端切片,不发新IPC,数据量小前瞻基建)
Tasks/Ideas/Projects.vue接store列表切片(pagedProjects/pagedIdeas)
向后兼容铁律:pageSize=0整组件v-if隐藏+切片返全量,等价改造前行为
旧调用方(store.loadTasks等签名)零改动,分页状态纯前端ref
筛选/排序变化page重置1防越界空页;i18n双语pagination键齐备
---
src/components/Paginator.vue | 168 +++++++++++++++++++++++++++++++++++
src/i18n/en/common.ts | 7 ++
src/i18n/zh-CN/common.ts | 7 ++
src/views/Ideas.vue | 26 +++++-
src/views/Projects.vue | 21 ++++-
src/views/Tasks.vue | 21 ++++-
6 files changed, 244 insertions(+), 6 deletions(-)
create mode 100644 src/components/Paginator.vue
diff --git a/src/components/Paginator.vue b/src/components/Paginator.vue
new file mode 100644
index 0000000..de8aaf7
--- /dev/null
+++ b/src/components/Paginator.vue
@@ -0,0 +1,168 @@
+
+
+
+
+ {{ $t('common.pagination.perPage') }}
+
+
+ {{ $t('common.pagination.total', { n: total }) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/i18n/en/common.ts b/src/i18n/en/common.ts
index d517b39..c53d03e 100644
--- a/src/i18n/en/common.ts
+++ b/src/i18n/en/common.ts
@@ -14,5 +14,12 @@ export default {
close: 'Close',
loading: 'Loading…',
unknownError: 'Unknown error',
+ // Pagination UI (F-260621-02 P3 baseline; off by default, opt-in per-page)
+ pagination: {
+ prev: 'Prev',
+ next: 'Next',
+ perPage: 'Per page',
+ total: '{n} total',
+ },
},
}
diff --git a/src/i18n/zh-CN/common.ts b/src/i18n/zh-CN/common.ts
index 8abb61d..f91d864 100644
--- a/src/i18n/zh-CN/common.ts
+++ b/src/i18n/zh-CN/common.ts
@@ -14,5 +14,12 @@ export default {
close: '关闭',
loading: '加载中…',
unknownError: '未知错误',
+ // 分页 UI(F-260621-02 P3 基建,默认不分页,用户选每页 N 条才触发)
+ pagination: {
+ prev: '上一页',
+ next: '下一页',
+ perPage: '每页',
+ total: '共 {n} 条',
+ },
},
}
diff --git a/src/views/Ideas.vue b/src/views/Ideas.vue
index 9d0905c..a4522d5 100644
--- a/src/views/Ideas.vue
+++ b/src/views/Ideas.vue
@@ -52,7 +52,7 @@
{{ $t('ideas.listEmpty') }}
+
+
@@ -132,6 +138,7 @@ import { stripMd } from '../utils/markdown'
import ConfirmDialog from '../components/ConfirmDialog.vue'
import IdeaDetail from '../components/ideas/IdeaDetail.vue'
import { useConfirm } from '../composables/useConfirm'
+import Paginator from '../components/Paginator.vue'
const { t } = useI18n()
const router = useRouter()
@@ -149,6 +156,10 @@ const searchQuery = ref('')
// 列表排序:score 高分在前 / time 新在前(默认 score,避免高分灵感被淹没)
const sortMode = ref<'score' | 'time'>('score')
+// 分页(F-260621-02 P3):pageSize=0 → 全量(向后兼容,等价改造前);用户选 N → 客户端切片
+const page = ref(1)
+const pageSize = ref(0)
+
// ── 新建灵感模态框 ──
const showCaptureModal = ref(false)
const newIdeaTitle = ref('')
@@ -218,9 +229,9 @@ async function reloadIdeas(immediate = false) {
}
// 筛选/排序变化即时重载;keyword 输入防抖重载
-watch(activeFilter, () => void reloadIdeas(true))
-watch(sortMode, () => void reloadIdeas(true))
-watch(searchQuery, () => void reloadIdeas(false))
+watch(activeFilter, () => { page.value = 1; void reloadIdeas(true) })
+watch(sortMode, () => { page.value = 1; void reloadIdeas(true) })
+watch(searchQuery, () => { page.value = 1; void reloadIdeas(false) })
const filteredIdeas = computed(() => {
let ideas = store.ideas
@@ -237,6 +248,13 @@ const filteredIdeas = computed(() => {
return ideas
})
+// 分页视图(F-260621-02 P3):pageSize=0 → 全量(等价改造前);否则取当前页窗口。
+const pagedIdeas = computed(() => {
+ if (pageSize.value <= 0) return filteredIdeas.value
+ const start = (page.value - 1) * pageSize.value
+ return filteredIdeas.value.slice(start, start + pageSize.value)
+})
+
const currentIdea = computed(() => {
if (!selectedId.value) return null
return store.ideas.find(i => i.id === selectedId.value) ?? null
diff --git a/src/views/Projects.vue b/src/views/Projects.vue
index f748885..92fab5e 100644
--- a/src/views/Projects.vue
+++ b/src/views/Projects.vue
@@ -140,9 +140,16 @@
+
+
+
{{ $t('projects.empty') }}
@@ -165,6 +172,7 @@ import { projectApi } from '@/api'
import { formatDate } from '@/utils/time'
import ConfirmDialog from '@/components/ConfirmDialog.vue'
import ProjectCard from '@/components/project/ProjectCard.vue'
+import Paginator from '@/components/Paginator.vue'
import { useConfirm } from '@/composables/useConfirm'
import type { ProjectRecord } from '@/api/types'
import type { ScannedProjectItem } from '@/api/project'
@@ -172,6 +180,17 @@ import type { ScannedProjectItem } from '@/api/project'
const store = useProjectStore()
const { t } = useI18n()
+// 分页(F-260621-02 P3):pageSize=0 → 全量(向后兼容,等价改造前);用户选 N → 客户端切片
+const page = ref(1)
+const pageSize = ref(0)
+
+// 分页视图:pageSize=0 → 全量;否则取当前页窗口
+const pagedProjects = computed(() => {
+ if (pageSize.value <= 0) return store.projects
+ const start = (page.value - 1) * pageSize.value
+ return store.projects.slice(start, start + pageSize.value)
+})
+
// 状态映射统一走 ../constants/project(与 ProjectDetail/Tasks/Dashboard 一致)
// formatDate 由 ../utils/time 提供(统一毫秒字符串解析,根治 Invalid Date)
diff --git a/src/views/Tasks.vue b/src/views/Tasks.vue
index 0925f69..a798703 100644
--- a/src/views/Tasks.vue
+++ b/src/views/Tasks.vue
@@ -77,6 +77,13 @@
+
+
+
@@ -115,6 +122,7 @@ import { useProjectStore } from '@/stores/project'
import { formatRelative } from '@/utils/time'
import { taskStatusLabel as statusLabel, taskStatusClass, priorityLabel, priorityClass } from '../constants/project'
import type { TaskRecord, TaskQuery } from '@/api/types'
+import Paginator from '../components/Paginator.vue'
const router = useRouter()
const store = useProjectStore()
@@ -125,6 +133,10 @@ const activeStatus = ref('all')
// Y-2: 本视图加载态(loadTasks 不翻转全局 state.loading,故用局部 ref 精确反映任务加载)
const loading = ref(false)
+// 分页(F-260621-02 P3):pageSize=0 → 全量(向后兼容,等价改造前);用户选 N → 客户端切片
+const page = ref(1)
+const pageSize = ref(0)
+
// ── 新建任务模态框 ──
const showCreateModal = ref(false)
const newTaskProjectId = ref('')
@@ -176,7 +188,12 @@ const projectIcons: Record = {
// 注:project 维度同样下沉后端(activeProject watch 构造 query),store.tasks 已按
// project_id 过滤。groupMap 保留分组逻辑(展示按项目聚合),无需再 filter。
const filteredGroups = computed(() => {
- const filtered = store.tasks
+ // 分页(F-260621-02):pageSize=0 → 全量切片(等价改造前);否则取当前页窗口。
+ // 先对扁平 store.tasks 切片,再分组(保持按项目聚合语义,分组在页窗口内)。
+ const all = store.tasks
+ const filtered = pageSize.value > 0
+ ? all.slice((page.value - 1) * pageSize.value, page.value * pageSize.value)
+ : all
const groupMap = new Map()
for (const task of filtered) {
@@ -254,6 +271,7 @@ async function confirmCreate() {
// F-260621-02(P1 status 下沉):重载 query 同时含 project_id + status,前端不再 filter status
watch(activeProject, () => {
store.setActiveTaskProject(activeProject.value)
+ page.value = 1
loading.value = true
store.loadTasks(buildTaskQuery()).finally(() => { loading.value = false })
})
@@ -262,6 +280,7 @@ watch(activeProject, () => {
// 并登记到 store 供 AR-11 listener 复用同一筛选(保 B-29 契约)
watch(activeStatus, (key) => {
store.setActiveTaskStatus(key)
+ page.value = 1
loading.value = true
store.loadTasks(buildTaskQuery()).finally(() => { loading.value = false })
})