mirror of
https://gitee.com/spark-store-project/spark-store
synced 2026-04-26 09:20:18 +08:00
feat: 添加 APM 应用管理功能并优化界面
- 新增 APM 应用管理功能,支持显示已安装应用及其依赖项 - 优化已安装应用列表界面,增加应用图标和名称显示 - 调整顶部操作栏布局,将设置和关于按钮移至搜索框旁 - 修复类型定义,增加 isDependency 字段和更多应用信息 - 改进暗色模式下的界面显示效果
This commit is contained in:
@@ -5,7 +5,7 @@ import fs from "node:fs";
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import pino from "pino";
|
import pino from "pino";
|
||||||
|
|
||||||
import { ChannelPayload, InstalledAppInfo } from "../../typedefinition";
|
import { ChannelPayload } from "../../typedefinition";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
|
||||||
const logger = pino({ name: "install-manager" });
|
const logger = pino({ name: "install-manager" });
|
||||||
@@ -79,28 +79,6 @@ const runCommandCapture = async (execCommand: string, execParams: string[]) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const parseInstalledList = (output: string) => {
|
|
||||||
const apps: Array<InstalledAppInfo> = [];
|
|
||||||
const lines = output.split("\n");
|
|
||||||
for (const line of lines) {
|
|
||||||
const trimmed = line.trim();
|
|
||||||
if (!trimmed) continue;
|
|
||||||
if (trimmed.startsWith("Listing")) continue;
|
|
||||||
if (trimmed.startsWith("[INFO]")) continue;
|
|
||||||
|
|
||||||
const match = trimmed.match(/^(\S+)\/\S+,\S+\s+(\S+)\s+(\S+)\s+\[(.+)\]$/);
|
|
||||||
if (!match) continue;
|
|
||||||
apps.push({
|
|
||||||
pkgname: match[1],
|
|
||||||
version: match[2],
|
|
||||||
arch: match[3],
|
|
||||||
flags: match[4],
|
|
||||||
raw: trimmed,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return apps;
|
|
||||||
};
|
|
||||||
|
|
||||||
/** 检测本机是否已安装 apm 命令 */
|
/** 检测本机是否已安装 apm 命令 */
|
||||||
const checkApmAvailable = async (): Promise<boolean> => {
|
const checkApmAvailable = async (): Promise<boolean> => {
|
||||||
const { code, stdout } = await runCommandCapture("which", ["apm"]);
|
const { code, stdout } = await runCommandCapture("which", ["apm"]);
|
||||||
@@ -251,7 +229,8 @@ ipcMain.on("queue-install", async (event, download_json) => {
|
|||||||
type: "info",
|
type: "info",
|
||||||
title: "APM 安装成功",
|
title: "APM 安装成功",
|
||||||
message: "恭喜您,APM 已成功安装",
|
message: "恭喜您,APM 已成功安装",
|
||||||
detail: "APM 应用需重启后方可展示和使用,若完成安装后无法在应用列表中展示,请重启电脑后继续。",
|
detail:
|
||||||
|
"APM 应用需重启后方可展示和使用,若完成安装后无法在应用列表中展示,请重启电脑后继续。",
|
||||||
buttons: ["确定"],
|
buttons: ["确定"],
|
||||||
defaultId: 0,
|
defaultId: 0,
|
||||||
});
|
});
|
||||||
@@ -422,10 +401,15 @@ async function processNextInQueue() {
|
|||||||
const timeoutChecker = setInterval(() => {
|
const timeoutChecker = setInterval(() => {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
// 只在进度为0时检查超时
|
// 只在进度为0时检查超时
|
||||||
if (lastProgress === 0 && now - lastProgressTime > zeroProgressTimeout) {
|
if (
|
||||||
|
lastProgress === 0 &&
|
||||||
|
now - lastProgressTime > zeroProgressTimeout
|
||||||
|
) {
|
||||||
clearInterval(timeoutChecker);
|
clearInterval(timeoutChecker);
|
||||||
child.kill();
|
child.kill();
|
||||||
reject(new Error(`下载卡在0%超过 ${zeroProgressTimeout / 1000} 秒`));
|
reject(
|
||||||
|
new Error(`下载卡在0%超过 ${zeroProgressTimeout / 1000} 秒`),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}, progressCheckInterval);
|
}, progressCheckInterval);
|
||||||
|
|
||||||
@@ -466,7 +450,7 @@ async function processNextInQueue() {
|
|||||||
}
|
}
|
||||||
sendLog(`下载失败,准备重试 (${retryCount}/${maxRetries})`);
|
sendLog(`下载失败,准备重试 (${retryCount}/${maxRetries})`);
|
||||||
// 等待2秒后重试
|
// 等待2秒后重试
|
||||||
await new Promise(r => setTimeout(r, 2000));
|
await new Promise((r) => setTimeout(r, 2000));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -573,8 +557,11 @@ ipcMain.handle("check-installed", async (_event, payload: any) => {
|
|||||||
"--installed",
|
"--installed",
|
||||||
]);
|
]);
|
||||||
if (code === 0) {
|
if (code === 0) {
|
||||||
|
const cleanStdout = stdout.replace(
|
||||||
// eslint-disable-next-line no-control-regex
|
// eslint-disable-next-line no-control-regex
|
||||||
const cleanStdout = stdout.replace(/\x1b\[[0-9;]*m/g, "");
|
/\x1b\[[0-9;]*m/g,
|
||||||
|
"",
|
||||||
|
);
|
||||||
const lines = cleanStdout.split("\n");
|
const lines = cleanStdout.split("\n");
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
const trimmed = line.trim();
|
const trimmed = line.trim();
|
||||||
@@ -625,7 +612,6 @@ ipcMain.handle("check-installed", async (_event, payload: any) => {
|
|||||||
if (isInstalled) return true;
|
if (isInstalled) return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return isInstalled;
|
return isInstalled;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -691,9 +677,133 @@ ipcMain.on("remove-installed", async (_event, payload) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle("list-installed", async () => {
|
||||||
|
const apmBasePath = "/var/lib/apm/apm/files/ace-env/var/lib/apm";
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!fs.existsSync(apmBasePath)) {
|
||||||
|
logger.warn(`APM base path not found: ${apmBasePath}`);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: "APM base path not found",
|
||||||
|
apps: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const packages = fs.readdirSync(apmBasePath, { withFileTypes: true });
|
||||||
|
const installedApps: Array<{
|
||||||
|
pkgname: string;
|
||||||
|
name: string;
|
||||||
|
version: string;
|
||||||
|
arch: string;
|
||||||
|
flags: string;
|
||||||
|
origin: "spark" | "apm";
|
||||||
|
icon?: string;
|
||||||
|
isDependency: boolean;
|
||||||
|
}> = [];
|
||||||
|
|
||||||
|
for (const pkg of packages) {
|
||||||
|
if (!pkg.isDirectory()) continue;
|
||||||
|
|
||||||
|
const pkgname = pkg.name;
|
||||||
|
const pkgPath = path.join(apmBasePath, pkgname);
|
||||||
|
|
||||||
|
const { code, stdout } = await runCommandCapture("apm", [
|
||||||
|
"list",
|
||||||
|
pkgname,
|
||||||
|
]);
|
||||||
|
if (code !== 0) {
|
||||||
|
logger.warn(`Failed to list package ${pkgname}: ${stdout}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cleanStdout = stdout.replace(
|
||||||
|
// eslint-disable-next-line no-control-regex
|
||||||
|
/\x1b\[[0-9;]*m/g,
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
const lines = cleanStdout.split("\n");
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
const trimmed = line.trim();
|
||||||
|
if (
|
||||||
|
!trimmed ||
|
||||||
|
trimmed.startsWith("Listing") ||
|
||||||
|
trimmed.startsWith("[INFO]") ||
|
||||||
|
trimmed.startsWith("警告")
|
||||||
|
)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const match = trimmed.match(
|
||||||
|
/^(\S+)\/\S+,\S+\s+(\S+)\s+(\S+)\s+\[(.+)\]$/,
|
||||||
|
);
|
||||||
|
if (!match) continue;
|
||||||
|
|
||||||
|
const [, listedPkgname, version, arch, flags] = match;
|
||||||
|
if (listedPkgname !== pkgname) continue;
|
||||||
|
|
||||||
|
let appName = pkgname;
|
||||||
|
let icon = "";
|
||||||
|
const entriesPath = path.join(pkgPath, "entries", "applications");
|
||||||
|
const hasEntries = fs.existsSync(entriesPath);
|
||||||
|
|
||||||
|
if (hasEntries) {
|
||||||
|
const desktopFiles = fs.readdirSync(entriesPath);
|
||||||
|
for (const file of desktopFiles) {
|
||||||
|
if (file.endsWith(".desktop")) {
|
||||||
|
const desktopPath = path.join(entriesPath, file);
|
||||||
|
const content = fs.readFileSync(desktopPath, "utf-8");
|
||||||
|
const nameMatch = content.match(/^Name=(.+)$/m);
|
||||||
|
const iconMatch = content.match(/^Icon=(.+)$/m);
|
||||||
|
if (nameMatch) appName = nameMatch[1].trim();
|
||||||
|
if (iconMatch) icon = iconMatch[1].trim();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
installedApps.push({
|
||||||
|
pkgname,
|
||||||
|
name: appName,
|
||||||
|
version,
|
||||||
|
arch,
|
||||||
|
flags,
|
||||||
|
origin: "apm",
|
||||||
|
icon: icon || undefined,
|
||||||
|
isDependency: !hasEntries,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
installedApps.sort((a, b) => {
|
||||||
|
const getOrder = (app: { pkgname: string; isDependency: boolean }) => {
|
||||||
|
if (app.isDependency) return 2;
|
||||||
|
if (app.pkgname.startsWith("amber-pm")) return 1;
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const aOrder = getOrder(a);
|
||||||
|
const bOrder = getOrder(b);
|
||||||
|
|
||||||
|
if (aOrder !== bOrder) return aOrder - bOrder;
|
||||||
|
return a.pkgname.localeCompare(b.pkgname);
|
||||||
|
});
|
||||||
|
|
||||||
|
return { success: true, apps: installedApps };
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(
|
||||||
|
`list-installed failed: ${error instanceof Error ? error.message : String(error)}`,
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: error instanceof Error ? error.message : String(error),
|
||||||
|
apps: [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
ipcMain.handle("list-upgradable", async () => {
|
ipcMain.handle("list-upgradable", async () => {
|
||||||
const { code, stdout, stderr } = await runCommandCapture(SHELL_CALLER_PATH, [
|
const { code, stdout, stderr } = await runCommandCapture("apm", [
|
||||||
"aptss",
|
|
||||||
"list",
|
"list",
|
||||||
"--upgradable",
|
"--upgradable",
|
||||||
]);
|
]);
|
||||||
@@ -710,6 +820,9 @@ ipcMain.handle("list-upgradable", async () => {
|
|||||||
return { success: true, apps };
|
return { success: true, apps };
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle("check-apm-available", async () => {
|
||||||
|
return await checkApmAvailable();
|
||||||
|
});
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
ipcMain.handle("uninstall-installed", async (_event, payload: any) => {
|
ipcMain.handle("uninstall-installed", async (_event, payload: any) => {
|
||||||
|
|||||||
@@ -101,10 +101,8 @@ function getStoreFilterFromArgv(): "spark" | "apm" | "both" {
|
|||||||
return "both";
|
return "both";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ipcMain.handle("get-store-filter", (): "spark" | "apm" | "both" =>
|
ipcMain.handle("get-store-filter", (): "spark" | "apm" | "both" =>
|
||||||
getStoreFilterFromArgv(),
|
getStoreFilterFromArgv(),
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
async function createWindow() {
|
async function createWindow() {
|
||||||
@@ -281,7 +279,6 @@ function getTrayIconPath(): string | null {
|
|||||||
const FALLBACK_TRAY_PNG =
|
const FALLBACK_TRAY_PNG =
|
||||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAHklEQVQ4T2NkYGD4z0ABYBwNwMAwGoChNQAAAABJRU5ErkJggg==";
|
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAHklEQVQ4T2NkYGD4z0ABYBwNwMAwGoChNQAAAABJRU5ErkJggg==";
|
||||||
|
|
||||||
|
|
||||||
function getTrayImage():
|
function getTrayImage():
|
||||||
| string
|
| string
|
||||||
| ReturnType<typeof nativeImage.createFromDataURL> {
|
| ReturnType<typeof nativeImage.createFromDataURL> {
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "spark-store",
|
"name": "spark-store",
|
||||||
"version": "4.9.9",
|
"version": "4.9.9alpha4",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "spark-store",
|
"name": "spark-store",
|
||||||
"version": "4.9.9",
|
"version": "4.9.9alpha4",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tailwindcss/vite": "^4.1.18",
|
"@tailwindcss/vite": "^4.1.18",
|
||||||
|
|||||||
45
src/App.vue
45
src/App.vue
@@ -20,27 +20,32 @@
|
|||||||
:active-category="activeCategory"
|
:active-category="activeCategory"
|
||||||
:category-counts="categoryCounts"
|
:category-counts="categoryCounts"
|
||||||
:theme-mode="themeMode"
|
:theme-mode="themeMode"
|
||||||
|
:apm-available="apmAvailable"
|
||||||
@toggle-theme="toggleTheme"
|
@toggle-theme="toggleTheme"
|
||||||
@select-category="selectCategory"
|
@select-category="selectCategory"
|
||||||
@close="isSidebarOpen = false"
|
@close="isSidebarOpen = false"
|
||||||
@open-about="openAboutModal"
|
@list="handleList"
|
||||||
|
@update="handleUpdate"
|
||||||
/>
|
/>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<main class="flex-1 px-4 py-6 lg:px-10">
|
<main class="flex-1">
|
||||||
|
<div
|
||||||
|
class="sticky top-0 z-30 border-b border-slate-200/70 bg-slate-50/95 px-4 py-4 backdrop-blur lg:px-10 dark:border-slate-800/70 dark:bg-slate-950/95"
|
||||||
|
>
|
||||||
<AppHeader
|
<AppHeader
|
||||||
:search-query="searchQuery"
|
:search-query="searchQuery"
|
||||||
:active-category="activeCategory"
|
:active-category="activeCategory"
|
||||||
:apps-count="filteredApps.length"
|
:apps-count="filteredApps.length"
|
||||||
@update-search="handleSearchInput"
|
@update-search="handleSearchInput"
|
||||||
@search-focus="handleSearchFocus"
|
@search-focus="handleSearchFocus"
|
||||||
@update="handleUpdate"
|
|
||||||
@list="handleList"
|
|
||||||
@open-install-settings="handleOpenInstallSettings"
|
@open-install-settings="handleOpenInstallSettings"
|
||||||
|
@open-about="openAboutModal"
|
||||||
@toggle-sidebar="isSidebarOpen = !isSidebarOpen"
|
@toggle-sidebar="isSidebarOpen = !isSidebarOpen"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="px-4 py-6 lg:px-10">
|
||||||
<template v-if="activeCategory === 'home'">
|
<template v-if="activeCategory === 'home'">
|
||||||
<div class="pt-6">
|
|
||||||
<HomeView
|
<HomeView
|
||||||
:links="homeLinks"
|
:links="homeLinks"
|
||||||
:lists="homeLists"
|
:lists="homeLists"
|
||||||
@@ -48,7 +53,6 @@
|
|||||||
:error="homeError"
|
:error="homeError"
|
||||||
@open-detail="openDetail"
|
@open-detail="openDetail"
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<AppGrid
|
<AppGrid
|
||||||
@@ -57,6 +61,7 @@
|
|||||||
@open-detail="openDetail"
|
@open-detail="openDetail"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<AppDetailModal
|
<AppDetailModal
|
||||||
@@ -134,10 +139,7 @@
|
|||||||
@success="onUninstallSuccess"
|
@success="onUninstallSuccess"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<AboutModal
|
<AboutModal :show="showAboutModal" @close="closeAboutModal" />
|
||||||
:show="showAboutModal"
|
|
||||||
@close="closeAboutModal"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -248,6 +250,7 @@ const updateError = ref("");
|
|||||||
const showUninstallModal = ref(false);
|
const showUninstallModal = ref(false);
|
||||||
const uninstallTargetApp: Ref<App | null> = ref(null);
|
const uninstallTargetApp: Ref<App | null> = ref(null);
|
||||||
const showAboutModal = ref(false);
|
const showAboutModal = ref(false);
|
||||||
|
const apmAvailable = ref(false);
|
||||||
|
|
||||||
/** 启动参数 --no-apm => 仅 Spark;--no-spark => 仅 APM;由主进程 IPC 提供 */
|
/** 启动参数 --no-apm => 仅 Spark;--no-spark => 仅 APM;由主进程 IPC 提供 */
|
||||||
const storeFilter = ref<"spark" | "apm" | "both">("both");
|
const storeFilter = ref<"spark" | "apm" | "both">("both");
|
||||||
@@ -403,16 +406,16 @@ const openDetail = async (app: App | Record<string, unknown>) => {
|
|||||||
if (fullApp.isMerged && (fullApp.sparkApp || fullApp.apmApp)) {
|
if (fullApp.isMerged && (fullApp.sparkApp || fullApp.apmApp)) {
|
||||||
const [sparkInstalled, apmInstalled] = await Promise.all([
|
const [sparkInstalled, apmInstalled] = await Promise.all([
|
||||||
fullApp.sparkApp
|
fullApp.sparkApp
|
||||||
? window.ipcRenderer.invoke("check-installed", {
|
? (window.ipcRenderer.invoke("check-installed", {
|
||||||
pkgname: fullApp.sparkApp.pkgname,
|
pkgname: fullApp.sparkApp.pkgname,
|
||||||
origin: "spark",
|
origin: "spark",
|
||||||
}) as Promise<boolean>
|
}) as Promise<boolean>)
|
||||||
: Promise.resolve(false),
|
: Promise.resolve(false),
|
||||||
fullApp.apmApp
|
fullApp.apmApp
|
||||||
? window.ipcRenderer.invoke("check-installed", {
|
? (window.ipcRenderer.invoke("check-installed", {
|
||||||
pkgname: fullApp.apmApp.pkgname,
|
pkgname: fullApp.apmApp.pkgname,
|
||||||
origin: "apm",
|
origin: "apm",
|
||||||
}) as Promise<boolean>
|
}) as Promise<boolean>)
|
||||||
: Promise.resolve(false),
|
: Promise.resolve(false),
|
||||||
]);
|
]);
|
||||||
if (sparkInstalled && !apmInstalled) {
|
if (sparkInstalled && !apmInstalled) {
|
||||||
@@ -425,9 +428,9 @@ const openDetail = async (app: App | Record<string, unknown>) => {
|
|||||||
|
|
||||||
const displayAppForScreenshots =
|
const displayAppForScreenshots =
|
||||||
fullApp.viewingOrigin !== undefined && fullApp.isMerged
|
fullApp.viewingOrigin !== undefined && fullApp.isMerged
|
||||||
? (fullApp.viewingOrigin === "spark"
|
? ((fullApp.viewingOrigin === "spark"
|
||||||
? fullApp.sparkApp
|
? fullApp.sparkApp
|
||||||
: fullApp.apmApp) ?? fullApp
|
: fullApp.apmApp) ?? fullApp)
|
||||||
: fullApp;
|
: fullApp;
|
||||||
|
|
||||||
currentApp.value = fullApp;
|
currentApp.value = fullApp;
|
||||||
@@ -762,6 +765,7 @@ const refreshInstalledApps = async () => {
|
|||||||
appInfo.flags = app.flags;
|
appInfo.flags = app.flags;
|
||||||
appInfo.arch = app.arch;
|
appInfo.arch = app.arch;
|
||||||
appInfo.currentStatus = "installed";
|
appInfo.currentStatus = "installed";
|
||||||
|
appInfo.isDependency = app.isDependency;
|
||||||
} else {
|
} else {
|
||||||
// 如果在当前应用列表中找不到该应用,创建一个最小的 App 对象
|
// 如果在当前应用列表中找不到该应用,创建一个最小的 App 对象
|
||||||
appInfo = {
|
appInfo = {
|
||||||
@@ -779,11 +783,12 @@ const refreshInstalledApps = async () => {
|
|||||||
update: "",
|
update: "",
|
||||||
size: "",
|
size: "",
|
||||||
img_urls: [],
|
img_urls: [],
|
||||||
icons: "",
|
icons: app.icon || "",
|
||||||
origin: app.origin || (app.arch?.includes("apm") ? "apm" : "spark"),
|
origin: app.origin || (app.arch?.includes("apm") ? "apm" : "spark"),
|
||||||
currentStatus: "installed",
|
currentStatus: "installed",
|
||||||
arch: app.arch,
|
arch: app.arch,
|
||||||
flags: app.flags,
|
flags: app.flags,
|
||||||
|
isDependency: app.isDependency,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
installedApps.value.push(appInfo);
|
installedApps.value.push(appInfo);
|
||||||
@@ -1061,6 +1066,12 @@ onMounted(async () => {
|
|||||||
|
|
||||||
// 从主进程获取启动参数(--no-apm / --no-spark),再加载数据
|
// 从主进程获取启动参数(--no-apm / --no-spark),再加载数据
|
||||||
storeFilter.value = await window.ipcRenderer.invoke("get-store-filter");
|
storeFilter.value = await window.ipcRenderer.invoke("get-store-filter");
|
||||||
|
|
||||||
|
// 检查 apm 是否可用
|
||||||
|
if (storeFilter.value !== "spark") {
|
||||||
|
apmAvailable.value = await window.ipcRenderer.invoke("check-apm-available");
|
||||||
|
}
|
||||||
|
|
||||||
await loadCategories();
|
await loadCategories();
|
||||||
|
|
||||||
// 分类目录加载后,并行加载主页数据和所有应用列表
|
// 分类目录加载后,并行加载主页数据和所有应用列表
|
||||||
|
|||||||
@@ -33,7 +33,9 @@
|
|||||||
class="h-24 w-24 rounded-3xl bg-white p-4 shadow-lg ring-1 ring-slate-900/5 dark:bg-slate-800"
|
class="h-24 w-24 rounded-3xl bg-white p-4 shadow-lg ring-1 ring-slate-900/5 dark:bg-slate-800"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<h2 class="mb-2 text-2xl font-bold text-slate-900 dark:text-white">
|
<h2
|
||||||
|
class="mb-2 text-2xl font-bold text-slate-900 dark:text-white"
|
||||||
|
>
|
||||||
星火应用商店
|
星火应用商店
|
||||||
</h2>
|
</h2>
|
||||||
<p class="mb-4 text-sm text-slate-500 dark:text-slate-400">
|
<p class="mb-4 text-sm text-slate-500 dark:text-slate-400">
|
||||||
@@ -42,16 +44,23 @@
|
|||||||
<div
|
<div
|
||||||
class="mb-6 inline-flex items-center gap-2 rounded-full bg-slate-100 px-4 py-2 dark:bg-slate-800"
|
class="mb-6 inline-flex items-center gap-2 rounded-full bg-slate-100 px-4 py-2 dark:bg-slate-800"
|
||||||
>
|
>
|
||||||
<span class="text-sm text-slate-600 dark:text-slate-300">版本号</span>
|
<span class="text-sm text-slate-600 dark:text-slate-300"
|
||||||
|
>版本号</span
|
||||||
|
>
|
||||||
<span
|
<span
|
||||||
class="font-mono text-sm font-semibold text-brand dark:text-brand"
|
class="font-mono text-sm font-semibold text-brand dark:text-brand"
|
||||||
>{{ version }}</span
|
>{{ version }}</span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<p class="mb-6 text-sm leading-relaxed text-slate-600 dark:text-slate-400">
|
<p
|
||||||
星火应用商店是专为 Linux 设计的应用商店,提供丰富的应用资源和便捷的安装体验。
|
class="mb-6 text-sm leading-relaxed text-slate-600 dark:text-slate-400"
|
||||||
|
>
|
||||||
|
星火应用商店是专为 Linux
|
||||||
|
设计的应用商店,提供丰富的应用资源和便捷的安装体验。
|
||||||
</p>
|
</p>
|
||||||
<div class="flex justify-center gap-4 text-sm text-slate-500 dark:text-slate-400">
|
<div
|
||||||
|
class="flex justify-center gap-4 text-sm text-slate-500 dark:text-slate-400"
|
||||||
|
>
|
||||||
<a
|
<a
|
||||||
href="https://gitee.com/spark-store-project/spark-store"
|
href="https://gitee.com/spark-store-project/spark-store"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
|||||||
@@ -97,9 +97,7 @@
|
|||||||
: 'from-brand to-brand-dark'
|
: 'from-brand to-brand-dark'
|
||||||
"
|
"
|
||||||
@click="handleInstall"
|
@click="handleInstall"
|
||||||
:disabled="
|
:disabled="installFeedback || isOtherVersionInstalled"
|
||||||
installFeedback || isOtherVersionInstalled
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
<i
|
<i
|
||||||
class="fas"
|
class="fas"
|
||||||
@@ -259,10 +257,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, useAttrs, ref, watch } from "vue";
|
import { computed, useAttrs, ref, watch } from "vue";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import {
|
import { useInstallFeedback, downloads } from "../global/downloadStatus";
|
||||||
useInstallFeedback,
|
|
||||||
downloads,
|
|
||||||
} 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";
|
||||||
|
|
||||||
@@ -289,7 +284,7 @@ const appPkgname = computed(() => props.app?.pkgname);
|
|||||||
|
|
||||||
const isIconLoaded = ref(false);
|
const isIconLoaded = ref(false);
|
||||||
|
|
||||||
const viewingOrigin = ref<"spark" | "apm">("apm");
|
const viewingOrigin = ref<"spark" | "apm">("spark");
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.app,
|
() => props.app,
|
||||||
@@ -297,9 +292,9 @@ watch(
|
|||||||
isIconLoaded.value = false;
|
isIconLoaded.value = false;
|
||||||
if (newApp) {
|
if (newApp) {
|
||||||
if (newApp.isMerged) {
|
if (newApp.isMerged) {
|
||||||
// 若父组件已根据安装状态设置了优先展示的版本,则使用;否则默认 APM
|
// 若父组件已根据安装状态设置了优先展示的版本,则使用;否则默认 Spark
|
||||||
viewingOrigin.value =
|
viewingOrigin.value =
|
||||||
newApp.viewingOrigin ?? (newApp.apmApp ? "apm" : "spark");
|
newApp.viewingOrigin ?? (newApp.sparkApp ? "spark" : "apm");
|
||||||
} else {
|
} else {
|
||||||
viewingOrigin.value = newApp.origin;
|
viewingOrigin.value = newApp.origin;
|
||||||
}
|
}
|
||||||
@@ -348,7 +343,9 @@ const installBtnText = computed(() => {
|
|||||||
return "已安装";
|
return "已安装";
|
||||||
}
|
}
|
||||||
if (isOtherVersionInstalled.value) {
|
if (isOtherVersionInstalled.value) {
|
||||||
return viewingOrigin.value === "spark" ? "已安装 APM 版" : "已安装 Spark 版";
|
return viewingOrigin.value === "spark"
|
||||||
|
? "已安装 APM 版"
|
||||||
|
: "已安装 Spark 版";
|
||||||
}
|
}
|
||||||
if (installFeedback.value) {
|
if (installFeedback.value) {
|
||||||
const status = activeDownload.value?.status;
|
const status = activeDownload.value?.status;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
<div class="flex flex-col gap-4 lg:flex-row lg:items-center">
|
<div class="flex flex-col gap-4 lg:flex-row lg:items-center">
|
||||||
<div class="flex items-center gap-3">
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-2xl border border-slate-200/70 bg-white/80 text-slate-500 shadow-sm backdrop-blur transition hover:bg-slate-50 lg:hidden dark:border-slate-800/70 dark:bg-slate-900/60 dark:text-slate-400 dark:hover:bg-slate-800"
|
class="inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-2xl border border-slate-200/70 bg-white/80 text-slate-500 shadow-sm backdrop-blur transition hover:bg-slate-50 lg:hidden dark:border-slate-800/70 dark:bg-slate-900/60 dark:text-slate-400 dark:hover:bg-slate-800"
|
||||||
@@ -10,27 +9,37 @@
|
|||||||
>
|
>
|
||||||
<i class="fas fa-bars"></i>
|
<i class="fas fa-bars"></i>
|
||||||
</button>
|
</button>
|
||||||
<TopActions
|
<div class="flex w-full flex-1 items-center gap-3">
|
||||||
@update="$emit('update')"
|
<div class="relative flex-1">
|
||||||
@list="$emit('list')"
|
|
||||||
@open-install-settings="$emit('open-install-settings')"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="w-full flex-1">
|
|
||||||
<label for="searchBox" class="sr-only">搜索应用</label>
|
<label for="searchBox" class="sr-only">搜索应用</label>
|
||||||
<div class="relative">
|
|
||||||
<i
|
<i
|
||||||
class="fas fa-search pointer-events-none absolute left-4 top-1/2 -translate-y-1/2 text-slate-400"
|
class="fas fa-search pointer-events-none absolute left-4 top-1/2 -translate-y-1/2 text-slate-400"
|
||||||
></i>
|
></i>
|
||||||
<input
|
<input
|
||||||
id="searchBox"
|
id="searchBox"
|
||||||
v-model="localSearchQuery"
|
v-model="localSearchQuery"
|
||||||
class="w-full rounded-2xl border border-slate-200/70 bg-white/80 py-3 pl-12 pr-4 text-sm text-slate-700 shadow-sm outline-none transition placeholder:text-slate-400 focus:border-brand/50 focus:ring-4 focus:ring-brand/10 dark:border-slate-800/70 dark:bg-slate-900/60 dark:text-slate-200"
|
class="w-full rounded-2xl border border-slate-200/70 bg-white/80 py-3 pl-12 pr-24 text-sm text-slate-700 shadow-sm outline-none transition placeholder:text-slate-400 focus:border-brand/50 focus:ring-4 focus:ring-brand/10 dark:border-slate-800/70 dark:bg-slate-900/60 dark:text-slate-200"
|
||||||
placeholder="搜索应用名 / 包名 / 标签,按回车键搜索"
|
placeholder="搜索应用名 / 包名 / 标签,按回车键搜索"
|
||||||
@keydown.enter="handleSearch"
|
@keydown.enter="handleSearch"
|
||||||
@focus="handleSearchFocus"
|
@focus="handleSearchFocus"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-2xl border border-slate-200/70 bg-white/80 text-slate-500 shadow-sm backdrop-blur transition hover:bg-slate-50 dark:border-slate-800/70 dark:bg-slate-900/60 dark:text-slate-400 dark:hover:bg-slate-800"
|
||||||
|
@click="$emit('open-install-settings')"
|
||||||
|
title="安装设置"
|
||||||
|
>
|
||||||
|
<i class="fas fa-cog"></i>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="inline-flex h-10 w-10 shrink-0 items-center justify-center rounded-2xl border border-slate-200/70 bg-white/80 text-slate-500 shadow-sm backdrop-blur transition hover:bg-slate-50 dark:border-slate-800/70 dark:bg-slate-900/60 dark:text-slate-400 dark:hover:bg-slate-800"
|
||||||
|
@click="$emit('open-about')"
|
||||||
|
title="关于"
|
||||||
|
>
|
||||||
|
<i class="fas fa-info-circle"></i>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -45,20 +54,17 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch } from "vue";
|
import { ref, watch } from "vue";
|
||||||
import TopActions from "./TopActions.vue";
|
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
searchQuery: string;
|
searchQuery: string;
|
||||||
activeCategory: string;
|
activeCategory: string;
|
||||||
appsCount: number;
|
appsCount: number;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "update-search", query: string): void;
|
(e: "update-search", query: string): void;
|
||||||
(e: "update"): void;
|
|
||||||
(e: "list"): void;
|
|
||||||
(e: "search-focus"): void;
|
(e: "search-focus"): void;
|
||||||
(e: "open-install-settings"): void;
|
(e: "open-install-settings"): void;
|
||||||
|
(e: "open-about"): void;
|
||||||
(e: "toggle-sidebar"): void;
|
(e: "toggle-sidebar"): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
|||||||
@@ -89,12 +89,21 @@
|
|||||||
|
|
||||||
<div class="border-t border-slate-200 pt-4 dark:border-slate-800">
|
<div class="border-t border-slate-200 pt-4 dark:border-slate-800">
|
||||||
<button
|
<button
|
||||||
|
v-if="apmAvailable"
|
||||||
type="button"
|
type="button"
|
||||||
class="flex w-full items-center gap-3 rounded-2xl border border-transparent px-4 py-3 text-left text-sm font-medium text-slate-600 transition hover:border-brand/30 hover:bg-brand/5 hover:text-brand focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand/40 dark:text-slate-300 dark:hover:bg-slate-800"
|
class="flex w-full items-center gap-3 rounded-2xl border border-transparent px-4 py-3 text-left text-sm font-medium text-slate-600 transition hover:border-brand/30 hover:bg-brand/5 hover:text-brand focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand/40 dark:text-slate-300 dark:hover:bg-slate-800"
|
||||||
@click="openAbout"
|
@click="$emit('list')"
|
||||||
>
|
>
|
||||||
<i class="fas fa-info-circle"></i>
|
<i class="fas fa-download"></i>
|
||||||
<span>关于</span>
|
<span>APM 应用管理</span>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="flex w-full items-center gap-3 rounded-2xl border border-transparent px-4 py-3 text-left text-sm font-medium text-slate-600 transition hover:border-brand/30 hover:bg-brand/5 hover:text-brand focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand/40 dark:text-slate-300 dark:hover:bg-slate-800"
|
||||||
|
@click="$emit('update')"
|
||||||
|
>
|
||||||
|
<i class="fas fa-sync-alt"></i>
|
||||||
|
<span>软件更新</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -110,13 +119,15 @@ defineProps<{
|
|||||||
activeCategory: string;
|
activeCategory: string;
|
||||||
categoryCounts: Record<string, number>;
|
categoryCounts: Record<string, number>;
|
||||||
themeMode: "light" | "dark" | "auto";
|
themeMode: "light" | "dark" | "auto";
|
||||||
|
apmAvailable: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "toggle-theme"): void;
|
(e: "toggle-theme"): void;
|
||||||
(e: "select-category", category: string): void;
|
(e: "select-category", category: string): void;
|
||||||
(e: "close"): void;
|
(e: "close"): void;
|
||||||
(e: "open-about"): void;
|
(e: "list"): void;
|
||||||
|
(e: "update"): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const toggleTheme = () => {
|
const toggleTheme = () => {
|
||||||
@@ -126,8 +137,4 @@ const toggleTheme = () => {
|
|||||||
const selectCategory = (category: string) => {
|
const selectCategory = (category: string) => {
|
||||||
emit("select-category", category);
|
emit("select-category", category);
|
||||||
};
|
};
|
||||||
|
|
||||||
const openAbout = () => {
|
|
||||||
emit("open-about");
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
:key="link.url + link.name"
|
:key="link.url + link.name"
|
||||||
:href="link.type === '_blank' ? undefined : link.url"
|
:href="link.type === '_blank' ? undefined : link.url"
|
||||||
@click.prevent="onLinkClick(link)"
|
@click.prevent="onLinkClick(link)"
|
||||||
class="flex flex-col items-start gap-2 rounded-2xl border border-slate-200/70 bg-white/90 p-4 shadow-sm hover:shadow-lg transition"
|
class="flex flex-col items-start gap-2 rounded-2xl border border-slate-200/70 bg-white/90 p-4 shadow-sm transition hover:shadow-lg dark:border-slate-800/70 dark:bg-slate-900/90"
|
||||||
:title="link.more as string"
|
:title="link.more as string"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
@@ -29,10 +29,12 @@
|
|||||||
class="h-20 w-full object-contain"
|
class="h-20 w-full object-contain"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
/>
|
/>
|
||||||
<div class="text-base font-semibold text-slate-900">
|
<div class="text-base font-semibold text-slate-900 dark:text-white">
|
||||||
{{ link.name }}
|
{{ link.name }}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-sm text-slate-500">{{ link.more }}</div>
|
<div class="text-sm text-slate-500 dark:text-slate-400">
|
||||||
|
{{ link.more }}
|
||||||
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -69,15 +69,28 @@
|
|||||||
:key="app.pkgname"
|
:key="app.pkgname"
|
||||||
class="flex flex-col gap-3 rounded-2xl border border-slate-200/70 bg-white/90 p-4 shadow-sm dark:border-slate-800/70 dark:bg-slate-900/70 sm:flex-row sm:items-center sm:justify-between"
|
class="flex flex-col gap-3 rounded-2xl border border-slate-200/70 bg-white/90 p-4 shadow-sm dark:border-slate-800/70 dark:bg-slate-900/70 sm:flex-row sm:items-center sm:justify-between"
|
||||||
>
|
>
|
||||||
|
<div class="flex items-center gap-3">
|
||||||
|
<div
|
||||||
|
v-if="app.icons"
|
||||||
|
class="flex h-12 w-12 shrink-0 items-center justify-center rounded-xl bg-slate-100 dark:bg-slate-800"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
v-if="app.icons.startsWith('/')"
|
||||||
|
:src="`file://${app.icons}`"
|
||||||
|
class="h-8 w-8 object-contain"
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
<i v-else class="fas fa-cube text-xl text-slate-400"></i>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<p
|
<p
|
||||||
class="text-base font-semibold text-slate-900 dark:text-white"
|
class="text-base font-semibold text-slate-900 dark:text-white"
|
||||||
>
|
>
|
||||||
{{ app.pkgname }}
|
{{ app.name }}
|
||||||
</p>
|
</p>
|
||||||
<span
|
<span
|
||||||
v-if="app.flags && app.flags.includes('automatic')"
|
v-if="app.isDependency"
|
||||||
class="rounded-md bg-rose-100 px-2 py-0.5 text-[11px] font-semibold text-rose-600 dark:bg-rose-500/20 dark:text-rose-400"
|
class="rounded-md bg-rose-100 px-2 py-0.5 text-[11px] font-semibold text-rose-600 dark:bg-rose-500/20 dark:text-rose-400"
|
||||||
>
|
>
|
||||||
依赖项
|
依赖项
|
||||||
@@ -86,15 +99,12 @@
|
|||||||
<div
|
<div
|
||||||
class="mt-1 flex flex-wrap items-center gap-2 text-xs text-slate-500 dark:text-slate-400"
|
class="mt-1 flex flex-wrap items-center gap-2 text-xs text-slate-500 dark:text-slate-400"
|
||||||
>
|
>
|
||||||
|
<span class="font-mono">{{ app.pkgname }}</span>
|
||||||
|
<span>·</span>
|
||||||
<span>{{ app.version }}</span>
|
<span>{{ app.version }}</span>
|
||||||
<span>·</span>
|
<span>·</span>
|
||||||
<span>{{ app.arch }}</span>
|
<span>{{ app.arch }}</span>
|
||||||
<template
|
</div>
|
||||||
v-if="app.flags && !app.flags.includes('automatic')"
|
|
||||||
>
|
|
||||||
<span>·</span>
|
|
||||||
<span>{{ app.flags }}</span>
|
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-wrap gap-3">
|
<div :class="['flex flex-wrap gap-3', $attrs.class]">
|
||||||
|
<button
|
||||||
|
v-if="apmAvailable"
|
||||||
|
type="button"
|
||||||
|
class="inline-flex items-center gap-2 rounded-2xl bg-gradient-to-r from-brand to-brand-dark px-4 py-2 text-sm font-semibold text-white shadow-lg transition hover:-translate-y-0.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand/40"
|
||||||
|
@click="handleList"
|
||||||
|
title="已安装应用"
|
||||||
|
></button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="inline-flex items-center gap-2 rounded-2xl bg-gradient-to-r from-brand to-brand-dark px-4 py-2 text-sm font-semibold text-white shadow-lg transition hover:-translate-y-0.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand/40"
|
class="inline-flex items-center gap-2 rounded-2xl bg-gradient-to-r from-brand to-brand-dark px-4 py-2 text-sm font-semibold text-white shadow-lg transition hover:-translate-y-0.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand/40"
|
||||||
@@ -18,19 +25,19 @@
|
|||||||
<i class="fas fa-cog"></i>
|
<i class="fas fa-cog"></i>
|
||||||
<span>安装设置</span>
|
<span>安装设置</span>
|
||||||
</button>
|
</button>
|
||||||
<!-- <button
|
|
||||||
type="button"
|
|
||||||
class="inline-flex items-center gap-2 rounded-2xl bg-slate-900/90 px-4 py-2 text-sm font-semibold text-white shadow-lg shadow-slate-900/40 transition hover:-translate-y-0.5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-900/40 dark:bg-white/90 dark:text-slate-900"
|
|
||||||
@click="handleList"
|
|
||||||
title="启动 apm-installer --list"
|
|
||||||
>
|
|
||||||
<i class="fas fa-download"></i>
|
|
||||||
<span>应用管理</span>
|
|
||||||
</button> -->
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const props = defineProps<{
|
||||||
|
apmAvailable: boolean;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
inheritAttrs: false,
|
||||||
|
});
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "update"): void;
|
(e: "update"): void;
|
||||||
(e: "list"): void;
|
(e: "list"): void;
|
||||||
@@ -44,4 +51,8 @@ const handleUpdate = () => {
|
|||||||
const handleSettings = () => {
|
const handleSettings = () => {
|
||||||
emit("open-install-settings");
|
emit("open-install-settings");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleList = () => {
|
||||||
|
emit("list");
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -107,6 +107,7 @@ export interface App {
|
|||||||
installed?: boolean; // Frontend state
|
installed?: boolean; // Frontend state
|
||||||
flags?: string; // Tags in apm packages manager, e.g. "automatic" for dependencies
|
flags?: string; // Tags in apm packages manager, e.g. "automatic" for dependencies
|
||||||
arch?: string; // Architecture, e.g. "amd64", "arm64"
|
arch?: string; // Architecture, e.g. "amd64", "arm64"
|
||||||
|
isDependency?: boolean; // Whether this is a dependency package
|
||||||
currentStatus: "not-installed" | "installed"; // Current installation status
|
currentStatus: "not-installed" | "installed"; // Current installation status
|
||||||
isMerged?: boolean; // FLAG for overlapping apps
|
isMerged?: boolean; // FLAG for overlapping apps
|
||||||
sparkApp?: App; // Optional reference to the spark version
|
sparkApp?: App; // Optional reference to the spark version
|
||||||
@@ -125,10 +126,14 @@ export interface UpdateAppItem {
|
|||||||
/**************Below are type from main process ********************/
|
/**************Below are type from main process ********************/
|
||||||
export interface InstalledAppInfo {
|
export interface InstalledAppInfo {
|
||||||
pkgname: string;
|
pkgname: string;
|
||||||
|
name: string;
|
||||||
version: string;
|
version: string;
|
||||||
arch: string;
|
arch: string;
|
||||||
flags: string;
|
flags: string;
|
||||||
raw: string;
|
origin: "spark" | "apm";
|
||||||
|
icon?: string;
|
||||||
|
isDependency: boolean;
|
||||||
|
raw?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user