mirror of
https://gitee.com/spark-store-project/spark-store
synced 2026-04-26 01:10:16 +08:00
fix: 修复应用还没有安装完,按钮就重新变成可安装状态 (#11)
fix:先用比较简单的方案解决安装之后卸载了还是显示已安装的问题 fix: 在安装成功的ipc信息之后再删除安装队列的任务 删除pnpm workspace
This commit is contained in:
@@ -4,7 +4,7 @@ import readline from 'node:readline';
|
|||||||
import { promisify } from 'node:util';
|
import { promisify } from 'node:util';
|
||||||
import pino from 'pino';
|
import pino from 'pino';
|
||||||
|
|
||||||
import { InstalledAppInfo } from '../../typedefinition';
|
import { ChannelPayload, InstalledAppInfo } from '../../typedefinition';
|
||||||
|
|
||||||
const logger = pino({ 'name': 'install-manager' });
|
const logger = pino({ 'name': 'install-manager' });
|
||||||
|
|
||||||
@@ -333,7 +333,7 @@ ipcMain.on('remove-installed', async (_event, pkgname: string) => {
|
|||||||
time: Date.now(),
|
time: Date.now(),
|
||||||
exitCode: code,
|
exitCode: code,
|
||||||
message: JSON.stringify(messageJSONObj)
|
message: JSON.stringify(messageJSONObj)
|
||||||
});
|
} satisfies ChannelPayload);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -5,3 +5,10 @@ export interface InstalledAppInfo {
|
|||||||
flags: string;
|
flags: string;
|
||||||
raw: string;
|
raw: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export type ChannelPayload = {
|
||||||
|
success: boolean;
|
||||||
|
message: string;
|
||||||
|
[k: string]: unknown;
|
||||||
|
};
|
||||||
25
src/App.vue
25
src/App.vue
@@ -57,11 +57,10 @@ import InstalledAppsModal from './components/InstalledAppsModal.vue';
|
|||||||
import UpdateAppsModal from './components/UpdateAppsModal.vue';
|
import UpdateAppsModal from './components/UpdateAppsModal.vue';
|
||||||
import UninstallConfirmModal from './components/UninstallConfirmModal.vue';
|
import UninstallConfirmModal from './components/UninstallConfirmModal.vue';
|
||||||
import { APM_STORE_BASE_URL, currentApp, currentAppIsInstalled } from './global/storeConfig';
|
import { APM_STORE_BASE_URL, currentApp, currentAppIsInstalled } from './global/storeConfig';
|
||||||
import { downloads } from './global/downloadStatus';
|
import { downloads, removeDownloadItem, watchDownloadsChange } from './global/downloadStatus';
|
||||||
import { handleInstall, handleRetry, handleUpgrade } from './modeuls/processInstall';
|
import { handleInstall, handleRetry, handleUpgrade } from './modeuls/processInstall';
|
||||||
import type { App, AppJson, DownloadItem, UpdateAppItem, InstalledAppInfo } from './global/typedefinition';
|
import type { App, AppJson, DownloadItem, UpdateAppItem, InstalledAppInfo, ChannelPayload } from './global/typedefinition';
|
||||||
import type { Ref } from 'vue';
|
import type { Ref } from 'vue';
|
||||||
|
|
||||||
const logger = pino();
|
const logger = pino();
|
||||||
|
|
||||||
// Axios 全局配置
|
// Axios 全局配置
|
||||||
@@ -371,6 +370,8 @@ const requestUninstall = (app: App) => {
|
|||||||
if (target) {
|
if (target) {
|
||||||
uninstallTargetApp.value = target as App;
|
uninstallTargetApp.value = target as App;
|
||||||
showUninstallModal.value = true;
|
showUninstallModal.value = true;
|
||||||
|
// TODO: 挪到卸载完成ipc回调里面
|
||||||
|
removeDownloadItem(app.pkgname);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -396,6 +397,14 @@ const onUninstallSuccess = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const installCompleteCallback = () => {
|
||||||
|
if (currentApp.value) {
|
||||||
|
checkAppInstalled(currentApp.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watchDownloadsChange(installCompleteCallback);
|
||||||
|
|
||||||
const uninstallInstalledApp = (app: App) => {
|
const uninstallInstalledApp = (app: App) => {
|
||||||
requestUninstall(app);
|
requestUninstall(app);
|
||||||
};
|
};
|
||||||
@@ -651,7 +660,7 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
window.ipcRenderer.on('deep-link-install', (_event: any, pkgname: string) => {
|
window.ipcRenderer.on('deep-link-install', (_event: Electron.IpcRendererEvent, pkgname: string) => {
|
||||||
const tryOpen = () => {
|
const tryOpen = () => {
|
||||||
const target = apps.value.find(a => a.pkgname === pkgname);
|
const target = apps.value.find(a => a.pkgname === pkgname);
|
||||||
if (target) {
|
if (target) {
|
||||||
@@ -673,6 +682,14 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
window.ipcRenderer.on('remove-complete', (_event: Electron.IpcRendererEvent, payload: ChannelPayload) => {
|
||||||
|
const pkgname = currentApp.value?.pkgname
|
||||||
|
if(payload.success && pkgname){
|
||||||
|
removeDownloadItem(pkgname);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
window.ipcRenderer.send('renderer-ready', { status: true });
|
window.ipcRenderer.send('renderer-ready', { status: true });
|
||||||
logger.info('Renderer process is ready!');
|
logger.info('Renderer process is ready!');
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -25,9 +25,9 @@
|
|||||||
class="inline-flex items-center gap-2 rounded-2xl bg-gradient-to-r px-4 py-2 text-sm font-semibold text-white shadow-lg disabled:opacity-40 transition hover:-translate-y-0.5"
|
class="inline-flex items-center gap-2 rounded-2xl bg-gradient-to-r px-4 py-2 text-sm font-semibold text-white shadow-lg disabled:opacity-40 transition hover:-translate-y-0.5"
|
||||||
:class="installFeedback ? 'from-emerald-500 to-emerald-600' : 'from-brand to-brand-dark'"
|
:class="installFeedback ? 'from-emerald-500 to-emerald-600' : 'from-brand to-brand-dark'"
|
||||||
@click="handleInstall"
|
@click="handleInstall"
|
||||||
:disabled="installFeedback">
|
:disabled="installFeedback || isCompleted">
|
||||||
<i class="fas" :class="installFeedback ? 'fa-check' : 'fa-download'"></i>
|
<i class="fas" :class="installFeedback ? 'fa-check' : 'fa-download'"></i>
|
||||||
<span>{{ installFeedback ? '已加入队列' : '安装' }}</span>
|
<span>{{ installBtnText }}</span>
|
||||||
</button>
|
</button>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<button type="button"
|
<button type="button"
|
||||||
@@ -109,7 +109,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref, useAttrs } from 'vue';
|
import { computed,useAttrs } from 'vue';
|
||||||
|
import { useDownloadItemStatus,useInstallFeedback } from '../global/downloadStatus';
|
||||||
import { APM_STORE_BASE_URL } from '../global/storeConfig';
|
import { APM_STORE_BASE_URL } from '../global/storeConfig';
|
||||||
import type { App } from '../global/typedefinition';
|
import type { App } from '../global/typedefinition';
|
||||||
|
|
||||||
@@ -131,8 +132,16 @@ const emit = defineEmits<{
|
|||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
|
||||||
const installFeedback = ref(false);
|
const appPkgname = computed(() => props.app?.pkgname);
|
||||||
|
const { installFeedback } = useInstallFeedback(appPkgname);
|
||||||
|
const { isCompleted } = useDownloadItemStatus(appPkgname);
|
||||||
|
const installBtnText = computed(() => {
|
||||||
|
if (isCompleted.value) {
|
||||||
|
// TODO: 似乎有一个时间差,安装好了之后并不是立马就可以从已安装列表看见
|
||||||
|
return "已安装";
|
||||||
|
}
|
||||||
|
return installFeedback.value ? "已加入队列" : "安装";
|
||||||
|
});
|
||||||
const iconPath = computed(() => {
|
const iconPath = computed(() => {
|
||||||
if (!props.app) return '';
|
if (!props.app) return '';
|
||||||
return `${APM_STORE_BASE_URL}/${window.apm_store.arch}/${props.app.category}/${props.app.pkgname}/icon.png`;
|
return `${APM_STORE_BASE_URL}/${window.apm_store.arch}/${props.app.category}/${props.app.pkgname}/icon.png`;
|
||||||
@@ -143,11 +152,7 @@ const closeModal = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleInstall = () => {
|
const handleInstall = () => {
|
||||||
installFeedback.value = true;
|
|
||||||
emit('install');
|
emit('install');
|
||||||
setTimeout(() => {
|
|
||||||
installFeedback.value = false;
|
|
||||||
}, 2000);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemove = () => {
|
const handleRemove = () => {
|
||||||
|
|||||||
@@ -1,4 +1,78 @@
|
|||||||
import { ref } from "vue";
|
import { computed,ComputedRef,ref,unref,watch } from "vue";
|
||||||
import type { DownloadItem } from './typedefinition';
|
import type { DownloadItem,DownloadItemStatus } from "./typedefinition";
|
||||||
|
|
||||||
export const downloads = ref<DownloadItem[]>([]);
|
export const downloads = ref<DownloadItem[]>([]);
|
||||||
|
|
||||||
|
export function removeDownloadItem(pkgname:string) {
|
||||||
|
const list = downloads.value;
|
||||||
|
for (let i = list.length - 1; i >= 0; i -= 1) {
|
||||||
|
if (list[i].pkgname === pkgname) {
|
||||||
|
list.splice(i,1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function watchDownloadsChange (cb: () => void) {
|
||||||
|
const statusById = new Map<number,DownloadItemStatus>();
|
||||||
|
|
||||||
|
for (const item of downloads.value) {
|
||||||
|
statusById.set(item.id,item.status);
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
downloads,
|
||||||
|
(list) => {
|
||||||
|
for (const item of list) {
|
||||||
|
const prevStatus = statusById.get(item.id);
|
||||||
|
if (item.status === "completed" && prevStatus !== "completed") {
|
||||||
|
cb();
|
||||||
|
}
|
||||||
|
statusById.set(item.id,item.status);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (statusById.size > list.length) {
|
||||||
|
const liveIds = new Set<number>();
|
||||||
|
for (const item of list) liveIds.add(item.id);
|
||||||
|
for (const id of statusById.keys()) {
|
||||||
|
if (!liveIds.has(id)) statusById.delete(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useDownloadItemStatus (
|
||||||
|
pkgname?: ComputedRef<string | undefined>,
|
||||||
|
) {
|
||||||
|
const status: ComputedRef<DownloadItemStatus | undefined> = computed(() => {
|
||||||
|
const name = unref(pkgname);
|
||||||
|
if (!name) return;
|
||||||
|
const task = downloads.value.find((d) => d.pkgname === name);
|
||||||
|
if (!task) return;
|
||||||
|
return task.status;
|
||||||
|
});
|
||||||
|
|
||||||
|
const isCompleted = computed(() => {
|
||||||
|
return status.value === "completed";
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
status,
|
||||||
|
isCompleted,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useInstallFeedback (pkgname?: ComputedRef<string | undefined>) {
|
||||||
|
const installFeedback = computed(() => {
|
||||||
|
const name = unref(pkgname);
|
||||||
|
if (!name) return false;
|
||||||
|
const task = downloads.value.find((d) => d.pkgname === name);
|
||||||
|
if (!task) return false;
|
||||||
|
return task.status !== "completed" && task.status !== "failed";
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
installFeedback,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,13 +11,15 @@ export interface DownloadResult extends InstallLog {
|
|||||||
exitCode: number | null;
|
exitCode: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type DownloadItemStatus = 'downloading' | 'installing' | 'paused' | 'completed' | 'failed' | 'queued'; // 可根据实际状态扩展
|
||||||
|
|
||||||
export interface DownloadItem {
|
export interface DownloadItem {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
pkgname: string;
|
pkgname: string;
|
||||||
version: string;
|
version: string;
|
||||||
icon: string;
|
icon: string;
|
||||||
status: 'downloading' | 'installing' | 'paused' | 'completed' | 'failed' | 'queued'; // 可根据实际状态扩展
|
status: DownloadItemStatus;
|
||||||
progress: number; // 0 ~ 100 的百分比,或 0 ~ 1 的小数(建议统一)
|
progress: number; // 0 ~ 100 的百分比,或 0 ~ 1 的小数(建议统一)
|
||||||
downloadedSize: number; // 已下载字节数
|
downloadedSize: number; // 已下载字节数
|
||||||
totalSize: number; // 总字节数(可能为 0 初始时)
|
totalSize: number; // 总字节数(可能为 0 初始时)
|
||||||
@@ -108,3 +110,12 @@ export interface InstalledAppInfo {
|
|||||||
flags: string;
|
flags: string;
|
||||||
raw: string;
|
raw: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ipcSender传递的信息
|
||||||
|
*/
|
||||||
|
export type ChannelPayload = {
|
||||||
|
success: boolean;
|
||||||
|
message: string;
|
||||||
|
[k: string]: unknown;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user