mirror of
https://gitee.com/spark-store-project/spark-store
synced 2026-04-26 09:20:18 +08:00
update:在商店混合模式下,同包名的软件合并在同一个详情页面并加入切换开关
This commit is contained in:
46
src/App.vue
46
src/App.vue
@@ -65,8 +65,8 @@
|
|||||||
:screenshots="screenshots"
|
:screenshots="screenshots"
|
||||||
:isinstalled="currentAppIsInstalled"
|
:isinstalled="currentAppIsInstalled"
|
||||||
@close="closeDetail"
|
@close="closeDetail"
|
||||||
@install="handleInstall"
|
@install="onDetailInstall"
|
||||||
@remove="requestUninstallFromDetail"
|
@remove="onDetailRemove"
|
||||||
@open-preview="openScreenPreview"
|
@open-preview="openScreenPreview"
|
||||||
@open-app="openDownloadedApp"
|
@open-app="openDownloadedApp"
|
||||||
/>
|
/>
|
||||||
@@ -163,6 +163,7 @@ import {
|
|||||||
handleInstall,
|
handleInstall,
|
||||||
handleRetry,
|
handleRetry,
|
||||||
handleUpgrade,
|
handleUpgrade,
|
||||||
|
handleRemove,
|
||||||
} from "./modules/processInstall";
|
} from "./modules/processInstall";
|
||||||
import type {
|
import type {
|
||||||
App,
|
App,
|
||||||
@@ -295,6 +296,27 @@ watch(currentStoreMode, async (newMode, oldMode) => {
|
|||||||
const filteredApps = computed(() => {
|
const filteredApps = computed(() => {
|
||||||
let result = [...apps.value];
|
let result = [...apps.value];
|
||||||
|
|
||||||
|
// 合并相同包名的应用 (混合模式)
|
||||||
|
if (currentStoreMode.value === "hybrid") {
|
||||||
|
const mergedMap = new Map<string, App>();
|
||||||
|
for (const app of result) {
|
||||||
|
const existing = mergedMap.get(app.pkgname);
|
||||||
|
if (existing) {
|
||||||
|
if (!existing.isMerged) {
|
||||||
|
existing.isMerged = true;
|
||||||
|
// 根据当前的 origin 分配到对应的属性
|
||||||
|
if (existing.origin === "spark") existing.sparkApp = { ...existing };
|
||||||
|
else if (existing.origin === "apm") existing.apmApp = { ...existing };
|
||||||
|
}
|
||||||
|
if (app.origin === "spark") existing.sparkApp = app;
|
||||||
|
else if (app.origin === "apm") existing.apmApp = app;
|
||||||
|
} else {
|
||||||
|
mergedMap.set(app.pkgname, { ...app });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = Array.from(mergedMap.values());
|
||||||
|
}
|
||||||
|
|
||||||
// 按分类筛选
|
// 按分类筛选
|
||||||
if (activeCategory.value !== "all") {
|
if (activeCategory.value !== "all") {
|
||||||
result = result.filter((app) => app.category === activeCategory.value);
|
result = result.filter((app) => app.category === activeCategory.value);
|
||||||
@@ -383,8 +405,12 @@ const openDetail = (app: App | Record<string, unknown>) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 尝试从全局 apps 中查找完整 App
|
// 首先尝试从当前已经处理好(合并/筛选)的 filteredApps 中查找,以便获取 isMerged 状态等
|
||||||
let fullApp = apps.value.find(a => a.pkgname === pkgname);
|
let fullApp = filteredApps.value.find(a => a.pkgname === pkgname);
|
||||||
|
// 如果没找到(可能是从已安装列表之类的其他入口打开的),回退到全局 apps 中查找完整 App
|
||||||
|
if (!fullApp) {
|
||||||
|
fullApp = apps.value.find(a => a.pkgname === pkgname);
|
||||||
|
}
|
||||||
if (!fullApp) {
|
if (!fullApp) {
|
||||||
// 构造一个最小可用的 App 对象
|
// 构造一个最小可用的 App 对象
|
||||||
fullApp = {
|
fullApp = {
|
||||||
@@ -769,6 +795,14 @@ const requestUninstallFromDetail = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onDetailRemove = (app: App) => {
|
||||||
|
requestUninstall(app);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDetailInstall = (app: App) => {
|
||||||
|
handleInstall(app);
|
||||||
|
};
|
||||||
|
|
||||||
const closeUninstallModal = () => {
|
const closeUninstallModal = () => {
|
||||||
showUninstallModal.value = false;
|
showUninstallModal.value = false;
|
||||||
uninstallTargetApp.value = null;
|
uninstallTargetApp.value = null;
|
||||||
@@ -785,8 +819,8 @@ const onUninstallSuccess = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const installCompleteCallback = (pkgname?: string) => {
|
const installCompleteCallback = (pkgname?: string, status?: string) => {
|
||||||
if (currentApp.value && (!pkgname || currentApp.value.pkgname === pkgname)) {
|
if (currentApp.value && (!pkgname || currentApp.value.pkgname === pkgname) && status === "completed") {
|
||||||
checkAppInstalled(currentApp.value);
|
checkAppInstalled(currentApp.value);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ describe("downloadStatus", () => {
|
|||||||
startTime: Date.now(),
|
startTime: Date.now(),
|
||||||
logs: [],
|
logs: [],
|
||||||
source: "Test",
|
source: "Test",
|
||||||
|
origin: "apm",
|
||||||
retry: false,
|
retry: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -26,12 +26,14 @@
|
|||||||
<span
|
<span
|
||||||
:class="[
|
:class="[
|
||||||
'rounded-md px-1.5 py-0.5 text-[10px] font-bold uppercase tracking-wider shadow-sm',
|
'rounded-md px-1.5 py-0.5 text-[10px] font-bold uppercase tracking-wider shadow-sm',
|
||||||
app.origin === 'spark'
|
app.isMerged
|
||||||
? 'bg-orange-100 text-orange-600 dark:bg-orange-900/30 dark:text-orange-400'
|
? 'bg-purple-100 text-purple-600 dark:bg-purple-900/30 dark:text-purple-400'
|
||||||
: 'bg-blue-100 text-blue-600 dark:bg-blue-900/30 dark:text-blue-400',
|
: app.origin === 'spark'
|
||||||
|
? 'bg-orange-100 text-orange-600 dark:bg-orange-900/30 dark:text-orange-400'
|
||||||
|
: 'bg-blue-100 text-blue-600 dark:bg-blue-900/30 dark:text-blue-400',
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
{{ app.origin === "spark" ? "Spark" : "APM" }}
|
{{ app.isMerged ? "Spark/APM" : app.origin === "spark" ? "Spark" : "APM" }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-sm text-slate-500 dark:text-slate-400">
|
<div class="text-sm text-slate-500 dark:text-slate-400">
|
||||||
|
|||||||
@@ -34,22 +34,42 @@
|
|||||||
<div class="space-y-1">
|
<div class="space-y-1">
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<p class="text-2xl font-bold text-slate-900 dark:text-white">
|
<p class="text-2xl font-bold text-slate-900 dark:text-white">
|
||||||
{{ app?.name || "" }}
|
{{ displayApp?.name || "" }}
|
||||||
</p>
|
</p>
|
||||||
|
<div v-if="app?.isMerged" class="flex gap-1 overflow-hidden rounded-md shadow-sm border border-slate-200 dark:border-slate-700 font-medium ml-1">
|
||||||
|
<button
|
||||||
|
v-if="app.sparkApp"
|
||||||
|
type="button"
|
||||||
|
class="px-2 py-0.5 text-[10px] uppercase tracking-wider transition-colors"
|
||||||
|
:class="viewingOrigin === 'spark' ? 'bg-orange-500 text-white' : 'bg-slate-100/50 text-slate-500 dark:bg-slate-800 dark:text-slate-400 hover:bg-slate-200 dark:hover:bg-slate-700'"
|
||||||
|
@click="viewingOrigin = 'spark'"
|
||||||
|
>
|
||||||
|
Spark
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="app.apmApp"
|
||||||
|
type="button"
|
||||||
|
class="px-2 py-0.5 text-[10px] uppercase tracking-wider transition-colors"
|
||||||
|
:class="viewingOrigin === 'apm' ? 'bg-blue-500 text-white' : 'bg-slate-100/50 text-slate-500 dark:bg-slate-800 dark:text-slate-400 hover:bg-slate-200 dark:hover:bg-slate-700'"
|
||||||
|
@click="viewingOrigin = 'apm'"
|
||||||
|
>
|
||||||
|
APM
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<span
|
<span
|
||||||
v-if="app"
|
v-else-if="displayApp"
|
||||||
:class="[
|
:class="[
|
||||||
'rounded-md px-1.5 py-0.5 text-[10px] font-bold uppercase tracking-wider shadow-sm',
|
'rounded-md px-1.5 py-0.5 text-[10px] font-bold uppercase tracking-wider shadow-sm',
|
||||||
app.origin === 'spark'
|
displayApp.origin === 'spark'
|
||||||
? 'bg-orange-100 text-orange-600 dark:bg-orange-900/30 dark:text-orange-400'
|
? 'bg-orange-100 text-orange-600 dark:bg-orange-900/30 dark:text-orange-400'
|
||||||
: 'bg-blue-100 text-blue-600 dark:bg-blue-900/30 dark:text-blue-400',
|
: 'bg-blue-100 text-blue-600 dark:bg-blue-900/30 dark:text-blue-400',
|
||||||
]"
|
]"
|
||||||
>
|
>
|
||||||
{{ app.origin === "spark" ? "Spark" : "APM" }}
|
{{ displayApp.origin === "spark" ? "Spark" : "APM" }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-sm text-slate-500 dark:text-slate-400">
|
<p class="text-sm text-slate-500 dark:text-slate-400">
|
||||||
{{ app?.pkgname || "" }} · {{ app?.version || "" }}
|
{{ displayApp?.pkgname || "" }} · {{ displayApp?.version || "" }}
|
||||||
<span v-if="downloadCount"> · 下载量:{{ downloadCount }}</span>
|
<span v-if="downloadCount"> · 下载量:{{ downloadCount }}</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -77,7 +97,7 @@
|
|||||||
<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"
|
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"
|
||||||
@click="emit('open-app', app?.pkgname || '')"
|
@click="emit('open-app', displayApp?.pkgname || '')"
|
||||||
>
|
>
|
||||||
<i class="fas fa-external-link-alt"></i>
|
<i class="fas fa-external-link-alt"></i>
|
||||||
<span>打开</span>
|
<span>打开</span>
|
||||||
@@ -130,82 +150,82 @@
|
|||||||
|
|
||||||
<div class="mt-6 grid gap-4 sm:grid-cols-2">
|
<div class="mt-6 grid gap-4 sm:grid-cols-2">
|
||||||
<div
|
<div
|
||||||
v-if="app?.author"
|
v-if="displayApp?.author"
|
||||||
class="rounded-2xl border border-slate-200/60 p-4 dark:border-slate-800/60"
|
class="rounded-2xl border border-slate-200/60 p-4 dark:border-slate-800/60"
|
||||||
>
|
>
|
||||||
<p class="text-xs uppercase tracking-wide text-slate-400">作者</p>
|
<p class="text-xs uppercase tracking-wide text-slate-400">作者</p>
|
||||||
<p class="text-sm font-medium text-slate-800 dark:text-slate-200">
|
<p class="text-sm font-medium text-slate-800 dark:text-slate-200">
|
||||||
{{ app.author }}
|
{{ displayApp.author }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="app?.contributor"
|
v-if="displayApp?.contributor"
|
||||||
class="rounded-2xl border border-slate-200/60 p-4 dark:border-slate-800/60"
|
class="rounded-2xl border border-slate-200/60 p-4 dark:border-slate-800/60"
|
||||||
>
|
>
|
||||||
<p class="text-xs uppercase tracking-wide text-slate-400">贡献者</p>
|
<p class="text-xs uppercase tracking-wide text-slate-400">贡献者</p>
|
||||||
<p class="text-sm font-medium text-slate-800 dark:text-slate-200">
|
<p class="text-sm font-medium text-slate-800 dark:text-slate-200">
|
||||||
{{ app.contributor }}
|
{{ displayApp.contributor }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="app?.size"
|
v-if="displayApp?.size"
|
||||||
class="rounded-2xl border border-slate-200/60 p-4 dark:border-slate-800/60"
|
class="rounded-2xl border border-slate-200/60 p-4 dark:border-slate-800/60"
|
||||||
>
|
>
|
||||||
<p class="text-xs uppercase tracking-wide text-slate-400">大小</p>
|
<p class="text-xs uppercase tracking-wide text-slate-400">大小</p>
|
||||||
<p class="text-sm font-medium text-slate-800 dark:text-slate-200">
|
<p class="text-sm font-medium text-slate-800 dark:text-slate-200">
|
||||||
{{ app.size }}
|
{{ displayApp.size }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="app?.update"
|
v-if="displayApp?.update"
|
||||||
class="rounded-2xl border border-slate-200/60 p-4 dark:border-slate-800/60"
|
class="rounded-2xl border border-slate-200/60 p-4 dark:border-slate-800/60"
|
||||||
>
|
>
|
||||||
<p class="text-xs uppercase tracking-wide text-slate-400">
|
<p class="text-xs uppercase tracking-wide text-slate-400">
|
||||||
更新时间
|
更新时间
|
||||||
</p>
|
</p>
|
||||||
<p class="text-sm font-medium text-slate-800 dark:text-slate-200">
|
<p class="text-sm font-medium text-slate-800 dark:text-slate-200">
|
||||||
{{ app.update }}
|
{{ displayApp.update }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="app?.website"
|
v-if="displayApp?.website"
|
||||||
class="rounded-2xl border border-slate-200/60 p-4 dark:border-slate-800/60"
|
class="rounded-2xl border border-slate-200/60 p-4 dark:border-slate-800/60"
|
||||||
>
|
>
|
||||||
<p class="text-xs uppercase tracking-wide text-slate-400">网站</p>
|
<p class="text-xs uppercase tracking-wide text-slate-400">网站</p>
|
||||||
<a
|
<a
|
||||||
:href="app.website"
|
:href="displayApp.website"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
class="text-sm font-medium text-brand hover:underline"
|
class="text-sm font-medium text-brand hover:underline"
|
||||||
>{{ app.website }}</a
|
>{{ displayApp.website }}</a
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="app?.version"
|
v-if="displayApp?.version"
|
||||||
class="rounded-2xl border border-slate-200/60 p-4 dark:border-slate-800/60"
|
class="rounded-2xl border border-slate-200/60 p-4 dark:border-slate-800/60"
|
||||||
>
|
>
|
||||||
<p class="text-xs uppercase tracking-wide text-slate-400">版本</p>
|
<p class="text-xs uppercase tracking-wide text-slate-400">版本</p>
|
||||||
<p class="text-sm font-medium text-slate-800 dark:text-slate-200">
|
<p class="text-sm font-medium text-slate-800 dark:text-slate-200">
|
||||||
{{ app.version }}
|
{{ displayApp.version }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="app?.tags"
|
v-if="displayApp?.tags"
|
||||||
class="rounded-2xl border border-slate-200/60 p-4 dark:border-slate-800/60"
|
class="rounded-2xl border border-slate-200/60 p-4 dark:border-slate-800/60"
|
||||||
>
|
>
|
||||||
<p class="text-xs uppercase tracking-wide text-slate-400">标签</p>
|
<p class="text-xs uppercase tracking-wide text-slate-400">标签</p>
|
||||||
<p class="text-sm font-medium text-slate-800 dark:text-slate-200">
|
<p class="text-sm font-medium text-slate-800 dark:text-slate-200">
|
||||||
{{ app.tags }}
|
{{ displayApp.tags }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="app?.more && app.more.trim() !== ''" class="mt-6 space-y-3">
|
<div v-if="displayApp?.more && displayApp.more.trim() !== ''" class="mt-6 space-y-3">
|
||||||
<h3 class="text-lg font-semibold text-slate-900 dark:text-white">
|
<h3 class="text-lg font-semibold text-slate-900 dark:text-white">
|
||||||
应用详情
|
应用详情
|
||||||
</h3>
|
</h3>
|
||||||
<div
|
<div
|
||||||
class="max-h-60 space-y-2 overflow-y-auto rounded-2xl border border-slate-200/60 bg-slate-50/80 p-4 text-sm leading-relaxed text-slate-600 dark:border-slate-800/60 dark:bg-slate-900/60 dark:text-slate-300"
|
class="max-h-60 space-y-2 overflow-y-auto rounded-2xl border border-slate-200/60 bg-slate-50/80 p-4 text-sm leading-relaxed text-slate-600 dark:border-slate-800/60 dark:bg-slate-900/60 dark:text-slate-300"
|
||||||
v-html="app.more.replace(/\n/g, '<br>')"
|
v-html="displayApp.more.replace(/\n/g, '<br>')"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -235,8 +255,8 @@ const props = defineProps<{
|
|||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "close"): void;
|
(e: "close"): void;
|
||||||
(e: "install"): void;
|
(e: "install", app: App): void;
|
||||||
(e: "remove"): void;
|
(e: "remove", app: App): void;
|
||||||
(e: "open-preview", index: number): void;
|
(e: "open-preview", index: number): void;
|
||||||
(e: "open-app", pkgname: string): void;
|
(e: "open-app", pkgname: string): void;
|
||||||
}>();
|
}>();
|
||||||
@@ -245,15 +265,33 @@ const appPkgname = computed(() => props.app?.pkgname);
|
|||||||
|
|
||||||
const isIconLoaded = ref(false);
|
const isIconLoaded = ref(false);
|
||||||
|
|
||||||
|
const viewingOrigin = ref<"spark" | "apm">("spark");
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.app,
|
() => props.app,
|
||||||
() => {
|
(newApp) => {
|
||||||
isIconLoaded.value = false;
|
isIconLoaded.value = false;
|
||||||
|
if (newApp) {
|
||||||
|
if (newApp.isMerged) {
|
||||||
|
viewingOrigin.value = newApp.sparkApp ? "spark" : "apm";
|
||||||
|
} else {
|
||||||
|
viewingOrigin.value = newApp.origin;
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
{ immediate: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const displayApp = computed(() => {
|
||||||
|
if (!props.app) return null;
|
||||||
|
if (!props.app.isMerged) return props.app;
|
||||||
|
return viewingOrigin.value === "spark"
|
||||||
|
? props.app.sparkApp || props.app
|
||||||
|
: props.app.apmApp || props.app;
|
||||||
|
});
|
||||||
|
|
||||||
const activeDownload = computed(() => {
|
const activeDownload = computed(() => {
|
||||||
return downloads.value.find((d) => d.pkgname === props.app?.pkgname);
|
return downloads.value.find((d) => d.pkgname === displayApp.value?.pkgname);
|
||||||
});
|
});
|
||||||
|
|
||||||
const { installFeedback } = useInstallFeedback(appPkgname);
|
const { installFeedback } = useInstallFeedback(appPkgname);
|
||||||
@@ -278,20 +316,20 @@ const installBtnText = computed(() => {
|
|||||||
return "安装";
|
return "安装";
|
||||||
});
|
});
|
||||||
const iconPath = computed(() => {
|
const iconPath = computed(() => {
|
||||||
if (!props.app) return "";
|
if (!displayApp.value) return "";
|
||||||
const arch = window.apm_store.arch || "amd64-apm";
|
const arch = window.apm_store.arch || "amd64-apm";
|
||||||
const finalArch =
|
const finalArch =
|
||||||
props.app.origin === "spark"
|
displayApp.value.origin === "spark"
|
||||||
? arch.replace("-apm", "-store")
|
? arch.replace("-apm", "-store")
|
||||||
: arch.replace("-store", "-apm");
|
: arch.replace("-store", "-apm");
|
||||||
return `${APM_STORE_BASE_URL}/${finalArch}/${props.app.category}/${props.app.pkgname}/icon.png`;
|
return `${APM_STORE_BASE_URL}/${finalArch}/${displayApp.value.category}/${displayApp.value.pkgname}/icon.png`;
|
||||||
});
|
});
|
||||||
|
|
||||||
const downloadCount = ref<string>("");
|
const downloadCount = ref<string>("");
|
||||||
|
|
||||||
// 监听 app 变化,获取新app的下载量
|
// 监听 app 变化,获取新app的下载量
|
||||||
watch(
|
watch(
|
||||||
() => props.app,
|
() => displayApp.value,
|
||||||
async (newApp) => {
|
async (newApp) => {
|
||||||
if (newApp) {
|
if (newApp) {
|
||||||
downloadCount.value = "";
|
downloadCount.value = "";
|
||||||
@@ -322,11 +360,15 @@ const closeModal = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleInstall = () => {
|
const handleInstall = () => {
|
||||||
emit("install");
|
if (displayApp.value) {
|
||||||
|
emit("install", displayApp.value);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemove = () => {
|
const handleRemove = () => {
|
||||||
emit("remove");
|
if (displayApp.value) {
|
||||||
|
emit("remove", displayApp.value);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const openPreview = (index: number) => {
|
const openPreview = (index: number) => {
|
||||||
|
|||||||
@@ -52,14 +52,14 @@ import AppCard from "./AppCard.vue";
|
|||||||
import { APM_STORE_BASE_URL } from "../global/storeConfig";
|
import { APM_STORE_BASE_URL } from "../global/storeConfig";
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
links: Array<Record<string, unknown>>;
|
links: Array<any>;
|
||||||
lists: Array<{ title: string; apps: Record<string, unknown>[] }>;
|
lists: Array<{ title: string; apps: any[] }>;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
error: string;
|
error: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
defineEmits<{
|
defineEmits<{
|
||||||
(e: "open-detail", app: Record<string, unknown>): void;
|
(e: "open-detail", app: any): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const computedImgUrl = (link: Record<string, any>) => {
|
const computedImgUrl = (link: Record<string, any>) => {
|
||||||
@@ -72,7 +72,7 @@ const computedImgUrl = (link: Record<string, any>) => {
|
|||||||
return `${APM_STORE_BASE_URL}/${finalArch}${link.imgUrl}`;
|
return `${APM_STORE_BASE_URL}/${finalArch}${link.imgUrl}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const onLinkClick = (link: Record<string, unknown>) => {
|
const onLinkClick = (link: any) => {
|
||||||
if (link.type === "_blank") {
|
if (link.type === "_blank") {
|
||||||
window.open(link.url, "_blank");
|
window.open(link.url, "_blank");
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -16,8 +16,6 @@ export const currentStoreMode = ref<StoreMode>(initialMode);
|
|||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
apm_store: {
|
apm_store: any;
|
||||||
arch: string;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -107,6 +107,10 @@ export interface App {
|
|||||||
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"
|
||||||
currentStatus: "not-installed" | "installed"; // Current installation status
|
currentStatus: "not-installed" | "installed"; // Current installation status
|
||||||
|
isMerged?: boolean; // FLAG for overlapping apps
|
||||||
|
sparkApp?: App; // Optional reference to the spark version
|
||||||
|
apmApp?: App; // Optional reference to the apm version
|
||||||
|
viewingOrigin?: "spark" | "apm"; // Currently viewed origin inside the app modal
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateAppItem {
|
export interface UpdateAppItem {
|
||||||
|
|||||||
@@ -23,11 +23,12 @@ import axios from "axios";
|
|||||||
let downloadIdCounter = 0;
|
let downloadIdCounter = 0;
|
||||||
const logger = pino({ name: "processInstall.ts" });
|
const logger = pino({ name: "processInstall.ts" });
|
||||||
|
|
||||||
export const handleInstall = () => {
|
export const handleInstall = (appObj?: App) => {
|
||||||
if (!currentApp.value?.pkgname) return;
|
const targetApp = appObj || currentApp.value;
|
||||||
|
if (!targetApp?.pkgname) return;
|
||||||
|
|
||||||
if (downloads.value.find((d) => d.pkgname === currentApp.value?.pkgname)) {
|
if (downloads.value.find((d) => d.pkgname === targetApp.pkgname)) {
|
||||||
logger.info(`任务已存在,忽略重复添加: ${currentApp.value.pkgname}`);
|
logger.info(`任务已存在,忽略重复添加: ${targetApp.pkgname}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,17 +36,17 @@ export const handleInstall = () => {
|
|||||||
// 创建下载任务
|
// 创建下载任务
|
||||||
const arch = window.apm_store.arch || "amd64-apm";
|
const arch = window.apm_store.arch || "amd64-apm";
|
||||||
const finalArch =
|
const finalArch =
|
||||||
currentApp.value.origin === "spark"
|
targetApp.origin === "spark"
|
||||||
? arch.replace("-apm", "-store")
|
? arch.replace("-apm", "-store")
|
||||||
: arch.replace("-store", "-apm");
|
: arch.replace("-store", "-apm");
|
||||||
|
|
||||||
const download: DownloadItem = {
|
const download: DownloadItem = {
|
||||||
id: downloadIdCounter,
|
id: downloadIdCounter,
|
||||||
name: currentApp.value.name,
|
name: targetApp.name,
|
||||||
pkgname: currentApp.value.pkgname,
|
pkgname: targetApp.pkgname,
|
||||||
version: currentApp.value.version,
|
version: targetApp.version,
|
||||||
icon: `${APM_STORE_BASE_URL}/${finalArch}/${currentApp.value.category}/${currentApp.value.pkgname}/icon.png`,
|
icon: `${APM_STORE_BASE_URL}/${finalArch}/${targetApp.category}/${targetApp.pkgname}/icon.png`,
|
||||||
origin: currentApp.value.origin,
|
origin: targetApp.origin,
|
||||||
status: "queued",
|
status: "queued",
|
||||||
progress: 0,
|
progress: 0,
|
||||||
downloadedSize: 0,
|
downloadedSize: 0,
|
||||||
@@ -56,8 +57,8 @@ export const handleInstall = () => {
|
|||||||
logs: [{ time: Date.now(), message: "开始下载..." }],
|
logs: [{ time: Date.now(), message: "开始下载..." }],
|
||||||
source: "APM Store",
|
source: "APM Store",
|
||||||
retry: false,
|
retry: false,
|
||||||
filename: currentApp.value.filename,
|
filename: targetApp.filename,
|
||||||
metalinkUrl: `${window.apm_store.arch}/${currentApp.value.category}/${currentApp.value.pkgname}/${currentApp.value.filename}.metalink`,
|
metalinkUrl: `${window.apm_store.arch}/${targetApp.category}/${targetApp.pkgname}/${targetApp.filename}.metalink`,
|
||||||
};
|
};
|
||||||
|
|
||||||
downloads.value.push(download);
|
downloads.value.push(download);
|
||||||
@@ -75,7 +76,7 @@ export const handleInstall = () => {
|
|||||||
.post(
|
.post(
|
||||||
"/handle_post",
|
"/handle_post",
|
||||||
{
|
{
|
||||||
path: `${window.apm_store.arch}/${currentApp.value.category}/${currentApp.value.pkgname}`,
|
path: `${window.apm_store.arch}/${targetApp.category}/${targetApp.pkgname}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
@@ -134,11 +135,12 @@ export const handleUpgrade = (app: App) => {
|
|||||||
window.ipcRenderer.send("queue-install", JSON.stringify(download));
|
window.ipcRenderer.send("queue-install", JSON.stringify(download));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const handleRemove = () => {
|
export const handleRemove = (appObj?: App) => {
|
||||||
if (!currentApp.value?.pkgname) return;
|
const targetApp = appObj || currentApp.value;
|
||||||
|
if (!targetApp?.pkgname) return;
|
||||||
window.ipcRenderer.send("remove-installed", {
|
window.ipcRenderer.send("remove-installed", {
|
||||||
pkgname: currentApp.value.pkgname,
|
pkgname: targetApp.pkgname,
|
||||||
origin: currentApp.value.origin,
|
origin: targetApp.origin,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user