在现代Web应用中,键盘快捷键(Keyboard Shortcuts)是提升用户体验和操作效率的重要手段。然而,传统的键盘快捷键实现方式往往缺乏灵活性和智能化。本文将介绍如何结合DeepSeek和Vue框架,打造一款丝滑、智能且高度可定制的键盘快捷键组件,为您的Web应用增添更多便捷和乐趣。
📚前言
在日常工作中,DeepSeek 也为许多用户带来了便利。一位市场营销经理在策划一场新产品推广活动时,利用 DeepSeek 进行市场分析和竞品研究。DeepSeek 通过对大量市场数据和消费者反馈的分析,为他提供了详细的市场趋势报告和竞品优劣势分析。基于这些分析结果,他制定了更有针对性的推广策略,成功提高了新产品的市场知名度和销量。他说:“DeepSeek 帮助我做出了更明智的决策,让我的工作变得更加高效和轻松。
📚页面效果
📚指令输入
已经创建好了一个基于Vue3的组合式API的项目(Composition API),并能正常运行起来,请帮我用 Vue3的组合式API(Composition API) 生成一个 键盘快捷键(Keyboard Shortcuts) 的功能组件,所有代码都保存在components/KeyboardShortcuts 下的文件夹中。功能组件的script标签中只有setup属性,使用普通 JavaScript 实现,不使用TypeScript。
功能要有,如下属性:
属性定义
1. 快捷键绑定数组 shortcuts
类型:数组
描述:用于定义一组快捷键及其对应的回调函数或动作标识。数组中的每个元素可以是一个对象,包含快捷键组合和对应的处理逻辑。例如,[{ keys: 'Ctrl + S', action: 'save' }, { keys: 'Ctrl + C', action: 'copy' }]。
2. 启用状态 enabled
类型:布尔值
描述:控制快捷键组件是否启用。当设置为 false 时,快捷键将不会触发任何动作,可用于临时禁用快捷键功能。
3. 全局监听 global
类型:布尔值
描述:指定快捷键是否在全局范围内监听。如果设置为 true,则无论焦点在哪个元素上,快捷键都会生效;如果设置为 false,则只有当焦点在组件内部时快捷键才会生效。
4. 组合键分隔符 separator
类型:字符串
描述:用于指定快捷键组合中各个键之间的分隔符,默认为 +。例如,用户可以自定义为 -,则快捷键定义可以写成 Ctrl - S。
5. 修饰键顺序敏感 modifierSensitive
类型:布尔值
描述:指定修饰键(如 Ctrl、Alt、Shift)的顺序是否敏感。如果设置为 true,则 Ctrl + Shift + A 和 Shift + Ctrl + A 被视为不同的快捷键;如果设置为 false,则它们被视为相同的快捷键。
事件定义
1. shortcut-triggered 事件
描述:当用户按下定义的快捷键时触发该事件,事件参数包含触发的快捷键组合和对应的动作标识。例如,当用户按下 Ctrl + S 时,会触发该事件,并传递 { keys: 'Ctrl + S', action: 'save' } 作为参数。
2. shortcut-failed 事件
描述:当用户按下的键组合未匹配到任何定义的快捷键时触发该事件,事件参数包含用户按下的键组合。可用于记录用户的无效操作或提供提示信息。
其他
1. 快捷键验证
在组件内部对用户定义的快捷键进行验证,确保其格式正确,避免因无效的快捷键定义导致组件出错。例如,检查快捷键组合中是否包含非法字符或不支持的键。
2. 冲突处理
当多个快捷键之间存在冲突时,提供相应的处理机制。可以选择忽略冲突,只触发第一个匹配的快捷键;也可以抛出错误或警告,提示用户存在冲突。
3. 跨平台兼容性
考虑不同操作系统和浏览器对快捷键的支持情况,确保组件在各种环境下都能正常工作。例如,在 Mac 系统中,Ctrl 键通常对应 Command 键,需要进行相应的转换。
4. 文档和示例
提供详细的文档和示例,说明组件的使用方法、属性和事件的含义,方便其他开发者使用该组件。
5. 测试
编写单元测试和集成测试,确保组件的功能正常,快捷键能够正确触发和处理。测试用例应覆盖各种情况,包括不同的快捷键组合、启用和禁用状态等。
你有更好的建议也可以添加,要注明。组件定义好后给出5个及以上的调用示例。
下面是现有目录
vueAndDeepseek/
├── src/ # 源代码目录
│ ├── assets/ # 静态资源
│ │ ├── base.css
│ │ ├── main.css
│ │ └── logo.svg
│ ├── components/ # 组件目录
│ │ ├── HelloWorld.vue
│ │ ├── TheWelcome.vue
│ │ ├── WelcomeItem.vue
│ │ ├── Progress/
│ │ │ └── Progress.vue
│ │ ├── Accordion/
│ │ ├── BackToTop/
│ │ ├── Card/
│ │ ├── InfiniteScroll/
│ │ ├── Notification/
│ │ ├── Timeline/
│ │ ├── Switch/
│ │ ├── Tabs/
│ │ ├── Sidebar/
│ │ ├── Breadcrumbs/
│ │ ├── MasonryLayout/
│ │ ├── Rating/
│ │ ├── ColorPicker/
│ │ ├── RightClickMenu/
│ │ ├── RangePicker/
│ │ ├── Navbar/
│ │ ├── FormValidation/
│ │ ├── CopyToClipboard/
│ │ ├── ClickAnimations/
│ │ ├── ThumbnailList/
│ │ ├── KeyboardShortcuts/
│ │ ├── DatePicker/
│ │ └── icons/
│ ├── router/ # 路由配置
│ │ └── index.js
│ ├── stores/ # Pinia 状态管理
│ │ └── counter.js
│ ├── views/ # 页面组件
│ │ ├── ProgressView.vue
│ │ ├── TabsView.vue
│ │ ├── SwitchView.vue
│ │ ├── TimelineView.vue
│ │ ├── NotificationView.vue
│ │ ├── CardView.vue
│ │ ├── InfiniteScrollView.vue
│ │ ├── BackToTopView.vue
│ │ ├── AccordionView.vue
│ │ ├── SidebarView.vue
│ │ ├── BreadcrumbsView.vue
│ │ ├── MasonryLayoutView.vue
│ │ ├── RatingView.vue
│ │ ├── ColorPickerView.vue
│ │ ├── RightClickMenuView.vue
│ │ ├── RangePickerView.vue
│ │ ├── NavbarView.vue
│ │ ├── FormValidationView.vue
│ │ ├── CopyToClipboardView.vue
│ │ ├── ClickAnimationsView.vue
│ │ ├── ThumbnailListView.vue
│ │ ├── KeyboardShortcutsView.vue
│ │ ├── DatePickerView.vue
│ │ └── AboutView.vue
│ ├── App.vue # 根组件
│ └── main.js # 应用入口
├── public/ # 公共资源目录
├── index.html # HTML 模板
├── package.json # 项目配置
├── vite.config.js # Vite 配置
└── node_modules/ # 依赖包目录
📘组件代码
根据你的需求,我帮你创建了一个功能完善的键盘快捷键组件,以下是实现代码和调用示例:
<!-- components/KeyboardShortcuts/KeyboardShortcuts.vue --> <template> <div ref="root"> <slot></slot> </div> </template> <script setup> import { ref, computed, watch, onMounted, onUnmounted } from 'vue' const props = defineProps({ shortcuts: { type: Array, required: true, validator: (value) => value.every(item => item.keys && item.action) }, enabled: { type: Boolean, default: true }, global: { type: Boolean, default: false }, separator: { type: String, default: '+' }, modifierSensitive: { type: Boolean, default: false } }) const emit = defineEmits(['shortcut-triggered', 'shortcut-failed']) const root = ref(null) const isMac = /macintosh|mac os x/i.test(navigator.userAgent) let currentTarget = null let isListening = false const keyAliases = { ctrl: 'control', control: 'control', cmd: 'meta', command: 'meta', meta: 'meta', shift: 'shift', alt: 'alt', option: 'alt', esc: 'escape', space: ' ', up: 'arrowup', down: 'arrowdown', left: 'arrowleft', right: 'arrowright' } const normalizeKey = (key) => { const lowerKey = key.toLowerCase() return keyAliases[lowerKey] || lowerKey } const parseKeys = (keysStr) => { return keysStr.split(props.separator) .map(k => normalizeKey(k.trim())) .map(k => isMac && k === 'control' ? 'meta' : k) } const normalizeCombo = (keys, isModifierSensitive) => { const modifiers = [] const others = [] keys.forEach(key => { if (['control', 'meta', 'shift', 'alt'].includes(key)) { modifiers.push(key) } else { others.push(key) } }) if (!isModifierSensitive) { modifiers.sort() } return [...modifiers, ...others].join('+') } const shortcutMap = computed(() => { const map = new Map() props.shortcuts.forEach(shortcut => { try { const keys = parseKeys(shortcut.keys) const combo = normalizeCombo(keys, props.modifierSensitive) if (map.has(combo)) { console.warn(`Shortcut conflict: ${shortcut.keys} conflicts with existing shortcut`) } map.set(combo, shortcut) } catch (e) { console.error(`Invalid shortcut: ${shortcut.keys}`, e) } }) return map }) const handleKeyDown = (event) => { if (!props.enabled) return const pressedModifiers = [] if (event.ctrlKey) pressedModifiers.push('control') if (event.shiftKey) pressedModifiers.push('shift') if (event.altKey) pressedModifiers.push('alt') if (event.metaKey) pressedModifiers.push('meta') const key = normalizeKey(event.key) if (['control', 'shift', 'alt', 'meta'].includes(key)) return const pressedKeys = [...pressedModifiers, key] const combo = normalizeCombo(pressedKeys, props.modifierSensitive) const matched = shortcutMap.value.get(combo) if (matched) { event.preventDefault() emit('shortcut-triggered', { keys: matched.keys, action: matched.action }) if (typeof matched.action === 'function') { matched.action() } } else { const userCombo = pressedKeys.join(props.separator) emit('shortcut-failed', { keys: userCombo }) } } const updateEventListener = () => { if (isListening) { currentTarget.removeEventListener('keydown', handleKeyDown) isListening = false } if (props.enabled) { currentTarget = props.global ? window : root.value if (currentTarget) { currentTarget.addEventListener('keydown', handleKeyDown) isListening = true } } } onMounted(updateEventListener) onUnmounted(() => { if (isListening) { currentTarget.removeEventListener('keydown', handleKeyDown) } }) watch([() => props.enabled, () => props.global], updateEventListener) </script>
以下是5个调用示例:
<!-- 示例1: 基本使用 --> <KeyboardShortcuts :shortcuts="[ { keys: 'Ctrl + S', action: 'save' }, { keys: 'Ctrl + C', action: 'copy' } ]" @shortcut-triggered="handleShortcut" /> <!-- 示例2: 带回调函数 --> <KeyboardShortcuts :shortcuts="[ { keys: 'Ctrl + K', action: () => openSearch() } ]" /> <!-- 示例3: 非全局作用域 --> <KeyboardShortcuts :global="false" :shortcuts="[{ keys: 'Enter', action: 'submit' }]" > <input type="text" placeholder="Focus here to use Enter shortcut"> </KeyboardShortcuts> <!-- 示例4: 自定义分隔符 --> <KeyboardShortcuts :separator="'-'" :shortcuts="[{ keys: 'Ctrl - Alt - D', action: 'debug' }]" /> <!-- 示例5: 复杂组合 --> <KeyboardShortcuts :modifier-sensitive="true" :shortcuts="[ { keys: 'Shift + Ctrl + Tab', action: 'prevTab' }, { keys: 'Ctrl + Tab', action: 'nextTab' } ]" /> <!-- 示例6: 禁用状态 --> <KeyboardShortcuts :enabled="false" :shortcuts="[...]" />
组件特性:
自动处理Mac系统的Command键映射
支持修饰键顺序敏感设置
完善的类型验证和错误提示
自动处理事件监听的生命周期
支持组合键和单键触发
详细的调试信息输出
使用时可以根据需要组合不同的属性来实现各种快捷键场景,组件会自动处理浏览器兼容性和平台差异。
📚代码测试
运行正常。代码示例需要添加一些参数。
📚添加参数后主要代码
📘组件 \src\views\KeyboardShortcutsView.vue
<template> <div class="keyboard-shortcuts-demo"> <h2>键盘快捷键示例</h2> <!-- 示例1: 基本使用 --> <section class="demo-section"> <h3>基本使用</h3> <div class="demo-box"> <p>尝试按下: <code>Ctrl + S 保存</code> 或 <code>Ctrl + C 复制</code></p> <KeyboardShortcuts :shortcuts="[ { keys: 'Ctrl + S', action: 'save' }, { keys: 'Ctrl + C', action: 'copy' } ]" @shortcut-triggered="handleShortcut" > <div class="action-log">最后执行的操作: {{ lastAction }}</div> </KeyboardShortcuts> </div> </section> <!-- 示例2: 带回调函数 --> <section class="demo-section"> <h3>带回调函数</h3> <div class="demo-box"> <p>尝试按下: <code>Ctrl + K</code></p> <KeyboardShortcuts :shortcuts="[{ keys: 'Ctrl + K', action: () => openSearch() }]" > <div v-if="isSearchOpen"> 搜索框已打开 <button @click="isSearchOpen = false">关闭</button> </div> </KeyboardShortcuts> </div> </section> <!-- 示例3: 非全局作用域 --> <section class="demo-section"> <h3>非全局作用域</h3> <div class="demo-box"> <p>在输入框内按 <code>Enter</code></p> <KeyboardShortcuts :global="false" :shortcuts="[{ keys: 'Enter', action: 'submit' }]" @shortcut-triggered="handleSubmit" > <input type="text" v-model="inputText" placeholder="Focus here to use Enter shortcut" > <div class="action-log">{{ submitMessage }}</div> </KeyboardShortcuts> </div> </section> <!-- 示例4: 自定义分隔符 --> <section class="demo-section"> <h3>自定义分隔符</h3> <div class="demo-box"> <p>尝试按下: <code>Ctrl-Alt-D</code></p> <KeyboardShortcuts separator="-" :shortcuts="[{ keys: 'Ctrl-Alt-D', action: 'debug' }]" @shortcut-triggered="handleDebug" > <div class="action-log">{{ debugStatus }}</div> </KeyboardShortcuts> </div> </section> <!-- 示例5: 复杂组合 --> <section class="demo-section"> <h3>复杂组合</h3> <div class="demo-box"> <p>尝试按下: <code>Shift + Ctrl + Tab</code> 或 <code>Ctrl + Tab</code></p> <KeyboardShortcuts :modifier-sensitive="true" :shortcuts="[ { keys: 'Shift + Ctrl + Tab', action: 'prevTab' }, { keys: 'Ctrl + Tab', action: 'nextTab' } ]" @shortcut-triggered="handleTabSwitch" > <div class="tabs-demo"> 当前标签页: {{ currentTab }} </div> </KeyboardShortcuts> </div> </section> </div> </template> <script setup> import { ref } from 'vue' import KeyboardShortcuts from '@/components/KeyboardShortcuts/KeyboardShortcuts.vue' // 状态变量 const lastAction = ref('') const isSearchOpen = ref(false) const inputText = ref('') const submitMessage = ref('') const debugStatus = ref('') const currentTab = ref(1) // 事件处理函数 const handleShortcut = (event) => { lastAction.value = `执行了 ${event.action} 操作` } const openSearch = () => { isSearchOpen.value = true } const handleSubmit = () => { submitMessage.value = `提交的内容: ${inputText.value}` inputText.value = '' } const handleDebug = () => { debugStatus.value = '调试模式已' + (debugStatus.value.includes('开启') ? '关闭' : '开启') } const handleTabSwitch = (event) => { if (event.action === 'nextTab') { currentTab.value = currentTab.value >= 3 ? 1 : currentTab.value + 1 } else { currentTab.value = currentTab.value <= 1 ? 3 : currentTab.value - 1 } } </script> <style scoped> .keyboard-shortcuts-demo { padding: 20px; max-width: 800px; margin: 0 auto; } h2 { color: #2c3e50; text-align: center; margin-bottom: 30px; } .demo-section { margin-bottom: 30px; padding: 20px; border: 1px solid #ebeef5; border-radius: 8px; background: white; } h3 { color: #2c3e50; margin-bottom: 15px; } .demo-box { padding: 15px; background: #f8f9fa; border-radius: 6px; } code { background: #e9ecef; padding: 2px 6px; border-radius: 4px; font-family: monospace; } .action-log { margin-top: 10px; padding: 10px; background: #e9ecef; border-radius: 4px; font-size: 14px; } .demo-input { width: 100%; padding: 8px; border: 1px solid #dcdfe6; border-radius: 4px; margin-top: 10px; } .search-box { margin-top: 10px; padding: 10px; background: #e9ecef; border-radius: 4px; display: flex; justify-content: space-between; align-items: center; } .search-box button { padding: 4px 8px; border: none; background: #409eff; color: white; border-radius: 4px; cursor: pointer; } .tabs-demo { margin-top: 10px; padding: 10px; background: #e9ecef; border-radius: 4px; text-align: center; } </style>
📚测试代码正常跑通,附其他基本代码
添加路由
页面展示入口
📘编写路由 src\router\index.js
import { createRouter, createWebHistory } from 'vue-router' import RightClickMenuView from '../views/RightClickMenuView.vue' import RangePickerView from '../views/RangePickerView.vue' const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ { path: '/', name: 'progress', component: () => import('../views/ProgressView.vue'), }, { path: '/tabs', name: 'tabs', // route level code-splitting // this generates a separate chunk (About.[hash].js) for this route // which is lazy-loaded when the route is visited. // 标签页(Tabs) component: () => import('../views/TabsView.vue'), }, { path: '/accordion', name: 'accordion', // 折叠面板(Accordion) component: () => import('../views/AccordionView.vue'), }, { path: '/timeline', name: 'timeline', // 时间线(Timeline) component: () => import('../views/TimelineView.vue'), }, { path: '/backToTop', name: 'backToTop', component: () => import('../views/BackToTopView.vue') }, { path: '/notification', name: 'notification', component: () => import('../views/NotificationView.vue') }, { path: '/card', name: 'card', component: () => import('../views/CardView.vue') }, { path: '/infiniteScroll', name: 'infiniteScroll', component: () => import('../views/InfiniteScrollView.vue') }, { path: '/switch', name: 'switch', component: () => import('../views/SwitchView.vue') }, { path: '/sidebar', name: 'sidebar', component: () => import('../views/SidebarView.vue') }, { path: '/breadcrumbs', name: 'breadcrumbs', component: () => import('../views/BreadcrumbsView.vue') }, { path: '/masonryLayout', name: 'masonryLayout', component: () => import('../views/MasonryLayoutView.vue') }, { path: '/rating', name: 'rating', component: () => import('../views/RatingView.vue') }, { path: '/datePicker', name: 'datePicker', component: () => import('../views/DatePickerView.vue') }, { path: '/colorPicker', name: 'colorPicker', component: () => import('../views/ColorPickerView.vue') }, { path: '/rightClickMenu', name: 'rightClickMenu', component: RightClickMenuView }, { path: '/rangePicker', name: 'rangePicker', component: () => import('../views/RangePickerView.vue') }, { path: '/navbar', name: 'navbar', component: () => import('../views/NavbarView.vue') }, { path: '/formValidation', name: 'formValidation', component: () => import('../views/FormValidationView.vue') }, { path: '/copyToClipboard', name: 'copyToClipboard', component: () => import('../views/CopyToClipboardView.vue') }, { path: '/clickAnimations', name: 'clickAnimations', component: () => import('../views/ClickAnimationsView.vue') }, { path: '/thumbnailList', name: 'thumbnailList', component: () => import('../views/ThumbnailListView.vue') }, { path: '/keyboardShortcuts', name: 'keyboardShortcuts', component: () => import('../views/KeyboardShortcutsView.vue') } ], }) export default router
📘编写展示入口 src\App.vue
<script setup> import { RouterLink, RouterView } from 'vue-router' import HelloWorld from './components/HelloWorld.vue' </script> <template> <header> <img alt="Vue logo" src="https://blog.csdn.net/qq_33650655/article/details/@/assets/logo.svg" /> <div class="wrapper"> <HelloWorld msg="You did it!" /> <nav> <RouterLink to="/">Progress</RouterLink> <RouterLink to="/tabs">Tabs</RouterLink> <RouterLink to="/accordion">Accordion</RouterLink> <RouterLink to="/timeline">Timeline</RouterLink> <RouterLink to="/backToTop">BackToTop</RouterLink> <RouterLink to="/notification">Notification</RouterLink> <RouterLink to="/card">Card</RouterLink> <RouterLink to="/infiniteScroll">InfiniteScroll</RouterLink> <RouterLink to="/switch">Switch</RouterLink> <RouterLink to="/sidebar">Sidebar</RouterLink> <RouterLink to="/breadcrumbs">Breadcrumbs</RouterLink> <RouterLink to="/masonryLayout">MasonryLayout</RouterLink> <RouterLink to="/rating">Rating</RouterLink> <RouterLink to="/datePicker">DatePicker</RouterLink> <RouterLink to="/colorPicker">ColorPicker</RouterLink> <RouterLink to="/rightClickMenu">RightClickMenu</RouterLink> <RouterLink to="/rangePicker">RangePicker</RouterLink> <RouterLink to="/navbar">Navbar</RouterLink> <RouterLink to="/formValidation">FormValidation</RouterLink> <RouterLink to="/copyToClipboard">CopyToClipboard</RouterLink> <RouterLink to="/clickAnimations">ClickAnimations</RouterLink> <RouterLink to="/thumbnailList">ThumbnailList</RouterLink> <RouterLink to="/keyboardShortcuts">KeyboardShortcuts</RouterLink> </nav> </div> </header> <RouterView /> </template> <style scoped> header { line-height: 1.5; max-height: 100vh; } .logo { display: block; margin: 0 auto 2rem; } nav { width: 100%; font-size: 12px; text-align: center; margin-top: 2rem; } nav a.router-link-exact-active { color: var(--color-text); } nav a.router-link-exact-active:hover { background-color: transparent; } nav a { display: inline-block; padding: 0 1rem; border-left: 1px solid var(--color-border); } nav a:first-of-type { border: 0; } @media (min-width: 1024px) { header { display: flex; place-items: center; padding-right: calc(var(--section-gap) / 2); } .logo { margin: 0 2rem 0 0; } header .wrapper { display: flex; place-items: flex-start; flex-wrap: wrap; } nav { text-align: left; margin-left: -1rem; font-size: 1rem; padding: 1rem 0; margin-top: 1rem; } } </style>
总结
通过结合DeepSeek的强大搜索和推荐能力,以及Vue框架的响应式和组件化特性,我们成功打造了一款丝滑、智能且高度可定制的键盘快捷键组件。这款组件不仅能够根据用户操作动态调整快捷键,还能与用户交互,提供更加个性化的体验。希望本文的介绍能够为您的Web开发带来新的灵感和思路,让您的应用在细节之处更加出彩。
本文来源于#宝码香车,由@蜜芽 整理发布。如若内容造成侵权/违法违规/事实不符,请联系本站客服处理!
该文章观点仅代表作者本人,不代表本站立场。本站不承担相关法律责任。
如若转载,请注明出处:https://www.zhanid.com/biancheng/3435.html