diff --git a/README.md b/README.md index d93ef556..62524a0b 100644 --- a/README.md +++ b/README.md @@ -22,9 +22,10 @@ - [x] 可以展示应用列表及其详细信息 - [ ] 实现应用下载&下载列表管理 - [x] 实现应用安装&重试安装 - - [ ] 实现应用卸载 + - [x] 实现应用卸载 - [ ] 实现应用更新 - - [ ] 支持显示本地是否已经安装 + - [ ] 显示本地已安装app + - [x] 支持显示本地是否已经安装 - [x] 实现应用搜索 diff --git a/electron/main/backend/install-manager.ts b/electron/main/backend/install-manager.ts index 356b393f..b35c656b 100644 --- a/electron/main/backend/install-manager.ts +++ b/electron/main/backend/install-manager.ts @@ -18,6 +18,25 @@ const tasks = new Map(); let idle = true; // Indicates if the installation manager is idle +const checkSuperUserCommand = async (): Promise => { + let superUserCmd = ''; + const execAsync = promisify(exec); + if (process.getuid && process.getuid() !== 0) { + const { stdout, stderr } = await execAsync('which pkexec'); + if (stderr) { + logger.error('没有找到 pkexec 命令'); + return; + } + logger.info(`找到提升权限命令: ${stdout.trim()}`); + superUserCmd = stdout.trim(); + + if (superUserCmd.length === 0) { + logger.error('没有找到提升权限的命令 pkexec!'); + } + } + return superUserCmd; +} + // Listen for download requests from renderer process ipcMain.on('queue-install', async (event, download_json) => { const download = JSON.parse(download_json); @@ -50,28 +69,7 @@ ipcMain.on('queue-install', async (event, download_json) => { const webContents = event.sender; // 开始组装安装命令 - const execAsync = promisify(exec); - let superUserCmd = ''; - if (process.getuid && process.getuid() !== 0) { - const { stdout, stderr } = await execAsync('which pkexec'); - if (stderr) { - logger.error('没有找到 pkexec 命令'); - return; - } - logger.info(`找到提升权限命令: ${stdout.trim()}`); - superUserCmd = stdout.trim(); - - if (superUserCmd.length === 0) { - logger.error('没有找到提升权限的命令 pkexec, 无法继续安装'); - webContents.send('install-error', { - id, - time: Date.now(), - message: '无法找到提升权限的命令 pkexec,请手动安装' - }); - return; - } - } - + let superUserCmd = await checkSuperUserCommand(); let execCommand = ''; let execParams = []; if (superUserCmd.length > 0) { @@ -200,4 +198,56 @@ ipcMain.handle('check-installed', async (_event, pkgname: string) => { }); }); return isInstalled; +}); + +ipcMain.on('remove-installed', async (_event, pkgname: string) => { + const webContents = _event.sender; + if (!pkgname) { + logger.warn('remove-installed missing pkgname'); + return; + } + logger.info(`卸载已安装应用: ${pkgname}`); + + let superUserCmd = await checkSuperUserCommand(); + let execCommand = ''; + let execParams = []; + if (superUserCmd.length > 0) { + execCommand = superUserCmd; + execParams.push('/usr/bin/apm'); + } else { + execCommand = '/usr/bin/apm'; + } + let child = spawn(execCommand, [...execParams, 'remove', '-y', pkgname], { + shell: true, + env: process.env + }); + let output = ''; + + child.stdout.on('data', (data) => { + output += data.toString(); + }); + + child.on('close', (code) => { + const success = code === 0; + // 拼接json消息 + const messageJSONObj = { + message: success ? '卸载完成' : `卸载失败,退出码 ${code}`, + stdout: output, + stderr: '' + }; + + if (success) { + logger.info(messageJSONObj); + } else { + logger.error(messageJSONObj); + } + + webContents.send('remove-complete', { + id: 0, + success: success, + time: Date.now(), + exitCode: code, + message: JSON.stringify(messageJSONObj) + }); + }); }); \ No newline at end of file diff --git a/src/App.vue b/src/App.vue index 66d1ac36..ca7b4e48 100644 --- a/src/App.vue +++ b/src/App.vue @@ -38,7 +38,7 @@ import AppDetailModal from './components/AppDetailModal.vue'; import ScreenPreview from './components/ScreenPreview.vue'; 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 { APM_STORE_ARCHITECTURE, APM_STORE_BASE_URL, currentApp, currentAppIsInstalled } from './global/storeConfig'; import { downloads } from './global/downloadStatus'; import { handleInstall, handleRetry, handleRemove } from './modeuls/processInstall'; @@ -60,7 +60,6 @@ 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); diff --git a/src/global/storeConfig.ts b/src/global/storeConfig.ts index f0204414..3eb386c9 100644 --- a/src/global/storeConfig.ts +++ b/src/global/storeConfig.ts @@ -2,4 +2,7 @@ import { ref } from "vue"; export const APM_STORE_BASE_URL=import.meta.env.VITE_APM_STORE_BASE_URL; export const APM_STORE_ARCHITECTURE='amd64-apm'; -export const currentApp = ref(null); \ No newline at end of file + +// 下面的变量用于存储当前应用的信息,其实用在多个组件中 +export const currentApp = ref(null); +export const currentAppIsInstalled = ref(false); diff --git a/src/modeuls/processInstall.ts b/src/modeuls/processInstall.ts index d4fa4004..22e4f31f 100644 --- a/src/modeuls/processInstall.ts +++ b/src/modeuls/processInstall.ts @@ -2,7 +2,7 @@ // console.log('[Receive Main-process message]:', ...args) // }) -import { currentApp } from "../global/storeConfig"; +import { currentApp, currentAppIsInstalled } from "../global/storeConfig"; import { APM_STORE_BASE_URL, APM_STORE_ARCHITECTURE } from "../global/storeConfig"; import { downloads } from "../global/downloadStatus"; @@ -53,11 +53,20 @@ export const handleRetry = (download_: DownloadItem) => { window.ipcRenderer.send('queue-install', JSON.stringify(download_)); }; -export const handleRemove = (download_: DownloadItem) => { +export const handleRemove = () => { if (!currentApp.value?.Pkgname) return; - console.log('请求卸载: ', currentApp.value.Pkgname); + window.ipcRenderer.send('remove-installed', currentApp.value.Pkgname); } +window.ipcRenderer.on('remove-complete', (_event, log: DownloadResult) => { + if (log.success) { + currentAppIsInstalled.value = false; + } else { + currentAppIsInstalled.value = true; + console.error('卸载失败:', log.message); + } +}); + window.ipcRenderer.on('install-status', (_event, log: InstallLog) => { const downloadObj: any = downloads.value.find(d => d.id === log.id); downloadObj.status = log.message;