@@ -3764,20 +3764,19 @@ async function showAIReport() {
37643764
37653765 // ── 发起流式请求,用 makeStreamingRenderer 渲染 ─────────────
37663766 try {
3767- const headers = {
3768- 'Content-Type' : 'application/json' ,
3769- 'X-Session-Token' : window . SESSION_TOKEN || '' ,
3770- } ;
3771- const uid = typeof _getUIDCookie === 'function' ? _getUIDCookie ( ) : '' ;
3772- if ( uid ) headers [ 'X-User-ID' ] = uid ;
3773-
3774- const res = await fetch ( '/api/ai/report?' + bankQS ( ) , {
3775- method : 'POST' , headers, body : JSON . stringify ( payload ) ,
3767+ // 使用 apiFetch 确保携带正确的 session token 和 user-id
3768+ const res = await apiFetch ( '/api/ai/report?' + bankQS ( ) , {
3769+ method : 'POST' ,
3770+ headers : { 'Content-Type' : 'application/json' } ,
3771+ body : JSON . stringify ( payload ) ,
37763772 } ) ;
3777- if ( ! res . ok ) throw new Error ( 'HTTP ' + res . status ) ;
3773+ if ( ! res . ok ) {
3774+ const errText = await res . text ( ) . catch ( ( ) => '' ) ;
3775+ throw new Error ( 'HTTP ' + res . status + ( errText ? ': ' + errText . slice ( 0 , 100 ) : '' ) ) ;
3776+ }
37783777
37793778 // 清除 loading,创建内容容器
3780- loadingEl . remove ( ) ;
3779+ if ( loadingEl . parentNode ) loadingEl . remove ( ) ;
37813780 const contentEl = document . createElement ( 'div' ) ;
37823781 contentEl . className = 'ai-report-msg' ;
37833782 messagesEl . appendChild ( contentEl ) ;
@@ -3809,23 +3808,22 @@ async function showAIReport() {
38093808 if ( renderer ) {
38103809 renderer . push ( obj . content ) ;
38113810 } else {
3812- // fallback:纯文字追加
38133811 contentEl . textContent += obj . content ;
38143812 }
3815- // 自动滚动到底部
38163813 messagesEl . scrollTop = messagesEl . scrollHeight ;
38173814 }
3818- } catch ( e ) { /* ignore parse errors */ }
3815+ } catch ( parseErr ) { /* ignore parse errors */ }
38193816 }
38203817 }
38213818 if ( renderer ) renderer . end ( ) ;
38223819
38233820 } catch ( e ) {
3824- loadingEl . remove ( ) ;
3821+ if ( loadingEl . parentNode ) loadingEl . remove ( ) ;
38253822 const errEl = document . createElement ( 'p' ) ;
38263823 errEl . style . cssText = 'color:var(--danger);padding:16px;font-size:13px' ;
38273824 errEl . textContent = '⚠ 报告生成失败:' + ( e . message || '网络错误' ) ;
38283825 messagesEl . appendChild ( errEl ) ;
3826+ console . error ( '[AI Report]' , e ) ;
38293827 }
38303828}
38313829
@@ -4639,6 +4637,66 @@ function refreshFavBadge() {
46394637}
46404638
46414639/** 打开收藏页 */
4640+ /** 打开错题练习独立页面 */
4641+ async function openWrongBook ( ) {
4642+ showScreen ( 's-wrongbook' ) ;
4643+ await _renderWrongBookScreen ( ) ;
4644+ }
4645+
4646+ async function _renderWrongBookScreen ( ) {
4647+ const bankNameEl = document . getElementById ( 'wb-bank-name' ) ;
4648+ if ( bankNameEl ) bankNameEl . textContent = ( S . bankInfo && S . bankInfo . name ) || '' ;
4649+
4650+ const listEl = document . getElementById ( 'wb-unit-list' ) ;
4651+ const emptyEl = document . getElementById ( 'wb-empty' ) ;
4652+ const cntAll = document . getElementById ( 'wb-cnt-all' ) ;
4653+
4654+ if ( listEl ) listEl . innerHTML = '<div style="padding:16px;color:var(--muted);font-size:13px">加载中…</div>' ;
4655+ if ( emptyEl ) emptyEl . style . display = 'none' ;
4656+
4657+ try {
4658+ const res = await apiFetch ( '/api/wrongbook?' + bankQS ( ) ) . then ( r => r . json ( ) ) ;
4659+ const allItems = res . items || [ ] ;
4660+
4661+ if ( ! allItems . length ) {
4662+ if ( listEl ) listEl . innerHTML = '' ;
4663+ if ( emptyEl ) emptyEl . style . display = '' ;
4664+ if ( cntAll ) cntAll . textContent = '0' ;
4665+ return ;
4666+ }
4667+
4668+ if ( cntAll ) cntAll . textContent = allItems . length ;
4669+
4670+ // 按 unit 分组
4671+ const unitMap = { } ;
4672+ allItems . forEach ( it => {
4673+ const unit = it . unit || '未分类' ;
4674+ if ( ! unitMap [ unit ] ) unitMap [ unit ] = 0 ;
4675+ unitMap [ unit ] ++ ;
4676+ } ) ;
4677+
4678+ if ( listEl ) {
4679+ listEl . innerHTML = Object . entries ( unitMap ) . map ( ( [ unit , cnt ] ) =>
4680+ `<div class="fav-unit-row" data-unit="${ esc ( unit ) } ">
4681+ <div class="fav-unit-left">
4682+ <div class="fav-unit-name">${ esc ( unit ) } </div>
4683+ <div class="fav-unit-count">${ cnt } 题</div>
4684+ </div>
4685+ <span class="fav-unit-chevron">›</span>
4686+ </div>`
4687+ ) . join ( '' ) ;
4688+
4689+ listEl . onclick = function ( e ) {
4690+ const row = e . target . closest ( '.fav-unit-row' ) ;
4691+ if ( ! row || ! row . dataset . unit ) return ;
4692+ startWrongBookReview ( row . dataset . unit , allItems ) ;
4693+ } ;
4694+ }
4695+ } catch ( e ) {
4696+ if ( listEl ) listEl . innerHTML = '<div style="padding:16px;color:var(--danger);font-size:13px">⚠ 加载失败</div>' ;
4697+ }
4698+ }
4699+
46424700function openFavorites ( ) {
46434701 showScreen ( 's-favorites' ) ;
46444702 _renderFavoritesScreen ( ) ;
0 commit comments