前端: 按 Web Interface Guidelines 全面重做

组件:
- Button: transition-all→明确属性列表, touch-action, aria-busy
- Input: name/spellcheck/inputmode/aria-invalid/aria-describedby, focus-visible ring,
  label 关联, 暴露 focus() 供错误定位, required 标记
- Modal: Escape 关闭 + focus trap + 焦点归还, overscroll-behavior: contain,
  aria-labelledby, body 滚动锁, touch-action
- 新增 Toast(aria-live polite) + pinia toast store

页面:
- ConsoleLayout: skip link, <main> 语义标签, aside/nav aria-label, aria-current,
  装饰 SVG aria-hidden, Intl 金额, translate=no 品牌/代码
- Dashboard: Intl.NumberFormat, 骨架屏 loading, aria-live 错误
- Keys: 字段级错误+焦点定位, toast 反馈(复制/吊销), name 属性
- Usage: select 加 label, 分页/筛选同步 URL(可深链), Intl 日期/金额, 表格 caption
- Login/Register: 字段级校验+错误定位, spellcheck=false, inputmode, autocomplete
- Landing: router-link 替换 href, aria-hidden, text-balance, scroll-mt
- index.html: theme-color + color-scheme

验证: playwright 22 项断言全过, WCAG AA 对比度 9 组全过, 零控制台错误
This commit is contained in:
Sakurasan
2026-08-15 13:32:18 +08:00
parent 5b67b66611
commit b25e9ec8a7
14 changed files with 646 additions and 185 deletions
+27
View File
@@ -0,0 +1,27 @@
// Toast 状态:轻量全局消息队列(操作反馈,aria-live 播报)
import { defineStore } from 'pinia'
export interface Toast {
id: number
kind: 'success' | 'error' | 'info'
message: string
}
let seq = 0
export const useToastStore = defineStore('toast', {
state: () => ({ items: [] as Toast[] }),
actions: {
push(kind: Toast['kind'], message: string) {
const id = ++seq
this.items.push({ id, kind, message })
setTimeout(() => this.dismiss(id), 4000)
},
success(message: string) { this.push('success', message) },
error(message: string) { this.push('error', message) },
info(message: string) { this.push('info', message) },
dismiss(id: number) {
this.items = this.items.filter((t) => t.id !== id)
},
},
})