860860 align-items : center;
861861 }
862862
863+ /* 存卦那行的可见提示。存不下时这里要说得出来,不能只往 live region 里写。 */
864+ .save-status {
865+ font-size : 13px ;
866+ color : var (--text-muted );
867+ }
868+
869+ .save-status .err {
870+ color : var (--seal );
871+ }
872+
863873 input [type = 'text' ],
864874 input [type = 'number' ],
865875 textarea {
@@ -2020,11 +2030,10 @@ <h2>卦历</h2>
20202030 </ main >
20212031
20222032 < footer class ="disclaimer ">
2023- < strong > 内容由 AI 生成,仅供娱乐,无实际预测功能。</ strong >
20242033 < p >
2025- 本应用实现的是中国传统占筮方法,依据梅花易数的体用生克、旺衰与取象规则推演,
2026- 所有文字由 AI 生成。卦象不构成任何建议、预测或决策依据,不应作为医疗、法律、财务
2027- 等重要决定的参考。 娱乐之外,请以自身判断与专业意见为准。
2034+ 本应用实现的是中国传统占筮方法,依据梅花易数的体用生克、旺衰与取象规则推演。
2035+ < strong > 内容由 AI 生成,仅供娱乐,无实际预测功能 。卦象不构成任何建议、预测或决策依据,不应作为医疗、法律、财务等重要决定的参考。 </ strong >
2036+ 娱乐之外,请以自身判断与专业意见为准。
20282037 </ p >
20292038 </ footer >
20302039 </ div >
@@ -2062,7 +2071,12 @@ <h2>卦历</h2>
20622071 } ) ;
20632072 if ( ! response . ok ) {
20642073 const detail = await response . json ( ) . catch ( ( ) => ( { } ) ) ;
2065- throw new Error ( detail . message || `请求失败(${ response . status } )` ) ;
2074+ const error = new Error ( detail . message || `请求失败(${ response . status } )` ) ;
2075+ // 带上状态码与错误码,调用方才分得清「这一卦存不了了」(终态)与
2076+ // 「这条已经存过了」(还能改批注重试)——两者对按钮的处理完全不同。
2077+ error . status = response . status ;
2078+ error . code = detail . error || '' ;
2079+ throw error ;
20662080 }
20672081 return response . json ( ) ;
20682082 }
@@ -3294,16 +3308,21 @@ <h2>卦历</h2>
32943308 const saveBtn = document . createElement ( 'button' ) ;
32953309 saveBtn . className = 'primary' ;
32963310 saveBtn . textContent = '存入卦历' ;
3297- saveBtn . addEventListener ( 'click' , ( ) => saveReading ( reading , saveBtn , noteInput ) ) ;
3298- save . append ( noteInput , saveBtn ) ;
3311+ // 可见的存卦状态。原先失败只往 live region 里写,屏幕上什么都不出现。
3312+ const saveStatus = document . createElement ( 'span' ) ;
3313+ saveStatus . className = 'save-status' ;
3314+ saveStatus . setAttribute ( 'aria-live' , 'polite' ) ;
3315+ saveBtn . addEventListener ( 'click' , ( ) => saveReading ( reading , saveBtn , noteInput , saveStatus ) ) ;
3316+ save . append ( noteInput , saveBtn , saveStatus ) ;
32993317 card . append ( save ) ;
33003318
33013319 slot . append ( card ) ;
33023320 mountClashRibbons ( card ) ;
33033321 }
33043322
3305- async function saveReading ( reading , button , noteInput ) {
3323+ async function saveReading ( reading , button , noteInput , status ) {
33063324 button . disabled = true ;
3325+ setSaveStatus ( status , '存入中…' ) ;
33073326 try {
33083327 // 只送 id 与批注,不再把整份 reading 回传给服务端——服务端认的是它自己
33093328 // 刚算出来过的卦。原先整份回传、原样落盘,那是整个包里唯一一处没设防的地方。
@@ -3312,14 +3331,32 @@ <h2>卦历</h2>
33123331 body : JSON . stringify ( { id : reading . id , note : noteInput . value } ) ,
33133332 } ) ;
33143333 button . textContent = '已存入卦历' ;
3334+ setSaveStatus ( status , '' ) ;
33153335 announce ( '已存入卦历' ) ;
33163336 } catch ( error ) {
3337+ // 失败要给看得见的反馈。原先只 announce(),那写进的是 .sr-only 的 live region,
3338+ // 屏幕上什么也没有,按钮又复原成可点——用户看到的就是「点了没反应」,
3339+ // 再点还是一样。服务重启后这一卦再也存不了,所以那一支连按钮都不还给用户。
3340+ if ( error . code === 'unknown_cast' ) {
3341+ button . textContent = '这一卦存不了了' ;
3342+ setSaveStatus ( status , error . message , true ) ;
3343+ announce ( error . message ) ;
3344+ return ;
3345+ }
33173346 button . disabled = false ;
33183347 button . textContent = '存入卦历' ;
3348+ setSaveStatus ( status , error . message , true ) ;
33193349 announce ( error . message ) ;
33203350 }
33213351 }
33223352
3353+ /** 存卦那行的可见提示。aria-live 让读屏软件也念得到。 */
3354+ function setSaveStatus ( status , text , isError = false ) {
3355+ status . textContent = text ;
3356+ status . classList . toggle ( 'err' , Boolean ( isError ) ) ;
3357+ status . setAttribute ( 'role' , isError ? 'alert' : 'status' ) ;
3358+ }
3359+
33233360 /* ---------- 卦库 ---------- */
33243361
33253362 async function loadLibrary ( ) {
0 commit comments