mirror of
https://gitee.com/spark-store-project/spark-store
synced 2026-05-30 01:31:06 +08:00
feat(update-center): 添加加载状态处理及UI优化
为更新中心添加加载状态管理,包括: - 在打开和刷新操作时显示加载状态 - 禁用刷新按钮防止重复操作 - 添加加载中的动画效果和提示文本 - 优化加载时的UI显示
This commit is contained in:
@@ -191,7 +191,14 @@ const loadAptssItemMetadata = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const metadata = parsePrintUrisOutput(metadataResult.stdout);
|
const metadata = parsePrintUrisOutput(metadataResult.stdout);
|
||||||
console.log(`[DEBUG] APTSS parsed metadata:`, metadata);
|
if (metadata) {
|
||||||
|
console.log(`[DEBUG] APTSS parsed metadata:`, {
|
||||||
|
...metadata,
|
||||||
|
downloadUrl: `${metadata.downloadUrl}.metalink`,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log(`[DEBUG] APTSS parsed metadata:`, metadata);
|
||||||
|
}
|
||||||
|
|
||||||
if (!metadata) {
|
if (!metadata) {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ const createStore = (
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
isOpen: ref(true),
|
isOpen: ref(true),
|
||||||
|
loading: ref(false),
|
||||||
showCloseConfirm: ref(true),
|
showCloseConfirm: ref(true),
|
||||||
showMigrationConfirm: ref(false),
|
showMigrationConfirm: ref(false),
|
||||||
searchQuery: ref(""),
|
searchQuery: ref(""),
|
||||||
@@ -220,4 +221,52 @@ describe("UpdateCenterModal", () => {
|
|||||||
|
|
||||||
expect(store.requestClose).toHaveBeenCalledTimes(1);
|
expect(store.requestClose).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("shows loading panel when loading with no items", () => {
|
||||||
|
const store = createStore({
|
||||||
|
items: [],
|
||||||
|
tasks: [],
|
||||||
|
warnings: [],
|
||||||
|
hasRunningTasks: false,
|
||||||
|
});
|
||||||
|
store.loading.value = true;
|
||||||
|
|
||||||
|
render(UpdateCenterModal, {
|
||||||
|
props: {
|
||||||
|
show: true,
|
||||||
|
store,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getByText("正在检查更新…")).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows refresh hint while loading with existing items", () => {
|
||||||
|
const store = createStore({ hasRunningTasks: false });
|
||||||
|
store.loading.value = true;
|
||||||
|
|
||||||
|
render(UpdateCenterModal, {
|
||||||
|
props: {
|
||||||
|
show: true,
|
||||||
|
store,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getByText("Spark Weather")).toBeTruthy();
|
||||||
|
expect(screen.getByText("正在刷新更新列表…")).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("disables refresh button while loading", () => {
|
||||||
|
const store = createStore({ hasRunningTasks: false });
|
||||||
|
store.loading.value = true;
|
||||||
|
|
||||||
|
render(UpdateCenterModal, {
|
||||||
|
props: {
|
||||||
|
show: true,
|
||||||
|
store,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getByRole("button", { name: /刷新/ })).toBeDisabled();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -61,10 +61,16 @@ describe("updateCenter store", () => {
|
|||||||
open.mockResolvedValue(snapshot);
|
open.mockResolvedValue(snapshot);
|
||||||
const store = createUpdateCenterStore();
|
const store = createUpdateCenterStore();
|
||||||
|
|
||||||
await store.open("apm");
|
const openPromise = store.open("apm");
|
||||||
|
|
||||||
|
expect(store.isOpen.value).toBe(true);
|
||||||
|
expect(store.loading.value).toBe(true);
|
||||||
|
|
||||||
|
await openPromise;
|
||||||
|
|
||||||
expect(open).toHaveBeenCalledWith("apm");
|
expect(open).toHaveBeenCalledWith("apm");
|
||||||
expect(store.isOpen.value).toBe(true);
|
expect(store.isOpen.value).toBe(true);
|
||||||
|
expect(store.loading.value).toBe(false);
|
||||||
expect(store.snapshot.value).toEqual(snapshot);
|
expect(store.snapshot.value).toEqual(snapshot);
|
||||||
expect(store.filteredItems.value).toEqual(snapshot.items);
|
expect(store.filteredItems.value).toEqual(snapshot.items);
|
||||||
});
|
});
|
||||||
@@ -76,7 +82,12 @@ describe("updateCenter store", () => {
|
|||||||
const store = createUpdateCenterStore();
|
const store = createUpdateCenterStore();
|
||||||
|
|
||||||
await store.open("apm");
|
await store.open("apm");
|
||||||
await store.refresh();
|
|
||||||
|
const refreshPromise = store.refresh();
|
||||||
|
expect(store.loading.value).toBe(true);
|
||||||
|
|
||||||
|
await refreshPromise;
|
||||||
|
expect(store.loading.value).toBe(false);
|
||||||
|
|
||||||
expect(refresh).toHaveBeenCalledWith("apm");
|
expect(refresh).toHaveBeenCalledWith("apm");
|
||||||
});
|
});
|
||||||
@@ -209,11 +220,13 @@ describe("updateCenter store", () => {
|
|||||||
it("blocks close requests while the snapshot reports running tasks", () => {
|
it("blocks close requests while the snapshot reports running tasks", () => {
|
||||||
const store = createUpdateCenterStore();
|
const store = createUpdateCenterStore();
|
||||||
store.isOpen.value = true;
|
store.isOpen.value = true;
|
||||||
|
store.loading.value = true;
|
||||||
store.snapshot.value = createSnapshot({ hasRunningTasks: true });
|
store.snapshot.value = createSnapshot({ hasRunningTasks: true });
|
||||||
|
|
||||||
store.requestClose();
|
store.requestClose();
|
||||||
|
|
||||||
expect(store.isOpen.value).toBe(false);
|
expect(store.isOpen.value).toBe(false);
|
||||||
|
expect(store.loading.value).toBe(false);
|
||||||
expect(store.showCloseConfirm.value).toBe(false);
|
expect(store.showCloseConfirm.value).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
:selected-count="selectedCount"
|
:selected-count="selectedCount"
|
||||||
:all-selected="store.allSelected.value"
|
:all-selected="store.allSelected.value"
|
||||||
:some-selected="store.someSelected.value"
|
:some-selected="store.someSelected.value"
|
||||||
|
:loading="store.loading.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"
|
||||||
@@ -42,16 +43,33 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex min-h-0 flex-1">
|
<div
|
||||||
<UpdateCenterList
|
v-if="store.loading.value && store.filteredItems.value.length === 0"
|
||||||
:items="store.filteredItems.value"
|
class="flex min-h-0 flex-1 items-center justify-center p-6"
|
||||||
:tasks="store.snapshot.value.tasks"
|
>
|
||||||
:selected-task-keys="store.selectedTaskKeys.value"
|
<div class="flex flex-col items-center gap-3 text-slate-500 dark:text-slate-400">
|
||||||
@toggle-selection="emit('toggle-selection', $event)"
|
<i class="fas fa-circle-notch fa-spin text-3xl"></i>
|
||||||
@ignore-item="store.ignoreItem"
|
<p class="text-sm">正在检查更新…</p>
|
||||||
@unignore-item="store.unignoreItem"
|
</div>
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
<template v-else>
|
||||||
|
<div
|
||||||
|
v-if="store.loading.value && store.filteredItems.value.length > 0"
|
||||||
|
class="border-b border-slate-200/70 px-6 py-2 text-center text-xs text-slate-400 dark:border-slate-800/70 dark:text-slate-500"
|
||||||
|
>
|
||||||
|
正在刷新更新列表…
|
||||||
|
</div>
|
||||||
|
<div class="flex min-h-0 flex-1">
|
||||||
|
<UpdateCenterList
|
||||||
|
:items="store.filteredItems.value"
|
||||||
|
:tasks="store.snapshot.value.tasks"
|
||||||
|
:selected-task-keys="store.selectedTaskKeys.value"
|
||||||
|
@toggle-selection="emit('toggle-selection', $event)"
|
||||||
|
@ignore-item="store.ignoreItem"
|
||||||
|
@unignore-item="store.unignoreItem"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<UpdateCenterMigrationConfirm
|
<UpdateCenterMigrationConfirm
|
||||||
:show="store.showMigrationConfirm.value"
|
:show="store.showMigrationConfirm.value"
|
||||||
|
|||||||
@@ -14,11 +14,12 @@
|
|||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="inline-flex items-center gap-2 rounded-2xl border border-slate-200/70 px-4 py-2 text-sm font-semibold text-slate-600 transition hover:bg-slate-50 dark:border-slate-700 dark:text-slate-200 dark:hover:bg-slate-800"
|
class="inline-flex items-center gap-2 rounded-2xl border border-slate-200/70 px-4 py-2 text-sm font-semibold text-slate-600 transition hover:bg-slate-50 disabled:opacity-40 dark:border-slate-700 dark:text-slate-200 dark:hover:bg-slate-800"
|
||||||
|
:disabled="loading"
|
||||||
@click="$emit('refresh')"
|
@click="$emit('refresh')"
|
||||||
>
|
>
|
||||||
<i class="fas fa-sync-alt"></i>
|
<i class="fas fa-sync-alt" :class="{ 'animate-spin': loading }"></i>
|
||||||
刷新
|
{{ loading ? "刷新中" : "刷新" }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -88,6 +89,7 @@ const props = defineProps<{
|
|||||||
selectedCount: number;
|
selectedCount: number;
|
||||||
allSelected: boolean;
|
allSelected: boolean;
|
||||||
someSelected: boolean;
|
someSelected: boolean;
|
||||||
|
loading?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ const EMPTY_SNAPSHOT: UpdateCenterSnapshot = {
|
|||||||
|
|
||||||
export interface UpdateCenterStore {
|
export interface UpdateCenterStore {
|
||||||
isOpen: Ref<boolean>;
|
isOpen: Ref<boolean>;
|
||||||
|
loading: Ref<boolean>;
|
||||||
showCloseConfirm: Ref<boolean>;
|
showCloseConfirm: Ref<boolean>;
|
||||||
showMigrationConfirm: Ref<boolean>;
|
showMigrationConfirm: Ref<boolean>;
|
||||||
searchQuery: Ref<string>;
|
searchQuery: Ref<string>;
|
||||||
@@ -54,6 +55,7 @@ const matchesSearch = (item: UpdateCenterItem, query: string): boolean => {
|
|||||||
|
|
||||||
export const createUpdateCenterStore = (): UpdateCenterStore => {
|
export const createUpdateCenterStore = (): UpdateCenterStore => {
|
||||||
const isOpen = ref(false);
|
const isOpen = ref(false);
|
||||||
|
const loading = ref(false);
|
||||||
const showCloseConfirm = ref(false);
|
const showCloseConfirm = ref(false);
|
||||||
const showMigrationConfirm = ref(false);
|
const showMigrationConfirm = ref(false);
|
||||||
const searchQuery = ref("");
|
const searchQuery = ref("");
|
||||||
@@ -134,17 +136,27 @@ export const createUpdateCenterStore = (): UpdateCenterStore => {
|
|||||||
const open = async (storeFilter: StoreFilter = "both"): Promise<void> => {
|
const open = async (storeFilter: StoreFilter = "both"): Promise<void> => {
|
||||||
lastStoreFilter = storeFilter;
|
lastStoreFilter = storeFilter;
|
||||||
resetSessionState();
|
resetSessionState();
|
||||||
const nextSnapshot = await window.updateCenter.open(storeFilter);
|
|
||||||
applySnapshot(nextSnapshot);
|
|
||||||
isOpen.value = true;
|
isOpen.value = true;
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const nextSnapshot = await window.updateCenter.open(storeFilter);
|
||||||
|
applySnapshot(nextSnapshot);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const refresh = async (
|
const refresh = async (
|
||||||
storeFilter: StoreFilter = lastStoreFilter,
|
storeFilter: StoreFilter = lastStoreFilter,
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
lastStoreFilter = storeFilter;
|
lastStoreFilter = storeFilter;
|
||||||
const nextSnapshot = await window.updateCenter.refresh(storeFilter);
|
loading.value = true;
|
||||||
applySnapshot(nextSnapshot);
|
try {
|
||||||
|
const nextSnapshot = await window.updateCenter.refresh(storeFilter);
|
||||||
|
applySnapshot(nextSnapshot);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const ignoreItem = async (
|
const ignoreItem = async (
|
||||||
@@ -197,6 +209,7 @@ export const createUpdateCenterStore = (): UpdateCenterStore => {
|
|||||||
|
|
||||||
const closeNow = (): void => {
|
const closeNow = (): void => {
|
||||||
resetSessionState();
|
resetSessionState();
|
||||||
|
loading.value = false;
|
||||||
isOpen.value = false;
|
isOpen.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -270,6 +283,7 @@ export const createUpdateCenterStore = (): UpdateCenterStore => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
isOpen,
|
isOpen,
|
||||||
|
loading,
|
||||||
showCloseConfirm,
|
showCloseConfirm,
|
||||||
showMigrationConfirm,
|
showMigrationConfirm,
|
||||||
searchQuery,
|
searchQuery,
|
||||||
|
|||||||
Reference in New Issue
Block a user