feat(update-center): 添加全选功能及状态管理

添加全选复选框组件及相关状态管理逻辑
实现全选/取消全选功能
添加部分选中状态显示
更新工具栏组件以支持新功能
This commit is contained in:
2026-04-12 22:02:01 +08:00
parent ca7520cb2e
commit 763af5c37e
4 changed files with 66 additions and 1 deletions

View File

@@ -20,9 +20,12 @@
<UpdateCenterToolbar <UpdateCenterToolbar
:search-query="store.searchQuery.value" :search-query="store.searchQuery.value"
:selected-count="selectedCount" :selected-count="selectedCount"
:all-selected="store.allSelected.value"
:some-selected="store.someSelected.value"
@refresh="store.refresh" @refresh="store.refresh"
@start-selected="emit('request-start-selected')" @start-selected="emit('request-start-selected')"
@request-close="store.requestClose" @request-close="store.requestClose"
@toggle-select-all="store.toggleSelectAll"
@update:search-query="emit('update:search-query', $event)" @update:search-query="emit('update:search-query', $event)"
/> />

View File

@@ -1,6 +1,6 @@
<template> <template>
<div <div
class="min-h-0 overflow-y-auto overscroll-contain border-r border-slate-200/70 p-6 dark:border-slate-800/70" class="min-h-0 overflow-y-auto overscroll-contain scrollbar-muted border-r border-slate-200/70 p-6 dark:border-slate-800/70"
> >
<div <div
v-if="items.length === 0" v-if="items.length === 0"

View File

@@ -40,6 +40,22 @@
</div> </div>
</div> </div>
<div class="flex items-center gap-3">
<label class="inline-flex cursor-pointer items-center gap-2 select-none">
<input
ref="selectAllRef"
type="checkbox"
class="h-4 w-4 rounded border-slate-300 accent-brand focus:ring-brand"
:checked="allSelected"
@change="$emit('toggle-select-all')"
/>
<span class="text-sm font-medium text-slate-700 dark:text-slate-200">全选</span>
</label>
<span class="text-sm text-slate-400 dark:text-slate-500">
已选 {{ selectedCount }}
</span>
</div>
<label class="block"> <label class="block">
<span class="sr-only">搜索更新</span> <span class="sr-only">搜索更新</span>
<input <input
@@ -54,18 +70,35 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, watch } from "vue";
const props = defineProps<{ const props = defineProps<{
searchQuery: string; searchQuery: string;
selectedCount: number; selectedCount: number;
allSelected: boolean;
someSelected: boolean;
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{
(e: "refresh"): void; (e: "refresh"): void;
(e: "start-selected"): void; (e: "start-selected"): void;
(e: "request-close"): void; (e: "request-close"): void;
(e: "toggle-select-all"): void;
(e: "update:search-query", value: string): void; (e: "update:search-query", value: string): void;
}>(); }>();
const selectAllRef = ref<HTMLInputElement | null>(null);
watch(
[() => props.someSelected, () => props.allSelected],
() => {
if (selectAllRef.value) {
selectAllRef.value.indeterminate = props.someSelected && !props.allSelected;
}
},
{ flush: "post" },
);
const handleInput = (event: Event): void => { const handleInput = (event: Event): void => {
const target = event.target as HTMLInputElement | null; const target = event.target as HTMLInputElement | null;
emit("update:search-query", target?.value ?? props.searchQuery); emit("update:search-query", target?.value ?? props.searchQuery);

View File

@@ -23,11 +23,14 @@ export interface UpdateCenterStore {
selectedTaskKeys: Ref<Set<string>>; selectedTaskKeys: Ref<Set<string>>;
snapshot: Ref<UpdateCenterSnapshot>; snapshot: Ref<UpdateCenterSnapshot>;
filteredItems: ComputedRef<UpdateCenterItem[]>; filteredItems: ComputedRef<UpdateCenterItem[]>;
allSelected: ComputedRef<boolean>;
someSelected: ComputedRef<boolean>;
bind: () => void; bind: () => void;
unbind: () => void; unbind: () => void;
open: () => Promise<void>; open: () => Promise<void>;
refresh: () => Promise<void>; refresh: () => Promise<void>;
toggleSelection: (taskKey: string) => void; toggleSelection: (taskKey: string) => void;
toggleSelectAll: () => void;
getSelectedItems: () => UpdateCenterItem[]; getSelectedItems: () => UpdateCenterItem[];
closeNow: () => void; closeNow: () => void;
startSelected: () => Promise<void>; startSelected: () => Promise<void>;
@@ -74,11 +77,25 @@ export const createUpdateCenterStore = (): UpdateCenterStore => {
snapshot.value = nextSnapshot; snapshot.value = nextSnapshot;
}; };
const selectableItems = computed(() =>
snapshot.value.items.filter((item) => item.ignored !== true),
);
const filteredItems = computed(() => { const filteredItems = computed(() => {
const query = searchQuery.value.trim(); const query = searchQuery.value.trim();
return snapshot.value.items.filter((item) => matchesSearch(item, query)); return snapshot.value.items.filter((item) => matchesSearch(item, query));
}); });
const allSelected = computed(() => {
const selectable = selectableItems.value;
return selectable.length > 0 && selectable.every((item) => selectedTaskKeys.value.has(item.taskKey));
});
const someSelected = computed(() => {
const selectable = selectableItems.value;
return selectable.length > 0 && selectable.some((item) => selectedTaskKeys.value.has(item.taskKey));
});
const handleState = (nextSnapshot: UpdateCenterSnapshot): void => { const handleState = (nextSnapshot: UpdateCenterSnapshot): void => {
applySnapshot(nextSnapshot); applySnapshot(nextSnapshot);
}; };
@@ -133,6 +150,15 @@ export const createUpdateCenterStore = (): UpdateCenterStore => {
selectedTaskKeys.value = nextSelection; selectedTaskKeys.value = nextSelection;
}; };
const toggleSelectAll = (): void => {
const selectable = selectableItems.value;
if (allSelected.value) {
selectedTaskKeys.value = new Set();
} else {
selectedTaskKeys.value = new Set(selectable.map((item) => item.taskKey));
}
};
const getSelectedItems = (): UpdateCenterItem[] => { const getSelectedItems = (): UpdateCenterItem[] => {
return snapshot.value.items.filter( return snapshot.value.items.filter(
(item) => (item) =>
@@ -217,11 +243,14 @@ export const createUpdateCenterStore = (): UpdateCenterStore => {
selectedTaskKeys, selectedTaskKeys,
snapshot, snapshot,
filteredItems, filteredItems,
allSelected,
someSelected,
bind, bind,
unbind, unbind,
open, open,
refresh, refresh,
toggleSelection, toggleSelection,
toggleSelectAll,
getSelectedItems, getSelectedItems,
closeNow, closeNow,
startSelected, startSelected,