feat: 扩展动画学科类型、添加Ctrl+K搜索快捷键、无障碍优化

- 前端动画类型选择扩展至12个学科(数学/物理/化学/生物/地理/语文/英语/历史/政治/美术/音乐/通用)
- 后端schema验证同步更新支持所有新学科类型
- 扩展inferAnimationType自动推断函数支持更多学科关键词
- 添加Ctrl+K键盘快捷键聚焦全局搜索框
- 搜索框添加键盘快捷键提示徽章
- 添加focus-visible焦点样式和prefers-reduced-motion动画减弱
- 添加text-ellipsis/clamp-2/clamp-3工具类
- 添加smooth scroll平滑滚动
This commit is contained in:
Zhang Jing Xuan
2026-07-30 15:07:26 +08:00
parent 6edd18ad8d
commit 56a7a274d9
4 changed files with 105 additions and 6 deletions
+31 -2
View File
@@ -15,7 +15,8 @@
<form class="header-center" @submit.prevent="submitSearch">
<el-icon><Search /></el-icon>
<input v-model="searchText" placeholder="搜索资源、模板或输入知识点快速创作" />
<input ref="searchInputRef" v-model="searchText" placeholder="搜索资源、模板或输入知识点快速创作" />
<kbd class="kbd-hint">Ctrl K</kbd>
<button v-if="searchText" type="button" title="清空搜索" @click="searchText = ''">×</button>
</form>
@@ -78,7 +79,7 @@
</template>
<script setup lang="ts">
import { computed, onMounted, ref, watch } from 'vue'
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import { Bell } from '@element-plus/icons-vue'
import { getNotifications, getUnreadCount, markRead, markAllRead, type NotificationItem } from '@/api/notification'
import { useRoute, useRouter } from 'vue-router'
@@ -138,7 +139,17 @@ function formatTime(ts: string | null): string {
}
const searchText = ref('')
const searchInputRef = ref<HTMLInputElement>()
function handleKeydown(e: KeyboardEvent) {
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
e.preventDefault()
searchInputRef.value?.focus()
}
}
onMounted(() => {
document.addEventListener('keydown', handleKeydown)
fetchUnread()
setInterval(fetchUnread, 60000)
@@ -173,6 +184,11 @@ function submitSearch() {
router.push('/search')
}
onUnmounted(() => {
document.removeEventListener('keydown', handleKeydown)
})
watch(() => route.query.keyword, (keyword) => {
searchText.value = typeof keyword === 'string' ? keyword : ''
}, { immediate: true })
@@ -272,6 +288,19 @@ watch(() => route.query.keyword, (keyword) => {
color: #00543d;
}
.kbd-hint {
font-size: 10px;
font-family: monospace;
color: #9aa7a0;
background: #fff;
border: 1px solid #d6e4d8;
border-radius: 4px;
padding: 1px 5px;
white-space: nowrap;
flex-shrink: 0;
font-weight: 600;
}
.header-center span {
overflow: hidden;
text-overflow: ellipsis;
+51
View File
@@ -402,6 +402,57 @@ html, body, #app {
color: var(--primary);
}
/* Focus visible for accessibility */
*:focus-visible {
outline: 2px solid var(--primary);
outline-offset: 2px;
}
/* Smooth scroll */
html {
scroll-behavior: smooth;
}
/* Reduce motion for accessibility */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
/* Better link defaults */
a {
color: var(--primary);
transition: color 0.2s ease;
}
a:hover {
color: var(--primary-dark);
}
/* Text truncation utility */
.text-ellipsis {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Line clamp utilities */
.clamp-2 {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.clamp-3 {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
/* ===== Print Styles ===== */
@media print {
.zj-sidebar,
+22 -3
View File
@@ -53,6 +53,14 @@
<el-option label="数学" value="math" />
<el-option label="物理" value="physics" />
<el-option label="化学" value="chemistry" />
<el-option label="生物" value="biology" />
<el-option label="地理" value="geography" />
<el-option label="语文" value="chinese" />
<el-option label="英语" value="english" />
<el-option label="历史" value="history" />
<el-option label="政治" value="politics" />
<el-option label="美术" value="art" />
<el-option label="音乐" value="music" />
<el-option label="通用" value="general" />
</el-select>
</div>
@@ -547,6 +555,9 @@ function typeLabel(type: string) {
chinese: '语文',
english: '英语',
history: '历史',
politics: '政治',
art: '美术',
music: '音乐',
general: '通用',
}
return map[type] || '通用'
@@ -613,9 +624,17 @@ function inferSubject(text: string) {
}
function inferAnimationType(text: string) {
if (/数学|函数|几何|圆柱|公式/.test(text)) return 'math'
if (/物理|力|电|光/.test(text)) return 'physics'
if (/化学|实验|反应/.test(text)) return 'chemistry'
if (/数学|几何|方程|圆周|公式|分数|小数/.test(text)) return 'math'
if (/物理|力学|电流|光学|磁场|运动/.test(text)) return 'physics'
if (/化学|实验|反应|元素|分子/.test(text)) return 'chemistry'
if (/生物|细胞|遗传|生态|植物/.test(text)) return 'biology'
if (/地理|气候|地形|地图|经度/.test(text)) return 'geography'
if (/语文|古诗|文言文|阅读|写作/.test(text)) return 'chinese'
if (/英语|单词|语法|English/.test(text)) return 'english'
if (/历史|朝代|战争|文物|古代/.test(text)) return 'history'
if (/政治|法律|宪法|经济/.test(text)) return 'politics'
if (/美术|绘画|色彩/.test(text)) return 'art'
if (/音乐|乐器|歌曲|节奏/.test(text)) return 'music'
return 'general'
}
</script>