Skip to content

Commit 48b1a29

Browse files
committed
feat(下载): 添加快照版下载支持
- 在下载页面添加稳定版/快照版切换功能 - 实现从 GitHub Actions 获取最新快照版构建 - 添加多语言翻译支持 - 更新依赖版本
1 parent 567845f commit 48b1a29

6 files changed

Lines changed: 3306 additions & 18 deletions

File tree

.vitepress/data/lang/en.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ export const download = {
6161
sourceGithub: "GitHub",
6262
sourceMirror: "Mirror",
6363
mirrorTip: "Mirror recommended for users in China Mainland",
64+
// Version type
65+
stable: "Stable",
66+
nightly: "Nightly",
67+
nightlyTip: "Nightly includes latest features, may be unstable",
6468
};
6569

6670
// Changelog component translations

.vitepress/data/lang/zh.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ export const download = {
6161
sourceGithub: "GitHub",
6262
sourceMirror: "镜像",
6363
mirrorTip: "中国大陆用户推荐使用镜像下载",
64+
// 版本类型
65+
stable: "稳定版",
66+
nightly: "快照版",
67+
nightlyTip: "快照版包含最新功能,可能不稳定",
6468
};
6569

6670
// Changelog 组件翻译

.vitepress/data/lang/zh_tw.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ export const download = {
6161
sourceGithub: "GitHub",
6262
sourceMirror: "鏡像",
6363
mirrorTip: "中國大陸用戶推薦使用鏡像下載",
64+
// 版本類型
65+
stable: "穩定版",
66+
nightly: "快照版",
67+
nightlyTip: "快照版包含最新功能,可能不穩定",
6468
};
6569

6670
// Changelog 組件翻譯

.vitepress/theme/components/DownloadSection/DownloadSection.vue

Lines changed: 251 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<script setup lang="ts">
22
import { useData } from "vitepress";
3-
import { computed, ref } from "vue";
3+
import { computed, ref, watch, onMounted } from "vue";
44
import {
55
downloadTranslations,
66
type DownloadKey,
@@ -16,49 +16,170 @@ const t = computed(
1616
const version = ref(ghdata.version);
1717
const copied = ref<string | null>(null);
1818
const useMirror = ref(false);
19+
const useNightly = ref(false);
20+
21+
// Nightly 配置
22+
const NIGHTLY_OWNER = "LanRhyme";
23+
const NIGHTLY_REPO = "MicYou";
24+
const NIGHTLY_WORKFLOW = "development.yml";
25+
26+
// 存储 nightly run 和 artifacts 信息
27+
const nightlyRunId = ref<number | null>(null);
28+
const nightlyArtifacts = ref<Map<string, string>>(new Map());
29+
const nightlyLoading = ref(false);
30+
const nightlyError = ref<string | null>(null);
31+
32+
// GitHub API 获取最新成功 run
33+
async function fetchLatestNightlyRun() {
34+
if (!useNightly.value) return;
35+
nightlyLoading.value = true;
36+
nightlyError.value = null;
37+
38+
try {
39+
// 获取最新成功的 workflow run
40+
const runsUrl = `https://api.github.com/repos/${NIGHTLY_OWNER}/${NIGHTLY_REPO}/actions/workflows/${NIGHTLY_WORKFLOW}/runs?status=success&per_page=1`;
41+
const runsRes = await fetch(runsUrl);
42+
if (!runsRes.ok) throw new Error("Failed to fetch runs");
43+
const runsData = await runsRes.json();
44+
45+
if (!runsData.workflow_runs?.length) {
46+
throw new Error("No successful runs found");
47+
}
48+
49+
const runId = runsData.workflow_runs[0].id;
50+
nightlyRunId.value = runId;
51+
52+
// 获取该 run 的 artifacts
53+
const artifactsUrl = `https://api.github.com/repos/${NIGHTLY_OWNER}/${NIGHTLY_REPO}/actions/runs/${runId}/artifacts`;
54+
const artifactsRes = await fetch(artifactsUrl);
55+
if (!artifactsRes.ok) throw new Error("Failed to fetch artifacts");
56+
const artifactsData = await artifactsRes.json();
57+
58+
// 构建 artifact 名称映射 (pattern prefix -> actual name)
59+
const map = new Map<string, string>();
60+
for (const artifact of artifactsData.artifacts || []) {
61+
const name = artifact.name;
62+
map.set(name, name);
63+
}
64+
nightlyArtifacts.value = map;
65+
} catch (e) {
66+
nightlyError.value = e instanceof Error ? e.message : "Unknown error";
67+
console.error("Failed to fetch nightly info:", e);
68+
} finally {
69+
nightlyLoading.value = false;
70+
}
71+
}
72+
73+
// 监听 nightly 模式切换
74+
watch(useNightly, (newVal) => {
75+
if (newVal) fetchLatestNightlyRun();
76+
});
77+
78+
// 初始化时如果 nightly 已开启则加载
79+
onMounted(() => {
80+
if (useNightly.value) fetchLatestNightlyRun();
81+
});
82+
83+
// 正则匹配 artifact 名称
84+
function findArtifact(pattern: string): string | null {
85+
const regex = new RegExp(
86+
`^${pattern.replace("{version}", "\\d+\\.\\d+\\.\\d+")}$`,
87+
);
88+
for (const [name] of nightlyArtifacts.value) {
89+
if (regex.test(name)) return name;
90+
}
91+
return null;
92+
}
1993
2094
const platforms: {
2195
name: string;
2296
icon: string;
2397
desc: DownloadKey;
24-
files: { name: DownloadKey; pattern?: string; copy?: string }[];
98+
files: {
99+
name: DownloadKey;
100+
pattern?: string;
101+
copy?: string;
102+
nightlyPattern?: string;
103+
}[];
25104
}[] = [
26105
{
27106
name: "Windows",
28107
icon: "simple-icons:windows",
29108
desc: "windowsDesc",
30109
files: [
31-
{ name: "installer", pattern: "MicYou-Win-{version}-installer.exe" },
32-
{ name: "portableJRE", pattern: "MicYou-Win-{version}.zip" },
33-
{ name: "portableNoJRE", pattern: "MicYou-Win-NoJRE-{version}.zip" },
110+
{
111+
name: "installer",
112+
pattern: "MicYou-Win-{version}-installer.exe",
113+
nightlyPattern: "MicYou-Win-{version}",
114+
},
115+
{
116+
name: "portableJRE",
117+
pattern: "MicYou-Win-{version}.zip",
118+
nightlyPattern: "MicYou-Win-{version}",
119+
},
120+
{
121+
name: "portableNoJRE",
122+
pattern: "MicYou-Win-NoJRE-{version}.zip",
123+
nightlyPattern: "MicYou-Win-NoJRE-{version}",
124+
},
34125
],
35126
},
36127
{
37128
name: "macOS",
38129
icon: "simple-icons:macos",
39130
desc: "macOSDesc",
40131
files: [
41-
{ name: "dmgArm", pattern: "MicYou-macOS-{version}-arm64.dmg" },
42-
{ name: "dmgIntel", pattern: "MicYou-macOS-{version}-x64.dmg" },
43-
{ name: "portableNoJRE", pattern: "MicYou-macOS-NoJRE-{version}.tar.gz" },
132+
{
133+
name: "dmgArm",
134+
pattern: "MicYou-macOS-{version}-arm64.dmg",
135+
nightlyPattern: "MicYou-macOS-arm64-{version}",
136+
},
137+
{
138+
name: "dmgIntel",
139+
pattern: "MicYou-macOS-{version}-x64.dmg",
140+
nightlyPattern: "MicYou-macOS-x64-{version}",
141+
},
142+
{
143+
name: "portableNoJRE",
144+
pattern: "MicYou-macOS-NoJRE-{version}.tar.gz",
145+
nightlyPattern: "MicYou-macOS-NoJRE-arm64-{version}",
146+
},
44147
],
45148
},
46149
{
47150
name: "Linux",
48151
icon: "simple-icons:linux",
49152
desc: "linuxDesc",
50153
files: [
51-
{ name: "deb", pattern: "MicYou-Linux-{version}.deb" },
52-
{ name: "rpm", pattern: "MicYou-Linux-{version}.rpm" },
154+
{
155+
name: "deb",
156+
pattern: "MicYou-Linux-{version}.deb",
157+
nightlyPattern: "MicYou-Linux-{version}",
158+
},
159+
{
160+
name: "rpm",
161+
pattern: "MicYou-Linux-{version}.rpm",
162+
nightlyPattern: "MicYou-Linux-{version}",
163+
},
53164
{ name: "arch", copy: "paru -S micyou-bin" },
54-
{ name: "portableNoJRE", pattern: "MicYou-Linux-NoJRE-{version}.tar.gz" },
165+
{
166+
name: "portableNoJRE",
167+
pattern: "MicYou-Linux-NoJRE-{version}.tar.gz",
168+
nightlyPattern: "MicYou-Linux-NoJRE-{version}",
169+
},
55170
],
56171
},
57172
{
58173
name: "Android",
59174
icon: "simple-icons:android",
60175
desc: "androidDesc",
61-
files: [{ name: "apk", pattern: "MicYou-Android-{version}.apk" }],
176+
files: [
177+
{
178+
name: "apk",
179+
pattern: "MicYou-Android-{version}.apk",
180+
nightlyPattern: "MicYou-Android-{version}",
181+
},
182+
],
62183
},
63184
];
64185
@@ -68,8 +189,26 @@ const githubUrl = (pattern: string) =>
68189
const mirrorUrl = (pattern: string) =>
69190
`https://atomgit.com/gh_mirrors/mi/MicYou/releases/download/v${version.value}/${pattern.replace("{version}", version.value)}`;
70191
71-
const getUrl = (pattern: string) =>
72-
useMirror.value ? mirrorUrl(pattern) : githubUrl(pattern);
192+
// nightly.link URL - 需要动态获取 artifact 名称
193+
const getNightlyUrl = (pattern: string): string | null => {
194+
if (!nightlyRunId.value) return null;
195+
const artifactName = findArtifact(pattern);
196+
if (!artifactName) return null;
197+
return `https://nightly.link/${NIGHTLY_OWNER}/${NIGHTLY_REPO}/actions/runs/${nightlyRunId.value}/${artifactName}.zip`;
198+
};
199+
200+
const getUrl = (pattern: string, nightlyPattern?: string) => {
201+
if (useNightly.value && nightlyPattern && nightlyRunId.value) {
202+
const url = getNightlyUrl(nightlyPattern);
203+
return url || githubUrl(pattern);
204+
}
205+
return useMirror.value ? mirrorUrl(pattern) : githubUrl(pattern);
206+
};
207+
208+
const isNightlyAvailable = (pattern?: string): boolean => {
209+
if (!pattern || !nightlyRunId.value) return false;
210+
return findArtifact(pattern) !== null;
211+
};
73212
74213
const copyCmd = async (cmd: string) => {
75214
await navigator.clipboard.writeText(cmd);
@@ -98,7 +237,29 @@ const changelogLink = computed(() => {
98237
</header>
99238

100239
<div class="card">
101-
<div class="mirror-switch">
240+
<!-- 版本类型切换 -->
241+
<div class="version-switch">
242+
<span class="version-label" :class="{ active: !useNightly }">{{ t.stable }}</span>
243+
<label class="switch">
244+
<input type="checkbox" v-model="useNightly">
245+
<span class="slider"></span>
246+
</label>
247+
<span class="version-label" :class="{ active: useNightly }">{{ t.nightly }}</span>
248+
<span class="switch-tip" v-if="useNightly">{{ t.nightlyTip }}</span>
249+
<span class="loading-indicator" v-if="useNightly && nightlyLoading">
250+
<iconify-icon icon="mdi:loading" class="spin" />
251+
</span>
252+
</div>
253+
<!-- 错误提示 -->
254+
<div class="error-msg" v-if="useNightly && nightlyError">
255+
<iconify-icon icon="mdi:alert-circle" />
256+
{{ nightlyError }}
257+
<button class="retry-btn" @click="fetchLatestNightlyRun">
258+
<iconify-icon icon="mdi:refresh" />
259+
</button>
260+
</div>
261+
<!-- 下载源切换 (仅稳定版显示) -->
262+
<div class="mirror-switch" v-if="!useNightly">
102263
<span class="source-label" :class="{ active: !useMirror }">{{ t.sourceGithub }}</span>
103264
<label class="switch">
104265
<input type="checkbox" v-model="useMirror">
@@ -117,10 +278,16 @@ const changelogLink = computed(() => {
117278
</div>
118279
<div class="opts">
119280
<template v-for="f in p.files" :key="f.pattern || f.copy">
120-
<a v-if="f.pattern" :href="getUrl(f.pattern)" class="btn" target="_blank">
281+
<a
282+
v-if="f.pattern"
283+
:href="getUrl(f.pattern, f.nightlyPattern)"
284+
class="btn"
285+
:class="{ disabled: useNightly && !isNightlyAvailable(f.nightlyPattern) }"
286+
target="_blank"
287+
>
121288
<iconify-icon icon="mdi:download" />{{ t[f.name] }}
122289
</a>
123-
<button v-else class="btn" :class="{ done: copied === f.copy }" @click="copyCmd(f.copy!)">
290+
<button v-else class="btn" :class="{ done: copied === f.copy }" @click="copyCmd(f.copy!)" :disabled="useNightly">
124291
<iconify-icon :icon="copied === f.copy ? 'mdi:check' : 'mdi:content-copy'" />
125292
{{ copied === f.copy ? t.copied : t[f.name] }}
126293
</button>
@@ -177,6 +344,27 @@ const changelogLink = computed(() => {
177344
border-bottom: 1px solid var(--vp-c-divider);
178345
}
179346
347+
.version-switch {
348+
display: flex;
349+
align-items: center;
350+
justify-content: center;
351+
gap: 12px;
352+
padding: 12px 24px;
353+
background: var(--vp-c-bg);
354+
border-bottom: 1px solid var(--vp-c-divider);
355+
}
356+
357+
.version-label {
358+
font-size: 0.875rem;
359+
color: var(--vp-c-text-3);
360+
transition: color 0.2s;
361+
}
362+
363+
.version-label.active {
364+
color: var(--vp-c-brand-1);
365+
font-weight: 500;
366+
}
367+
180368
.source-label {
181369
font-size: 0.875rem;
182370
color: var(--vp-c-text-3);
@@ -316,6 +504,52 @@ input:checked + .slider:before {
316504
transform: translateY(-1px);
317505
}
318506
507+
.btn:disabled,
508+
.btn.disabled {
509+
opacity: 0.5;
510+
cursor: not-allowed;
511+
transform: none;
512+
pointer-events: none;
513+
}
514+
515+
.loading-indicator {
516+
margin-left: 8px;
517+
}
518+
519+
.spin {
520+
animation: spin 1s linear infinite;
521+
}
522+
523+
@keyframes spin {
524+
from { transform: rotate(0deg); }
525+
to { transform: rotate(360deg); }
526+
}
527+
528+
.error-msg {
529+
display: flex;
530+
align-items: center;
531+
justify-content: center;
532+
gap: 8px;
533+
padding: 12px 24px;
534+
background: var(--vp-c-danger-soft);
535+
color: var(--vp-c-danger-1);
536+
font-size: 0.875rem;
537+
}
538+
539+
.retry-btn {
540+
display: inline-flex;
541+
align-items: center;
542+
padding: 4px 8px;
543+
border-radius: 4px;
544+
background: var(--vp-c-bg);
545+
border: 1px solid var(--vp-c-divider);
546+
cursor: pointer;
547+
}
548+
549+
.retry-btn:hover {
550+
background: var(--vp-c-brand-soft);
551+
}
552+
319553
.notes {
320554
text-align: center;
321555
margin-top: 32px;

0 commit comments

Comments
 (0)