diff --git a/src/api/types.ts b/src/api/types.ts index a2d88ee4..e283cbeb 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -955,6 +955,8 @@ export interface Plugin { installed?: boolean // 运行状态 state?: boolean + // 插件源码、依赖和运行时加载状态 + runtime_status?: 'source_missing' | 'dependency_pending' | 'ready' | 'active' | 'blocked_by_policy' | 'load_failed' // 是否有详情页面 has_page?: boolean // 是否有新版本 @@ -985,6 +987,17 @@ export interface Plugin { user_rating?: number | null } +export interface PluginRuntimeSummary { + // 本轮插件源码、依赖和加载是否已收敛 + ready: boolean + // 插件运行状态变化代次 + generation: number + // 仍处于准备阶段的插件数量 + pending_count: number + // 加载失败或被策略阻止的插件数量 + failed_count: number +} + // 插件评分结果 export interface PluginRating { plugin_id: string diff --git a/src/components/cards/PluginAppCard.vue b/src/components/cards/PluginAppCard.vue index b2f80a61..59c70cc9 100644 --- a/src/components/cards/PluginAppCard.vue +++ b/src/components/cards/PluginAppCard.vue @@ -18,12 +18,15 @@ const PluginVersionHistoryDialog = defineAsyncComponent( ) const ProgressDialog = defineAsyncComponent(() => import('@/components/dialog/ProgressDialog.vue')) +type InstallHandler = (releaseVersion?: string, repoUrl?: string) => unknown + // 输入参数 const props = defineProps({ plugin: Object as PropType, width: String, height: String, count: Number, + installHandler: Function as PropType, }) const globalSettingsStore = useGlobalSettingsStore() @@ -146,6 +149,13 @@ async function installPlugin(releaseVersion?: string, repoUrl?: string) { if (!isConfirmed) return } + if (props.installHandler) { + versionHistoryDialogController?.close() + versionHistoryDialogController = null + await props.installHandler(releaseVersion, repoUrl) + return + } + try { showInstallProgress( t('plugin.installing', { @@ -187,6 +197,7 @@ function showPluginDetail() { { plugin: props.plugin, count: props.count, + installHandler: props.installHandler, }, { install: () => emit('install'), diff --git a/src/components/cards/PluginCard.vue b/src/components/cards/PluginCard.vue index fcceff61..dd9a91ab 100644 --- a/src/components/cards/PluginCard.vue +++ b/src/components/cards/PluginCard.vue @@ -33,6 +33,10 @@ const props = defineProps({ type: Boolean, default: false, }, + runtimeSettling: { + type: Boolean, + default: false, + }, }) const globalSettingsStore = useGlobalSettingsStore() @@ -44,6 +48,38 @@ const { t } = useI18n() const hasCardRating = computed(() => (props.plugin?.rating_count || 0) > 0) const hasCardStatus = computed(() => Boolean(props.plugin?.has_update) || hasCardRating.value) +const runtimeStatus = computed(() => props.plugin?.runtime_status) +const runtimePending = computed( + () => props.runtimeSettling && ['source_missing', 'dependency_pending', 'ready'].includes(runtimeStatus.value || ''), +) +const runtimeUnavailable = computed( + () => + ['blocked_by_policy', 'load_failed'].includes(runtimeStatus.value || '') || + (!props.runtimeSettling && ['source_missing', 'dependency_pending', 'ready'].includes(runtimeStatus.value || '')), +) +const runtimeActionsBlocked = computed(() => runtimePending.value || runtimeUnavailable.value) +const runtimePendingStatusKeys: Partial, string>> = { + source_missing: 'plugin.sourceRestoring', + dependency_pending: 'plugin.dependencyInstalling', + ready: 'plugin.runtimeLoading', +} +const runtimeUnavailableStatusKeys: Partial, string>> = { + source_missing: 'plugin.sourceMissing', + dependency_pending: 'plugin.dependencyPending', + ready: 'plugin.runtimeReady', + blocked_by_policy: 'plugin.blockedByPolicy', + load_failed: 'plugin.runtimeLoadFailed', +} +const showRuntimeStatusDot = computed(() => !runtimeStatus.value || runtimeStatus.value === 'active') +const runtimeStatusDotColor = computed(() => (props.plugin?.state ? 'success' : 'secondary')) +const runtimeStatusText = computed(() => { + const status = runtimeStatus.value + const statusKey = status + ? (runtimePending.value ? runtimePendingStatusKeys : runtimeUnavailableStatusKeys)[status] + : undefined + + return statusKey ? t(statusKey) : '' +}) const cardRatingValue = computed(() => Number(props.plugin?.average_rating || 0).toFixed(1)) const cardRatingSummary = computed(() => t('plugin.ratingSummary', { @@ -410,6 +446,7 @@ async function visitPluginPage() { // 打开插件详情 function openPluginDetail() { + if (runtimeActionsBlocked.value) return if (props.plugin?.has_page) showPluginInfo() else showPluginConfig() } @@ -625,9 +662,12 @@ watch( :class="{ 'app-hover-lift-card--hovering': hover.isHovering && !props.sortable, 'cursor-move': props.sortable, + 'plugin-card--runtime-blocked': runtimeActionsBlocked, + 'plugin-card--runtime-pending': runtimePending, + 'plugin-card--runtime-unavailable': runtimeUnavailable, }" :style="accentStyle" - :ripple="!props.sortable" + :ripple="!props.sortable && !runtimeActionsBlocked" >
@@ -635,7 +675,13 @@ watch( class="text-white px-2 pb-0 text-lg text-shadow whitespace-nowrap overflow-hidden text-ellipsis" :class="{ 'plugin-card__title--with-status': hasCardStatus }" > - + {{ props.plugin?.plugin_name }} v{{ props.plugin?.plugin_version }} @@ -650,7 +696,7 @@ watch( class="relative flex-shrink-0 self-center pb-3" :class="{ 'cursor-move': props.sortable && display.mdAndUp.value }" > - +
+
+ + + {{ runtimeStatusText }} +
- + @@ -701,6 +762,7 @@ watch( v-show="item.show" :key="i" :base-color="item.props.color" + :disabled="runtimeActionsBlocked && [1, 2, 4, 8].includes(item.value)" @click="item.props.click" >