Compare commits

...

18 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
shenmo7192 3d4af0c492 改为5.0.1 2026-04-25 14:40:37 +08:00
shenmo7192 c39b25e393 5.1 2026-04-25 14:33:21 +08:00
shenmo7192 2086152aa5 refactor: 移除缓存清除函数并直接使用原始URL
移除cacheBuster函数及其所有调用,改为直接使用原始URL进行请求
2026-04-25 14:25:52 +08:00
shenmo7192 f8f112a782 !388 feat(build): add loong64 to build
Merge pull request !388 from AAA Elysia 猫猫侠 ⁧~喵/elysia/add-loong64
2026-04-21 14:04:55 +00:00
Elysia 6a9091b2ec feat(build): add loong64
- Downgrad electron for the sake of loong64
- Add my project to CREDIT.md

Signed-off-by: Elysia <a.elysia@proton.me>
2026-04-19 09:37:21 +08:00
shenmo7192 994dbaf9b9 !387 优化dark模式下应用管理的关闭按钮和刷新按钮、软件更新的关闭按钮的hover效果
Merge pull request !387 from zeqi/Erotica
2026-04-16 22:59:56 +00:00
zeqi 9c9f0b6076 优化dark模式下应用管理的关闭按钮和刷新按钮、软件更新的关闭按钮的hover效果
Signed-off-by: zeqi <a202128502@163.com>
2026-04-16 15:27:55 +00:00
22 changed files with 426 additions and 310 deletions
+20
View File
@@ -23,3 +23,23 @@
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
2. https://github.com/elysia-best/apm-app-store MulanPSL-2.0
Copyright (c) 2026-present The Spark Project Contributors
apm-store is licensed under Mulan PSL v2.
You can use this software according to the terms and conditions of the Mulan PSL v2.
You may obtain a copy of Mulan PSL v2 at:
http://license.coscl.org.cn/MulanPSL2
THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
See the Mulan PSL v2 for more details.
+20 -116
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 { spawn, ChildProcess, exec } from "node:child_process";
import { promisify } from "node:util"; import { promisify } from "node:util";
import fs from "node:fs"; import fs from "node:fs";
@@ -114,25 +114,6 @@ const checkSparkAvailable = async (): Promise<boolean> => {
return found; 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 parseUpgradableList = (output: string) => {
const apps: Array<{ const apps: Array<{
pkgname: string; pkgname: string;
@@ -215,61 +196,23 @@ ipcMain.on("queue-install", async (event, download_json) => {
const execParams = []; const execParams = [];
const downloadDir = `/tmp/spark-store/download/${pkgname}`; const downloadDir = `/tmp/spark-store/download/${pkgname}`;
// APM 应用:若本机没有 apm 命令,弹窗提示并可选提权安装 APM(安装后需重启电脑) // APM 应用:若本机没有 apm 命令,通知前端弹窗引导安装 APM
if (origin === "apm") { if (origin === "apm") {
const hasApm = await checkApmAvailable(); const hasApm = await checkApmAvailable();
if (!hasApm) { if (!hasApm) {
const win = BrowserWindow.fromWebContents(webContents); webContents.send("trigger-apm-install-dialog");
const { response } = await dialog.showMessageBox(win ?? undefined, { webContents.send("install-complete", {
type: "question", id,
title: "需要安装 APM", success: false,
message: "此应用需要使用 APM 安装。", time: Date.now(),
detail: exitCode: -1,
"APM是星火应用商店的容器包管理器,安装APM后方可安装此应用,是否确认安装?", message: JSON.stringify({
buttons: ["确认", "取消"], message: "未安装 APM,无法继续安装此应用",
defaultId: 0, stdout: "",
cancelId: 1, stderr: "",
}),
}); });
if (response !== 0) { return;
webContents.send("install-complete", {
id,
success: false,
time: Date.now(),
exitCode: -1,
message: JSON.stringify({
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,
});
}
} }
} }
@@ -447,6 +390,7 @@ async function processNextInQueue() {
const aria2Args = [ const aria2Args = [
`--dir=${downloadDir}`, `--dir=${downloadDir}`,
"--allow-overwrite=true", "--allow-overwrite=true",
"--async-dns=false",
"--summary-interval=1", "--summary-interval=1",
"--connect-timeout=10", "--connect-timeout=10",
"--timeout=15", "--timeout=15",
@@ -462,8 +406,8 @@ async function processNextInQueue() {
sendStatus("downloading"); sendStatus("downloading");
// 下载重试逻辑:每次超时时间递增,最多3次 // 下载重试逻辑:共10次,5次3秒,3次5秒,2次10秒
const timeoutList = [3000, 5000, 15000]; // 第一次3秒,第二次5秒,第三次15秒 const timeoutList = [3000, 3000, 3000, 3000, 3000, 5000, 5000, 5000, 10000, 10000];
let retryCount = 0; let retryCount = 0;
let downloadSuccess = false; let downloadSuccess = false;
@@ -1089,51 +1033,11 @@ ipcMain.handle("check-spark-available", async () => {
}); });
// 显示 APM 安装对话框(在点击安装按钮时提前检查) // 显示 APM 安装对话框(在点击安装按钮时提前检查)
// 前端已改为 Vue 弹窗,此后端处理仅作为兜底
ipcMain.handle("show-apm-install-dialog", async (event) => { ipcMain.handle("show-apm-install-dialog", async (event) => {
const webContents = event.sender; const webContents = event.sender;
const win = BrowserWindow.fromWebContents(webContents); webContents.send("trigger-apm-install-dialog");
const superUserCmd = await checkSuperUserCommand(); return { success: false, cancelled: true };
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) {
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 // eslint-disable-next-line @typescript-eslint/no-explicit-any
+3 -2
View File
@@ -89,6 +89,7 @@ export const downloadPackage = async ({
const aria2Args = [ const aria2Args = [
`--dir=${downloadDir}`, `--dir=${downloadDir}`,
"--allow-overwrite=true", "--allow-overwrite=true",
"--async-dns=false",
"--summary-interval=1", "--summary-interval=1",
"--connect-timeout=10", "--connect-timeout=10",
"--timeout=15", "--timeout=15",
@@ -104,8 +105,8 @@ export const downloadPackage = async ({
onStatus?.("downloading"); onStatus?.("downloading");
// 下载重试逻辑:每次超时时间递增,最多3次 // 下载重试逻辑:共10次,5次3秒,3次5秒,2次10秒
const timeoutList = [3000, 5000, 15000]; const timeoutList = [3000, 3000, 3000, 3000, 3000, 5000, 5000, 5000, 10000, 10000];
let retryCount = 0; let retryCount = 0;
let downloadSuccess = false; let downloadSuccess = false;
+40 -11
View File
@@ -376,7 +376,9 @@ export const loadUpdateCenterItems = async (
storeFilter: StoreFilter = "both", storeFilter: StoreFilter = "both",
runCommand: UpdateCenterCommandRunner = runCommandCapture, runCommand: UpdateCenterCommandRunner = runCommandCapture,
): Promise<UpdateCenterLoadItemsResult> => { ): Promise<UpdateCenterLoadItemsResult> => {
console.log(`[UpdateCenter] loadUpdateCenterItems called with storeFilter=${storeFilter}`); console.log(
`[UpdateCenter] loadUpdateCenterItems called with storeFilter=${storeFilter}`,
);
const [sparkEnabled, apmEnabled] = await Promise.all([ const [sparkEnabled, apmEnabled] = await Promise.all([
isSourceEnabled(storeFilter, "spark") isSourceEnabled(storeFilter, "spark")
? isCommandAvailable(runCommand, "aptss") ? isCommandAvailable(runCommand, "aptss")
@@ -385,7 +387,9 @@ export const loadUpdateCenterItems = async (
? isCommandAvailable(runCommand, "apm") ? isCommandAvailable(runCommand, "apm")
: Promise.resolve(false), : Promise.resolve(false),
]); ]);
console.log(`[UpdateCenter] sparkEnabled=${sparkEnabled}, apmEnabled=${apmEnabled}`); console.log(
`[UpdateCenter] sparkEnabled=${sparkEnabled}, apmEnabled=${apmEnabled}`,
);
const [aptssResult, apmResult, aptssInstalledResult, apmInstalledResult] = const [aptssResult, apmResult, aptssInstalledResult, apmInstalledResult] =
await Promise.all([ await Promise.all([
@@ -409,10 +413,18 @@ export const loadUpdateCenterItems = async (
: Promise.resolve({ code: 0, stdout: "", stderr: "" }), : Promise.resolve({ code: 0, stdout: "", stderr: "" }),
]); ]);
console.log(`[UpdateCenter] aptssResult: code=${aptssResult.code}, stdout=${aptssResult.stdout.substring(0, 500)}, stderr=${aptssResult.stderr.substring(0, 500)}`); console.log(
console.log(`[UpdateCenter] apmResult: code=${apmResult.code}, stdout=${apmResult.stdout.substring(0, 500)}, stderr=${apmResult.stderr.substring(0, 500)}`); `[UpdateCenter] aptssResult: code=${aptssResult.code}, stdout=${aptssResult.stdout.substring(0, 500)}, stderr=${aptssResult.stderr.substring(0, 500)}`,
console.log(`[UpdateCenter] aptssInstalledResult: code=${aptssInstalledResult.code}, stdout=${aptssInstalledResult.stdout.substring(0, 500)}`); );
console.log(`[UpdateCenter] apmInstalledResult: code=${apmInstalledResult.code}, stdout=${apmInstalledResult.stdout.substring(0, 500)}`); console.log(
`[UpdateCenter] apmResult: code=${apmResult.code}, stdout=${apmResult.stdout.substring(0, 500)}, stderr=${apmResult.stderr.substring(0, 500)}`,
);
console.log(
`[UpdateCenter] aptssInstalledResult: code=${aptssInstalledResult.code}, stdout=${aptssInstalledResult.stdout.substring(0, 500)}`,
);
console.log(
`[UpdateCenter] apmInstalledResult: code=${apmInstalledResult.code}, stdout=${apmInstalledResult.stdout.substring(0, 500)}`,
);
const aptssAvailable = const aptssAvailable =
sparkEnabled && (aptssResult.code === 0 || aptssInstalledResult.code === 0); sparkEnabled && (aptssResult.code === 0 || aptssInstalledResult.code === 0);
@@ -438,8 +450,14 @@ export const loadUpdateCenterItems = async (
apmEnabled && apmResult.code === 0 apmEnabled && apmResult.code === 0
? parseApmUpgradableOutput(apmResult.stdout) ? parseApmUpgradableOutput(apmResult.stdout)
: []; : [];
console.log(`[UpdateCenter] parsed aptssItems count=${aptssItems.length}`, aptssItems.map((i) => `${i.pkgname} ${i.currentVersion}->${i.nextVersion}`)); console.log(
console.log(`[UpdateCenter] parsed apmItems count=${apmItems.length}`, apmItems.map((i) => `${i.pkgname} ${i.currentVersion}->${i.nextVersion}`)); `[UpdateCenter] parsed aptssItems count=${aptssItems.length}`,
aptssItems.map((i) => `${i.pkgname} ${i.currentVersion}->${i.nextVersion}`),
);
console.log(
`[UpdateCenter] parsed apmItems count=${apmItems.length}`,
apmItems.map((i) => `${i.pkgname} ${i.currentVersion}->${i.nextVersion}`),
);
const installedSources = buildInstalledSourceMap( const installedSources = buildInstalledSourceMap(
aptssAvailable && aptssInstalledResult.code === 0 aptssAvailable && aptssInstalledResult.code === 0
@@ -461,15 +479,26 @@ export const loadUpdateCenterItems = async (
? enrichApmItems(categorizedApmItems, runCommand) ? enrichApmItems(categorizedApmItems, runCommand)
: Promise.resolve({ items: [], warnings: [] }), : Promise.resolve({ items: [], warnings: [] }),
]); ]);
console.log(`[UpdateCenter] enrichedAptssItems: count=${enrichedAptssItems.items.length}, warnings=${enrichedAptssItems.warnings.length}`, enrichedAptssItems.warnings); console.log(
console.log(`[UpdateCenter] enrichedApmItems: count=${enrichedApmItems.items.length}, warnings=${enrichedApmItems.warnings.length}`, enrichedApmItems.warnings); `[UpdateCenter] enrichedAptssItems: count=${enrichedAptssItems.items.length}, warnings=${enrichedAptssItems.warnings.length}`,
enrichedAptssItems.warnings,
);
console.log(
`[UpdateCenter] enrichedApmItems: count=${enrichedApmItems.items.length}, warnings=${enrichedApmItems.warnings.length}`,
enrichedApmItems.warnings,
);
const mergedItems = mergeUpdateSources( const mergedItems = mergeUpdateSources(
enrichItemIcons(enrichedAptssItems.items), enrichItemIcons(enrichedAptssItems.items),
enrichItemIcons(enrichedApmItems.items), enrichItemIcons(enrichedApmItems.items),
installedSources, installedSources,
); );
console.log(`[UpdateCenter] mergedItems count=${mergedItems.length}`, mergedItems.map((i) => `${i.pkgname} (${i.source}) ${i.currentVersion}->${i.nextVersion}`)); console.log(
`[UpdateCenter] mergedItems count=${mergedItems.length}`,
mergedItems.map(
(i) => `${i.pkgname} (${i.source}) ${i.currentVersion}->${i.nextVersion}`,
),
);
return { return {
items: mergedItems, items: mergedItems,
+21 -7
View File
@@ -263,16 +263,26 @@ const compareVersions = (left: string, right: string): number => {
export const parseAptssUpgradableOutput = ( export const parseAptssUpgradableOutput = (
output: string, output: string,
): UpdateCenterItem[] => { ): UpdateCenterItem[] => {
console.log(`[UpdateCenter] parseAptssUpgradableOutput input (first 1000 chars): ${output.substring(0, 1000)}`); console.log(
`[UpdateCenter] parseAptssUpgradableOutput input (first 1000 chars): ${output.substring(0, 1000)}`,
);
const result = parseUpgradableOutput(output, "aptss"); const result = parseUpgradableOutput(output, "aptss");
console.log(`[UpdateCenter] parseAptssUpgradableOutput result count=${result.length}`); console.log(
`[UpdateCenter] parseAptssUpgradableOutput result count=${result.length}`,
);
return result; return result;
}; };
export const parseApmUpgradableOutput = (output: string): UpdateCenterItem[] => { export const parseApmUpgradableOutput = (
console.log(`[UpdateCenter] parseApmUpgradableOutput input (first 1000 chars): ${output.substring(0, 1000)}`); output: string,
): UpdateCenterItem[] => {
console.log(
`[UpdateCenter] parseApmUpgradableOutput input (first 1000 chars): ${output.substring(0, 1000)}`,
);
const result = parseUpgradableOutput(output, "apm"); const result = parseUpgradableOutput(output, "apm");
console.log(`[UpdateCenter] parseApmUpgradableOutput result count=${result.length}`); console.log(
`[UpdateCenter] parseApmUpgradableOutput result count=${result.length}`,
);
return result; return result;
}; };
@@ -283,10 +293,14 @@ export const parsePrintUrisOutput = (
"downloadUrl" | "fileName" | "size" | "sha512" "downloadUrl" | "fileName" | "size" | "sha512"
> | null => { > | null => {
const trimmed = output.trim(); const trimmed = output.trim();
console.log(`[UpdateCenter] parsePrintUrisOutput input (first 500 chars): ${trimmed.substring(0, 500)}`); console.log(
`[UpdateCenter] parsePrintUrisOutput input (first 500 chars): ${trimmed.substring(0, 500)}`,
);
const match = trimmed.match(PRINT_URIS_PATTERN); const match = trimmed.match(PRINT_URIS_PATTERN);
if (!match) { if (!match) {
console.log(`[UpdateCenter] parsePrintUrisOutput: no match found for pattern ${PRINT_URIS_PATTERN}`); console.log(
`[UpdateCenter] parsePrintUrisOutput: no match found for pattern ${PRINT_URIS_PATTERN}`,
);
return null; return null;
} }
+10 -3
View File
@@ -166,7 +166,9 @@ export const createUpdateCenterService = (
storeFilter: StoreFilter = currentStoreFilter, storeFilter: StoreFilter = currentStoreFilter,
): Promise<UpdateCenterServiceState> => { ): Promise<UpdateCenterServiceState> => {
currentStoreFilter = storeFilter; currentStoreFilter = storeFilter;
console.log(`[UpdateCenter] service.refresh called with storeFilter=${storeFilter}`); console.log(
`[UpdateCenter] service.refresh called with storeFilter=${storeFilter}`,
);
queue.startRefresh(); queue.startRefresh();
emit(); emit();
@@ -176,11 +178,16 @@ export const createUpdateCenterService = (
const loadedItems = normalizeLoadedItems( const loadedItems = normalizeLoadedItems(
await options.loadItems(currentStoreFilter), await options.loadItems(currentStoreFilter),
); );
console.log(`[UpdateCenter] loadItems returned: items=${loadedItems.items.length}, warnings=${loadedItems.warnings.length}`, loadedItems.warnings); console.log(
`[UpdateCenter] loadItems returned: items=${loadedItems.items.length}, warnings=${loadedItems.warnings.length}`,
loadedItems.warnings,
);
const items = sortIgnoredItems( const items = sortIgnoredItems(
applyIgnoredEntries(loadedItems.items, ignoredEntries), applyIgnoredEntries(loadedItems.items, ignoredEntries),
); );
console.log(`[UpdateCenter] after applying ignored: items=${items.length}`); console.log(
`[UpdateCenter] after applying ignored: items=${items.length}`,
);
queue.setItems(items); queue.setItems(items);
queue.finishRefresh(loadedItems.warnings); queue.finishRefresh(loadedItems.warnings);
return emit(); return emit();
+12 -7
View File
@@ -98,13 +98,18 @@ logger.info("User Agent: " + getUserAgent());
/** 根据启动参数 --no-apm / --no-spark 决定只展示的来源 */ /** 根据启动参数 --no-apm / --no-spark 决定只展示的来源 */
function getStoreFilterFromArgv(): "spark" | "apm" | "both" { function getStoreFilterFromArgv(): "spark" | "apm" | "both" {
const argv = process.argv; if (process.arch === "loong64") {
const noApm = argv.includes("--no-apm"); // Currently loong64 only have spark support
const noSpark = argv.includes("--no-spark"); return "spark";
if (noApm && noSpark) return "both"; } else {
if (noApm) return "spark"; const argv = process.argv;
if (noSpark) return "apm"; const noApm = argv.includes("--no-apm");
return "both"; const noSpark = argv.includes("--no-spark");
if (noApm && noSpark) return "both";
if (noApm) return "spark";
if (noSpark) return "apm";
return "both";
}
} }
ipcMain.handle("get-store-filter", (): "spark" | "apm" | "both" => ipcMain.handle("get-store-filter", (): "spark" | "apm" | "both" =>
+6
View File
@@ -23,6 +23,12 @@ if ! command -v apt >/dev/null 2>&1; then
ARGS="$ARGS --no-spark" ARGS="$ARGS --no-spark"
fi 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 添加 --disable-gpu 的逻辑,
# 现在 arm64 设备无论是否使用 wayland 均不再添加此参数。 # 现在 arm64 设备无论是否使用 wayland 均不再添加此参数。
+17 -15
View File
@@ -1,12 +1,12 @@
{ {
"name": "spark-store", "name": "spark-store",
"version": "5.0.0beta4", "version": "5.0.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "spark-store", "name": "spark-store",
"version": "5.0.0beta4", "version": "5.0.0",
"license": "GPL-3.0", "license": "GPL-3.0",
"dependencies": { "dependencies": {
"@tailwindcss/vite": "^4.1.18", "@tailwindcss/vite": "^4.1.18",
@@ -28,7 +28,7 @@
"@vue/test-utils": "^2.4.3", "@vue/test-utils": "^2.4.3",
"conventional-changelog": "^7.1.1", "conventional-changelog": "^7.1.1",
"conventional-changelog-angular": "^8.1.0", "conventional-changelog-angular": "^8.1.0",
"electron": "^40.0.0", "electron": "^39.2.7",
"eslint": "^9.39.2", "eslint": "^9.39.2",
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.5", "eslint-plugin-prettier": "^5.5.5",
@@ -4870,15 +4870,15 @@
} }
}, },
"node_modules/electron": { "node_modules/electron": {
"version": "40.8.5", "version": "39.2.7",
"resolved": "https://registry.npmjs.org/electron/-/electron-40.8.5.tgz", "resolved": "https://registry.npmjs.org/electron/-/electron-39.2.7.tgz",
"integrity": "sha512-pgTY/VPQKaiU4sTjfU96iyxCXrFm4htVPCMRT4b7q9ijNTRgtLmLvcmzp2G4e7xDrq9p7OLHSmu1rBKFf6Y1/A==", "integrity": "sha512-KU0uFS6LSTh4aOIC3miolcbizOFP7N1M46VTYVfqIgFiuA2ilfNaOHLDS9tCMvwwHRowAsvqBrh9NgMXcTOHCQ==",
"dev": true, "dev": true,
"hasInstallScript": true, "hasInstallScript": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@electron/get": "^2.0.0", "@electron/get": "^2.0.0",
"@types/node": "^24.9.0", "@types/node": "^22.7.7",
"extract-zip": "^2.0.1" "extract-zip": "^2.0.1"
}, },
"bin": { "bin": {
@@ -4889,19 +4889,21 @@
} }
}, },
"node_modules/electron/node_modules/@types/node": { "node_modules/electron/node_modules/@types/node": {
"version": "24.12.0", "version": "22.19.17",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.0.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.17.tgz",
"integrity": "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ==", "integrity": "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"undici-types": "~7.16.0" "undici-types": "~6.21.0"
} }
}, },
"node_modules/electron/node_modules/undici-types": { "node_modules/electron/node_modules/undici-types": {
"version": "7.16.0", "version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"dev": true "dev": true,
"license": "MIT"
}, },
"node_modules/emoji-regex": { "node_modules/emoji-regex": {
"version": "8.0.0", "version": "8.0.0",
+3 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "spark-store", "name": "spark-store",
"version": "5.0.0", "version": "5.1.1",
"main": "dist-electron/main/index.js", "main": "dist-electron/main/index.js",
"description": "Client for Spark App Store", "description": "Client for Spark App Store",
"author": "elysia-best <elysia-best@simplelinux.cn.eu.org>", "author": "elysia-best <elysia-best@simplelinux.cn.eu.org>",
@@ -30,6 +30,7 @@
"build:vite": "vue-tsc --noEmit && vite build --mode production", "build:vite": "vue-tsc --noEmit && vite build --mode production",
"build:rpm": "vue-tsc --noEmit && vite build --mode production && electron-builder --config electron-builder.yml --linux rpm", "build:rpm": "vue-tsc --noEmit && vite build --mode production && electron-builder --config electron-builder.yml --linux rpm",
"build:deb": "vue-tsc --noEmit && vite build --mode production && electron-builder --config electron-builder.yml --linux deb", "build:deb": "vue-tsc --noEmit && vite build --mode production && electron-builder --config electron-builder.yml --linux deb",
"build:deb-loong64": "vue-tsc --noEmit && vite build --mode production && env ELECTRON_MIRROR=https://github.com/darkyzhou/electron-loong64/releases/download/ electron_use_remote_checksums=1 electron-builder --config electron-builder.yml --loong64 --linux deb",
"preview": "vite preview --mode debug", "preview": "vite preview --mode debug",
"lint": "eslint --ext .ts,.vue src electron", "lint": "eslint --ext .ts,.vue src electron",
"lint:fix": "eslint --ext .ts,.vue src electron --fix", "lint:fix": "eslint --ext .ts,.vue src electron --fix",
@@ -56,7 +57,7 @@
"@vue/test-utils": "^2.4.3", "@vue/test-utils": "^2.4.3",
"conventional-changelog": "^7.1.1", "conventional-changelog": "^7.1.1",
"conventional-changelog-angular": "^8.1.0", "conventional-changelog-angular": "^8.1.0",
"electron": "^40.0.0", "electron": "^39.2.7",
"eslint": "^9.39.2", "eslint": "^9.39.2",
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.5", "eslint-plugin-prettier": "^5.5.5",
@@ -37,6 +37,7 @@ void DownloadManager::startDownload(const QString &packageName, const QString &u
QStringList arguments = { QStringList arguments = {
"--enable-rpc=false", "--enable-rpc=false",
"--console-log-level=warn", "--console-log-level=warn",
"--async-dns=false",
"--summary-interval=1", "--summary-interval=1",
"--allow-overwrite=true", "--allow-overwrite=true",
"--connect-timeout=30", "--connect-timeout=30",
+35 -9
View File
@@ -152,6 +152,12 @@
@success="onUninstallSuccess" @success="onUninstallSuccess"
/> />
<ApmInstallConfirmModal
:show="showApmInstallDialog"
@close="closeApmInstallDialog"
@confirm="confirmApmInstall"
/>
<AboutModal :show="showAboutModal" @close="closeAboutModal" /> <AboutModal :show="showAboutModal" @close="closeAboutModal" />
<SettingsModal :show="showSettingsModal" @close="closeSettingsModal" /> <SettingsModal :show="showSettingsModal" @close="closeSettingsModal" />
@@ -173,6 +179,7 @@ import DownloadDetail from "./components/DownloadDetail.vue";
import InstalledAppsModal from "./components/InstalledAppsModal.vue"; import InstalledAppsModal from "./components/InstalledAppsModal.vue";
import UpdateCenterModal from "./components/UpdateCenterModal.vue"; import UpdateCenterModal from "./components/UpdateCenterModal.vue";
import UninstallConfirmModal from "./components/UninstallConfirmModal.vue"; import UninstallConfirmModal from "./components/UninstallConfirmModal.vue";
import ApmInstallConfirmModal from "./components/ApmInstallConfirmModal.vue";
import AboutModal from "./components/AboutModal.vue"; import AboutModal from "./components/AboutModal.vue";
import SettingsModal from "./components/SettingsModal.vue"; import SettingsModal from "./components/SettingsModal.vue";
import { import {
@@ -181,6 +188,7 @@ import {
currentAppSparkInstalled, currentAppSparkInstalled,
currentAppApmInstalled, currentAppApmInstalled,
currentStoreMode, currentStoreMode,
showApmInstallDialog,
getHybridDefaultOrigin, getHybridDefaultOrigin,
loadPriorityConfig, loadPriorityConfig,
} from "./global/storeConfig"; } from "./global/storeConfig";
@@ -238,8 +246,6 @@ const fetchWithRetry = async <T,>(
} }
}; };
const cacheBuster = (url: string) => `${url}?cb=${Date.now()}`;
// 响应式状态 // 响应式状态
const themeMode = ref<"light" | "dark" | "auto">("auto"); const themeMode = ref<"light" | "dark" | "auto">("auto");
const systemIsDark = ref( const systemIsDark = ref(
@@ -401,7 +407,7 @@ const fetchAppFromStore = async (
const arch = window.apm_store.arch || "amd64"; const arch = window.apm_store.arch || "amd64";
const finalArch = origin === "spark" ? `${arch}-store` : `${arch}-apm`; const finalArch = origin === "spark" ? `${arch}-store` : `${arch}-apm`;
const appJsonUrl = `${APM_STORE_BASE_URL}/${finalArch}/${category}/${pkgname}/app.json`; const appJsonUrl = `${APM_STORE_BASE_URL}/${finalArch}/${category}/${pkgname}/app.json`;
const response = await fetch(cacheBuster(appJsonUrl)); const response = await fetch(appJsonUrl);
if (!response.ok) return null; if (!response.ok) return null;
const appJson = await response.json(); const appJson = await response.json();
return { return {
@@ -672,7 +678,7 @@ const loadHome = async () => {
// homelinks.json // homelinks.json
try { try {
const res = await fetch(cacheBuster(`${base}/homelinks.json`)); const res = await fetch(`${base}/homelinks.json`);
if (res.ok) { if (res.ok) {
const links = await res.json(); const links = await res.json();
const taggedLinks = links.map((l: HomeLink) => ({ const taggedLinks = links.map((l: HomeLink) => ({
@@ -687,14 +693,14 @@ const loadHome = async () => {
// homelist.json // homelist.json
try { try {
const res2 = await fetch(cacheBuster(`${base}/homelist.json`)); const res2 = await fetch(`${base}/homelist.json`);
if (res2.ok) { if (res2.ok) {
const lists = await res2.json(); const lists = await res2.json();
for (const item of lists) { for (const item of lists) {
if (item.type === "appList" && item.jsonUrl) { if (item.type === "appList" && item.jsonUrl) {
try { try {
const url = `${APM_STORE_BASE_URL}/${finalArch}${item.jsonUrl}`; const url = `${APM_STORE_BASE_URL}/${finalArch}${item.jsonUrl}`;
const r = await fetch(cacheBuster(url)); const r = await fetch(url);
if (r.ok) { if (r.ok) {
const appsJson = await r.json(); const appsJson = await r.json();
const rawApps = appsJson || []; const rawApps = appsJson || [];
@@ -712,7 +718,7 @@ const loadHome = async () => {
try { try {
const realAppUrl = `${APM_STORE_BASE_URL}/${finalArch}/${baseApp.category}/${baseApp.pkgname}/app.json`; const realAppUrl = `${APM_STORE_BASE_URL}/${finalArch}/${baseApp.category}/${baseApp.pkgname}/app.json`;
const realRes = await fetch(cacheBuster(realAppUrl)); const realRes = await fetch(realAppUrl);
if (realRes.ok) { if (realRes.ok) {
const realApp = await realRes.json(); const realApp = await realRes.json();
if (realApp.Filename) if (realApp.Filename)
@@ -966,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) => { const installCompleteCallback = (pkgname?: string) => {
if (currentApp.value && (!pkgname || currentApp.value.pkgname === pkgname)) { if (currentApp.value && (!pkgname || currentApp.value.pkgname === pkgname)) {
checkAppInstalled(currentApp.value); checkAppInstalled(currentApp.value);
@@ -1081,7 +1103,7 @@ const loadCategories = async () => {
const path = `/${finalArch}/categories.json`; const path = `/${finalArch}/categories.json`;
try { try {
const response = await axiosInstance.get(cacheBuster(path)); const response = await axiosInstance.get(path);
const data = response.data; const data = response.data;
Object.keys(data).forEach((key) => { Object.keys(data).forEach((key) => {
if (categoryData[key]) { if (categoryData[key]) {
@@ -1134,7 +1156,7 @@ const loadApps = async (onFirstBatch?: () => void) => {
logger.info(`加载分类: ${category} (来源: ${mode})`); logger.info(`加载分类: ${category} (来源: ${mode})`);
const categoryApps = await fetchWithRetry<AppJson[]>( const categoryApps = await fetchWithRetry<AppJson[]>(
cacheBuster(path), path,
); );
const normalizedApps = (categoryApps || []).map((appJson) => ({ const normalizedApps = (categoryApps || []).map((appJson) => ({
@@ -1278,6 +1300,10 @@ onMounted(async () => {
} }
}); });
window.ipcRenderer.on("trigger-apm-install-dialog", () => {
showApmInstallDialog.value = true;
});
window.ipcRenderer.on( window.ipcRenderer.on(
"deep-link-install", "deep-link-install",
(_event: IpcRendererEvent, pkgname: string) => { (_event: IpcRendererEvent, pkgname: string) => {
@@ -191,15 +191,18 @@ describe("update-center load items", () => {
], ],
}); });
const result = await loadUpdateCenterItems("both", async (command, args) => { const result = await loadUpdateCenterItems(
const key = `${command} ${args.join(" ")}`; "both",
const match = commandResults.get(key); async (command, args) => {
if (!match) { const key = `${command} ${args.join(" ")}`;
throw new Error(`Missing mock for ${key}`); const match = commandResults.get(key);
} if (!match) {
throw new Error(`Missing mock for ${key}`);
}
return match; return match;
}); },
);
expect(result.warnings).toEqual([]); expect(result.warnings).toEqual([]);
expect(result.items).toContainEqual({ expect(result.items).toContainEqual({
@@ -233,61 +236,64 @@ describe("update-center load items", () => {
], ],
}); });
const result = await loadUpdateCenterItems("both", async (command, args) => { const result = await loadUpdateCenterItems(
const key = `${command} ${args.join(" ")}`; "both",
async (command, args) => {
const key = `${command} ${args.join(" ")}`;
if (key === WHICH_APTSS_KEY) { if (key === WHICH_APTSS_KEY) {
return { code: 0, stdout: "/usr/bin/aptss\n", stderr: "" }; return { code: 0, stdout: "/usr/bin/aptss\n", stderr: "" };
} }
if (key === WHICH_APM_KEY) { if (key === WHICH_APM_KEY) {
return { code: 127, stdout: "", stderr: "apm: command not found" }; return { code: 127, stdout: "", stderr: "apm: command not found" };
} }
if (key === APTSS_LIST_UPGRADABLE_KEY) { if (key === APTSS_LIST_UPGRADABLE_KEY) {
return { return {
code: 0, code: 0,
stdout: "spark-notes/stable 2.0.0 amd64 [upgradable from: 1.0.0]", stdout: "spark-notes/stable 2.0.0 amd64 [upgradable from: 1.0.0]",
stderr: "", stderr: "",
}; };
} }
if (key === DPKG_QUERY_INSTALLED_KEY) { if (key === DPKG_QUERY_INSTALLED_KEY) {
return { return {
code: 0, code: 0,
stdout: "spark-notes\tinstall ok installed\n", stdout: "spark-notes\tinstall ok installed\n",
stderr: "", stderr: "",
}; };
} }
if (key === APTSS_NOTES_PRINT_URIS_KEY) { if (key === APTSS_NOTES_PRINT_URIS_KEY) {
return { return {
code: 0, code: 0,
stdout: stdout:
"'https://example.invalid/spark-notes_2.0.0_amd64.deb' spark-notes_2.0.0_amd64.deb 654321 SHA512:beadfeed", "'https://example.invalid/spark-notes_2.0.0_amd64.deb' spark-notes_2.0.0_amd64.deb 654321 SHA512:beadfeed",
stderr: "", stderr: "",
}; };
} }
if (key === APTSS_NOTES_PRINT_URIS_KEY) { if (key === APTSS_NOTES_PRINT_URIS_KEY) {
return { return {
code: 0, code: 0,
stdout: stdout:
"'https://example.invalid/spark-notes_2.0.0_amd64.deb' spark-notes_2.0.0_amd64.deb 654321 SHA512:beadfeed", "'https://example.invalid/spark-notes_2.0.0_amd64.deb' spark-notes_2.0.0_amd64.deb 654321 SHA512:beadfeed",
stderr: "", stderr: "",
}; };
} }
if (key === "apm list --upgradable" || key === "apm list --installed") { if (key === "apm list --upgradable" || key === "apm list --installed") {
return { return {
code: 127, code: 127,
stdout: "", stdout: "",
stderr: "apm: command not found", stderr: "apm: command not found",
}; };
} }
throw new Error(`Unexpected command ${key}`); throw new Error(`Unexpected command ${key}`);
}); },
);
expect(result.items).toEqual([ expect(result.items).toEqual([
{ {
@@ -416,52 +422,55 @@ describe("update-center load items", () => {
], ],
}); });
const result = await loadUpdateCenterItems("both", async (command, args) => { const result = await loadUpdateCenterItems(
const key = `${command} ${args.join(" ")}`; "both",
async (command, args) => {
const key = `${command} ${args.join(" ")}`;
if (key === WHICH_APTSS_KEY) { if (key === WHICH_APTSS_KEY) {
return { code: 0, stdout: "/usr/bin/aptss\n", stderr: "" }; return { code: 0, stdout: "/usr/bin/aptss\n", stderr: "" };
} }
if (key === WHICH_APM_KEY) { if (key === WHICH_APM_KEY) {
return { code: 127, stdout: "", stderr: "apm: command not found" }; return { code: 127, stdout: "", stderr: "apm: command not found" };
} }
if (key === APTSS_LIST_UPGRADABLE_KEY) { if (key === APTSS_LIST_UPGRADABLE_KEY) {
return { return {
code: 0, code: 0,
stdout: "spark-notes/stable 2.0.0 amd64 [upgradable from: 1.0.0]", stdout: "spark-notes/stable 2.0.0 amd64 [upgradable from: 1.0.0]",
stderr: "", stderr: "",
}; };
} }
if (key === DPKG_QUERY_INSTALLED_KEY) { if (key === DPKG_QUERY_INSTALLED_KEY) {
return { return {
code: 0, code: 0,
stdout: "spark-notes\tinstall ok installed\n", stdout: "spark-notes\tinstall ok installed\n",
stderr: "", stderr: "",
}; };
} }
if (key === APTSS_NOTES_PRINT_URIS_KEY) { if (key === APTSS_NOTES_PRINT_URIS_KEY) {
return { return {
code: 0, code: 0,
stdout: stdout:
"'https://example.invalid/spark-notes_2.0.0_amd64.deb' spark-notes_2.0.0_amd64.deb 654321 SHA512:beadfeed", "'https://example.invalid/spark-notes_2.0.0_amd64.deb' spark-notes_2.0.0_amd64.deb 654321 SHA512:beadfeed",
stderr: "", stderr: "",
}; };
} }
if (key === "apm list --upgradable" || key === "apm list --installed") { if (key === "apm list --upgradable" || key === "apm list --installed") {
return { return {
code: 127, code: 127,
stdout: "", stdout: "",
stderr: "apm: command not found", stderr: "apm: command not found",
}; };
} }
throw new Error(`Unexpected command ${key}`); throw new Error(`Unexpected command ${key}`);
}); },
);
expect(result.items).toEqual([ expect(result.items).toEqual([
{ {
+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 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 <div
v-if="displayApp?.more && displayApp.more.trim() !== ''" v-if="displayApp?.more && displayApp.more.trim() !== ''"
@@ -633,4 +650,19 @@ const onOverlayWheel = (e: WheelEvent) => {
if (target.closest(".overflow-y-auto, .overflow-auto")) return; if (target.closest(".overflow-y-auto, .overflow-auto")) return;
e.preventDefault(); 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> </script>
+2 -2
View File
@@ -62,7 +62,7 @@
</div> </div>
<button <button
type="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" 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" :disabled="loading"
@click="$emit('refresh')" @click="$emit('refresh')"
> >
@@ -71,7 +71,7 @@
</button> </button>
<button <button
type="button" type="button"
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:border-slate-700" 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('close')" @click="$emit('close')"
aria-label="关闭" aria-label="关闭"
> >
+3 -1
View File
@@ -47,7 +47,9 @@
v-if="store.loading.value && store.filteredItems.value.length === 0" v-if="store.loading.value && store.filteredItems.value.length === 0"
class="flex min-h-0 flex-1 items-center justify-center p-6" class="flex min-h-0 flex-1 items-center justify-center p-6"
> >
<div class="flex flex-col items-center gap-3 text-slate-500 dark:text-slate-400"> <div
class="flex flex-col items-center gap-3 text-slate-500 dark:text-slate-400"
>
<i class="fas fa-circle-notch fa-spin text-3xl"></i> <i class="fas fa-circle-notch fa-spin text-3xl"></i>
<p class="text-sm">正在检查更新</p> <p class="text-sm">正在检查更新</p>
</div> </div>
@@ -33,7 +33,7 @@
<button <button
type="button" type="button"
aria-label="关闭" 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: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')" @click="$emit('request-close')"
> >
<i class="fas fa-xmark"></i> <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 currentApp = ref<App | null>(null);
export const currentAppSparkInstalled = ref(false); export const currentAppSparkInstalled = ref(false);
export const currentAppApmInstalled = ref(false); export const currentAppApmInstalled = ref(false);
export const showApmInstallDialog = ref(false);
export const currentStoreMode = ref<StoreMode>("hybrid"); export const currentStoreMode = ref<StoreMode>("hybrid");
+5 -16
View File
@@ -5,6 +5,7 @@ import {
currentApp, currentApp,
currentAppSparkInstalled, currentAppSparkInstalled,
currentAppApmInstalled, currentAppApmInstalled,
showApmInstallDialog,
} from "../global/storeConfig"; } from "../global/storeConfig";
import { APM_STORE_BASE_URL } from "../global/storeConfig"; import { APM_STORE_BASE_URL } from "../global/storeConfig";
import { downloads, getNextDownloadId } from "../global/downloadStatus"; import { downloads, getNextDownloadId } from "../global/downloadStatus";
@@ -28,14 +29,8 @@ export const handleInstall = async (appObj?: App) => {
if (targetApp.origin === "apm") { if (targetApp.origin === "apm") {
const hasApm = await window.ipcRenderer.invoke("check-apm-available"); const hasApm = await window.ipcRenderer.invoke("check-apm-available");
if (!hasApm) { if (!hasApm) {
// 发送事件到主进程显示 APM 安装对话框 showApmInstallDialog.value = true;
const { success, cancelled } = await window.ipcRenderer.invoke( return;
"show-apm-install-dialog",
);
if (!success || cancelled) {
// 用户取消或未安装成功,不继续安装应用
return;
}
} }
} }
@@ -119,14 +114,8 @@ export const handleUpgrade = async (app: App) => {
if (app.origin === "apm") { if (app.origin === "apm") {
const hasApm = await window.ipcRenderer.invoke("check-apm-available"); const hasApm = await window.ipcRenderer.invoke("check-apm-available");
if (!hasApm) { if (!hasApm) {
// 发送事件到主进程显示 APM 安装对话框 showApmInstallDialog.value = true;
const { success, cancelled } = await window.ipcRenderer.invoke( return;
"show-apm-install-dialog",
);
if (!success || cancelled) {
// 用户取消或未安装成功,不继续更新应用
return;
}
} }
} }
+1 -1
View File
@@ -3,7 +3,7 @@ Dir::Cache::archives "/var/cache/apt/archives";
Dir::Cache "/var/lib/aptss/"; Dir::Cache "/var/lib/aptss/";
Dir::Etc::SourceParts "/opt/durapps/spark-store/bin/apt-fast-conf/sources.list.d/"; Dir::Etc::SourceParts "/opt/durapps/spark-store/bin/apt-fast-conf/sources.list.d/";
Dir::State::lists "/var/lib/aptss/lists/"; Dir::State::lists "/var/lib/aptss/lists/";
APT::Sandbox::User "root";
APT::Get::Fix-Broken true; APT::Get::Fix-Broken true;
APT::Get::List-Cleanup="0"; APT::Get::List-Cleanup="0";
+8 -22
View File
@@ -83,6 +83,9 @@ cleanup_aptfast()
if [ -n "$LISTTEMP" ] && [ -d "$LISTTEMP" ]; then if [ -n "$LISTTEMP" ] && [ -d "$LISTTEMP" ]; then
rm -rf "$LISTTEMP" rm -rf "$LISTTEMP"
fi fi
if [ -n "$tmpdir" ] && [ -d "$tmpdir" ]; then
rm -rf "$tmpdir"
fi
} }
exit_cleanup_state() exit_cleanup_state()
{ {
@@ -120,11 +123,8 @@ _create_lock()
# unlock and remove the lock file # unlock and remove the lock file
_remove_lock() _remove_lock()
{ {
# Only unlock if lock file exists (was created by _create_lock) flock -u "$LCK_FD" 2>/dev/null
if [ -f "$LCK_FILE.lock" ]; then rm -f "$LCK_FILE.lock"
flock -u "$LCK_FD" 2>/dev/null
rm -f "$LCK_FILE.lock"
fi
} }
# Search for known options and decide if root privileges are needed. # Search for known options and decide if root privileges are needed.
@@ -134,7 +134,6 @@ for argument in "$@"; do
case "$argument" in case "$argument" in
upgrade | full-upgrade | install | dist-upgrade | build-dep) upgrade | full-upgrade | install | dist-upgrade | build-dep)
option="install" option="install"
_create_lock
;; ;;
clean | autoclean) clean | autoclean)
option="clean" option="clean"
@@ -313,6 +312,9 @@ https_proxy=
[ "$TMP_http_proxy" = "$TMP_RANDOM" ] || http_proxy="$TMP_http_proxy" [ "$TMP_http_proxy" = "$TMP_RANDOM" ] || http_proxy="$TMP_http_proxy"
[ "$TMP_https_proxy" = "$TMP_RANDOM" ] || https_proxy="$TMP_https_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. # Disable colors if not executed in terminal.
if [ ! -t 1 ]; then if [ ! -t 1 ]; then
@@ -456,22 +458,12 @@ get_uris(){
exit "$CLEANUP_STATE" exit "$CLEANUP_STATE"
fi fi
prepare_auth prepare_auth
local tmpdir
tmpdir=$(mktemp -d) || { tmpdir=$(mktemp -d) || {
msg "Failed to create tmp dir" "warning" msg "Failed to create tmp dir" "warning"
msg "无法创建临时目录" "warning" msg "无法创建临时目录" "warning"
exit 1 exit 1
} }
cleanup_tmpdir() {
if [ -n "$tmpdir" ] && [ -d "$tmpdir" ]; then
rm -rf "$tmpdir"
fi
}
trap cleanup_tmpdir EXIT
## --print-uris format is: ## --print-uris format is:
# 'fileurl' filename filesize checksum_hint:filechecksum # 'fileurl' filename filesize checksum_hint:filechecksum
# 修改:process_package函数增加第二个参数表示当前线程的临时输出文件 # 修改:process_package函数增加第二个参数表示当前线程的临时输出文件
@@ -824,9 +816,6 @@ elif [ "$option" == "download" ]; then
"${_APTMGR}" "$@" "${_APTMGR}" "$@"
fi fi
# Clean up temporary directory for download command
cleanup_aptfast
elif [ "$option" == "source" ]; then elif [ "$option" == "source" ]; then
msg msg
msg "Working... this may take a while." "normal" msg "Working... this may take a while." "normal"
@@ -853,9 +842,6 @@ elif [ "$option" == "source" ]; then
# dpkg-source -x "$(basename "$srcfile")" # dpkg-source -x "$(basename "$srcfile")"
#done < "$DLLIST" #done < "$DLLIST"
# Clean up temporary directory for source command
cleanup_aptfast
# Execute package manager directly if unknown options are passed. # Execute package manager directly if unknown options are passed.
else else
"${_APTMGR}" "${APT_SCRIPT_WARNING[@]}" "$@" "${_APTMGR}" "${APT_SCRIPT_WARNING[@]}" "$@"