Compare commits

...

11 Commits

Author SHA1 Message Date
zinface d8e94c87bc fix(app/detail): 详情页未更新警示横幅去除 Emoji 2026-06-10 10:41:53 +08:00
zinface e97052b93c feat: 添加软件更新警醒横幅
- 在应用详情页顶部添加警醒横幅
- 当软件超过一年未更新时显示警告提示
2026-06-09 09:54:30 +08:00
shenmo7192 8e8617218a !391 增加对aosc os的支持
Merge pull request !391 from Melorise/add-support-for-aosc
2026-05-22 15:06:40 +00:00
Melorise 97f49201b7 增加对aosc os的支持 2026-05-20 10:31:49 +08:00
shenmo7192 f62665cd73 release: bump version to 5.1.1 and fix dns download issue
add --async-dns=false aria2 parameter to all download jobs to fix potential dns resolution failures during package download
2026-05-16 02:35:29 +08:00
shenmo7192 e16acbd0a5 refactor(installer): 调整下载重试超时配置和次数
更新了下载重试的超时时间列表和总重试次数,从原3次调整为10次,优化下载成功率
2026-05-13 21:13:25 +08:00
shenmo7192 8a5f8d154f feat: 添加APM安装确认弹窗并重构APM检查流程
1. 新增全局状态控制APM安装弹窗显示
2. 新建ApmInstallConfirmModal弹窗组件
3. 将主进程的APM安装弹窗逻辑迁移到前端Vue组件
4. 更新package.json版本到5.1.0
5. 简化安装和升级流程中的APM检查逻辑
2026-05-12 21:54:47 +08:00
shenmo7192 8c8b53fc29 update tool/apt-fast/ss-apt-fast.
Signed-off-by: shenmo <jifengshenmo@outlook.com>
2026-05-09 15:04:46 +00:00
shenmo7192 c50655c106 !389 dark下的效果还是怪怪的重新改一下,还有统一应用管理和软件更新的按钮样式
Merge pull request !389 from zeqi/Erotica
2026-05-03 04:02:34 +00:00
zeqi 4b37aa4da4 dark下的效果还是怪怪的重新改一下,还有统一应用管理和软件更新的按钮样式
Signed-off-by: zeqi <a202128502@163.com>
2026-05-03 02:35:28 +00:00
shenmo7192 ce5de692f7 修复 Ubuntu 26.04 上无法正常安装的问题
Signed-off-by: shenmo <jifengshenmo@outlook.com>
2026-04-30 15:29:13 +00:00
14 changed files with 189 additions and 160 deletions
+9 -105
View File
@@ -1,4 +1,4 @@
import { BrowserWindow, dialog, ipcMain, WebContents } from "electron";
import { ipcMain, WebContents } from "electron";
import { spawn, ChildProcess, exec } from "node:child_process";
import { promisify } from "node:util";
import fs from "node:fs";
@@ -114,25 +114,6 @@ const checkSparkAvailable = async (): Promise<boolean> => {
return found;
};
/** 提权执行 shell-caller aptss install apm 安装 APM,安装后需用户重启电脑 */
const runInstallApm = async (superUserCmd: string): Promise<boolean> => {
const execCommand = superUserCmd || SHELL_CALLER_PATH;
const execParams = superUserCmd
? [SHELL_CALLER_PATH, "aptss", "install", "apm"]
: [SHELL_CALLER_PATH, "aptss", "install", "apm"];
logger.info(`执行安装 APM: ${execCommand} ${execParams.join(" ")}`);
const { code, stdout, stderr } = await runCommandCapture(
execCommand,
execParams,
);
if (code !== 0) {
logger.error({ code, stdout, stderr }, "安装 APM 失败");
return false;
}
logger.info("安装 APM 完成");
return true;
};
const parseUpgradableList = (output: string) => {
const apps: Array<{
pkgname: string;
@@ -215,62 +196,24 @@ ipcMain.on("queue-install", async (event, download_json) => {
const execParams = [];
const downloadDir = `/tmp/spark-store/download/${pkgname}`;
// APM 应用:若本机没有 apm 命令,弹窗提示并可选提权安装 APM(安装后需重启电脑)
// APM 应用:若本机没有 apm 命令,通知前端弹窗引导安装 APM
if (origin === "apm") {
const hasApm = await checkApmAvailable();
if (!hasApm) {
const win = BrowserWindow.fromWebContents(webContents);
const { response } = await dialog.showMessageBox(win ?? undefined, {
type: "question",
title: "需要安装 APM",
message: "此应用需要使用 APM 安装。",
detail:
"APM是星火应用商店的容器包管理器,安装APM后方可安装此应用,是否确认安装?",
buttons: ["确认", "取消"],
defaultId: 0,
cancelId: 1,
});
if (response !== 0) {
webContents.send("trigger-apm-install-dialog");
webContents.send("install-complete", {
id,
success: false,
time: Date.now(),
exitCode: -1,
message: JSON.stringify({
message: "用户取消安装 APM,无法继续安装此应用",
message: "安装 APM,无法继续安装此应用",
stdout: "",
stderr: "",
}),
});
return;
}
const installApmOk = await runInstallApm(superUserCmd);
if (!installApmOk) {
webContents.send("install-complete", {
id,
success: false,
time: Date.now(),
exitCode: -1,
message: JSON.stringify({
message: "安装 APM 失败,请检查网络或权限后重试",
stdout: "",
stderr: "",
}),
});
return;
} else {
// 安装APM成功,提示用户已安装成功,需要重启后方可展示应用
await dialog.showMessageBox(win ?? undefined, {
type: "info",
title: "APM 安装成功",
message: "恭喜您,APM 已成功安装",
detail:
"恭喜您,APM 已成功安装!您的应用已在安装中~\n首次安装APM后,需要重启电脑后方可在启动器展示应用。您可在应用安装完毕后择机重启电脑\n若您需要立即使用应用,可在应用安装后先在应用商店中打开您的应用。",
buttons: ["确定"],
defaultId: 0,
});
}
}
}
if (origin === "spark") {
@@ -447,6 +390,7 @@ async function processNextInQueue() {
const aria2Args = [
`--dir=${downloadDir}`,
"--allow-overwrite=true",
"--async-dns=false",
"--summary-interval=1",
"--connect-timeout=10",
"--timeout=15",
@@ -462,8 +406,8 @@ async function processNextInQueue() {
sendStatus("downloading");
// 下载重试逻辑:每次超时时间递增,最多3次
const timeoutList = [3000, 5000, 15000]; // 第一次3秒,第二次5秒,第三次15秒
// 下载重试逻辑:共10次,5次3秒,3次5秒,2次10秒
const timeoutList = [3000, 3000, 3000, 3000, 3000, 5000, 5000, 5000, 10000, 10000];
let retryCount = 0;
let downloadSuccess = false;
@@ -1089,51 +1033,11 @@ ipcMain.handle("check-spark-available", async () => {
});
// 显示 APM 安装对话框(在点击安装按钮时提前检查)
// 前端已改为 Vue 弹窗,此后端处理仅作为兜底
ipcMain.handle("show-apm-install-dialog", async (event) => {
const webContents = event.sender;
const win = BrowserWindow.fromWebContents(webContents);
const superUserCmd = await checkSuperUserCommand();
const { response } = await dialog.showMessageBox(win ?? undefined, {
type: "question",
title: "需要安装 APM",
message: "此应用需要使用 APM 安装。",
detail:
"APM 是星火应用商店的软件包兼容工具,此应用使用星火 APM 提供支持,安装APM后方可安装此应用,是否确认安装?",
buttons: ["确认", "取消"],
defaultId: 0,
cancelId: 1,
});
if (response !== 0) {
webContents.send("trigger-apm-install-dialog");
return { success: false, cancelled: true };
}
const installApmOk = await runInstallApm(superUserCmd);
if (!installApmOk) {
await dialog.showMessageBox(win ?? undefined, {
type: "error",
title: "安装失败",
message: "安装 APM 失败",
detail: "请检查网络或权限后重试",
buttons: ["确定"],
defaultId: 0,
});
return { success: false, cancelled: false };
}
// 安装APM成功,提示用户已安装成功,需要重启后方可展示应用
await dialog.showMessageBox(win ?? undefined, {
type: "info",
title: "APM 安装成功",
message: "恭喜您,APM 已成功安装",
detail:
"恭喜您,APM 已成功安装!\n首次安装APM后,需要重启电脑后方可使用全部功能。您可在应用安装完毕后择机重启电脑。",
buttons: ["确定"],
defaultId: 0,
});
return { success: true, cancelled: false };
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any
+3 -2
View File
@@ -89,6 +89,7 @@ export const downloadPackage = async ({
const aria2Args = [
`--dir=${downloadDir}`,
"--allow-overwrite=true",
"--async-dns=false",
"--summary-interval=1",
"--connect-timeout=10",
"--timeout=15",
@@ -104,8 +105,8 @@ export const downloadPackage = async ({
onStatus?.("downloading");
// 下载重试逻辑:每次超时时间递增,最多3次
const timeoutList = [3000, 5000, 15000];
// 下载重试逻辑:共10次,5次3秒,3次5秒,2次10秒
const timeoutList = [3000, 3000, 3000, 3000, 3000, 5000, 5000, 5000, 10000, 10000];
let retryCount = 0;
let downloadSuccess = false;
+6
View File
@@ -23,6 +23,12 @@ if ! command -v apt >/dev/null 2>&1; then
ARGS="$ARGS --no-spark"
fi
# 检查是否是AOSC OS
if grep -q "ID=aosc" /etc/os-release; then
echo "检测到 AOSC OS"
ARGS="$ARGS --no-spark"
fi
# 注意:已移除原先针对 arm64 + wayland 添加 --disable-gpu 的逻辑,
# 现在 arm64 设备无论是否使用 wayland 均不再添加此参数。
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "spark-store",
"version": "5.0.1",
"version": "5.1.1",
"main": "dist-electron/main/index.js",
"description": "Client for Spark App Store",
"author": "elysia-best <elysia-best@simplelinux.cn.eu.org>",
@@ -37,6 +37,7 @@ void DownloadManager::startDownload(const QString &packageName, const QString &u
QStringList arguments = {
"--enable-rpc=false",
"--console-log-level=warn",
"--async-dns=false",
"--summary-interval=1",
"--allow-overwrite=true",
"--connect-timeout=30",
+28
View File
@@ -152,6 +152,12 @@
@success="onUninstallSuccess"
/>
<ApmInstallConfirmModal
:show="showApmInstallDialog"
@close="closeApmInstallDialog"
@confirm="confirmApmInstall"
/>
<AboutModal :show="showAboutModal" @close="closeAboutModal" />
<SettingsModal :show="showSettingsModal" @close="closeSettingsModal" />
@@ -173,6 +179,7 @@ import DownloadDetail from "./components/DownloadDetail.vue";
import InstalledAppsModal from "./components/InstalledAppsModal.vue";
import UpdateCenterModal from "./components/UpdateCenterModal.vue";
import UninstallConfirmModal from "./components/UninstallConfirmModal.vue";
import ApmInstallConfirmModal from "./components/ApmInstallConfirmModal.vue";
import AboutModal from "./components/AboutModal.vue";
import SettingsModal from "./components/SettingsModal.vue";
import {
@@ -181,6 +188,7 @@ import {
currentAppSparkInstalled,
currentAppApmInstalled,
currentStoreMode,
showApmInstallDialog,
getHybridDefaultOrigin,
loadPriorityConfig,
} from "./global/storeConfig";
@@ -964,6 +972,22 @@ const onUninstallSuccess = () => {
}
};
const closeApmInstallDialog = () => {
showApmInstallDialog.value = false;
};
const confirmApmInstall = async () => {
showApmInstallDialog.value = false;
closeDetail();
await nextTick();
const apmApp = apps.value.find((a) => a.pkgname === "apm");
if (apmApp) {
openDetail(apmApp);
} else {
searchQuery.value = "apm";
}
};
const installCompleteCallback = (pkgname?: string) => {
if (currentApp.value && (!pkgname || currentApp.value.pkgname === pkgname)) {
checkAppInstalled(currentApp.value);
@@ -1276,6 +1300,10 @@ onMounted(async () => {
}
});
window.ipcRenderer.on("trigger-apm-install-dialog", () => {
showApmInstallDialog.value = true;
});
window.ipcRenderer.on(
"deep-link-install",
(_event: IpcRendererEvent, pkgname: string) => {
+81
View File
@@ -0,0 +1,81 @@
<template>
<Transition
enter-active-class="duration-200 ease-out"
enter-from-class="opacity-0 scale-95"
enter-to-class="opacity-100 scale-100"
leave-active-class="duration-150 ease-in"
leave-from-class="opacity-100 scale-100"
leave-to-class="opacity-0 scale-95"
>
<div
v-if="show"
class="fixed inset-0 z-[80] flex items-center justify-center bg-slate-900/70 p-4"
@click.self="handleClose"
>
<div
class="relative w-full max-w-lg overflow-hidden rounded-3xl border border-white/10 bg-white/95 p-6 shadow-2xl dark:border-slate-800 dark:bg-slate-900"
>
<div class="mb-6 flex items-center gap-4">
<div
class="flex h-16 w-16 items-center justify-center rounded-2xl bg-gradient-to-br from-brand/20 to-brand/10 shadow-inner dark:from-brand/20 dark:to-brand/10"
>
<i class="fas fa-box-open text-2xl text-brand"></i>
</div>
<div>
<h3 class="text-xl font-bold text-slate-900 dark:text-white">
需要安装 APM
</h3>
<p class="text-sm text-slate-500 dark:text-slate-400">
APM 是星火应用商店的软件包兼容工具此应用使用星火 APM 提供支持
</p>
<p class="mt-1 text-sm text-slate-500 dark:text-slate-400">
是否前往商店安装
<span class="font-semibold text-slate-700 dark:text-slate-200"
>APM</span
>
</p>
</div>
</div>
<div class="flex items-center justify-end gap-3">
<button
type="button"
class="rounded-xl border border-slate-200 bg-white px-4 py-2 text-sm font-medium text-slate-600 transition hover:bg-slate-50 dark:border-slate-700 dark:bg-slate-800 dark:text-slate-300 dark:hover:bg-slate-700"
@click="handleClose"
>
取消
</button>
<button
type="button"
class="inline-flex items-center gap-2 rounded-xl bg-gradient-to-r from-brand to-brand-dark px-4 py-2 text-sm font-semibold text-white shadow-lg shadow-brand/30 transition hover:-translate-y-0.5"
@click="handleConfirm"
>
<i class="fas fa-download"></i>
前往安装
</button>
</div>
</div>
</div>
</Transition>
</template>
<script setup lang="ts">
defineProps<{
show: boolean;
}>();
const emit = defineEmits<{
(e: "close"): void;
(e: "confirm"): void;
}>();
const handleClose = () => {
emit("close");
};
const handleConfirm = () => {
emit("confirm");
};
</script>
+32
View File
@@ -262,6 +262,23 @@
<!-- 右侧应用详情+ 截图 -->
<div class="flex-1 min-w-0 space-y-5">
<!-- 警醒横幅 - 早于一年未更新 -->
<div
v-if="displayApp?.update && isOutdated(displayApp.update)"
class="rounded-2xl border-l-4 border-amber-500 bg-amber-50 p-4 dark:bg-amber-950/30 dark:border-amber-600"
>
<div class="flex items-center gap-3">
<i class="fas fa-exclamation-triangle text-amber-500 text-lg"></i>
<div>
<p class="text-sm font-medium text-amber-800 dark:text-amber-300">
注意该软件已超过一年未更新
</p>
<p class="text-xs text-amber-700/80 dark:text-amber-400/80 mt-0.5">
最后更新于 {{ displayApp.update }}可能存在兼容性或安全问题请谨慎安装
</p>
</div>
</div>
</div>
<!-- 应用详情 -->
<div
v-if="displayApp?.more && displayApp.more.trim() !== ''"
@@ -633,4 +650,19 @@ const onOverlayWheel = (e: WheelEvent) => {
if (target.closest(".overflow-y-auto, .overflow-auto")) return;
e.preventDefault();
};
// 判断是否超过一年未更新
const isOutdated = (updateDate: string) => {
if (!updateDate) return false;
try {
// 假设日期格式如 "2023-01-15" 或 "2024年12月20日"
const date = new Date(updateDate);
if (isNaN(date.getTime())) return false;
const oneYearAgo = new Date();
oneYearAgo.setFullYear(oneYearAgo.getFullYear() - 1);
return date < oneYearAgo;
} catch {
return false;
}
};
</script>
+1 -1
View File
@@ -62,7 +62,7 @@
</div>
<button
type="button"
class="inline-flex items-center gap-2 rounded-2xl border border-slate-200/70 px-4 py-2 text-sm font-semibold text-slate-600 transition hover:bg-slate-50 disabled:opacity-40 dark:border-slate-700 dark:text-slate-200 dark:hover:text-white"
class="inline-flex items-center gap-2 rounded-2xl border border-slate-200/70 px-4 py-2 text-sm font-semibold text-slate-600 transition hover:bg-slate-50 disabled:opacity-40 dark:border-slate-700 dark:text-slate-200 dark:hover:bg-slate-800"
:disabled="loading"
@click="$emit('refresh')"
>
@@ -33,7 +33,7 @@
<button
type="button"
aria-label="关闭"
class="inline-flex h-11 w-11 items-center justify-center rounded-full border border-slate-200/70 text-slate-500 transition hover:text-slate-900 dark:hover:text-white dark:border-slate-700 dark:text-slate-300"
class="inline-flex h-10 w-10 items-center justify-center rounded-full border border-slate-200/70 text-slate-500 transition hover:text-slate-900 dark:hover:text-white dark:border-slate-700 dark:hover:bg-slate-800"
@click="$emit('request-close')"
>
<i class="fas fa-xmark"></i>
+1
View File
@@ -11,6 +11,7 @@ export const APM_STORE_STATS_BASE_URL: string =
export const currentApp = ref<App | null>(null);
export const currentAppSparkInstalled = ref(false);
export const currentAppApmInstalled = ref(false);
export const showApmInstallDialog = ref(false);
export const currentStoreMode = ref<StoreMode>("hybrid");
+3 -14
View File
@@ -5,6 +5,7 @@ import {
currentApp,
currentAppSparkInstalled,
currentAppApmInstalled,
showApmInstallDialog,
} from "../global/storeConfig";
import { APM_STORE_BASE_URL } from "../global/storeConfig";
import { downloads, getNextDownloadId } from "../global/downloadStatus";
@@ -28,16 +29,10 @@ export const handleInstall = async (appObj?: App) => {
if (targetApp.origin === "apm") {
const hasApm = await window.ipcRenderer.invoke("check-apm-available");
if (!hasApm) {
// 发送事件到主进程显示 APM 安装对话框
const { success, cancelled } = await window.ipcRenderer.invoke(
"show-apm-install-dialog",
);
if (!success || cancelled) {
// 用户取消或未安装成功,不继续安装应用
showApmInstallDialog.value = true;
return;
}
}
}
if (
downloads.value.find(
@@ -119,16 +114,10 @@ export const handleUpgrade = async (app: App) => {
if (app.origin === "apm") {
const hasApm = await window.ipcRenderer.invoke("check-apm-available");
if (!hasApm) {
// 发送事件到主进程显示 APM 安装对话框
const { success, cancelled } = await window.ipcRenderer.invoke(
"show-apm-install-dialog",
);
if (!success || cancelled) {
// 用户取消或未安装成功,不继续更新应用
showApmInstallDialog.value = true;
return;
}
}
}
if (
downloads.value.find(
+1 -1
View File
@@ -3,7 +3,7 @@ Dir::Cache::archives "/var/cache/apt/archives";
Dir::Cache "/var/lib/aptss/";
Dir::Etc::SourceParts "/opt/durapps/spark-store/bin/apt-fast-conf/sources.list.d/";
Dir::State::lists "/var/lib/aptss/lists/";
APT::Sandbox::User "root";
APT::Get::Fix-Broken true;
APT::Get::List-Cleanup="0";
+6 -20
View File
@@ -83,6 +83,9 @@ cleanup_aptfast()
if [ -n "$LISTTEMP" ] && [ -d "$LISTTEMP" ]; then
rm -rf "$LISTTEMP"
fi
if [ -n "$tmpdir" ] && [ -d "$tmpdir" ]; then
rm -rf "$tmpdir"
fi
}
exit_cleanup_state()
{
@@ -120,11 +123,8 @@ _create_lock()
# unlock and remove the lock file
_remove_lock()
{
# Only unlock if lock file exists (was created by _create_lock)
if [ -f "$LCK_FILE.lock" ]; then
flock -u "$LCK_FD" 2>/dev/null
rm -f "$LCK_FILE.lock"
fi
}
# Search for known options and decide if root privileges are needed.
@@ -134,7 +134,6 @@ for argument in "$@"; do
case "$argument" in
upgrade | full-upgrade | install | dist-upgrade | build-dep)
option="install"
_create_lock
;;
clean | autoclean)
option="clean"
@@ -313,6 +312,9 @@ https_proxy=
[ "$TMP_http_proxy" = "$TMP_RANDOM" ] || http_proxy="$TMP_http_proxy"
[ "$TMP_https_proxy" = "$TMP_RANDOM" ] || https_proxy="$TMP_https_proxy"
if [ "$option" == "install" ]; then
_create_lock
fi
# Disable colors if not executed in terminal.
if [ ! -t 1 ]; then
@@ -456,22 +458,12 @@ get_uris(){
exit "$CLEANUP_STATE"
fi
prepare_auth
local tmpdir
tmpdir=$(mktemp -d) || {
msg "Failed to create tmp dir" "warning"
msg "无法创建临时目录" "warning"
exit 1
}
cleanup_tmpdir() {
if [ -n "$tmpdir" ] && [ -d "$tmpdir" ]; then
rm -rf "$tmpdir"
fi
}
trap cleanup_tmpdir EXIT
## --print-uris format is:
# 'fileurl' filename filesize checksum_hint:filechecksum
# 修改:process_package函数增加第二个参数表示当前线程的临时输出文件
@@ -824,9 +816,6 @@ elif [ "$option" == "download" ]; then
"${_APTMGR}" "$@"
fi
# Clean up temporary directory for download command
cleanup_aptfast
elif [ "$option" == "source" ]; then
msg
msg "Working... this may take a while." "normal"
@@ -853,9 +842,6 @@ elif [ "$option" == "source" ]; then
# dpkg-source -x "$(basename "$srcfile")"
#done < "$DLLIST"
# Clean up temporary directory for source command
cleanup_aptfast
# Execute package manager directly if unknown options are passed.
else
"${_APTMGR}" "${APT_SCRIPT_WARNING[@]}" "$@"