const $ = selector => document.querySelector(selector); const esc = value => String(value).replace(/[&<>"']/g, char => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[char])); const findBook = id => books.find(book => book.id === id); const uniqueIds = ids => [...new Set(ids)].filter(id => findBook(id)); const today = () => new Date().toLocaleDateString('sv-SE'); const storageKey = 'weread-demo-goals-v2'; let page = 'overview', shelfTab = 'reading', discoverTab = 'books', searchQuery = '', period = 'week'; let noteBook = 'all', noteType = 'all', goalFilter = 'all', dataState = 'ready'; let detailBook = null, bookTab = 'info', reviewFilter = '全部', selectedChapter = 'all'; let editingGoalId = null, draftIds = new Set(), draftSources = [], goals = initialGoals.map(goal => ({...goal, ids:[...goal.ids], sources:[...goal.sources]})); let toastTimer, storageWarning = ''; // 本地数据损坏时保留原存储并恢复示例;保存前仍要求用户通过有效表单提交。 try { const saved = localStorage.getItem(storageKey); if (saved) { const parsed = JSON.parse(saved); if (!Array.isArray(parsed) || !parsed.every(goal => typeof goal.id === 'string' && typeof goal.name === 'string' && /^\d{4}-\d{2}-\d{2}$/.test(goal.start) && /^\d{4}-\d{2}-\d{2}$/.test(goal.end) && goal.start <= goal.end && Array.isArray(goal.ids) && Array.isArray(goal.sources))) throw new Error('invalid'); goals = parsed.map(goal => ({...goal,ids:uniqueIds(goal.ids)})); } } catch { storageWarning = '本地目标暂时无法读取,已显示示例目标;原存储未修改。'; } function toast(message) { clearTimeout(toastTimer); $('#toast').textContent = message; $('#toast').classList.remove('hidden'); toastTimer = setTimeout(() => $('#toast').classList.add('hidden'), 3500); } function persistGoals(next) { try { localStorage.setItem(storageKey, JSON.stringify(next)); } catch { $('#goal-error').textContent = '浏览器无法保存数据,请允许本地存储后重试。'; return false; } goals = next; return true; } function setTheme(theme) { document.documentElement.dataset.theme = theme; $('#theme').innerHTML = ``; $('#theme').setAttribute('aria-label', `切换至${theme === 'dark' ? '亮' : '暗'}色主题`); } try { setTheme(localStorage.getItem('weread-demo-theme') === 'dark' ? 'dark' : 'light'); } catch { setTheme('light'); } $('#theme').onclick = () => { const theme = document.documentElement.dataset.theme === 'dark' ? 'light' : 'dark'; setTheme(theme); try { localStorage.setItem('weread-demo-theme', theme); } catch { toast('主题已切换,本次浏览有效。'); } }; function cover(book, mini = false) { if (mini) return ``; return `
${book.en}${book.short.replaceAll('\n','
')}
${book.author}READING SPACE · 示意封面
`; } function bookCard(book) { return ``; } function bookRow(book, source = '') { return `
${cover(book,true)}
${book.title}

${book.author}${source ? ' · '+esc(source) : ''}

${book.status === 'done' ? '已读完' : `${book.progress}%`}
`; } function welcome(title, subtitle, action = '') { return `
A LITTLE READING, EVERY DAY

${title}

${subtitle}

${action}
`; } const pageNames = {overview:'阅读概览',shelf:'我的书架',goals:'阅读目标',notes:'笔记与划线',discover:'发现好书'}; function navigate(next) { page = next; $('#crumb').textContent = pageNames[page]; document.querySelectorAll('[data-page]').forEach(button => {button.classList.toggle('active',button.dataset.page === page);button.setAttribute('aria-current',button.dataset.page === page ? 'page' : 'false');}); renderPage(); } function renderPage() { const views = {overview:renderOverview,shelf:renderShelf,goals:renderGoals,notes:renderNotes,discover:renderDiscover}; $('#page-content').innerHTML = views[page](); } function tabs(items, selected, action) { return `
${items.map(([id,label]) => ``).join('')}
`; } // 已读状态与目标日期无关;先按书籍 ID 去重,再计算完成率。 function goalMetrics(goal, date = today()) { const ids = uniqueIds(goal.ids), done = ids.filter(id => findBook(id).status === 'done').length; const status = ids.length && done === ids.length ? '已完成' : date < goal.start ? '未开始' : date > goal.end ? '已到期' : '进行中'; return {total:ids.length,done,percent:ids.length ? Math.round(done/ids.length*100):0,status}; } function goalCard(goal) { const result = goalMetrics(goal); return `
${result.status}

${esc(goal.name)}

${goal.start} — ${goal.end}
${goal.ids.slice(0,4).map(id=>cover(findBook(id),true)).join('')}${result.total>4?`+ ${result.total-4} 本`:''}
${result.done} / ${result.total} 本已读完${result.percent}%
`; } function renderGoals() { const list = goals.filter(goal => goalFilter === 'all' || (goalFilter === 'complete' ? goalMetrics(goal).status === '已完成' : goalMetrics(goal).status !== '已完成')); return welcome('把想读,变成读过','几本书、一份书单,或一段想留给阅读的时间。', '')+ `
YOUR NEXT CHAPTER

目标不必宏大,开始就很好。

每份清单都有自己的节奏。已经读完的书,也会成为你完成目标的一部分。

${goals.length}个目标${goals.filter(g=>goalMetrics(g).status==='已完成').length}个已完成${uniqueIds(goals.flatMap(g=>g.ids)).length}本不同的书
❧
`+ tabs([['all','全部目标'],['active','未完成'],['complete','已完成']],goalFilter,'goal-filter')+`
${list.map(goalCard).join('') || '
这里还没有目标,创建一份属于你的阅读清单。
'}
`; } function summaryGoals() { return `

我的阅读目标

${goals.slice(0,3).map(goal=>{const m=goalMetrics(goal);return ``;}).join('')}
`; } function renderOverview() { const values = {week:[42,65,33,57,86,71,48],month:[48,54,65,38,70,52,64,75,68,42,46],year:[8,11,14,9,18,15,12,14,27.7],overall:[75,96,128.7]}[period]; const labels = {week:['一','二','三','四','五','六','日'],month:['1日','2日','3日','4日','5日','6日','7日','8日','9日','10日','11日'],year:['1月','2月','3月','4月','5月','6月','7月','8月','9月'],overall:['2024','2025','2026']}[period]; const max = Math.ceil(Math.max(...values)/10)*10; const totalMinutes = values.reduce((a,b)=>a+b,0)*(period==='year'||period==='overall'?60:1); const duration = `${Math.floor(totalMinutes/60)}小时${Math.round(totalMinutes%60)}分钟`; const days = {week:7,month:11,year:132,overall:412}[period]; const naturalDays = {week:7,month:11,year:254,overall:985}[period]; const units = period==='year'||period==='overall'?'时':'分'; const stats = [['阅读 / 收听时长',duration,'当前所选周期','clock'],['有效阅读天数',`${days}天`,'单日满 1 分钟计一天','flag'],['自然日均阅读',`${Math.round(totalMinutes/naturalDays)}分钟`,'按周期自然日计算','chart'],['笔记总数','10条','划线 2 · 想法 2 · 书签 6','note']]; return welcome('在字里行间,遇见自己','阅读、目标与灵感,在这里慢慢积累。','')+ `
${tabs([['week','本周'],['month','本月'],['year','本年'],['overall','总计']],period,'period')}统计示例 · ${period==='overall'?'2024—2026':'2026 年'}
${stats.map(([label,value,hint,icon])=>`
${label}
${value}
${hint}
`).join('')}

阅读的日常 每一天,都算数

${period==='year'?'按月':period==='overall'?'按年':'按日'}
${duration}阅读与收听合计
${[max,Math.round(max*2/3),Math.round(max/3),0].map(n=>``).join('')}
${values.map((v,i)=>``).join('')}

继续阅读

${books.filter(b=>b.status==='reading').map(bookCard).join('')}

读得最多

${books.slice(0,3).map((b,i)=>`
${i+1}. ${b.title}${[2,1,1][i]}小时${[12,35,6][i]}分钟
`).join('')}

偏爱的阅读方向

${[['文学',80],['心理学',60],['科幻',40]].map(([label,val])=>`
${label}相对偏好 ${val}
`).join('')}

相对偏好权重,不是阅读占比

留给自己的时段

${Array.from({length:24},(_,i)=>``).join('')}
06:0018:00次日 05:00

偏好夜间阅读 · 示例时段分布

更多阅读画像 · 作者、听书、勋章与年度回顾

偏好作者与出版方

刘慈欣3 本 · 5小时30分钟
示例出版社4 本
示例版权方3 本

阅读与收听

文字阅读80%

示例分布;正式接入只在接口返回时展示。

阅读足迹

好友排行本周第 3 · 示例
勋章初读 · 笔记 · 坚持
`; } function listCard(list, index) { return ``; } function renderLists(lists) { return `
${lists.map(listCard).join('') || '
没有匹配的书单,换个关键词试试。
'}
`; } function renderShelf() { let content; if (shelfTab==='lists') content=renderLists(booklists.filter(l=>l.source==='我的书单')); else if (shelfTab==='audio') content=`
♫

晚间听书 · 文学选读

示例演播者 · 24 集 · 内容已完结

内容完结不代表已听完;当前不纳入按本阅读目标。

`; else if(shelfTab==='articles') content=`
▤

文章收藏

这是书架中的收藏入口。

当前书架接口不提供入口内文章列表;可在发现页搜索文章。

`; else content=`
${books.filter(b=>shelfTab==='all'||b.status===shelfTab).map(bookCard).join('')}
`; return welcome('我的书架','8 本电子书 · 1 个有声专辑 · 1 个文章收藏入口,共 10 个书架条目。')+tabs([['all','全部电子书 8'],['reading','在读 4'],['done','已读完 2'],['wish','想读 2'],['lists','我的书单 2'],['audio','有声书 1'],['articles','文章收藏']],shelfTab,'shelf-tab')+content; } function openList(id) { const list=booklists.find(l=>l.id===id); showDetail('书单详情',`
A COLLECTION OF GOOD BOOKS

${list.name}

${list.description}

${list.source} · ${list.ids.length} 本 · 示例成员

${list.ids.map(id=>bookRow(findBook(id))).join('')}
${list.source.startsWith('公开')?'

公开书单成员为设计示意,完整成员获取方式待真实接口验证。

':''}`); } function showDetail(title, html) { $('#detail-title').textContent=title;$('#detail-content').innerHTML=html;if(!$('#detail').open)$('#detail').showModal(); } function openGoalDetail(id) { const goal=goals.find(g=>g.id===id), m=goalMetrics(goal); showDetail('阅读目标',`
${m.status}${goal.start} — ${goal.end}

${esc(goal.name)}

${m.done} / ${m.total}本已读完${m.percent}%已完成

已读完直接计入完成,不受目标日期影响。

${goal.sources.length?`

加入过的书单:${goal.sources.map(esc).join('、')} · 保存时的书目

`:''}
${goal.ids.map(id=>bookRow(findBook(id))).join('')}
`); } function openGoalEditor(id=null, prefill=[], sources=[]) { $('#detail').close(); editingGoalId=id; const goal=id?goals.find(g=>g.id===id):null; draftIds=new Set(goal?goal.ids:prefill);draftSources=[...(goal?goal.sources:sources)]; $('#editor-title').textContent=goal?'编辑阅读目标':'新建阅读目标';$('#goal-name').value=goal?goal.name:''; $('#goal-start').value=goal?goal.start:today();$('#goal-end').value=goal?goal.end:`${today().slice(0,4)}-12-31`; $('#goal-end').min=$('#goal-start').value;$('#goal-end').setCustomValidity('');$('#goal-name').setCustomValidity(''); $('#picker-search').value='';$('#goal-error').textContent='';renderPicker();$('#goal-editor').showModal(); } function selectedGoalBooks() { return uniqueIds([...draftIds]); } function renderPicker() { const query=$('#picker-search').value.trim().toLowerCase(); const list=books.filter(b=>`${b.title}${b.author}`.toLowerCase().includes(query)); $('#selection-count').textContent=`已选 ${draftIds.size} 本 · ${[...draftIds].filter(id=>findBook(id).status==='done').length} 本已读完`; $('#goal-picker').innerHTML=`

快捷加入书单当前书目

${booklists.filter(l=>!query||l.name.includes(query)||l.ids.some(id=>list.some(b=>b.id===id))).map(l=>``).join('')}
${list.map(b=>``).join('')||'
没有匹配的书籍
'}
`; } function saveGoal(event) { event.preventDefault();const name=$('#goal-name').value.trim(),start=$('#goal-start').value,end=$('#goal-end').value,ids=selectedGoalBooks(); if(!name){$('#goal-name').setCustomValidity('请输入目标名称');$('#goal-name').reportValidity();return;} if(endg.id===editingGoalId?goal:g):[goal,...goals]; if(!persistGoals(next))return; $('#goal-editor').close();navigate('goals');toast('阅读目标已保存,已读完的书已计入完成。'); } function chooseTarget(ids,sources=[]) { showDetail('加入阅读目标',`

选择已有目标,或为这些书新建目标。重复书籍只加入一次。

${goals.map(g=>`
${esc(g.name)}

${g.start} — ${g.end} · ${g.ids.length} 本

`).join('')}
`); pendingBooks=ids;pendingSources=sources; } let pendingBooks=[],pendingSources=[]; function appendGoal(id) { const next=goals.map(g=>g.id===id?{...g,ids:uniqueIds([...g.ids,...pendingBooks]),sources:[...new Set([...g.sources,...pendingSources])]}:g); try { localStorage.setItem(storageKey,JSON.stringify(next)); } catch { toast('无法保存到浏览器,请允许本地存储后重试。');return; } goals=next;renderPage();openGoalDetail(id);toast('已加入目标,重复书籍已自动去重。'); } function renderDiscover() { const options=[['books','书籍'],['lists','书单'],['recommend','为你推荐'],['all','综合'],['author','作者'],['novel','网文'],['audio','听书'],['fulltext','全文'],['account','公众号'],['article','文章']]; const matched=books.filter(b=>`${b.title}${b.author}`.toLowerCase().includes(searchQuery.toLowerCase())); let content=''; if(dataState!=='ready') { const copy={loading:['正在寻找好书','加载状态示意,点击下方按钮查看结果。'],empty:['暂时没有找到','换个关键词,或看看为你推荐。'],error:['暂时无法获取数据','连接失败示意,你的阅读目标仍保存在本地。']}[dataState]; content=`
${dataState==='loading'?'LOADING':dataState==='error'?'TRY AGAIN':'NOT FOUND'}

${copy[0]}

${copy[1]}

`; } else if(discoverTab==='lists') content=renderLists(booklists.filter(l=>`${l.name}${l.description}`.includes(searchQuery))); else if(discoverTab==='books'||discoverTab==='recommend') content=`${discoverTab==='recommend'?'
PICKED FOR YOUR NEXT CHAPTER

读过的书,会带你遇见下一本。

根据你对文学与心理学的兴趣推荐 · 推荐理由为示例

':''}
${(discoverTab==='recommend'?matched.filter(b=>b.status==='wish'||b.id==='b0'):matched).map(bookCard).join('')||'
没有找到相关书籍,试试其他书名或作者。
'}
`; else if(discoverTab==='author') content=`
${[...new Set(matched.map(b=>b.author))].map(name=>`
${name.slice(0,1)}
${name}

作者结果 · 示例

`).join('')||'
没有匹配的作者。
'}
`; else if(discoverTab==='all') content=`

书籍

${matched.slice(0,4).map(bookCard).join('')||'
没有匹配的书籍。
'}

书单

${renderLists(booklists.filter(l=>l.name.includes(searchQuery)))}`; else { const examples={novel:['星河来信','网文小说 · 示例作品','连载中 · 128 章'],audio:['夜晚的文学课','有声专辑 · 示例演播者','24 集 · 内容已完结'],fulltext:['关于阅读的一段文字','全文搜索 · 原创示例文本','阅读让日常拥有了更宽阔的边界。'],account:['一页书房','公众号 · 示例账号','记录阅读、日常与思考'],article:['如何为自己留一点阅读时间','文章 · 原创示例','2026-09-11 · 一页书房']}[discoverTab]; const matches=!searchQuery||examples.join('').includes(searchQuery); content=matches?`
${discoverTab==='audio'?'♫':'▤'}

${examples[0]}

${examples[1]}

${examples[2]}

`:'
没有匹配的演示内容,可清空关键词查看该类型示意。
'; } return welcome('下一本,遇见什么?','寻找一本书,也发现一份恰好适合你的书单。')+ `
${tabs(options,discoverTab,'discover-tab')}
${content}

搜索、推荐、书单均为设计样例,未请求微信读书服务。

`; } function renderNotes() { const list=personalNotes.filter(note=>(noteBook==='all'||note.bookId===noteBook)&&(noteType==='all'||note.type===noteType)); const counts=personalNotes.filter(note=>noteBook==='all'||note.bookId===noteBook); const bookmarks=noteBook==='all'?6:noteBook==='b0'?3:noteBook==='b1'?2:noteBook==='b2'?1:0; return welcome('把读过的,留在心里','划线、想法与每一次停顿,都有自己的意义。','')+ `
${counts.length+bookmarks}条笔记划线 ${counts.filter(n=>n.type==='highlight').length}想法 ${counts.filter(n=>n.type==='thought').length}书签 ${bookmarks}
${tabs([['all','全部内容'],['highlight','划线'],['thought','想法 / 个人点评'],['bookmark','书签数量']],noteType,'note-type')}${noteType==='bookmark'?`
${bookmarks} 个书签 · 当前接口只提供数量,不提供书签内容。
`:list.map(note=>`
${note.type==='thought'?'我的想法':'划线示意'}
${note.text}
《${findBook(note.bookId).title}》 · ${note.date}

原创示例文本,非书中原文

`).join('')||'
当前筛选没有笔记。
'}

总笔记数包含书签;导出包含划线和个人想法,不含公开点评及书签内容。

`; } function exportNotes() { // 导出按当前选中书目取划线和想法全集,内容类型筛选不影响导出完整性。 const notes=personalNotes.filter(note=>noteBook==='all'||note.bookId===noteBook); const text='# 阅间笔记 · 演示数据\n\n原创示例,非书中原文。书签仅提供数量,不导出内容。\n\n'+notes.map(n=>`## ${findBook(n.bookId).title}\n\n${n.type==='thought'?'想法':'划线示意'} · ${n.date}\n\n${n.text}`).join('\n\n'); const url=URL.createObjectURL(new Blob([text],{type:'text/markdown;charset=utf-8'})); showDetail('导出笔记', `

已生成当前书目的划线与想法。可下载 Markdown,或直接选中下方内容复制;不含书签内容。

下载 Markdown
`); $('#detail').addEventListener('close',()=>URL.revokeObjectURL(url),{once:true}); } function openBook(id) { detailBook=id;bookTab='info';selectedChapter='all';renderBookDetail(); } function renderBookDetail() { const book=findBook(detailBook); showDetail('书籍详情',`
${cover(book)}
READING SPACE / BOOK

${book.title}

${book.author}

评分 ${Math.round(Number(book.score)*10)}/100 · 示例评分

${book.status==='done'?'已读完':`已读 ${book.progress}%`} · 累计 2小时18分钟

${[['info','简介'],['chapters','目录'],['notes','我的笔记'],['popular','热门划线'],['reviews','读者点评'],['similar','相似推荐']].map(([id,label])=>``).join('')}
${renderBookTab(book)}

全部为设计示例 · 目录、文字和读者资料非真实接口内容。

`); } function renderBookTab(book) { if(bookTab==='info')return `

${book.desc}

分类:${book.id==='b1'?'科幻':'文学 / 人文'} · 示例
最近阅读:2026-09-11
出版社:示例出版社
译者 / ISBN:暂无演示数据
字数:约 20 万字 · 示例
出版日期:暂无演示数据
`; if(bookTab==='chapters')return `

目录结构示意;选择章节可查看该章热门划线与热度。

${['序章 · 故事的开始','第一章 · 在旅途中','第二章 · 看见新的世界','第三章 · 留给自己的答案'].map((name,i)=>`
${String(i+1).padStart(2,'0')}   ${name} · 示例章节
`).join('')}`; if(bookTab==='notes') { const notes=personalNotes.filter(n=>n.bookId===book.id); return notes.map(n=>`
${n.type==='thought'?'个人想法':'划线示意'}
${n.text}
${n.date} · 原创示例文本
`).join('')||'
这本书暂无个人笔记示例。
'; } if(bookTab==='popular')return `
${selectedChapter==='all'?'全书':`示例章节 ${Number(selectedChapter)+1}`} · 热门划线最多展示 20 条
${['阅读让我们有机会,在自己的生活之外再生活一次。','那些认真思考过的问题,会在未来的某一天回应你。'].map((text,i)=>`
${[1286,862][i]} 人划线 · 示例热度
${text}
原创示例文本,非书中原文
`).join('')}

章节热度提供人数与位置,原文来自热门划线;这里用示例文本说明展示方式。

`; if(bookTab==='reviews')return tabs([['全部','全部'],['推荐','推荐'],['一般','一般'],['不行','不行'],['最新','最新']],reviewFilter,'review-filter')+publicReviews.filter(r=>reviewFilter==='全部'||reviewFilter==='最新'||r.type===reviewFilter).map(review=>`
${review.name} · 示例读者${'★'.repeat(review.rating)}${'☆'.repeat(5-review.rating)}

${review.text}

${review.date} · 公开点评示例
`).join(''); return `

相似推荐示例 · 根据主题与阅读兴趣推荐

${books.filter(b=>b.id!==book.id).slice(0,4).map(bookCard).join('')}
`; } function openThought(text, subtitle) { showDetail('想法详情',`
示例内容
${esc(text)}
${esc(subtitle)} · 2026-09-11

这里展示单条想法的完整内容;不生成未经接口提供的评论或点赞列表。

`); } function handleAction(button) { const action=button.dataset.action,id=button.dataset.id; switch(action) { case 'page':navigate(id);break; case 'new-goal':openGoalEditor();break; case 'edit-goal':openGoalEditor(id);break; case 'goal-detail':openGoalDetail(id);break; case 'goal-filter':goalFilter=id;renderPage();break; case 'close-detail':$('#detail').close();break; case 'close-editor':$('#goal-editor').close();break; case 'pick-list':{ const list=booklists.find(l=>l.id===id);list.ids.forEach(bookId=>draftIds.add(bookId)); if(!draftSources.includes(list.name))draftSources.push(list.name);renderPicker();break; } case 'book':openBook(id);break; case 'list':openList(id);break; case 'add-book':chooseTarget([id]);break; case 'add-list':{const list=booklists.find(l=>l.id===id);chooseTarget(list.ids,[list.name]);break;} case 'append-goal':appendGoal(id);break; case 'new-with-selection':openGoalEditor(null,pendingBooks,pendingSources);break; case 'shelf-tab':shelfTab=id;renderPage();break; case 'period':period=id;renderPage();break; case 'discover-tab':discoverTab=id;renderPage();break; case 'retry':dataState='ready';renderPage();break; case 'notebook':noteBook=id;renderPage();break; case 'note-type':noteType=id;renderPage();break; case 'export-notes':exportNotes();break; case 'personal-note':{const note=personalNotes.find(n=>n.id===id);openThought(note.text,findBook(note.bookId).title+' · 个人笔记示例');break;} case 'book-tab':bookTab=id;renderBookDetail();break; case 'chapter':selectedChapter=id;bookTab='popular';renderBookDetail();break; case 'review-filter':reviewFilter=id;renderBookDetail();break; case 'review-detail':{const r=publicReviews.find(r=>r.id===id);openThought(r.text,r.name+' · 公开点评示例');break;} case 'highlight-thoughts':showDetail('划线下的想法',`
${id==='0'?'阅读让我们有机会,在自己的生活之外再生活一次。':'那些认真思考过的问题,会在未来的某一天回应你。'}

原创划线示意

山间来信 · 示例读者2026-09-11

这句话让我想起,每一段阅读经历都有自己的时间。

仅展示一条想法示例,真实内容需分页获取。

`);break; case 'thought-detail':openThought('这句话让我想起,每一段阅读经历都有自己的时间。','山间来信 · 公开想法示例');break; case 'author-books':searchQuery=button.dataset.name;discoverTab='books';$('#global-search').value=searchQuery;renderPage();break; case 'article-search':searchQuery='';discoverTab='article';navigate('discover');break; case 'search-example':openThought('这是此类搜索结果的详情布局示意。实际内容、可跳转地址及字段以未来接口返回为准。','搜索结果 · '+id);break; case 'year-report':showDetail('年度回顾',`
2026 / A YEAR IN BOOKS

翻过的每一页,都算数。

128小时42分钟 · 132 个阅读日 · 示例年度数据

偏爱文学,习惯在夜晚阅读。每一次阅读,都是给自己的时间。

年报布局示意,真实年度报告入口只使用服务端返回的链接。

`);break; } } document.addEventListener('click',event=>{ const action=event.target.closest('[data-action]');if(action){handleAction(action);return;} const nav=event.target.closest('[data-page]');if(nav){searchQuery='';$('#global-search').value='';navigate(nav.dataset.page);} }); document.addEventListener('change',event=>{ if(event.target.matches('[data-pick-book]')){const id=event.target.dataset.pickBook;event.target.checked?draftIds.add(id):draftIds.delete(id);renderPicker();} if(event.target.id==='data-state'){dataState=event.target.value;renderPage();} if(event.target.id==='chapter-filter'){selectedChapter=event.target.value;renderBookDetail();} }); $('#goal-start').onchange=()=>{$('#goal-end').min=$('#goal-start').value;}; $('#goal-name').oninput=()=>$('#goal-name').setCustomValidity(''); $('#goal-form').onsubmit=saveGoal;$('#picker-search').oninput=renderPicker; $('#global-search').oninput=event=>{searchQuery=event.target.value.trim();dataState='ready';if(page!=='discover')discoverTab='books';navigate('discover');}; document.addEventListener('input',event=>{ if(event.target.id==='discover-search'){ const position=event.target.selectionStart;searchQuery=event.target.value;$('#global-search').value=searchQuery;renderPage(); $('#discover-search').focus();$('#discover-search').setSelectionRange(position,position); } }); document.addEventListener('keydown',event=>{if((event.metaKey||event.ctrlKey)&&event.key==='k'){event.preventDefault();$('#global-search').focus();}}); // 原生 dialog 提供焦点管理与 Escape 关闭;只有点击实际遮罩时才关闭。 document.querySelectorAll('dialog').forEach(dialog=>dialog.addEventListener('click',event=>{ const rect=dialog.getBoundingClientRect();if(event.target===dialog&&(event.clientXrect.right||event.clientYrect.bottom))dialog.close(); })); navigate('overview');if(storageWarning)toast(storageWarning);