diff --git a/package.json b/package.json
index 9db5c3a..5a1ea6f 100644
--- a/package.json
+++ b/package.json
@@ -6,7 +6,7 @@
"homepage": "https://example.org",
"scripts": {
"dev": "vite",
- "build": "vite build",
+ "build": "node scripts/generate-about-content.mjs && vite build",
"tauri": "tauri",
"tauri:dev": "node scripts/tauri-dev.cjs",
"tauri:build": "tauri build",
diff --git a/scripts/ci/apply-version.mjs b/scripts/ci/apply-version.mjs
index 0154627..c2da0e3 100644
--- a/scripts/ci/apply-version.mjs
+++ b/scripts/ci/apply-version.mjs
@@ -70,3 +70,13 @@ const pkgPath = path.join(process.cwd(), "package.json")
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"))
pkg.version = version
fs.writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`, "utf-8")
+
+const tauriConfPath = path.join(process.cwd(), "src-tauri", "tauri.conf.json")
+const tauriConf = JSON.parse(fs.readFileSync(tauriConfPath, "utf-8"))
+tauriConf.version = version
+fs.writeFileSync(tauriConfPath, `${JSON.stringify(tauriConf, null, 2)}\n`, "utf-8")
+
+const cargoPath = path.join(process.cwd(), "src-tauri", "Cargo.toml")
+let cargoContent = fs.readFileSync(cargoPath, "utf-8")
+cargoContent = cargoContent.replace(/^version = "[^"]+"/m, `version = "${version}"`)
+fs.writeFileSync(cargoPath, cargoContent, "utf-8")
diff --git a/scripts/generate-about-content.mjs b/scripts/generate-about-content.mjs
new file mode 100644
index 0000000..8c4d01d
--- /dev/null
+++ b/scripts/generate-about-content.mjs
@@ -0,0 +1,69 @@
+import fs from "fs"
+import path from "path"
+
+const cwd = process.cwd()
+const readmePath = path.join(cwd, "README.md")
+const outputPath = path.join(cwd, "public", "about-content.json")
+const pkgPath = path.join(cwd, "package.json")
+
+if (!fs.existsSync(readmePath)) {
+ console.error("README.md not found")
+ process.exit(1)
+}
+
+const readmeContent = fs.readFileSync(readmePath, "utf-8")
+
+// 提取 README 的主要内容(跳过徽章部分)
+const lines = readmeContent.split("\n")
+let startIndex = 0
+
+// 找到第一个非空行且不是徽章的行
+for (let i = 0; i < lines.length; i++) {
+ const line = lines[i].trim()
+ // 跳过徽章行、空行、标题行
+ if (line && !line.startsWith("<") && !line.startsWith("[") && !line.startsWith("!")) {
+ startIndex = i
+ break
+ }
+}
+
+// 提取主要内容
+const mainContent = lines.slice(startIndex).join("\n")
+
+// 简单的 markdown 转 HTML(保留基本格式)
+function markdownToHtml(md) {
+ return md
+ .replace(/^### (.*$)/gim, "
$1
")
+ .replace(/^## (.*$)/gim, "$1
")
+ .replace(/^# (.*$)/gim, "$1
")
+ .replace(/\*\*(.*)\*\*/gim, "$1")
+ .replace(/\*(.*)\*/gim, "$1")
+ .replace(/`([^`]+)`/gim, "$1")
+ .replace(/^- (.*$)/gim, "$1")
+ .replace(/(.*<\/li>\n?)+/gim, "")
+ .replace(/\n/gim, "
")
+}
+
+// 读取 package.json 获取版本号
+let version = "1.0.0"
+if (fs.existsSync(pkgPath)) {
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"))
+ version = pkg.version || version
+}
+
+const aboutData = {
+ title: "SecScore",
+ description: "教育积分管理软件",
+ version: `v${version}`,
+ content: markdownToHtml(mainContent),
+ rawMarkdown: mainContent,
+}
+
+// 确保 public 目录存在
+const publicDir = path.join(cwd, "public")
+if (!fs.existsSync(publicDir)) {
+ fs.mkdirSync(publicDir, { recursive: true })
+}
+
+fs.writeFileSync(outputPath, JSON.stringify(aboutData, null, 2), "utf-8")
+console.log(`About content generated: ${outputPath}`)
diff --git a/src/components/Settings.tsx b/src/components/Settings.tsx
index 0d7c71d..5e9e1d8 100644
--- a/src/components/Settings.tsx
+++ b/src/components/Settings.tsx
@@ -77,6 +77,8 @@ export const Settings: React.FC<{
const [recoveryDialogString, setRecoveryDialogString] = useState("")
const [recoveryDialogFilename, setRecoveryDialogFilename] = useState("")
+ const [aboutContent, setAboutContent] = useState<{ title: string; description: string; content: string; rawMarkdown: string } | null>(null)
+
const [logsDialogVisible, setLogsDialogVisible] = useState(false)
const [logsText, setLogsText] = useState("")
const [logsLoading, setLogsLoading] = useState(false)
@@ -165,8 +167,21 @@ export const Settings: React.FC<{
await loadMcpStatus()
}
+ const loadAboutContent = async () => {
+ try {
+ const res = await fetch("/about-content.json")
+ if (res.ok) {
+ const data = await res.json()
+ setAboutContent(data)
+ }
+ } catch {
+ // ignore if about-content.json not found
+ }
+ }
+
useEffect(() => {
loadAll()
+ loadAboutContent()
const api = (window as any).api
if (!api) return
@@ -1122,17 +1137,19 @@ export const Settings: React.FC<{
label: t("settings.about.title"),
children: (
- SecScore
+
+ {aboutContent?.title || "SecScore"}
+
- {t("settings.about.appName")}
+ {aboutContent?.description || t("settings.about.appName")}
{t("settings.about.version")}
-
v1.0.0
+
{aboutContent?.version || "v1.0.0"}
{t("settings.about.copyright")}
{"CopyRight © 2025-" + currentYear + " SECTL"}
-
Electron
+
Tauri
{(window as any).electron?.process?.versions?.electron || "-"}
Chromium
{(window as any).electron?.process?.versions?.chrome || "-"}
@@ -1165,6 +1182,21 @@ export const Settings: React.FC<{
+ {aboutContent?.content && (
+ <>
+
+
+ >
+ )}
{t("settings.mcp.title")}