mirror of
https://gitee.com/spark-store-project/spark-store
synced 2026-04-26 01:10:16 +08:00
feat(install): added basis install process
Now it is able to install apps from the render process and properly display logs on the app detial page.
This commit is contained in:
52
src/App.vue
52
src/App.vue
@@ -29,6 +29,8 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted, watch, nextTick } from 'vue';
|
||||
import axios from 'axios';
|
||||
import pino from 'pino';
|
||||
import AppSidebar from './components/AppSidebar.vue';
|
||||
import AppHeader from './components/AppHeader.vue';
|
||||
import AppGrid from './components/AppGrid.vue';
|
||||
@@ -36,8 +38,11 @@ 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 } from './global/StoreConfig';
|
||||
import axios from 'axios';
|
||||
import { APM_STORE_ARCHITECTURE, APM_STORE_BASE_URL, currentApp } from './global/storeConfig';
|
||||
import { downloads } from './global/downloadStatus';
|
||||
import { handleInstall } from './js/processInstall';
|
||||
|
||||
const logger = pino();
|
||||
|
||||
// Axios 全局配置
|
||||
const axiosInstance = axios.create({
|
||||
@@ -53,14 +58,11 @@ const activeCategory = ref('all');
|
||||
const searchQuery = ref('');
|
||||
const showModal = ref(false);
|
||||
const showPreview = ref(false);
|
||||
const currentApp = ref(null);
|
||||
const currentScreenIndex = ref(0);
|
||||
const screenshots = ref([]);
|
||||
const loading = ref(true);
|
||||
const downloads = ref([]);
|
||||
const showDownloadDetailModal = ref(false);
|
||||
const currentDownload = ref(null);
|
||||
let downloadIdCounter = 0;
|
||||
|
||||
// 计算属性
|
||||
const filteredApps = computed(() => {
|
||||
@@ -170,40 +172,6 @@ const nextScreen = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleInstall = () => {
|
||||
if (!currentApp.value?.Pkgname) return;
|
||||
|
||||
// 创建下载任务
|
||||
const download = {
|
||||
id: ++downloadIdCounter,
|
||||
name: currentApp.value.Name,
|
||||
pkgname: currentApp.value.Pkgname,
|
||||
version: currentApp.value.Version,
|
||||
icon: `${APM_STORE_BASE_URL}/${APM_STORE_ARCHITECTURE}/${currentApp.value._category}/${currentApp.value.Pkgname}/icon.png`,
|
||||
status: 'downloading',
|
||||
progress: 0,
|
||||
downloadedSize: 0,
|
||||
totalSize: 0,
|
||||
speed: 0,
|
||||
timeRemaining: 0,
|
||||
startTime: Date.now(),
|
||||
logs: [
|
||||
{ time: Date.now(), message: '开始下载...' }
|
||||
],
|
||||
source: 'APM Store'
|
||||
};
|
||||
|
||||
downloads.value.push(download);
|
||||
|
||||
// 模拟下载进度(实际应该调用真实的下载 API)
|
||||
simulateDownload(download);
|
||||
|
||||
const encodedPkg = encodeURIComponent(currentApp.value.Pkgname);
|
||||
openApmStoreUrl(`apmstore://install?pkg=${encodedPkg}`, {
|
||||
fallbackText: `/usr/bin/apm-installer --install ${currentApp.value.Pkgname}`
|
||||
});
|
||||
};
|
||||
|
||||
const handleUpdate = () => {
|
||||
openApmStoreUrl('apmstore://action?cmd=update', {
|
||||
fallbackText: 'apm-update-tool'
|
||||
@@ -365,6 +333,7 @@ const cancelDownload = (id) => {
|
||||
});
|
||||
// 延迟删除,让用户看到取消状态
|
||||
setTimeout(() => {
|
||||
logger.info(`删除下载: ${download.pkgname}`);
|
||||
downloads.value.splice(index, 1);
|
||||
}, 1000);
|
||||
}
|
||||
@@ -410,13 +379,14 @@ const loadCategories = async () => {
|
||||
const response = await axiosInstance.get(`/${APM_STORE_ARCHITECTURE}/categories.json`);
|
||||
categories.value = response.data;
|
||||
} catch (error) {
|
||||
console.error('读取 categories.json 失败', error);
|
||||
logger.error('读取 categories.json 失败', error);
|
||||
}
|
||||
};
|
||||
|
||||
const loadApps = async () => {
|
||||
loading.value = true;
|
||||
try {
|
||||
logger.info('开始加载应用数据...');
|
||||
const promises = Object.keys(categories.value).map(async category => {
|
||||
try {
|
||||
const response = await axiosInstance.get(`/${APM_STORE_ARCHITECTURE}/${category}/applist.json`);
|
||||
@@ -437,7 +407,7 @@ const loadApps = async () => {
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('加载应用数据失败', error);
|
||||
logger.error('加载应用数据失败', error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
<script setup>
|
||||
import { computed, defineProps, defineEmits, onMounted, onBeforeUnmount, ref, watch } from 'vue';
|
||||
import { APM_STORE_ARCHITECTURE, APM_STORE_BASE_URL } from '../global/StoreConfig';
|
||||
import { APM_STORE_ARCHITECTURE, APM_STORE_BASE_URL } from '../global/storeConfig';
|
||||
|
||||
const props = defineProps({
|
||||
app: {
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
|
||||
<script setup>
|
||||
import { computed, defineProps, defineEmits } from 'vue';
|
||||
import { APM_STORE_ARCHITECTURE, APM_STORE_BASE_URL } from '../global/StoreConfig';
|
||||
import { APM_STORE_ARCHITECTURE, APM_STORE_BASE_URL } from '../global/storeConfig';
|
||||
|
||||
const props = defineProps({
|
||||
show: {
|
||||
|
||||
@@ -120,7 +120,7 @@ const emit = defineEmits([
|
||||
'show-detail'
|
||||
]);
|
||||
|
||||
const isExpanded = ref(true);
|
||||
const isExpanded = ref(false);
|
||||
|
||||
const activeDownloads = computed(() => {
|
||||
return props.downloads.filter(d =>
|
||||
@@ -187,6 +187,7 @@ const showDownloadDetail = (download) => {
|
||||
|
||||
.queue-header:hover {
|
||||
background: var(--glass);
|
||||
border-radius: 12px 12px 0 0;
|
||||
}
|
||||
|
||||
.queue-info {
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
export const APM_STORE_BASE_URL=import.meta.env.VITE_APM_STORE_BASE_URL;
|
||||
export const APM_STORE_ARCHITECTURE='amd64-apm';
|
||||
4
src/global/downloadStatus.ts
Normal file
4
src/global/downloadStatus.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { ref } from "vue";
|
||||
import { DownloadItem } from './typedefinition';
|
||||
|
||||
export const downloads = ref(<DownloadItem[]>[]);
|
||||
5
src/global/storeConfig.ts
Normal file
5
src/global/storeConfig.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
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<any>(null);
|
||||
32
src/global/typedefinition.ts
Normal file
32
src/global/typedefinition.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
export interface InstallLog {
|
||||
id: number;
|
||||
success: boolean;
|
||||
time: number;
|
||||
exitCode: number | null;
|
||||
message: string;
|
||||
}
|
||||
|
||||
export interface DownloadResult extends InstallLog {
|
||||
success: boolean;
|
||||
exitCode: number | null;
|
||||
}
|
||||
|
||||
export interface DownloadItem {
|
||||
id: number;
|
||||
name: string;
|
||||
pkgname: string;
|
||||
version: string;
|
||||
icon: string;
|
||||
status: 'installing' | 'paused' | 'completed' | 'failed' | 'queued'; // 可根据实际状态扩展
|
||||
progress: number; // 0 ~ 100 的百分比,或 0 ~ 1 的小数(建议统一)
|
||||
downloadedSize: number; // 已下载字节数
|
||||
totalSize: number; // 总字节数(可能为 0 初始时)
|
||||
speed: number; // 当前下载速度,单位如 B/s
|
||||
timeRemaining: number; // 剩余时间(秒),0 表示未知
|
||||
startTime: number; // Date.now() 返回的时间戳(毫秒)
|
||||
logs: Array<{
|
||||
time: number; // 日志时间戳
|
||||
message: string; // 日志消息
|
||||
}>;
|
||||
source: string; // 例如 'APM Store'
|
||||
}
|
||||
70
src/js/processInstall.ts
Normal file
70
src/js/processInstall.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
// window.ipcRenderer.on('main-process-message', (_event, ...args) => {
|
||||
// console.log('[Receive Main-process message]:', ...args)
|
||||
// })
|
||||
|
||||
import { currentApp } from "../global/storeConfig";
|
||||
import { APM_STORE_BASE_URL, APM_STORE_ARCHITECTURE } from "../global/storeConfig";
|
||||
import { downloads } from "../global/downloadStatus";
|
||||
|
||||
import { InstallLog, DownloadItem, DownloadResult } from '../global/typedefinition';
|
||||
|
||||
let downloadIdCounter = 0;
|
||||
|
||||
export const handleInstall = () => {
|
||||
if (!currentApp.value?.Pkgname) return;
|
||||
|
||||
downloadIdCounter += 1;
|
||||
// 创建下载任务
|
||||
const download: DownloadItem = {
|
||||
id: downloadIdCounter,
|
||||
name: currentApp.value.Name,
|
||||
pkgname: currentApp.value.Pkgname,
|
||||
version: currentApp.value.Version,
|
||||
icon: `${APM_STORE_BASE_URL}/${APM_STORE_ARCHITECTURE}/${currentApp.value._category}/${currentApp.value.Pkgname}/icon.png`,
|
||||
status: 'queued',
|
||||
progress: 0,
|
||||
downloadedSize: 0,
|
||||
totalSize: 0,
|
||||
speed: 0,
|
||||
timeRemaining: 0,
|
||||
startTime: Date.now(),
|
||||
logs: [
|
||||
{ time: Date.now(), message: '开始下载...' }
|
||||
],
|
||||
source: 'APM Store'
|
||||
};
|
||||
|
||||
downloads.value.push(download);
|
||||
|
||||
// 模拟下载进度(实际应该调用真实的下载 API)
|
||||
// simulateDownload(download);
|
||||
|
||||
// Send to main process to start download
|
||||
window.ipcRenderer.send('queue-install', download);
|
||||
|
||||
const encodedPkg = encodeURIComponent(currentApp.value.Pkgname);
|
||||
// openApmStoreUrl(`apmstore://install?pkg=${encodedPkg}`, {
|
||||
// fallbackText: `/usr/bin/apm-installer --install ${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;
|
||||
});
|
||||
window.ipcRenderer.on('install-log', (_event, log: InstallLog) => {
|
||||
const downloadObj: any = downloads.value.find(d => d.id === log.id);
|
||||
downloadObj.logs.push({
|
||||
time: log.time,
|
||||
message: log.message
|
||||
});
|
||||
});
|
||||
|
||||
window.ipcRenderer.on('install-complete', (_event, log: DownloadResult) => {
|
||||
const downloadObj: any = downloads.value.find(d => d.id === log.id);
|
||||
if (log.success) {
|
||||
downloadObj.status = 'completed';
|
||||
} else {
|
||||
downloadObj.status = 'failed';
|
||||
}
|
||||
});
|
||||
@@ -1,11 +1,10 @@
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
|
||||
//import './style.css'
|
||||
import './3rdparty/fontawesome-free-6.7.2/css/all.min.css'
|
||||
import './assets/css/appstyle.css'
|
||||
|
||||
import './demos/ipc'
|
||||
// import './demos/ipc'
|
||||
// If you want use Node.js, the`nodeIntegration` needs to be enabled in the Main process.
|
||||
// import './demos/node'
|
||||
|
||||
|
||||
Reference in New Issue
Block a user