mirror of
https://gitee.com/spark-store-project/spark-store
synced 2026-04-26 01:10:16 +08:00
feat(install): 实现安装管理器,支持安装、检查已安装状态和初步卸载功能
This commit is contained in:
@@ -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<number, DownloadTask>();
|
||||
const tasks = new Map<number, InstallTask>();
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
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<void>((resolve) => {
|
||||
child.on('close', (code) => {
|
||||
if (code === 0 && output.includes(pkgname)) {
|
||||
isInstalled = true;
|
||||
logger.info(`应用已安装: ${pkgname}`);
|
||||
} else {
|
||||
logger.info(`应用未安装: ${pkgname}`);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
return isInstalled;
|
||||
});
|
||||
@@ -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))
|
||||
|
||||
17
src/App.vue
17
src/App.vue
@@ -11,8 +11,8 @@
|
||||
<AppGrid :apps="filteredApps" :loading="loading" @open-detail="openDetail" />
|
||||
</main>
|
||||
|
||||
<AppDetailModal :show="showModal" :app="currentApp" :screenshots="screenshots" @close="closeDetail"
|
||||
@install="handleInstall" @open-preview="openScreenPreview" />
|
||||
<AppDetailModal :show="showModal" :app="currentApp" :screenshots="screenshots" :isinstalled="currentAppIsInstalled" @close="closeDetail"
|
||||
@install="handleInstall" @remove="handleRemove" @open-preview="openScreenPreview" />
|
||||
|
||||
<ScreenPreview :show="showPreview" :screenshots="screenshots" :current-screen-index="currentScreenIndex"
|
||||
@close="closeScreenPreview" @prev="prevScreen" @next="nextScreen" />
|
||||
@@ -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++) {
|
||||
|
||||
@@ -15,9 +15,12 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-actions">
|
||||
<button class="install-btn" @click="handleInstall">
|
||||
<button v-if="!isinstalled" class="install-btn" @click="handleInstall">
|
||||
<i class="fas fa-download"></i> 安装
|
||||
</button>
|
||||
<button v-else class="install-btn remove" @click="handelRemove">
|
||||
<i class="fas fa-trash"></i> 卸载
|
||||
</button>
|
||||
<button class="close-modal" @click="closeModal" aria-label="关闭">×</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -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) => {
|
||||
|
||||
<style scoped>
|
||||
/* 该组件样式已在全局样式中定义 */
|
||||
.install-btn.remove {
|
||||
border-color: rgb(231, 76, 60);
|
||||
box-shadow: 0 4px 12px rgb(231, 76, 60);
|
||||
background: linear-gradient(90deg, rgb(231, 76, 60), rgb(231, 76, 60));
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -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';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user