diff --git a/electron/main/backend/download-manager.ts b/electron/main/backend/install-manager.ts similarity index 82% rename from electron/main/backend/download-manager.ts rename to electron/main/backend/install-manager.ts index 3badfe31..356b393f 100644 --- a/electron/main/backend/download-manager.ts +++ b/electron/main/backend/install-manager.ts @@ -6,7 +6,7 @@ import pino from 'pino'; const logger = pino({ 'name': 'download-manager' }); -type DownloadTask = { +type InstallTask = { id: number; execCommand: string; execParams: string[]; @@ -14,7 +14,7 @@ type DownloadTask = { webContents: WebContents | null; }; -const tasks = new Map(); +const tasks = new Map(); let idle = true; // Indicates if the installation manager is idle @@ -82,7 +82,7 @@ ipcMain.on('queue-install', async (event, download_json) => { } execParams.push('install', '-y', pkgname); - const task: DownloadTask = { + const task: InstallTask = { id, execCommand, execParams, @@ -166,4 +166,38 @@ function processNextInQueue(index: number) { if (tasks.size > 0) processNextInQueue(0); }); -} \ No newline at end of file +} + +ipcMain.handle('check-installed', async (_event, pkgname: string) => { + if (!pkgname) { + logger.warn('check-installed missing pkgname'); + return false; + } + let isInstalled = false; + + logger.info(`检查应用是否已安装: ${pkgname}`); + + let child = spawn('/usr/bin/apm', ['list', '--installed', pkgname], { + shell: true, + env: process.env + }); + + let output = ''; + + child.stdout.on('data', (data) => { + output += data.toString(); + }); + + await new Promise((resolve) => { + child.on('close', (code) => { + if (code === 0 && output.includes(pkgname)) { + isInstalled = true; + logger.info(`应用已安装: ${pkgname}`); + } else { + logger.info(`应用未安装: ${pkgname}`); + } + resolve(); + }); + }); + return isInstalled; +}); \ No newline at end of file diff --git a/electron/main/index.ts b/electron/main/index.ts index 0be3d0bb..b73d028d 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -10,7 +10,7 @@ if (!app.requestSingleInstanceLock()) { } import './handle-url-scheme.js' -import './backend/download-manager.js' +import './backend/install-manager.js' const require = createRequire(import.meta.url) const __dirname = path.dirname(fileURLToPath(import.meta.url)) diff --git a/src/App.vue b/src/App.vue index b64d1b81..66d1ac36 100644 --- a/src/App.vue +++ b/src/App.vue @@ -11,8 +11,8 @@ - + @@ -40,7 +40,7 @@ import DownloadQueue from './components/DownloadQueue.vue'; import DownloadDetail from './components/DownloadDetail.vue'; import { APM_STORE_ARCHITECTURE, APM_STORE_BASE_URL, currentApp } from './global/storeConfig'; import { downloads } from './global/downloadStatus'; -import { handleInstall, handleRetry } from './modeuls/processInstall'; +import { handleInstall, handleRetry, handleRemove } from './modeuls/processInstall'; const logger = pino(); @@ -60,6 +60,7 @@ const showModal = ref(false); const showPreview = ref(false); const currentScreenIndex = ref(0); const screenshots = ref([]); +const currentAppIsInstalled = ref(false); const loading = ref(true); const showDownloadDetailModal = ref(false); const currentDownload = ref(null); @@ -127,6 +128,10 @@ const openDetail = (app) => { loadScreenshots(app); showModal.value = true; + // 检测本地是否已经安装了该应用 + currentAppIsInstalled.value = false; + checkAppInstalled(app); + // 确保模态框显示后滚动到顶部 nextTick(() => { const modal = document.querySelector('.modal'); @@ -134,6 +139,12 @@ const openDetail = (app) => { }); }; +const checkAppInstalled = (app) => { + window.ipcRenderer.invoke('check-installed', app.Pkgname).then((isInstalled) => { + currentAppIsInstalled.value = isInstalled; + }); +}; + const loadScreenshots = (app) => { screenshots.value = []; for (let i = 1; i <= 5; i++) { diff --git a/src/components/AppDetailModal.vue b/src/components/AppDetailModal.vue index d36efdb4..98b66b9e 100644 --- a/src/components/AppDetailModal.vue +++ b/src/components/AppDetailModal.vue @@ -15,9 +15,12 @@ @@ -95,10 +98,14 @@ const props = defineProps({ screenshots: { type: Array, required: true + }, + isinstalled: { + type: Boolean, + required: true } }); -const emit = defineEmits(['close', 'install', 'open-preview']); +const emit = defineEmits(['close', 'install', 'remove', 'open-preview']); const iconPath = computed(() => { return props.app ? `${APM_STORE_BASE_URL}/${APM_STORE_ARCHITECTURE}/${props.app._category}/${props.app.Pkgname}/icon.png` : ''; @@ -112,6 +119,10 @@ const handleInstall = () => { emit('install'); }; +const handelRemove = () => { + emit('remove'); +}; + const openPreview = (index) => { emit('open-preview', index); }; @@ -119,4 +130,9 @@ const openPreview = (index) => { diff --git a/src/modeuls/processInstall.ts b/src/modeuls/processInstall.ts index adb16b78..d4fa4004 100644 --- a/src/modeuls/processInstall.ts +++ b/src/modeuls/processInstall.ts @@ -53,6 +53,11 @@ export const handleRetry = (download_: DownloadItem) => { window.ipcRenderer.send('queue-install', JSON.stringify(download_)); }; +export const handleRemove = (download_: DownloadItem) => { + if (!currentApp.value?.Pkgname) return; + console.log('请求卸载: ', currentApp.value.Pkgname); +} + window.ipcRenderer.on('install-status', (_event, log: InstallLog) => { const downloadObj: any = downloads.value.find(d => d.id === log.id); downloadObj.status = log.message; @@ -72,4 +77,4 @@ window.ipcRenderer.on('install-complete', (_event, log: DownloadResult) => { } else { downloadObj.status = 'failed'; } -}); \ No newline at end of file +});