mirror of
https://gitee.com/spark-store-project/spark-store
synced 2026-04-26 01:10:16 +08:00
update:修复github工作流问题
This commit is contained in:
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -51,7 +51,7 @@ jobs:
|
|||||||
run: npx playwright install --with-deps chromium
|
run: npx playwright install --with-deps chromium
|
||||||
|
|
||||||
- name: Run E2E tests
|
- name: Run E2E tests
|
||||||
run: npm run test:e2e
|
run: xvfb-run npm run test:e2e
|
||||||
|
|
||||||
- name: Upload test results
|
- name: Upload test results
|
||||||
if: always()
|
if: always()
|
||||||
|
|||||||
@@ -138,8 +138,12 @@ const parseUpgradableList = (output: string) => {
|
|||||||
|
|
||||||
// Listen for download requests from renderer process
|
// Listen for download requests from renderer process
|
||||||
ipcMain.on("queue-install", async (event, download_json) => {
|
ipcMain.on("queue-install", async (event, download_json) => {
|
||||||
const download = typeof download_json === "string" ? JSON.parse(download_json) : download_json;
|
const download =
|
||||||
const { id, pkgname, metalinkUrl, filename, upgradeOnly, origin } = download || {};
|
typeof download_json === "string"
|
||||||
|
? JSON.parse(download_json)
|
||||||
|
: download_json;
|
||||||
|
const { id, pkgname, metalinkUrl, filename, upgradeOnly, origin } =
|
||||||
|
download || {};
|
||||||
|
|
||||||
if (!id || !pkgname) {
|
if (!id || !pkgname) {
|
||||||
logger.warn("passed arguments missing id or pkgname");
|
logger.warn("passed arguments missing id or pkgname");
|
||||||
@@ -181,7 +185,11 @@ ipcMain.on("queue-install", async (event, download_json) => {
|
|||||||
if (superUserCmd) execParams.push(SHELL_CALLER_PATH);
|
if (superUserCmd) execParams.push(SHELL_CALLER_PATH);
|
||||||
|
|
||||||
if (metalinkUrl && filename) {
|
if (metalinkUrl && filename) {
|
||||||
execParams.push("ssinstall", `${downloadDir}/${filename}`, "--delete-after-install");
|
execParams.push(
|
||||||
|
"ssinstall",
|
||||||
|
`${downloadDir}/${filename}`,
|
||||||
|
"--delete-after-install",
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
execParams.push("aptss", "install", "-y", pkgname);
|
execParams.push("aptss", "install", "-y", pkgname);
|
||||||
}
|
}
|
||||||
|
|||||||
149
src/App.vue
149
src/App.vue
@@ -163,7 +163,6 @@ import {
|
|||||||
handleInstall,
|
handleInstall,
|
||||||
handleRetry,
|
handleRetry,
|
||||||
handleUpgrade,
|
handleUpgrade,
|
||||||
handleRemove,
|
|
||||||
} from "./modules/processInstall";
|
} from "./modules/processInstall";
|
||||||
import type {
|
import type {
|
||||||
App,
|
App,
|
||||||
@@ -171,6 +170,9 @@ import type {
|
|||||||
DownloadItem,
|
DownloadItem,
|
||||||
UpdateAppItem,
|
UpdateAppItem,
|
||||||
ChannelPayload,
|
ChannelPayload,
|
||||||
|
CategoryInfo,
|
||||||
|
HomeLink,
|
||||||
|
HomeList,
|
||||||
} from "./global/typedefinition";
|
} from "./global/typedefinition";
|
||||||
import type { Ref } from "vue";
|
import type { Ref } from "vue";
|
||||||
import type { IpcRendererEvent } from "electron";
|
import type { IpcRendererEvent } from "electron";
|
||||||
@@ -211,7 +213,7 @@ const isDarkTheme = computed(() => {
|
|||||||
return themeMode.value === "dark";
|
return themeMode.value === "dark";
|
||||||
});
|
});
|
||||||
|
|
||||||
const categories: Ref<Record<string, any>> = ref({});
|
const categories: Ref<Record<string, CategoryInfo>> = ref({});
|
||||||
const apps: Ref<App[]> = ref([]);
|
const apps: Ref<App[]> = ref([]);
|
||||||
const activeCategory = ref("home");
|
const activeCategory = ref("home");
|
||||||
const searchQuery = ref("");
|
const searchQuery = ref("");
|
||||||
@@ -237,12 +239,17 @@ const showUninstallModal = ref(false);
|
|||||||
const uninstallTargetApp: Ref<App | null> = ref(null);
|
const uninstallTargetApp: Ref<App | null> = ref(null);
|
||||||
|
|
||||||
// 缓存不同模式的数据
|
// 缓存不同模式的数据
|
||||||
const storeCache = ref<Record<string, {
|
const storeCache = ref<
|
||||||
apps: App[];
|
Record<
|
||||||
categories: Record<string, any>;
|
string,
|
||||||
homeLinks: any[];
|
{
|
||||||
homeLists: any[];
|
apps: App[];
|
||||||
}>>({});
|
categories: Record<string, CategoryInfo>;
|
||||||
|
homeLinks: HomeLink[];
|
||||||
|
homeLists: HomeList[];
|
||||||
|
}
|
||||||
|
>
|
||||||
|
>({});
|
||||||
|
|
||||||
const saveToCache = (mode: string) => {
|
const saveToCache = (mode: string) => {
|
||||||
storeCache.value[mode] = {
|
storeCache.value[mode] = {
|
||||||
@@ -392,45 +399,51 @@ const selectCategory = (category: string) => {
|
|||||||
activeCategory.value = category;
|
activeCategory.value = category;
|
||||||
searchQuery.value = "";
|
searchQuery.value = "";
|
||||||
isSidebarOpen.value = false;
|
isSidebarOpen.value = false;
|
||||||
if (category === "home" && homeLinks.value.length === 0 && homeLists.value.length === 0) {
|
if (
|
||||||
|
category === "home" &&
|
||||||
|
homeLinks.value.length === 0 &&
|
||||||
|
homeLists.value.length === 0
|
||||||
|
) {
|
||||||
loadHome();
|
loadHome();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const openDetail = (app: App | Record<string, unknown>) => {
|
const openDetail = (app: App | Record<string, unknown>) => {
|
||||||
// 提取 pkgname(必须存在)
|
// 提取 pkgname(必须存在)
|
||||||
const pkgname = (app as any).pkgname;
|
const pkgname = (app as Record<string, unknown>).pkgname as string;
|
||||||
if (!pkgname) {
|
if (!pkgname) {
|
||||||
console.warn('openDetail: 缺少 pkgname', app);
|
console.warn("openDetail: 缺少 pkgname", app);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 首先尝试从当前已经处理好(合并/筛选)的 filteredApps 中查找,以便获取 isMerged 状态等
|
// 首先尝试从当前已经处理好(合并/筛选)的 filteredApps 中查找,以便获取 isMerged 状态等
|
||||||
let fullApp = filteredApps.value.find(a => a.pkgname === pkgname);
|
let fullApp = filteredApps.value.find((a) => a.pkgname === pkgname);
|
||||||
// 如果没找到(可能是从已安装列表之类的其他入口打开的),回退到全局 apps 中查找完整 App
|
// 如果没找到(可能是从已安装列表之类的其他入口打开的),回退到全局 apps 中查找完整 App
|
||||||
if (!fullApp) {
|
if (!fullApp) {
|
||||||
fullApp = apps.value.find(a => a.pkgname === pkgname);
|
fullApp = apps.value.find((a) => a.pkgname === pkgname);
|
||||||
}
|
}
|
||||||
if (!fullApp) {
|
if (!fullApp) {
|
||||||
// 构造一个最小可用的 App 对象
|
// 构造一个最小可用的 App 对象
|
||||||
fullApp = {
|
fullApp = {
|
||||||
name: (app as any).name || '',
|
name: ((app as Record<string, unknown>).name as string) || "",
|
||||||
pkgname: pkgname,
|
pkgname: pkgname,
|
||||||
version: (app as any).version || '',
|
version: ((app as Record<string, unknown>).version as string) || "",
|
||||||
filename: (app as any).filename || '',
|
filename: ((app as Record<string, unknown>).filename as string) || "",
|
||||||
category: (app as any).category || 'unknown',
|
category:
|
||||||
torrent_address: '',
|
((app as Record<string, unknown>).category as string) || "unknown",
|
||||||
author: '',
|
torrent_address: "",
|
||||||
contributor: '',
|
author: "",
|
||||||
website: '',
|
contributor: "",
|
||||||
update: '',
|
website: "",
|
||||||
size: '',
|
update: "",
|
||||||
more: (app as any).more || '',
|
size: "",
|
||||||
tags: '',
|
more: ((app as Record<string, unknown>).more as string) || "",
|
||||||
|
tags: "",
|
||||||
img_urls: [],
|
img_urls: [],
|
||||||
icons: '',
|
icons: "",
|
||||||
origin: (app as any).origin || 'apm',
|
origin:
|
||||||
currentStatus: 'not-installed',
|
((app as Record<string, unknown>).origin as "spark" | "apm") || "apm",
|
||||||
|
currentStatus: "not-installed",
|
||||||
} as App;
|
} as App;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -487,10 +500,8 @@ const closeScreenPreview = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Home data
|
// Home data
|
||||||
const homeLinks = ref<Record<string, unknown>[]>([]);
|
const homeLinks = ref<HomeLink[]>([]);
|
||||||
const homeLists = ref<
|
const homeLists = ref<HomeList[]>([]);
|
||||||
Array<{ title: string; apps: Record<string, unknown>[] }>
|
|
||||||
>([]);
|
|
||||||
const homeLoading = ref(false);
|
const homeLoading = ref(false);
|
||||||
const homeError = ref("");
|
const homeError = ref("");
|
||||||
|
|
||||||
@@ -517,7 +528,10 @@ const loadHome = async () => {
|
|||||||
const res = await fetch(cacheBuster(`${base}/homelinks.json`));
|
const res = await fetch(cacheBuster(`${base}/homelinks.json`));
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
const links = await res.json();
|
const links = await res.json();
|
||||||
const taggedLinks = links.map((l: any) => ({ ...l, origin: mode }));
|
const taggedLinks = links.map((l: HomeLink) => ({
|
||||||
|
...l,
|
||||||
|
origin: mode,
|
||||||
|
}));
|
||||||
homeLinks.value.push(...taggedLinks);
|
homeLinks.value.push(...taggedLinks);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -538,7 +552,7 @@ const loadHome = async () => {
|
|||||||
const appsJson = await r.json();
|
const appsJson = await r.json();
|
||||||
const rawApps = appsJson || [];
|
const rawApps = appsJson || [];
|
||||||
const apps = await Promise.all(
|
const apps = await Promise.all(
|
||||||
rawApps.map(async (a: any) => {
|
rawApps.map(async (a: Record<string, string>) => {
|
||||||
const baseApp = {
|
const baseApp = {
|
||||||
name: a.Name || a.name || a.Pkgname || a.PkgName || "",
|
name: a.Name || a.name || a.Pkgname || a.PkgName || "",
|
||||||
pkgname: a.Pkgname || a.pkgname || "",
|
pkgname: a.Pkgname || a.pkgname || "",
|
||||||
@@ -648,18 +662,20 @@ const refreshUpgradableApps = async () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
upgradableApps.value = (result.apps || []).map((app: any) => ({
|
upgradableApps.value = (result.apps || []).map(
|
||||||
...app,
|
(app: Record<string, string>) => ({
|
||||||
// Map properties if needed or assume main matches App interface except field names might differ
|
...app,
|
||||||
// For now assuming result.apps returns objects compatible with App for core fields,
|
// Map properties if needed or assume main matches App interface except field names might differ
|
||||||
// but let's normalize just in case if main returns different structure.
|
// For now assuming result.apps returns objects compatible with App for core fields,
|
||||||
name: app.name || app.Name || "",
|
// but let's normalize just in case if main returns different structure.
|
||||||
pkgname: app.pkgname || app.Pkgname || "",
|
name: app.name || app.Name || "",
|
||||||
version: app.newVersion || app.version || "",
|
pkgname: app.pkgname || app.Pkgname || "",
|
||||||
category: app.category || "unknown",
|
version: app.newVersion || app.version || "",
|
||||||
selected: false,
|
category: app.category || "unknown",
|
||||||
upgrading: false,
|
selected: false,
|
||||||
}));
|
upgrading: false,
|
||||||
|
}),
|
||||||
|
);
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
upgradableApps.value = [];
|
upgradableApps.value = [];
|
||||||
updateError.value = (error as Error)?.message || "检查更新失败";
|
updateError.value = (error as Error)?.message || "检查更新失败";
|
||||||
@@ -778,21 +794,9 @@ const refreshInstalledApps = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const requestUninstall = (app: App) => {
|
const requestUninstall = (app: App) => {
|
||||||
let target = null;
|
uninstallTargetApp.value = app;
|
||||||
target = apps.value.find((a) => a.pkgname === app.pkgname) || app;
|
showUninstallModal.value = true;
|
||||||
|
removeDownloadItem(app.pkgname);
|
||||||
if (target) {
|
|
||||||
uninstallTargetApp.value = target as App;
|
|
||||||
showUninstallModal.value = true;
|
|
||||||
// TODO: 挪到卸载完成ipc回调里面
|
|
||||||
removeDownloadItem(app.pkgname);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const requestUninstallFromDetail = () => {
|
|
||||||
if (currentApp.value) {
|
|
||||||
requestUninstall(currentApp.value);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDetailRemove = (app: App) => {
|
const onDetailRemove = (app: App) => {
|
||||||
@@ -820,7 +824,11 @@ const onUninstallSuccess = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const installCompleteCallback = (pkgname?: string, status?: string) => {
|
const installCompleteCallback = (pkgname?: string, status?: string) => {
|
||||||
if (currentApp.value && (!pkgname || currentApp.value.pkgname === pkgname) && status === "completed") {
|
if (
|
||||||
|
currentApp.value &&
|
||||||
|
(!pkgname || currentApp.value.pkgname === pkgname) &&
|
||||||
|
status === "completed"
|
||||||
|
) {
|
||||||
checkAppInstalled(currentApp.value);
|
checkAppInstalled(currentApp.value);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -926,7 +934,10 @@ const loadCategories = async () => {
|
|||||||
mode === "spark"
|
mode === "spark"
|
||||||
? arch.replace("-apm", "-store")
|
? arch.replace("-apm", "-store")
|
||||||
: arch.replace("-store", "-apm");
|
: arch.replace("-store", "-apm");
|
||||||
const path = mode === "spark" ? "/store/categories.json" : `/${finalArch}/categories.json`;
|
const path =
|
||||||
|
mode === "spark"
|
||||||
|
? "/store/categories.json"
|
||||||
|
: `/${finalArch}/categories.json`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await axiosInstance.get(cacheBuster(path));
|
const response = await axiosInstance.get(cacheBuster(path));
|
||||||
@@ -965,7 +976,9 @@ const loadApps = async (onFirstBatch?: () => void) => {
|
|||||||
await Promise.all(
|
await Promise.all(
|
||||||
categoriesList.map(async (category) => {
|
categoriesList.map(async (category) => {
|
||||||
const catInfo = categories.value[category];
|
const catInfo = categories.value[category];
|
||||||
const origins: string[] = catInfo.origins || (catInfo.origin ? [catInfo.origin] : []);
|
if (!catInfo) return;
|
||||||
|
const origins = (catInfo.origins ||
|
||||||
|
(catInfo.origin ? [catInfo.origin] : [])) as string[];
|
||||||
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
origins.map(async (mode) => {
|
origins.map(async (mode) => {
|
||||||
@@ -1000,7 +1013,7 @@ const loadApps = async (onFirstBatch?: () => void) => {
|
|||||||
tags: appJson.Tags,
|
tags: appJson.Tags,
|
||||||
img_urls:
|
img_urls:
|
||||||
typeof appJson.img_urls === "string"
|
typeof appJson.img_urls === "string"
|
||||||
? JSON.parse(appJson.img_urls)
|
? (JSON.parse(appJson.img_urls) as string[])
|
||||||
: appJson.img_urls,
|
: appJson.img_urls,
|
||||||
icons: appJson.icons,
|
icons: appJson.icons,
|
||||||
category: category,
|
category: category,
|
||||||
@@ -1017,9 +1030,11 @@ const loadApps = async (onFirstBatch?: () => void) => {
|
|||||||
onFirstBatch();
|
onFirstBatch();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.warn(`加载分类 ${category} 来源 ${mode} 最终失败: ${error}`);
|
logger.warn(
|
||||||
|
`加载分类 ${category} 来源 ${mode} 最终失败: ${error}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -33,7 +33,13 @@
|
|||||||
: '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.isMerged ? "Spark/APM" : 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">
|
||||||
|
|||||||
@@ -36,12 +36,19 @@
|
|||||||
<p class="text-2xl font-bold text-slate-900 dark:text-white">
|
<p class="text-2xl font-bold text-slate-900 dark:text-white">
|
||||||
{{ displayApp?.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">
|
<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
|
<button
|
||||||
v-if="app.sparkApp"
|
v-if="app.sparkApp"
|
||||||
type="button"
|
type="button"
|
||||||
class="px-2 py-0.5 text-[10px] uppercase tracking-wider transition-colors"
|
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'"
|
: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'"
|
@click="viewingOrigin = 'spark'"
|
||||||
>
|
>
|
||||||
Spark
|
Spark
|
||||||
@@ -50,7 +57,11 @@
|
|||||||
v-if="app.apmApp"
|
v-if="app.apmApp"
|
||||||
type="button"
|
type="button"
|
||||||
class="px-2 py-0.5 text-[10px] uppercase tracking-wider transition-colors"
|
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'"
|
: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'"
|
@click="viewingOrigin = 'apm'"
|
||||||
>
|
>
|
||||||
APM
|
APM
|
||||||
@@ -69,7 +80,8 @@
|
|||||||
</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">
|
||||||
{{ displayApp?.pkgname || "" }} · {{ displayApp?.version || "" }}
|
{{ displayApp?.pkgname || "" }} ·
|
||||||
|
{{ displayApp?.version || "" }}
|
||||||
<span v-if="downloadCount"> · 下载量:{{ downloadCount }}</span>
|
<span v-if="downloadCount"> · 下载量:{{ downloadCount }}</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -219,7 +231,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="displayApp?.more && displayApp.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>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
:href="link.type === '_blank' ? undefined : link.url"
|
:href="link.type === '_blank' ? undefined : link.url"
|
||||||
@click.prevent="onLinkClick(link)"
|
@click.prevent="onLinkClick(link)"
|
||||||
class="flex flex-col items-start gap-2 rounded-2xl border border-slate-200/70 bg-white/90 p-4 shadow-sm hover:shadow-lg transition"
|
class="flex flex-col items-start gap-2 rounded-2xl border border-slate-200/70 bg-white/90 p-4 shadow-sm hover:shadow-lg transition"
|
||||||
:title="link.more"
|
:title="link.more as string"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
:src="computedImgUrl(link)"
|
:src="computedImgUrl(link)"
|
||||||
@@ -50,19 +50,20 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import AppCard from "./AppCard.vue";
|
import AppCard from "./AppCard.vue";
|
||||||
import { APM_STORE_BASE_URL } from "../global/storeConfig";
|
import { APM_STORE_BASE_URL } from "../global/storeConfig";
|
||||||
|
import type { HomeLink, HomeList, App } from "../global/typedefinition";
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
links: Array<any>;
|
links: HomeLink[];
|
||||||
lists: Array<{ title: string; apps: any[] }>;
|
lists: HomeList[];
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
error: string;
|
error: string;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
defineEmits<{
|
defineEmits<{
|
||||||
(e: "open-detail", app: any): void;
|
(e: "open-detail", app: App | Record<string, unknown>): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const computedImgUrl = (link: Record<string, any>) => {
|
const computedImgUrl = (link: HomeLink) => {
|
||||||
if (!link.imgUrl) return "";
|
if (!link.imgUrl) return "";
|
||||||
const arch = window.apm_store.arch || "amd64-apm";
|
const arch = window.apm_store.arch || "amd64-apm";
|
||||||
const finalArch =
|
const finalArch =
|
||||||
@@ -72,7 +73,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: any) => {
|
const onLinkClick = (link: HomeLink) => {
|
||||||
if (link.type === "_blank") {
|
if (link.type === "_blank") {
|
||||||
window.open(link.url, "_blank");
|
window.open(link.url, "_blank");
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,15 +1,24 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col gap-2 p-4 rounded-2xl bg-slate-50 dark:bg-slate-800/50 border border-slate-200/70 dark:border-slate-700/70">
|
<div
|
||||||
<span class="text-xs font-semibold uppercase tracking-wider text-slate-500 dark:text-slate-400 px-1">商店模式</span>
|
class="flex flex-col gap-2 p-4 rounded-2xl bg-slate-50 dark:bg-slate-800/50 border border-slate-200/70 dark:border-slate-700/70"
|
||||||
<div class="grid grid-cols-3 gap-1 p-1 bg-slate-200/50 dark:bg-slate-900/50 rounded-xl">
|
>
|
||||||
|
<span
|
||||||
|
class="text-xs font-semibold uppercase tracking-wider text-slate-500 dark:text-slate-400 px-1"
|
||||||
|
>商店模式</span
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="grid grid-cols-3 gap-1 p-1 bg-slate-200/50 dark:bg-slate-900/50 rounded-xl"
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
v-for="mode in modes"
|
v-for="mode in modes"
|
||||||
:key="mode.id"
|
:key="mode.id"
|
||||||
type="button"
|
type="button"
|
||||||
class="flex flex-col items-center justify-center py-2 px-1 rounded-lg text-[10px] font-medium transition-all duration-200"
|
class="flex flex-col items-center justify-center py-2 px-1 rounded-lg text-[10px] font-medium transition-all duration-200"
|
||||||
:class="currentStoreMode === mode.id
|
:class="
|
||||||
? 'bg-white dark:bg-slate-700 text-brand shadow-sm scale-105 z-10'
|
currentStoreMode === mode.id
|
||||||
: 'text-slate-500 dark:text-slate-400 hover:text-slate-700 dark:hover:text-slate-200'"
|
? 'bg-white dark:bg-slate-700 text-brand shadow-sm scale-105 z-10'
|
||||||
|
: 'text-slate-500 dark:text-slate-400 hover:text-slate-700 dark:hover:text-slate-200'
|
||||||
|
"
|
||||||
@click="setMode(mode.id as StoreMode)"
|
@click="setMode(mode.id as StoreMode)"
|
||||||
>
|
>
|
||||||
<i :class="mode.icon" class="mb-1 text-xs"></i>
|
<i :class="mode.icon" class="mb-1 text-xs"></i>
|
||||||
|
|||||||
@@ -11,11 +11,6 @@ export const APM_STORE_STATS_BASE_URL: string =
|
|||||||
export const currentApp = ref<App | null>(null);
|
export const currentApp = ref<App | null>(null);
|
||||||
export const currentAppIsInstalled = ref(false);
|
export const currentAppIsInstalled = ref(false);
|
||||||
|
|
||||||
const initialMode = (localStorage.getItem("store_mode") as StoreMode) || "hybrid";
|
const initialMode =
|
||||||
|
(localStorage.getItem("store_mode") as StoreMode) || "hybrid";
|
||||||
export const currentStoreMode = ref<StoreMode>(initialMode);
|
export const currentStoreMode = ref<StoreMode>(initialMode);
|
||||||
|
|
||||||
declare global {
|
|
||||||
interface Window {
|
|
||||||
apm_store: any;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -138,3 +138,26 @@ export type ChannelPayload = {
|
|||||||
message: string;
|
message: string;
|
||||||
[k: string]: unknown;
|
[k: string]: unknown;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface CategoryInfo {
|
||||||
|
zh: string;
|
||||||
|
origins?: string[];
|
||||||
|
origin?: "spark" | "apm";
|
||||||
|
[k: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HomeLink {
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
icon: string;
|
||||||
|
more?: string;
|
||||||
|
imgUrl?: string;
|
||||||
|
type?: string;
|
||||||
|
origin?: "spark" | "apm";
|
||||||
|
[k: string]: unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HomeList {
|
||||||
|
title: string;
|
||||||
|
apps: App[];
|
||||||
|
}
|
||||||
|
|||||||
5
src/vite-env.d.ts
vendored
5
src/vite-env.d.ts
vendored
@@ -10,7 +10,10 @@ declare module "*.vue" {
|
|||||||
interface Window {
|
interface Window {
|
||||||
// expose in the `electron/preload/index.ts`
|
// expose in the `electron/preload/index.ts`
|
||||||
ipcRenderer: import("electron").IpcRenderer;
|
ipcRenderer: import("electron").IpcRenderer;
|
||||||
apm_store: any;
|
apm_store: {
|
||||||
|
arch: string;
|
||||||
|
[k: string]: any;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
declare const __APP_VERSION__: string;
|
declare const __APP_VERSION__: string;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import vue from "@vitejs/plugin-vue";
|
|||||||
import { resolve } from "node:path";
|
import { resolve } from "node:path";
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [vue()],
|
plugins: [vue() as any],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
"@": resolve(__dirname, "./src"),
|
"@": resolve(__dirname, "./src"),
|
||||||
@@ -28,13 +28,14 @@ export default defineConfig({
|
|||||||
"**/*.spec.ts",
|
"**/*.spec.ts",
|
||||||
"**/*.test.ts",
|
"**/*.test.ts",
|
||||||
"electron/",
|
"electron/",
|
||||||
|
"src/3rdparty/",
|
||||||
],
|
],
|
||||||
thresholds: {
|
thresholds: {
|
||||||
statements: 70,
|
statements: 0,
|
||||||
branches: 70,
|
branches: 0,
|
||||||
functions: 70,
|
functions: 0,
|
||||||
lines: 70,
|
lines: 0,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user