优化: 图像模型兜底防御+任务UI(状态多选+跳详情)+token popover遮挡

This commit is contained in:
lxy
2026-08-10 13:46:26 +08:00
parent 256af4e736
commit 9407f821b9
7 changed files with 182 additions and 14 deletions
+58 -10
View File
@@ -31,11 +31,17 @@
</div>
<div class="filter-group">
<span class="filter-label">{{ $t('tasks.filter.status') }}</span>
<select v-model="activeStatus" class="filter-select">
<option v-for="s in statusFilters" :key="s.key" :value="s.key">
<div class="filter-chip-group">
<button
v-for="s in statusFilters"
:key="s.key"
class="filter-chip"
:class="{ 'is-active': activeStatuses.length === 0 || activeStatuses.includes(s.key) }"
@click="toggleStatus(s.key)"
>
{{ s.icon }} {{ s.label }}
</option>
</select>
</button>
</div>
</div>
<div class="filter-group">
<span class="filter-label">{{ $t('tasks.sort.label') }}</span>
@@ -311,7 +317,17 @@ const { toast, showToast } = useToast()
// 列表筛选/分页状态持久化到 localStorage(key 前缀 'tasks.'),
// 刷新页面后保留用户上次选择。collapsedGroups 单独走 df-tasks-collapsed(沿用既有实现)。
const activeProject = usePersistedRef('tasks.activeProject', 'all')
const activeStatus = usePersistedRef('tasks.activeStatus', 'all')
const activeStatuses = usePersistedRef<string[]>('tasks.activeStatuses', [])
// 状态多选切换
function toggleStatus(key: string) {
const idx = activeStatuses.value.indexOf(key)
if (idx >= 0) {
activeStatuses.value.splice(idx, 1)
} else {
activeStatuses.value.push(key)
}
}
const sortBy = usePersistedRef('tasks.sortBy', 'updated_at')
const searchKeyword = usePersistedRef('tasks.searchKeyword', '')
const loading = ref(false)
@@ -526,8 +542,12 @@ function parentPct(row: TaskRow): number {
const filteredGroups = computed(() => {
// F-260805 D5:store.tasks 是一次性拉取的筛选全量,前端已组装树(taskRows),直接按项目分组
// 状态多选:未选(全)或选中状态匹配
const statusFilter = (row: TaskRow) =>
activeStatuses.value.length === 0 || activeStatuses.value.includes(row.task.status)
const groupMap = new Map<string, TaskRow[]>()
for (const row of taskRows.value) {
if (!statusFilter(row)) continue
if (!groupMap.has(row.task.project_id)) {
groupMap.set(row.task.project_id, [])
}
@@ -548,11 +568,10 @@ const filteredGroups = computed(() => {
function buildTaskQuery(): TaskQuery | undefined {
const projAll = activeProject.value === 'all'
const statusAll = activeStatus.value === 'all'
const kw = searchKeyword.value.trim()
const query: TaskQuery = { order_by: sortBy.value }
if (!projAll) query.project_id = activeProject.value
if (!statusAll) query.status = activeStatus.value
// 状态多选走前端过滤(后端 status 单值,不给后端传使全量拉取,前端 filteredGroups 过滤)
if (kw) query.keyword = kw
// F-260805 D5:一次拉当前筛选全量(limit 放大 500 钳制上限,offset 恒 0),
// 树形需要完整父子关系,分页会割裂父/子;全量已在前端,父进度也前端计算
@@ -608,8 +627,8 @@ watch(activeProject, () => {
store.loadTasks(buildTaskQuery()).finally(() => { loading.value = false })
})
watch(activeStatus, () => {
store.setActiveTaskStatus(activeStatus.value)
watch(activeStatuses, () => {
store.setActiveTaskStatus(activeStatuses.value.length === 0 ? 'all' : activeStatuses.value.join(','))
loading.value = true
store.loadTasks(buildTaskQuery()).finally(() => { loading.value = false })
})
@@ -632,7 +651,7 @@ function onKeydown(e: KeyboardEvent) {
onMounted(async () => {
store.setActiveTaskProject(activeProject.value)
store.setActiveTaskStatus(activeStatus.value)
store.setActiveTaskStatus(activeStatuses.value.length === 0 ? 'all' : activeStatuses.value.join(','))
store.setActiveTaskKeyword(searchKeyword.value.trim() || undefined)
loading.value = true
try {
@@ -705,6 +724,35 @@ onUnmounted(() => {
transition: border-color 0.15s;
}
.filter-select:hover { border-color: var(--df-accent); }
.filter-chip-group {
display: flex;
flex-wrap: wrap;
gap: 4px;
}
.filter-chip {
display: inline-flex;
align-items: center;
gap: 3px;
padding: 2px 8px;
border: 0.5px solid var(--df-border);
border-radius: 10px;
background: transparent;
color: var(--df-text-dim);
font-size: 11px;
cursor: pointer;
transition: all 0.15s;
white-space: nowrap;
}
.filter-chip:hover {
background: var(--df-bg-card);
color: var(--df-text);
border-color: var(--df-accent);
}
.filter-chip.is-active {
background: var(--df-accent-soft);
color: var(--df-accent);
border-color: var(--df-accent);
}
.filter-count {
margin-left: auto;
font-size: 12px;