Files
tamakyi 03cb5ddcf0 fix(ui): 提交防重复覆盖验证码路径、waline 按页加载、懒加载、safe-area、硬编码文案接字典
1) 防重复点击原来漏了最主要的一条路径
   footer 的全局 submit 监听会禁用按钮,但**程序化 form.submit() 不触发 submit 事件** ——
   走 reCAPTCHA / Turnstile 的流程是在回调里调 form.submit() 的,而默认配置
   captcha_type = recaptcha,也就是主提问流一直没有防重复。登录、注册、找回密码、
   投票、注销账号同样走这个回调。
   → 把「切提交中状态」抽成 tbBeginSubmit(form, trigger) 挂到 window,
     验证码回调先调它再 submit。7 个 data-callback="onSubmit" 的模板全部受益。
     顺带加了 data-tb-restore 的重复覆盖保护,避免 bfcache 复位后按钮卡在转圈。

2) waline.js / waline.css 从全站改按页加载
   只有 home.html 与 item.html 用评论区,但此前挂在 header 里 —— 登录、后台、投票、
   私聊每页都下载解析 waline.js(2.15 未压缩约 200KB)。实测 /login 已无引用。

3) 图片懒加载:explore 列表头像与题目配图加 loading="lazy"(首屏推荐位不加,避免影响 LCP)。

4) 移动端底栏:body 只预留固定 56px,而底栏 padding 含 safe-area-inset-bottom。
   带 Home Indicator 的 iPhone 上 inset 约 34px、底栏实际约 88px,最后约 32px
   (页脚备案、存活计时)被压在栏下,滚到底也看不到。改成 calc(56px + env(...))。

5) 硬编码中文接字典(en/ja 用户此前直接看到中文):
   history-template 的「查看回答 / 加载更多 / 加载中... / 无更多留言」、
   单题页「@xxx 以前回答过的问题」(字典里本来就有 ask.history)、
   投票页「(最多 N 项)」改为带参数的整句(原先前半走字典、后半手写中文)。
   新增 4 个 key × 4 语言。

实测:箱主语言设为 en 后箱主页显示 View answer ×2 / Load more / No more posts,中文零残留。
2026-10-29 16:28:53 +08:00

84 lines
4.5 KiB
HTML

{{$pinTitle := t .LangCode "ask.pinned"}}
<div id="question-list">
{{range $index, $elem := .PageQuestions}}
<div data-qid="{{$elem.ID}}">
<hr>
<a class="uk-button uk-button-default uk-button-small uk-float-right"
href="/_/{{$.PageUser.Domain}}/{{$elem.ID}}">{{t $.LangCode "qlist.viewAnswer"}}</a>
<div class="uk-text-left uk-text-small uk-text-muted">
{{if $elem.IsPinned}}<span class="tb-pin-flag" title="{{t $.LangCode "ask.pinned"}}">⭐</span>{{end}}
{{Date $elem.CreatedAt "Y-m-d H:i:s"}}
</div>
<p class="uk-text-small uk-text-break tb-preline tb-clamp-5">{{MarkdownPlainText $elem.Content}}</p>
</div>
{{end}}
</div>
<!-- 翻页游标与服务端 EncodePinCursor 保持一致:
isPinned(0/1) + createdAt 的 UnixNano + id 组成的复合键集游标。 -->
<!-- 初始 more 由服务端给出:首屏已展示全部提问时按钮直接不渲染,
之后每次加载以接口返回的 has_more 为准,最后一页加载完立即隐藏按钮。 -->
<div x-data="{ more: {{ if .PageQuestionHasMore }}true{{ else }}false{{ end }}, loading: false, cursor: '{{.PageQuestionCursor}}' }">
<button x-show="more" x-bind:disabled="loading" x-on:click.debounce="() => {
if (loading) return
loading = true
fetch(`/api/v1/user/{{.PageUser.Domain}}/questions?cursor=${cursor}`)
.then(response => response.json())
.then(resp => {
loading = false
// 外层为 ctx.JSON 的统一包装 {code, data, message},内层才是分页负载;
// code !== 0(接口报错)时保持 more 不变,仅复位 loading,下次可重试。
if (!resp || resp.code !== 0) return
const payload = resp.data || {}
let items = Array.isArray(payload.data) ? payload.data : []
if (items.length === 0) {
more = false
return
}
// 防重兜底:先扫描 #question-list 下全部已渲染条目(含服务端首屏与既往
// 追加)的 data-qid 构造“已见”集合,仅追加未出现过的提问。若服务端因游标
// 未前进等原因返回了与已见内容相同的批次(重复加载历史 bug 的表现形态),
// 直接按“到底”处理并隐藏按钮,杜绝无限重复追加。
const seenQIDs = new Set()
document.querySelectorAll('#question-list [data-qid]').forEach(el => seenQIDs.add(Number(el.dataset.qid)))
items = items.filter(q => !seenQIDs.has(q.id))
if (items.length === 0) {
more = false
return
}
// 服务端下发复合键集游标(见上方注释),前端不自行拼接;
// has_more 由服务端多取一条探测得出,最后一页加载完立即隐藏按钮。
more = payload.has_more !== false
cursor = payload.next_cursor || cursor
items.forEach(question => {
const div = document.createElement('div')
div.dataset.qid = question.id
div.innerHTML = `
<div>
<hr>
<a class='uk-button uk-button-default uk-button-small uk-float-right'
href='/_/{{.PageUser.Domain}}/${question.id}'>{{t .LangCode "qlist.viewAnswer"}}</a>
<div class='uk-text-left uk-text-small uk-text-muted'>
${question.is_pinned ? '<span class=\'tb-pin-flag\' title=\'{{$pinTitle}}\'>⭐</span>' : ''}
${dayjs(question.created_at).format('YYYY-MM-DD HH:mm:ss')}
</div>
<p class='uk-text-small uk-text-break tb-preline tb-clamp-5'></p>
</div>`
div.getElementsByTagName('p')[0].innerText = question.content
document.getElementById('question-list').appendChild(div)
})
})
// 网络失败/响应非法 JSON 时复位 loading,避免按钮永远停留在加载中状态
.catch(() => { loading = false })
}" class="uk-button uk-button-default uk-width-1-1 uk-margin-small-bottom">
<span x-show="!loading">{{t .LangCode "qlist.loadMore"}}</span>
<span x-show="loading"><div uk-spinner="ratio: 0.8"></div> {{t .LangCode "qlist.loading"}}</span>
</button>
<div x-show="!more" class="uk-text-meta uk-text-center">
<hr>
{{t .LangCode "qlist.noMore"}}
<br><br>
</div>
</div>