mirror of
https://gitee.com/spark-store-project/spark-store
synced 2026-04-26 09:20:18 +08:00
feat: 添加关于对话框并优化主题切换按钮样式
- 新增 AboutModal 组件显示应用版本和相关信息 - 重构 ThemeToggle 组件为更简洁的图标按钮 - 在侧边栏添加关于按钮并实现打开对话框功能 - 通过预加载脚本获取 package.json 版本号 - 支持命令行参数 --version/-v 显示版本号
This commit is contained in:
@@ -19,6 +19,28 @@ import { isLoaded } from "../global.js";
|
|||||||
import { tasks } from "./backend/install-manager.js";
|
import { tasks } from "./backend/install-manager.js";
|
||||||
import { sendTelemetryOnce } from "./backend/telemetry.js";
|
import { sendTelemetryOnce } from "./backend/telemetry.js";
|
||||||
|
|
||||||
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
process.env.APP_ROOT = path.join(__dirname, "../..");
|
||||||
|
|
||||||
|
/** 与项目 package.json 一致的版本号:打包用 app.getVersion(),未打包时读 package.json */
|
||||||
|
function getAppVersion(): string {
|
||||||
|
if (app.isPackaged) return app.getVersion();
|
||||||
|
const pkgPath = path.join(process.env.APP_ROOT ?? __dirname, "package.json");
|
||||||
|
try {
|
||||||
|
const raw = fs.readFileSync(pkgPath, "utf8");
|
||||||
|
const pkg = JSON.parse(raw) as { version?: string };
|
||||||
|
return typeof pkg.version === "string" ? pkg.version : "dev";
|
||||||
|
} catch {
|
||||||
|
return "dev";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理 --version 参数(在单实例检查之前)
|
||||||
|
if (process.argv.includes("--version") || process.argv.includes("-v")) {
|
||||||
|
console.log(getAppVersion());
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
// Assure single instance application
|
// Assure single instance application
|
||||||
if (!app.requestSingleInstanceLock()) {
|
if (!app.requestSingleInstanceLock()) {
|
||||||
app.exit(0);
|
app.exit(0);
|
||||||
@@ -28,7 +50,6 @@ import "./backend/install-manager.js";
|
|||||||
import "./handle-url-scheme.js";
|
import "./handle-url-scheme.js";
|
||||||
|
|
||||||
const logger = pino({ name: "index.ts" });
|
const logger = pino({ name: "index.ts" });
|
||||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
||||||
|
|
||||||
// The built directory structure
|
// The built directory structure
|
||||||
//
|
//
|
||||||
@@ -40,8 +61,6 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|||||||
// ├─┬ dist
|
// ├─┬ dist
|
||||||
// │ └── index.html > Electron-Renderer
|
// │ └── index.html > Electron-Renderer
|
||||||
//
|
//
|
||||||
process.env.APP_ROOT = path.join(__dirname, "../..");
|
|
||||||
|
|
||||||
export const MAIN_DIST = path.join(process.env.APP_ROOT, "dist-electron");
|
export const MAIN_DIST = path.join(process.env.APP_ROOT, "dist-electron");
|
||||||
export const RENDERER_DIST = path.join(process.env.APP_ROOT, "dist");
|
export const RENDERER_DIST = path.join(process.env.APP_ROOT, "dist");
|
||||||
export const VITE_DEV_SERVER_URL = process.env.VITE_DEV_SERVER_URL;
|
export const VITE_DEV_SERVER_URL = process.env.VITE_DEV_SERVER_URL;
|
||||||
@@ -64,18 +83,6 @@ if (!app.requestSingleInstanceLock()) {
|
|||||||
let win: BrowserWindow | null = null;
|
let win: BrowserWindow | null = null;
|
||||||
const preload = path.join(__dirname, "../preload/index.mjs");
|
const preload = path.join(__dirname, "../preload/index.mjs");
|
||||||
const indexHtml = path.join(RENDERER_DIST, "index.html");
|
const indexHtml = path.join(RENDERER_DIST, "index.html");
|
||||||
/** 与项目 package.json 一致的版本号:打包用 app.getVersion(),未打包时读 package.json */
|
|
||||||
function getAppVersion(): string {
|
|
||||||
if (app.isPackaged) return app.getVersion();
|
|
||||||
const pkgPath = path.join(process.env.APP_ROOT ?? __dirname, "package.json");
|
|
||||||
try {
|
|
||||||
const raw = fs.readFileSync(pkgPath, "utf8");
|
|
||||||
const pkg = JSON.parse(raw) as { version?: string };
|
|
||||||
return typeof pkg.version === "string" ? pkg.version : "dev";
|
|
||||||
} catch {
|
|
||||||
return "dev";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const getUserAgent = (): string => {
|
const getUserAgent = (): string => {
|
||||||
return `Spark-Store/${getAppVersion()}`;
|
return `Spark-Store/${getAppVersion()}`;
|
||||||
|
|||||||
@@ -36,6 +36,15 @@ contextBridge.exposeInMainWorld("apm_store", {
|
|||||||
return arch;
|
return arch;
|
||||||
}
|
}
|
||||||
})(),
|
})(),
|
||||||
|
version: (() => {
|
||||||
|
// 从 package.json 读取版本号
|
||||||
|
try {
|
||||||
|
const pkg = require("../../package.json");
|
||||||
|
return pkg.version || "unknown";
|
||||||
|
} catch {
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
})(),
|
||||||
});
|
});
|
||||||
|
|
||||||
// --------- Preload scripts loading ---------
|
// --------- Preload scripts loading ---------
|
||||||
|
|||||||
16
src/App.vue
16
src/App.vue
@@ -23,6 +23,7 @@
|
|||||||
@toggle-theme="toggleTheme"
|
@toggle-theme="toggleTheme"
|
||||||
@select-category="selectCategory"
|
@select-category="selectCategory"
|
||||||
@close="isSidebarOpen = false"
|
@close="isSidebarOpen = false"
|
||||||
|
@open-about="openAboutModal"
|
||||||
/>
|
/>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
@@ -132,6 +133,11 @@
|
|||||||
@close="closeUninstallModal"
|
@close="closeUninstallModal"
|
||||||
@success="onUninstallSuccess"
|
@success="onUninstallSuccess"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<AboutModal
|
||||||
|
:show="showAboutModal"
|
||||||
|
@close="closeAboutModal"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -150,6 +156,7 @@ import DownloadDetail from "./components/DownloadDetail.vue";
|
|||||||
import InstalledAppsModal from "./components/InstalledAppsModal.vue";
|
import InstalledAppsModal from "./components/InstalledAppsModal.vue";
|
||||||
import UpdateAppsModal from "./components/UpdateAppsModal.vue";
|
import UpdateAppsModal from "./components/UpdateAppsModal.vue";
|
||||||
import UninstallConfirmModal from "./components/UninstallConfirmModal.vue";
|
import UninstallConfirmModal from "./components/UninstallConfirmModal.vue";
|
||||||
|
import AboutModal from "./components/AboutModal.vue";
|
||||||
import {
|
import {
|
||||||
APM_STORE_BASE_URL,
|
APM_STORE_BASE_URL,
|
||||||
currentApp,
|
currentApp,
|
||||||
@@ -240,6 +247,7 @@ const updateLoading = ref(false);
|
|||||||
const updateError = ref("");
|
const updateError = ref("");
|
||||||
const showUninstallModal = ref(false);
|
const showUninstallModal = ref(false);
|
||||||
const uninstallTargetApp: Ref<App | null> = ref(null);
|
const uninstallTargetApp: Ref<App | null> = ref(null);
|
||||||
|
const showAboutModal = ref(false);
|
||||||
|
|
||||||
/** 启动参数 --no-apm => 仅 Spark;--no-spark => 仅 APM;由主进程 IPC 提供 */
|
/** 启动参数 --no-apm => 仅 Spark;--no-spark => 仅 APM;由主进程 IPC 提供 */
|
||||||
const storeFilter = ref<"spark" | "apm" | "both">("both");
|
const storeFilter = ref<"spark" | "apm" | "both">("both");
|
||||||
@@ -834,6 +842,14 @@ const uninstallInstalledApp = (app: App) => {
|
|||||||
requestUninstall(app);
|
requestUninstall(app);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const openAboutModal = () => {
|
||||||
|
showAboutModal.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeAboutModal = () => {
|
||||||
|
showAboutModal.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
// TODO: 目前 APM 商店不能暂停下载
|
// TODO: 目前 APM 商店不能暂停下载
|
||||||
const pauseDownload = (id: DownloadItem) => {
|
const pauseDownload = (id: DownloadItem) => {
|
||||||
const download = downloads.value.find((d) => d.id === id.id);
|
const download = downloads.value.find((d) => d.id === id.id);
|
||||||
|
|||||||
116
src/components/AboutModal.vue
Normal file
116
src/components/AboutModal.vue
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
<template>
|
||||||
|
<Teleport to="body">
|
||||||
|
<Transition
|
||||||
|
enter-active-class="transition duration-200 ease-out"
|
||||||
|
enter-from-class="opacity-0"
|
||||||
|
enter-to-class="opacity-100"
|
||||||
|
leave-active-class="transition duration-150 ease-in"
|
||||||
|
leave-from-class="opacity-100"
|
||||||
|
leave-to-class="opacity-0"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="show"
|
||||||
|
class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4 backdrop-blur-sm"
|
||||||
|
@click.self="close"
|
||||||
|
>
|
||||||
|
<Transition
|
||||||
|
enter-active-class="transition duration-200 ease-out"
|
||||||
|
enter-from-class="scale-95 opacity-0"
|
||||||
|
enter-to-class="scale-100 opacity-100"
|
||||||
|
leave-active-class="transition duration-150 ease-in"
|
||||||
|
leave-from-class="scale-100 opacity-100"
|
||||||
|
leave-to-class="scale-95 opacity-0"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="show"
|
||||||
|
class="w-full max-w-md overflow-hidden rounded-3xl border border-slate-200 bg-white shadow-2xl dark:border-slate-700 dark:bg-slate-900"
|
||||||
|
>
|
||||||
|
<div class="p-8 text-center">
|
||||||
|
<div class="mb-6 flex justify-center">
|
||||||
|
<img
|
||||||
|
:src="amberLogo"
|
||||||
|
alt="Spark Store"
|
||||||
|
class="h-24 w-24 rounded-3xl bg-white p-4 shadow-lg ring-1 ring-slate-900/5 dark:bg-slate-800"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<h2 class="mb-2 text-2xl font-bold text-slate-900 dark:text-white">
|
||||||
|
星火应用商店
|
||||||
|
</h2>
|
||||||
|
<p class="mb-4 text-sm text-slate-500 dark:text-slate-400">
|
||||||
|
Spark Store
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
class="mb-6 inline-flex items-center gap-2 rounded-full bg-slate-100 px-4 py-2 dark:bg-slate-800"
|
||||||
|
>
|
||||||
|
<span class="text-sm text-slate-600 dark:text-slate-300">版本号</span>
|
||||||
|
<span
|
||||||
|
class="font-mono text-sm font-semibold text-brand dark:text-brand"
|
||||||
|
>{{ version }}</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<p class="mb-6 text-sm leading-relaxed text-slate-600 dark:text-slate-400">
|
||||||
|
星火应用商店是专为 Linux 设计的应用商店,提供丰富的应用资源和便捷的安装体验。
|
||||||
|
</p>
|
||||||
|
<div class="flex justify-center gap-4 text-sm text-slate-500 dark:text-slate-400">
|
||||||
|
<a
|
||||||
|
href="https://gitee.com/spark-store-project/spark-store"
|
||||||
|
target="_blank"
|
||||||
|
class="flex items-center gap-1 transition hover:text-brand"
|
||||||
|
>
|
||||||
|
<i class="fab fa-git-alt"></i>
|
||||||
|
Gitee
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://github.com/spark-store-project/spark-store"
|
||||||
|
target="_blank"
|
||||||
|
class="flex items-center gap-1 transition hover:text-brand"
|
||||||
|
>
|
||||||
|
<i class="fab fa-github"></i>
|
||||||
|
GitHub
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="border-t border-slate-100 bg-slate-50 px-6 py-4 dark:border-slate-800 dark:bg-slate-800/50"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="w-full rounded-xl bg-brand px-4 py-2.5 text-sm font-medium text-white transition hover:bg-brand/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand/40"
|
||||||
|
@click="close"
|
||||||
|
>
|
||||||
|
确定
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
</Teleport>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from "vue";
|
||||||
|
import amberLogo from "../assets/imgs/spark-store.svg";
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
show: boolean;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: "close"): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const version = ref("unknown");
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// 从预加载脚本获取版本号
|
||||||
|
const apmStore = (window as any).apm_store;
|
||||||
|
if (apmStore?.version) {
|
||||||
|
version.value = apmStore.version;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const close = () => {
|
||||||
|
emit("close");
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -17,17 +17,19 @@
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<div class="flex items-center gap-1">
|
||||||
type="button"
|
<ThemeToggle :theme-mode="themeMode" @toggle="toggleTheme" />
|
||||||
class="inline-flex h-10 w-10 items-center justify-center rounded-2xl text-slate-400 hover:bg-slate-100 lg:hidden dark:hover:bg-slate-800"
|
<button
|
||||||
@click="$emit('close')"
|
type="button"
|
||||||
title="关闭侧边栏"
|
class="inline-flex h-10 w-10 items-center justify-center rounded-2xl text-slate-400 hover:bg-slate-100 lg:hidden dark:hover:bg-slate-800"
|
||||||
>
|
@click="$emit('close')"
|
||||||
<i class="fas fa-times"></i>
|
title="关闭侧边栏"
|
||||||
</button>
|
>
|
||||||
|
<i class="fas fa-times"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ThemeToggle :theme-mode="themeMode" @toggle="toggleTheme" />
|
|
||||||
<StoreModeSwitcher />
|
<StoreModeSwitcher />
|
||||||
|
|
||||||
<div class="flex-1 space-y-2 overflow-y-auto scrollbar-muted pr-2">
|
<div class="flex-1 space-y-2 overflow-y-auto scrollbar-muted pr-2">
|
||||||
@@ -84,6 +86,17 @@
|
|||||||
>
|
>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="border-t border-slate-200 pt-4 dark:border-slate-800">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="flex w-full items-center gap-3 rounded-2xl border border-transparent px-4 py-3 text-left text-sm font-medium text-slate-600 transition hover:border-brand/30 hover:bg-brand/5 hover:text-brand focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand/40 dark:text-slate-300 dark:hover:bg-slate-800"
|
||||||
|
@click="openAbout"
|
||||||
|
>
|
||||||
|
<i class="fas fa-info-circle"></i>
|
||||||
|
<span>关于</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -103,6 +116,7 @@ const emit = defineEmits<{
|
|||||||
(e: "toggle-theme"): void;
|
(e: "toggle-theme"): void;
|
||||||
(e: "select-category", category: string): void;
|
(e: "select-category", category: string): void;
|
||||||
(e: "close"): void;
|
(e: "close"): void;
|
||||||
|
(e: "open-about"): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const toggleTheme = () => {
|
const toggleTheme = () => {
|
||||||
@@ -112,4 +126,8 @@ const toggleTheme = () => {
|
|||||||
const selectCategory = (category: string) => {
|
const selectCategory = (category: string) => {
|
||||||
emit("select-category", category);
|
emit("select-category", category);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const openAbout = () => {
|
||||||
|
emit("open-about");
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,24 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="flex items-center justify-between rounded-2xl border border-slate-200/80 bg-white/70 px-4 py-3 text-sm font-medium text-slate-600 shadow-sm transition hover:border-brand/40 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand/40 dark:border-slate-800/70 dark:bg-slate-900/60 dark:text-slate-300"
|
class="inline-flex h-10 w-10 items-center justify-center rounded-2xl text-slate-500 transition hover:bg-slate-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand/40 dark:text-slate-400 dark:hover:bg-slate-800"
|
||||||
:aria-pressed="themeMode === 'dark'"
|
:title="title"
|
||||||
|
tabindex="-1"
|
||||||
@click="toggle"
|
@click="toggle"
|
||||||
>
|
>
|
||||||
<span class="flex items-center gap-2">
|
<i class="fas" :class="iconClass"></i>
|
||||||
<i class="fas" :class="iconClass"></i>
|
|
||||||
<span>{{ label }}</span>
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
class="relative inline-flex h-6 w-12 items-center rounded-full bg-slate-300/80 transition dark:bg-slate-700"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
:class="[
|
|
||||||
'inline-block h-4 w-4 rounded-full bg-white shadow transition',
|
|
||||||
togglePosition,
|
|
||||||
]"
|
|
||||||
></span>
|
|
||||||
</span>
|
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -37,21 +25,15 @@ const toggle = () => {
|
|||||||
emit("toggle");
|
emit("toggle");
|
||||||
};
|
};
|
||||||
|
|
||||||
const label = computed(() => {
|
const title = computed(() => {
|
||||||
if (props.themeMode === "auto") return "跟随系统";
|
if (props.themeMode === "auto") return "自动模式";
|
||||||
if (props.themeMode === "dark") return "深色主题";
|
if (props.themeMode === "dark") return "深色模式";
|
||||||
return "浅色主题";
|
return "浅色模式";
|
||||||
});
|
});
|
||||||
|
|
||||||
const iconClass = computed(() => {
|
const iconClass = computed(() => {
|
||||||
if (props.themeMode === "auto") return "fa-adjust text-slate-500";
|
if (props.themeMode === "auto") return "fa-adjust";
|
||||||
if (props.themeMode === "dark") return "fa-moon text-amber-200";
|
if (props.themeMode === "dark") return "fa-moon";
|
||||||
return "fa-sun text-amber-400";
|
return "fa-sun";
|
||||||
});
|
|
||||||
|
|
||||||
const togglePosition = computed(() => {
|
|
||||||
if (props.themeMode === "auto") return "translate-x-4";
|
|
||||||
if (props.themeMode === "dark") return "translate-x-7";
|
|
||||||
return "translate-x-1";
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user