✨ 首页统计条点击跳转任务列表筛选

1. Dashboard 近7日统计条形图和数字可点击
2. 点击成功/失败/待执行跳转任务列表并按对应任务ID筛选
3. 任务列表页显示筛选提示条,支持清除筛选
This commit is contained in:
sugarBecomer
2026-04-24 12:32:23 +08:00
parent dd6c308996
commit 94c5532b82
3 changed files with 44 additions and 9 deletions
+3
View File
@@ -173,6 +173,9 @@ export interface TaskStatisticsV2Item {
successTask: number
failTask: number
pendingTask: number
successTaskIds?: number[]
failTaskIds?: number[]
pendingTaskIds?: number[]
}
export interface DashboardSummary {
+19 -1
View File
@@ -27,6 +27,7 @@ import { extractPageData } from '@/types/api'
const router = useRouter()
const route = useRoute()
const tableRef = ref<{ refresh: () => void } | null>(null)
const filterIds = ref('')
// ---- 确认弹窗状态 ----
const confirmOpen = ref(false)
@@ -186,7 +187,11 @@ onMounted(() => {
const qUsername = route.query.addUsername as string
if (qUserId && qUsername) {
openAddDialogWithUser(Number(qUserId), qUsername)
// 清除 query 参数,避免刷新时重复弹窗
router.replace({ path: route.path })
}
const qIds = route.query.ids as string
if (qIds) {
filterIds.value = qIds
router.replace({ path: route.path })
}
})
@@ -408,9 +413,17 @@ const searchFields: SearchField[] = [
]
const fetchFn = async (params: PageParams): Promise<PageResponse<CpTask>> => {
if (filterIds.value) {
(params as any).ids = filterIds.value
}
const res = await cpTaskApi.getList(params)
return extractPageData(res)
}
function clearFilterIds() {
filterIds.value = ''
tableRef.value?.refresh()
}
</script>
<template>
@@ -423,6 +436,11 @@ const fetchFn = async (params: PageParams): Promise<PageResponse<CpTask>> => {
</Button>
</div>
<div v-if="filterIds" class="flex items-center gap-2 rounded-md border border-primary/30 bg-primary/5 px-3 py-2 text-sm">
<span class="text-primary">当前按统计数据筛选中,共 {{ filterIds.split(',').length }} 条任务</span>
<Button size="sm" variant="ghost" class="ml-auto h-7 px-2 text-xs" @click="clearFilterIds">清除筛选</Button>
</div>
<DataTable
ref="tableRef"
:columns="columns"
+22 -8
View File
@@ -1,5 +1,6 @@
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue'
import { useRouter } from 'vue-router'
import { useUserStore } from '@/stores/user'
import { cpTaskApi } from '@/api/cpdaily'
import { authApi } from '@/api/auth'
@@ -10,6 +11,7 @@ import 'md-editor-v3/lib/preview.css'
import type { TaskStatisticsV2Item, DashboardSummary } from '@/types/cpdaily'
const userStore = useUserStore()
const router = useRouter()
const statistics = ref<TaskStatisticsV2Item[]>([])
const summary = ref<DashboardSummary | null>(null)
const loading = ref(false)
@@ -83,6 +85,11 @@ async function fetchAnnouncement() {
}
}
function goToTaskList(ids: number[] | undefined) {
if (!ids || ids.length === 0) return
router.push({ path: '/cpdaily/task/index', query: { ids: ids.join(',') } })
}
onMounted(() => {
fetchStatistics()
fetchSummary()
@@ -197,25 +204,32 @@ onMounted(() => {
<div class="flex-1 flex h-5 sm:h-6 rounded overflow-hidden bg-muted/30">
<div
v-if="item.successTask > 0"
class="bg-primary transition-all"
class="bg-primary transition-all cursor-pointer hover:opacity-80"
:style="{ width: (item.successTask / maxTaskCount * 100) + '%' }"
:title="`成功: ${item.successTask}`"
:title="`成功: ${item.successTask} — 点击查看`"
@click="goToTaskList(item.successTaskIds)"
></div>
<div
v-if="item.failTask > 0"
class="bg-destructive transition-all"
class="bg-destructive transition-all cursor-pointer hover:opacity-80"
:style="{ width: (item.failTask / maxTaskCount * 100) + '%' }"
:title="`失败: ${item.failTask}`"
:title="`失败: ${item.failTask} — 点击查看`"
@click="goToTaskList(item.failTaskIds)"
></div>
<div
v-if="item.pendingTask > 0"
class="bg-muted-foreground/30 transition-all"
class="bg-muted-foreground/30 transition-all cursor-pointer hover:opacity-80"
:style="{ width: (item.pendingTask / maxTaskCount * 100) + '%' }"
:title="`待执行: ${item.pendingTask}`"
:title="`待执行: ${item.pendingTask} — 点击查看`"
@click="goToTaskList(item.pendingTaskIds)"
></div>
</div>
<span class="text-xs text-muted-foreground w-16 sm:w-20 shrink-0">
{{ item.successTask }}/{{ item.failTask }}/{{ item.pendingTask }}
<span class="text-xs text-muted-foreground w-24 sm:w-28 shrink-0 flex gap-0.5">
<span class="cursor-pointer text-primary hover:underline" @click="goToTaskList(item.successTaskIds)">{{ item.successTask }}</span>
<span>/</span>
<span class="cursor-pointer text-destructive hover:underline" @click="goToTaskList(item.failTaskIds)">{{ item.failTask }}</span>
<span>/</span>
<span class="cursor-pointer hover:underline" @click="goToTaskList(item.pendingTaskIds)">{{ item.pendingTask }}</span>
</span>
</div>
</div>