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:
@@ -5,7 +5,7 @@ from typing import Optional, Any
|
|||||||
|
|
||||||
class AnimationGenerate(BaseModel):
|
class AnimationGenerate(BaseModel):
|
||||||
prompt: str = Field(..., min_length=1, description="教学动画描述")
|
prompt: str = Field(..., min_length=1, description="教学动画描述")
|
||||||
anim_type: str = Field(default="general", pattern=r"^(math|physics|chemistry|biology|geography|general)$")
|
anim_type: str = Field(default="general", pattern=r"^(math|physics|chemistry|biology|geography|chinese|english|history|politics|art|music|general)$")
|
||||||
subject: str = Field(default="", max_length=50)
|
subject: str = Field(default="", max_length=50)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,8 @@
|
|||||||
|
|
||||||
<form class="header-center" @submit.prevent="submitSearch">
|
<form class="header-center" @submit.prevent="submitSearch">
|
||||||
<el-icon><Search /></el-icon>
|
<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>
|
<button v-if="searchText" type="button" title="清空搜索" @click="searchText = ''">×</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
@@ -78,7 +79,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<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 { Bell } from '@element-plus/icons-vue'
|
||||||
import { getNotifications, getUnreadCount, markRead, markAllRead, type NotificationItem } from '@/api/notification'
|
import { getNotifications, getUnreadCount, markRead, markAllRead, type NotificationItem } from '@/api/notification'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
@@ -138,7 +139,17 @@ function formatTime(ts: string | null): string {
|
|||||||
}
|
}
|
||||||
const searchText = ref('')
|
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(() => {
|
onMounted(() => {
|
||||||
|
document.addEventListener('keydown', handleKeydown)
|
||||||
fetchUnread()
|
fetchUnread()
|
||||||
setInterval(fetchUnread, 60000)
|
setInterval(fetchUnread, 60000)
|
||||||
|
|
||||||
@@ -173,6 +184,11 @@ function submitSearch() {
|
|||||||
router.push('/search')
|
router.push('/search')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
document.removeEventListener('keydown', handleKeydown)
|
||||||
|
})
|
||||||
|
|
||||||
watch(() => route.query.keyword, (keyword) => {
|
watch(() => route.query.keyword, (keyword) => {
|
||||||
searchText.value = typeof keyword === 'string' ? keyword : ''
|
searchText.value = typeof keyword === 'string' ? keyword : ''
|
||||||
}, { immediate: true })
|
}, { immediate: true })
|
||||||
@@ -272,6 +288,19 @@ watch(() => route.query.keyword, (keyword) => {
|
|||||||
color: #00543d;
|
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 {
|
.header-center span {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
|||||||
@@ -402,6 +402,57 @@ html, body, #app {
|
|||||||
color: var(--primary);
|
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 ===== */
|
/* ===== Print Styles ===== */
|
||||||
@media print {
|
@media print {
|
||||||
.zj-sidebar,
|
.zj-sidebar,
|
||||||
|
|||||||
@@ -53,6 +53,14 @@
|
|||||||
<el-option label="数学" value="math" />
|
<el-option label="数学" value="math" />
|
||||||
<el-option label="物理" value="physics" />
|
<el-option label="物理" value="physics" />
|
||||||
<el-option label="化学" value="chemistry" />
|
<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-option label="通用" value="general" />
|
||||||
</el-select>
|
</el-select>
|
||||||
</div>
|
</div>
|
||||||
@@ -547,6 +555,9 @@ function typeLabel(type: string) {
|
|||||||
chinese: '语文',
|
chinese: '语文',
|
||||||
english: '英语',
|
english: '英语',
|
||||||
history: '历史',
|
history: '历史',
|
||||||
|
politics: '政治',
|
||||||
|
art: '美术',
|
||||||
|
music: '音乐',
|
||||||
general: '通用',
|
general: '通用',
|
||||||
}
|
}
|
||||||
return map[type] || '通用'
|
return map[type] || '通用'
|
||||||
@@ -613,9 +624,17 @@ function inferSubject(text: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function inferAnimationType(text: string) {
|
function inferAnimationType(text: string) {
|
||||||
if (/数学|函数|几何|圆柱|公式/.test(text)) return 'math'
|
if (/数学|几何|方程|圆周|公式|分数|小数/.test(text)) return 'math'
|
||||||
if (/物理|力|电|光/.test(text)) return 'physics'
|
if (/物理|力学|电流|光学|磁场|运动/.test(text)) return 'physics'
|
||||||
if (/化学|实验|反应/.test(text)) return 'chemistry'
|
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'
|
return 'general'
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user