Files
spark-store/src/components/UpdateCenterModal.vue
T
xiyidaiwa 0f62a7e36a feat(update-center): 支持被系统锁定(hold)包的强制安装与更新中心体验优化
- 标记 apt-mark hold 的 spark(aptss) 源包,默认禁用勾选并提示开启强制安装
- 更新中心发起更新时透传 forceHeld,后端 Spark 本地 deb 走 shell-caller
  force-ssinstall 分支(单次免密 pkexec:unhold→ssinstall→hold),避免二次提权
- 无可选项时禁用全选并提示"无可更新项";更新选中后从列表中移除已启动项
- DownloadDetail 紧凑布局避免整页滚动;Toolbar 全选与更新选中按钮同行
- 继承 PKGNAME_PATTERN 与 metalinkUrl 白名单(https+*.spark-app.store+拒..)纵深防御
- apm 源无 hold 概念,forceHeld 在 apm 分支忽略(正确行为)
2026-08-13 16:42:21 +08:00

144 lines
5.1 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<Transition
enter-active-class="duration-200 ease-out"
enter-from-class="opacity-0 scale-95"
enter-to-class="opacity-100 scale-100"
leave-active-class="duration-150 ease-in"
leave-from-class="opacity-100 scale-100"
leave-to-class="opacity-0 scale-95"
>
<div
v-if="show"
class="fixed inset-0 z-50 flex items-start justify-center bg-slate-900/70 px-4 py-6 lg:py-10"
@wheel="onOverlayWheel"
@click="onOverlayClick"
>
<div
class="flex max-h-[90vh] w-full max-w-6xl flex-col overflow-hidden rounded-3xl border border-white/10 bg-white/95 shadow-2xl dark:border-slate-800 dark:bg-slate-900"
@click.stop
>
<UpdateCenterToolbar
:search-query="store.searchQuery.value"
:selected-count="selectedCount"
:selectable-count="store.selectableCount.value"
:all-selected="store.allSelected.value"
:some-selected="store.someSelected.value"
:loading="store.loading.value"
@refresh="store.refresh"
@start-selected="emit('request-start-selected')"
@request-close="store.requestClose"
@toggle-select-all="store.toggleSelectAll"
@update:search-query="emit('update:search-query', $event)"
/>
<div
v-if="store.snapshot.value.warnings.length > 0"
class="mx-6 mt-4 rounded-2xl border border-amber-200 bg-amber-50/80 px-4 py-3 text-sm text-amber-900 dark:border-amber-500/30 dark:bg-amber-500/10 dark:text-amber-100"
>
<p
v-for="warning in store.snapshot.value.warnings"
:key="warning"
class="leading-6"
>
{{ warning }}
</p>
</div>
<div
v-if="store.loading.value && store.filteredItems.value.length === 0"
class="flex min-h-0 flex-1 items-center justify-center p-6"
>
<div
class="flex flex-col items-center gap-3 text-slate-500 dark:text-slate-400"
>
<i class="fas fa-circle-notch fa-spin text-3xl"></i>
<p class="text-sm">正在检查更新</p>
</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
v-if="heldLockedCount > 0"
class="mx-6 mt-4 rounded-2xl border border-amber-200 bg-amber-50/80 px-4 py-3 text-sm text-amber-900 dark:border-amber-500/30 dark:bg-amber-500/10 dark:text-amber-100"
>
<p class="leading-6">
检测到 {{ heldLockedCount }} 个软件被系统锁定hold),默认不会升级如需强制升级请在对应软件上打开强制安装开关
</p>
</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"
:forced-task-keys="store.forcedTaskKeys.value"
:apps="apps"
@toggle-selection="emit('toggle-selection', $event)"
@toggle-force="store.toggleForce"
@ignore-item="store.ignoreItem"
@unignore-item="store.unignoreItem"
/>
</div>
</template>
<UpdateCenterMigrationConfirm
:show="store.showMigrationConfirm.value"
@close="emit('dismiss-migration-confirm')"
@confirm="emit('confirm-migration-start')"
/>
</div>
</div>
</Transition>
</template>
<script setup lang="ts">
import { computed } from "vue";
import type { App } from "@/global/typedefinition";
import type { UpdateCenterStore } from "@/modules/updateCenter";
import UpdateCenterList from "./update-center/UpdateCenterList.vue";
import UpdateCenterMigrationConfirm from "./update-center/UpdateCenterMigrationConfirm.vue";
import UpdateCenterToolbar from "./update-center/UpdateCenterToolbar.vue";
const emit = defineEmits<{
(e: "update:search-query", value: string): void;
(e: "toggle-selection", taskKey: string): void;
(e: "request-start-selected"): void;
(e: "confirm-migration-start"): void;
(e: "dismiss-migration-confirm"): void;
}>();
const props = defineProps<{
show: boolean;
store: UpdateCenterStore;
apps: App[];
}>();
const selectedCount = computed(() => props.store.getSelectedItems().length);
// 未开启强制安装的被锁定(held)软件数量,用于顶部提示
const heldLockedCount = computed(
() =>
props.store.snapshot.value.items.filter(
(i) =>
i.held === true && !props.store.forcedTaskKeys.value.has(i.taskKey),
).length,
);
const onOverlayWheel = (e: WheelEvent) => {
const target = e.target as HTMLElement;
if (target.closest(".overflow-y-auto, .overflow-auto")) return;
e.preventDefault();
};
const onOverlayClick = () => {
props.store.requestClose();
};
</script>