修复: 前端UX一致性(Dashboard状态映射/状态徽章全局/Knowledge下一条与失败反馈/Ideas筛选与错误条/ConfirmDialog安全/快捷菜单状态机/分页越界) + 后端校验(queue/count-list一致/软删拒改/父聚合/promote CAS/MCP状态机收口/update白名单剔除id/created_at) + 销账
This commit is contained in:
@@ -149,21 +149,26 @@ export function useKnowledgeStore() {
|
||||
return record ?? null
|
||||
}
|
||||
|
||||
async function updateStatus(id: string, status: string) {
|
||||
await runWithCatch(state, t('knowledge.err.updateStatusFailed'), async () => {
|
||||
async function updateStatus(id: string, status: string): Promise<boolean> {
|
||||
// WK-3:返成功布尔(失败时 runWithCatch 已置 state.error),调用方据此中止推进/报错
|
||||
const ok = await runWithCatch(state, t('knowledge.err.updateStatusFailed'), async () => {
|
||||
await knowledgeApi.updateStatus(id, status)
|
||||
// 从 items 和 candidates 中同步移除
|
||||
state.items = state.items.filter(k => k.id !== id)
|
||||
state.candidates = state.candidates.filter(k => k.id !== id)
|
||||
return true
|
||||
})
|
||||
return ok === true
|
||||
}
|
||||
|
||||
async function archive(id: string) {
|
||||
await runWithCatch(state, t('knowledge.err.archiveFailed'), async () => {
|
||||
async function archive(id: string): Promise<boolean> {
|
||||
const ok = await runWithCatch(state, t('knowledge.err.archiveFailed'), async () => {
|
||||
await knowledgeApi.archive(id)
|
||||
state.items = state.items.filter(k => k.id !== id)
|
||||
state.candidates = state.candidates.filter(k => k.id !== id)
|
||||
return true
|
||||
})
|
||||
return ok === true
|
||||
}
|
||||
|
||||
// ── 配置 ──
|
||||
|
||||
+20
-2
@@ -6,7 +6,7 @@ import { createIdeasStore } from './project/ideas'
|
||||
import { createWorkflowStore } from './project/workflow'
|
||||
import { state, clearError } from './project/state'
|
||||
import { taskApi } from '@/api/task'
|
||||
import type { DfDataChangedPayload, TaskQuery } from '@/api/types'
|
||||
import type { DfDataChangedPayload, TaskQuery, IdeaQuery } from '@/api/types'
|
||||
|
||||
// ── barrel 组合器(零行为变更,纯重构) ──
|
||||
//
|
||||
@@ -41,21 +41,34 @@ function createStore() {
|
||||
// 避免刷新把已选 status 冲掉)。
|
||||
let _activeTaskProject: string | undefined = undefined
|
||||
let _activeTaskStatus: string | undefined = undefined
|
||||
// WK-10:Tasks 视图搜索关键字登记(数据变更联动刷新时一并下沉 keyword,避免冲掉搜索结果)
|
||||
let _activeTaskKeyword: string | undefined = undefined
|
||||
function setActiveTaskProject(projectKey: string | undefined) {
|
||||
_activeTaskProject = projectKey
|
||||
}
|
||||
function setActiveTaskStatus(statusKey: string | undefined) {
|
||||
_activeTaskStatus = statusKey
|
||||
}
|
||||
function setActiveTaskKeyword(keyword: string | undefined) {
|
||||
_activeTaskKeyword = keyword
|
||||
}
|
||||
// 构造当前 task 视图筛选对应的 query(供数据变更 listener 复用同一筛选拉取)
|
||||
function buildActiveTaskQuery(): TaskQuery | undefined {
|
||||
if (_activeTaskProject === undefined) return undefined // 视图未挂载,不触发
|
||||
const query: TaskQuery = {}
|
||||
if (_activeTaskProject !== 'all') query.project_id = _activeTaskProject
|
||||
if (_activeTaskStatus && _activeTaskStatus !== 'all') query.status = _activeTaskStatus
|
||||
if (_activeTaskKeyword) query.keyword = _activeTaskKeyword
|
||||
return query
|
||||
}
|
||||
|
||||
// WK-4:Ideas 视图当前查询登记(数据变更联动刷新按当前筛选重载,不再无参 loadIdeas 全量冲掉筛选)。
|
||||
// undefined = Ideas 视图未挂载(如 Dashboard)→ 退化为全量刷新,保持面板统计新鲜。
|
||||
let _activeIdeaQuery: IdeaQuery | undefined = undefined
|
||||
function setActiveIdeaQuery(q: IdeaQuery | undefined) {
|
||||
_activeIdeaQuery = q
|
||||
}
|
||||
|
||||
let _dataChangedUnlisten: (() => void) | null = null
|
||||
|
||||
async function startDataChangedListener() {
|
||||
@@ -76,7 +89,8 @@ function createStore() {
|
||||
// Dashboard 数字(与 Tasks 视图分页/筛选解耦,不受 q undefined 影响)
|
||||
void loadStats()
|
||||
} else if (entity === 'idea') {
|
||||
void ideasStore.loadIdeas()
|
||||
// WK-4:按 Ideas 视图当前 query 重载(未挂载时退化为全量刷新 Dashboard 面板)
|
||||
void ideasStore.loadIdeas(_activeIdeaQuery)
|
||||
}
|
||||
})
|
||||
return _dataChangedUnlisten
|
||||
@@ -179,6 +193,10 @@ function createStore() {
|
||||
setActiveTaskProject,
|
||||
// F-260621-02(P1 status 下沉):Tasks 视图登记当前 status 筛选,供 listener 复用同一筛选拉取
|
||||
setActiveTaskStatus,
|
||||
// WK-10:Tasks 视图登记当前搜索关键字,供 listener 复用同一筛选拉取
|
||||
setActiveTaskKeyword,
|
||||
// WK-4: Ideas 视图登记当前查询,供 listener 按筛选重载(不冲掉筛选视图)
|
||||
setActiveIdeaQuery,
|
||||
pendingApproval: computed(() => state.pendingApproval),
|
||||
// computed
|
||||
stats,
|
||||
|
||||
@@ -12,8 +12,11 @@ export function createIdeasStore() {
|
||||
* F-260621-02:status/keyword/order_by 下沉后端 WHERE,取代前端 computed filter。
|
||||
* - 传 `query` → 多条件查询(状态/关键词/排序/分页)。
|
||||
* - 不传 → 全量(created_at DESC,等价旧行为)。
|
||||
*
|
||||
* WK-7:成功时清 state.error(load 后不残留旧错误),失败由 runWithCatch 置 error。
|
||||
*/
|
||||
async function loadIdeas(query?: IdeaQuery) {
|
||||
state.error = null
|
||||
await runWithCatch(state, t('ideas.err.loadFailed'), async () => {
|
||||
state.ideas = await ideaApi.list(query)
|
||||
})
|
||||
@@ -36,11 +39,14 @@ export function createIdeasStore() {
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteIdea(id: string) {
|
||||
await runWithCatch(state, t('ideas.err.deleteFailed'), async () => {
|
||||
async function deleteIdea(id: string): Promise<boolean> {
|
||||
// WK-7:返成功布尔(失败时 runWithCatch 已置 state.error),调用方据此保留选中态
|
||||
const ok = await runWithCatch(state, t('ideas.err.deleteFailed'), async () => {
|
||||
await ideaApi.delete(id)
|
||||
state.ideas = state.ideas.filter(i => i.id !== id)
|
||||
return true
|
||||
})
|
||||
return ok === true
|
||||
}
|
||||
|
||||
async function evaluateIdea(id: string) {
|
||||
@@ -51,15 +57,16 @@ export function createIdeasStore() {
|
||||
}
|
||||
|
||||
async function promoteIdea(id: string) {
|
||||
const res = await ideaApi.promote(id)
|
||||
await loadIdeas() // 后端已回写 status=promoted/promoted_to,刷新列表
|
||||
return res
|
||||
// WK-4:不再内部全量 loadIdeas(会把 store.ideas 冲成全集、冲掉当前筛选视图)。
|
||||
// 由视图(Ideas.vue promoteToProject)操作后按当前 buildIdeaQuery() 主动重载。
|
||||
return await ideaApi.promote(id)
|
||||
}
|
||||
|
||||
async function relateIdeas(subjectId: string, targetIds: string[]) {
|
||||
await runWithCatch(state, t('ideas.err.relateFailed'), async () => {
|
||||
await ideaApi.relateIdeas(subjectId, targetIds)
|
||||
await loadIdeas() // 后端已原子更新全部受影响灵感,刷新列表保持 store 一致
|
||||
// WK-4:后端原子更新受影响灵感,原全量 loadIdeas 会冲掉当前筛选视图;
|
||||
// 改由视图(Ideas.vue onUpdateRelated)按当前 query 重载。
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import { taskApi } from '@/api'
|
||||
import type { TaskQuery, ProjectId } from '@/api/types'
|
||||
import { t } from '@/i18n/i18n-helpers'
|
||||
import { runWithCatch } from '@/composables/useStoreAction'
|
||||
import { runWithCatch, runWithCatchGuarded } from '@/composables/useStoreAction'
|
||||
import { state } from './state'
|
||||
|
||||
/** 任务 CRUD 子 store(共享全局 state) */
|
||||
export function createTasksStore() {
|
||||
// WK-10:并发 loadTasks 竞态守卫(快速切筛选/搜索防旧响应晚到覆盖新结果,
|
||||
// 对齐 knowledge store 的 _itemsSeq 模式)。旧请求的失败也不污染当前视图。
|
||||
let _tasksSeq = 0
|
||||
|
||||
/**
|
||||
* 加载任务列表。
|
||||
*
|
||||
@@ -17,8 +21,10 @@ export function createTasksStore() {
|
||||
* status/keyword 下沉后端 WHERE(P1/P2),取代 Tasks.vue 旧的前端内存 filter。
|
||||
*/
|
||||
async function loadTasks(queryOrProjectId?: TaskQuery | string) {
|
||||
await runWithCatch(state, t('tasks.err.loadFailed'), async () => {
|
||||
state.tasks = await taskApi.list(queryOrProjectId)
|
||||
const seq = ++_tasksSeq
|
||||
await runWithCatchGuarded(state, t('tasks.err.loadFailed'), () => seq === _tasksSeq, async () => {
|
||||
const result = await taskApi.list(queryOrProjectId)
|
||||
if (seq === _tasksSeq) state.tasks = result
|
||||
})
|
||||
}
|
||||
|
||||
@@ -51,13 +57,16 @@ export function createTasksStore() {
|
||||
})
|
||||
}
|
||||
|
||||
async function deleteTask(id: string) {
|
||||
await runWithCatch(state, t('tasks.err.deleteFailed'), async () => {
|
||||
async function deleteTask(id: string): Promise<boolean> {
|
||||
// WK-13:返成功布尔(失败时 runWithCatch 已置 state.error),快捷删除据此 toast 反馈
|
||||
const ok = await runWithCatch(state, t('tasks.err.deleteFailed'), async () => {
|
||||
// 后端已级联软删子任务(delete_task 返回 { ok, cascaded });
|
||||
// 本地同步移除父任务 + 其直接子任务(1 级嵌套,防悬挂 parent_id)
|
||||
await taskApi.delete(id)
|
||||
state.tasks = state.tasks.filter(t => t.id !== id && t.parent_id !== id)
|
||||
return true
|
||||
})
|
||||
return ok === true
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user