新增: 三实体列表查询维度补全(status/关键词/排序/分页下沉后端)

任务/项目/灵感三实体新增 list_by_query 动态 WHERE(累积式 where_clauses
+params_vec 收口)+ order_by 白名单防注入 + limit/offset 钳制。命令层
list_{tasks,projects,ideas} 吃 Option<XxxQuery> 双参向后兼容(旧无参/单参
路径等价全量)。前端 Tasks/Ideas status/keyword 筛选下沉后端 query。

F-260621-02
This commit is contained in:
2026-06-22 01:03:37 +08:00
parent 72e57c6232
commit c9b6e28433
18 changed files with 878 additions and 76 deletions

View File

@@ -114,7 +114,7 @@ import { useI18n } from 'vue-i18n'
import { useProjectStore } from '@/stores/project'
import { formatRelative } from '@/utils/time'
import { taskStatusLabel as statusLabel, taskStatusClass, priorityLabel, priorityClass } from '../constants/project'
import type { TaskRecord } from '@/api/types'
import type { TaskRecord, TaskQuery } from '@/api/types'
const router = useRouter()
const store = useProjectStore()
@@ -169,14 +169,14 @@ const projectIcons: Record<string, string> = {
'flux': '⚡',
}
// F-260621-02(P1 status 下沉):status 过滤从前端内存 filter 改为构造 query 调后端,
// store.tasks 已是筛选后的结果(后端 WHERE)。filteredGroups 仅做按项目分组(project
// 维度也有后端 query 下沉,这里 groupMap 仅做展示分组,不再二次 filter)。
//
// 注:project 维度同样下沉后端(activeProject watch 构造 query),store.tasks 已按
// project_id 过滤。groupMap 保留分组逻辑(展示按项目聚合),无需再 filter。
const filteredGroups = computed(() => {
let filtered = store.tasks
if (activeProject.value !== 'all') {
filtered = filtered.filter(t => t.project_id === activeProject.value)
}
if (activeStatus.value !== 'all') {
filtered = filtered.filter(t => t.status === activeStatus.value)
}
const filtered = store.tasks
const groupMap = new Map<string, TaskRecord[]>()
for (const task of filtered) {
@@ -200,10 +200,23 @@ const filteredGroups = computed(() => {
// statusLabel / priorityLabel / priorityClass 由 ../constants/project 提供(与 ProjectDetail 文案统一)
// F-260621-02(P1 status 下沉):按当前 activeProject + activeStatus 构造后端 query。
// - 全 all → undefined(全量,等价旧行为)
// - 任一具体值 → query 下沉后端 WHERE(project_id / status)
function buildTaskQuery(): TaskQuery | undefined {
const projAll = activeProject.value === 'all'
const statusAll = activeStatus.value === 'all'
if (projAll && statusAll) return undefined
const query: TaskQuery = {}
if (!projAll) query.project_id = activeProject.value
if (!statusAll) query.status = activeStatus.value
return query
}
async function refresh() {
loading.value = true
try {
await store.loadTasks(activeProject.value === 'all' ? undefined : activeProject.value)
await store.loadTasks(buildTaskQuery())
} finally {
loading.value = false
}
@@ -236,17 +249,27 @@ async function confirmCreate() {
}
}
// B-260615-29: 切换项目筛选时按 projectId 重载(避免仅前端 filter,跨项目视图不同步)
// B-260615-29: 切换项目筛选时按筛选重载(避免仅前端 filter,跨项目视图不同步)
// CR-260615-26(R-1): 同步登记当前筛选到 store,供 AR-11 listener 按筛选拉取
watch(activeProject, (key) => {
store.setActiveTaskProject(key)
// F-260621-02(P1 status 下沉):重载 query 同时含 project_id + status,前端不再 filter status
watch(activeProject, () => {
store.setActiveTaskProject(activeProject.value)
loading.value = true
store.loadTasks(key === 'all' ? undefined : key).finally(() => { loading.value = false })
store.loadTasks(buildTaskQuery()).finally(() => { loading.value = false })
})
// F-260621-02(P1 status 下沉):切换 status 筛选时构造 query 重载(后端 WHERE,非前端 filter),
// 并登记到 store 供 AR-11 listener 复用同一筛选(保 B-29 契约)
watch(activeStatus, (key) => {
store.setActiveTaskStatus(key)
loading.value = true
store.loadTasks(buildTaskQuery()).finally(() => { loading.value = false })
})
onMounted(async () => {
// R-1: 登记初始筛选(all=全量),让数据变更 listener 知道视图已挂载、按全量拉取
store.setActiveTaskProject(activeProject.value)
store.setActiveTaskStatus(activeStatus.value)
loading.value = true
try {
await Promise.all([store.loadProjects(), store.loadTasks()])