@@ -57,6 +57,7 @@ document.addEventListener("DOMContentLoaded", function () {
5757 let currentIndex = 0 ;
5858 let showingBeta = false ;
5959 let smartTeachAvailable = false ;
60+ let smartTeachDownloadAvailable = false ;
6061
6162 // --- API与镜像处理 ---
6263 function buildApiUrls ( endpoint ) {
@@ -74,11 +75,37 @@ document.addEventListener("DOMContentLoaded", function () {
7475
7576 // 提取版本号
7677 function extractVersionFromUrl ( url ) {
77- const regex = / I n k C a n v a s F o r C l a s s \. C E \. ( \d + \. \d + \. \d + \. \d + ) \. z i p / ;
78+ // 匹配 .zip 或 .exe 文件中的版本号
79+ const regex = / I n k C a n v a s F o r C l a s s \. C E \. ( \d + \. \d + \. \d + \. \d + ) \. ( z i p | e x e ) / ;
7880 const match = url . match ( regex ) ;
7981 return match ? match [ 1 ] : null ;
8082 }
8183
84+ // 判断是否为测试版(基于发布日期分界)
85+ function isBetaRelease ( release , isBetaRepo ) {
86+ // 2025/12/1 作为分界日期
87+ const boundaryDate = new Date ( '2025-12-01T00:00:00Z' ) ;
88+ const releaseDate = new Date ( release . published_at ) ;
89+
90+ if ( releaseDate < boundaryDate ) {
91+ // 2025/12/1 之前:根据仓库判断
92+ return isBetaRepo ;
93+ } else {
94+ // 2025/12/1 及之后:版本号 < 1.7.18.0 时根据仓库判断,>= 1.7.18.0 时全部为测试版
95+ const versionMatch = release . tag_name . match ( / ( \d + ) \. ( \d + ) \. ( \d + ) \. ( \d + ) / ) ;
96+ if ( versionMatch ) {
97+ const [ , major , minor , patch , build ] = versionMatch . map ( Number ) ;
98+ // 比较 1.7.18.0
99+ if ( major < 1 || ( major === 1 && minor < 7 ) || ( major === 1 && minor === 7 && patch < 18 ) ) {
100+ return isBetaRepo ;
101+ } else {
102+ return true ; // >= 1.7.18.0 全部为测试版
103+ }
104+ }
105+ return isBetaRepo ;
106+ }
107+ }
108+
82109 // 测试智教下载源的可用性
83110 async function testSmartTeachAvailability ( ) {
84111 try {
@@ -108,23 +135,53 @@ document.addEventListener("DOMContentLoaded", function () {
108135 }
109136
110137 function convertDownloadUrl ( url , isBeta = false ) {
111- // 如果是 .exe 文件,强制走镜像源
138+ // .exe 文件强制走最快 GitHub 镜像源
112139 if ( url . endsWith ( '.exe' ) ) {
113140 if ( fastestMirror && url . startsWith ( "https://github.com/" ) ) {
114141 return url . replace ( "https://github.com/" , `${ fastestMirror } /https://github.com/` ) ;
115142 }
116143 return url ;
117144 }
118- // 智教优先(非 .exe)
119- if ( smartTeachAvailable ) {
120- return buildSmartTeachUrl ( url , isBeta ) ;
121- }
122- if ( fastestMirror && url . startsWith ( "https://github.com/" ) ) {
123- return url . replace ( "https://github.com/" , `${ fastestMirror } /https://github.com/` ) ;
145+
146+ // .zip 文件优先尝试使用智教联盟,如果不可用则使用 GitHub 镜像
147+ if ( url . endsWith ( '.zip' ) ) {
148+ // 如果智教联盟可用,返回智教联盟URL供后续检查
149+ if ( smartTeachAvailable ) {
150+ return buildSmartTeachUrl ( url , isBeta ) ;
151+ }
152+ // 智教不可用,使用最快 GitHub 镜像
153+ if ( fastestMirror && url . startsWith ( "https://github.com/" ) ) {
154+ return url . replace ( "https://github.com/" , `${ fastestMirror } /https://github.com/` ) ;
155+ }
124156 }
157+
125158 return url ;
126159 }
127160
161+ // 检查智教联盟中文件是否存在
162+ async function checkSmartTeachFileExists ( url , isBeta = false ) {
163+ try {
164+ const fileName = url . split ( '/' ) . pop ( ) ;
165+ const basePath = isBeta ? COMMUNITY_BETA_PATH : COMMUNITY_PATH ;
166+ const testUrl = `${ SMART_TEACH_DOMAIN } ${ basePath } /${ fileName } ` ;
167+
168+ const controller = new AbortController ( ) ;
169+ const timeoutId = setTimeout ( ( ) => controller . abort ( ) , 3000 ) ;
170+
171+ const response = await fetch ( testUrl , {
172+ method : 'HEAD' ,
173+ signal : controller . signal ,
174+ cache : 'no-store'
175+ } ) ;
176+
177+ clearTimeout ( timeoutId ) ;
178+ // 302 Found 或 403 Forbidden 都认为文件存在
179+ return response . status === 302 || response . status === 403 || response . status === 200 ;
180+ } catch ( e ) {
181+ return false ;
182+ }
183+ }
184+
128185 async function fetchDataWithMirrors ( urls , errorMessage = "获取数据失败" ) {
129186 for ( const url of urls ) {
130187 try {
@@ -186,13 +243,14 @@ document.addEventListener("DOMContentLoaded", function () {
186243 const r = currentReleases [ idx ] ;
187244 const assetsHtml = r . assets
188245 . map ( a => {
189- // 提取原始下载URL的版本号
190- const version = extractVersionFromUrl ( a . browser_download_url ) ;
246+ // 提取原始下载URL的版本号,如果失败则使用 tag_name
247+ const version = extractVersionFromUrl ( a . browser_download_url ) || r . tag_name ;
191248 const downloadUrl = convertDownloadUrl ( a . browser_download_url , r . _isBeta ) ;
192249 return `
193250 <button data-download-url="${ downloadUrl } "
194251 data-original-url="${ a . browser_download_url } "
195252 data-version="${ version } "
253+ data-is-beta="${ r . _isBeta } "
196254 class="btn btn--tonal download-btn">
197255 <span class="material-symbols-outlined">download</span>
198256 <span>${ a . name } (${ ( a . size / 1024 / 1024 ) . toFixed ( 2 ) } MB)</span>
@@ -309,16 +367,31 @@ document.addEventListener("DOMContentLoaded", function () {
309367
310368 // 下载按钮事件监听
311369 function handleDownloadBtnClick ( ) {
312- elements . releaseList . addEventListener ( 'click' , function ( e ) {
370+ elements . releaseList . addEventListener ( 'click' , async function ( e ) {
313371 const downloadBtn = e . target . closest ( '.download-btn' ) ;
314372 if ( downloadBtn ) {
315373 e . preventDefault ( ) ;
316374 const downloadUrl = downloadBtn . getAttribute ( 'data-download-url' ) ;
317375 const version = downloadBtn . getAttribute ( 'data-version' ) || '最新版' ;
318376 const originalUrl = downloadBtn . getAttribute ( 'data-original-url' ) ;
377+ const isBeta = downloadBtn . getAttribute ( 'data-is-beta' ) === 'true' ;
378+
379+ let effectiveUrl = originalUrl ;
380+
381+ // 对于 .zip 文件,如果智教可用,先检查文件是否存在
382+ if ( originalUrl . endsWith ( '.zip' ) && smartTeachAvailable ) {
383+ const exists = await checkSmartTeachFileExists ( originalUrl , isBeta ) ;
384+ if ( exists ) {
385+ effectiveUrl = downloadUrl ;
386+ } else if ( fastestMirror ) {
387+ // 文件不存在,使用最快镜像
388+ effectiveUrl = originalUrl . replace ( "https://github.com/" , `${ fastestMirror } /https://github.com/` ) ;
389+ }
390+ } else if ( originalUrl . endsWith ( '.exe' ) && fastestMirror ) {
391+ // .exe 文件使用最快镜像
392+ effectiveUrl = originalUrl . replace ( "https://github.com/" , `${ fastestMirror } /https://github.com/` ) ;
393+ }
319394
320- // 如果智教不可达,使用原URL
321- const effectiveUrl = smartTeachAvailable ? downloadUrl : originalUrl ;
322395 showDownloadModal ( effectiveUrl , version ) ;
323396 }
324397 } ) ;
@@ -383,10 +456,20 @@ document.addEventListener("DOMContentLoaded", function () {
383456 releasesBeta = await fetchDataWithMirrors ( betaUrls , "Beta 版本获取失败" ) || [ ] ;
384457 }
385458
386- const allReleases = [ ...releasesOfficial . map ( r => ( { ...r , _isBeta : false } ) ) , ...releasesBeta . map ( r => ( { ...r , _isBeta : true } ) ) ] ;
459+ // 为发行版设置 _isBeta 标志(基于发布日期分界)
460+ const allReleases = [
461+ ...releasesOfficial . map ( r => ( {
462+ ...r ,
463+ _isBeta : isBetaRelease ( r , false )
464+ } ) ) ,
465+ ...releasesBeta . map ( r => ( {
466+ ...r ,
467+ _isBeta : isBetaRelease ( r , true )
468+ } ) )
469+ ] ;
387470 const sortedReleases = allReleases . sort ( ( a , b ) => new Date ( b . published_at ) - new Date ( a . published_at ) ) ;
388471
389- currentReleases = showingBeta ? sortedReleases : releasesOfficial ;
472+ currentReleases = showingBeta ? sortedReleases : allReleases . filter ( r => ! r . _isBeta ) ;
390473
391474 currentIndex = 0 ;
392475 elements . releaseLoading . style . display = 'none' ;
@@ -429,7 +512,13 @@ document.addEventListener("DOMContentLoaded", function () {
429512 const releaseUrls = buildApiUrls ( `${ GITHUB_REPO_COMMUNITY } /releases` ) ;
430513 releasesOfficial = await fetchDataWithMirrors ( releaseUrls , "正式版本获取失败" ) || [ ] ;
431514
432- currentReleases = releasesOfficial ;
515+ // 为发行版设置 _isBeta 标志(基于发布日期分界)
516+ const processedReleases = releasesOfficial . map ( r => ( {
517+ ...r ,
518+ _isBeta : isBetaRelease ( r , false )
519+ } ) ) ;
520+
521+ currentReleases = processedReleases ;
433522 currentIndex = 0 ;
434523
435524 elements . releaseLoading . style . display = 'none' ;
0 commit comments