mirror of
https://gitee.com/spark-store-project/spark-store
synced 2026-04-30 03:10:16 +08:00
feat(update-center): 添加全选功能及状态管理
添加全选复选框组件及相关状态管理逻辑 实现全选/取消全选功能 添加部分选中状态显示 更新工具栏组件以支持新功能
This commit is contained in:
@@ -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)"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user