|
@@ -5,10 +5,9 @@
|
|
</template>
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
<script setup>
|
|
- import { ref, onMounted, watch, nextTick } from 'vue'
|
|
|
|
|
|
+ import { ref, onMounted, watch, nextTick, onUnmounted } from 'vue'
|
|
import Keyboard from 'simple-keyboard'
|
|
import Keyboard from 'simple-keyboard'
|
|
import 'simple-keyboard/build/css/index.css'
|
|
import 'simple-keyboard/build/css/index.css'
|
|
- import chineseLayout from 'simple-keyboard-layouts/build/layouts/chinese'
|
|
|
|
|
|
|
|
const props = defineProps({
|
|
const props = defineProps({
|
|
keyboardClass: {
|
|
keyboardClass: {
|
|
@@ -28,6 +27,7 @@
|
|
const currentLayout = ref('default')
|
|
const currentLayout = ref('default')
|
|
const isChineseMode = ref(false)
|
|
const isChineseMode = ref(false)
|
|
const isShiftOn = ref(false)
|
|
const isShiftOn = ref(false)
|
|
|
|
+ const chineseLayoutModule = ref(null)
|
|
|
|
|
|
const displayOptions = {
|
|
const displayOptions = {
|
|
'{bksp}': '⌫',
|
|
'{bksp}': '⌫',
|
|
@@ -58,46 +58,147 @@
|
|
]
|
|
]
|
|
}
|
|
}
|
|
|
|
|
|
- const chineseLayoutConfig = {
|
|
|
|
- ...englishLayout,
|
|
|
|
- layoutCandidates: chineseLayout.layoutCandidates
|
|
|
|
|
|
+ // 安全的中文布局配置
|
|
|
|
+ const getChineseLayoutConfig = () => {
|
|
|
|
+ if (!chineseLayoutModule.value) return englishLayout
|
|
|
|
+
|
|
|
|
+ try {
|
|
|
|
+ // 确保 layoutCandidates 存在且是有效的函数/对象
|
|
|
|
+ const layoutCandidates = chineseLayoutModule.value.layoutCandidates
|
|
|
|
+ if (!layoutCandidates) return englishLayout
|
|
|
|
+
|
|
|
|
+ return {
|
|
|
|
+ ...englishLayout,
|
|
|
|
+ layoutCandidates: typeof layoutCandidates === 'function' ?
|
|
|
|
+ layoutCandidates : () => ([])
|
|
|
|
+ }
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.warn('Chinese layout configuration error:', error)
|
|
|
|
+ return englishLayout
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
- onMounted(() => {
|
|
|
|
|
|
+ // 加载中文布局
|
|
|
|
+ const loadChineseLayout = async () => {
|
|
|
|
+ try {
|
|
|
|
+ const module = await import('simple-keyboard-layouts/build/layouts/chinese')
|
|
|
|
+ chineseLayoutModule.value = module.default || module
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.warn('Failed to load Chinese layout:', error)
|
|
|
|
+ chineseLayoutModule.value = null
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ onMounted(async () => {
|
|
|
|
+ await loadChineseLayout()
|
|
initializeKeyboard()
|
|
initializeKeyboard()
|
|
})
|
|
})
|
|
|
|
|
|
function initializeKeyboard() {
|
|
function initializeKeyboard() {
|
|
- keyboard.value = new Keyboard(keyboardEl.value, {
|
|
|
|
- onChange: input => {
|
|
|
|
- emit('onChange', input)
|
|
|
|
- },
|
|
|
|
- onKeyPress: button => {
|
|
|
|
- handleKeyPress(button)
|
|
|
|
- },
|
|
|
|
- layout: isChineseMode.value ? chineseLayoutConfig : englishLayout,
|
|
|
|
- layoutName: currentLayout.value,
|
|
|
|
- display: displayOptions,
|
|
|
|
- buttonTheme: [
|
|
|
|
- {
|
|
|
|
- class: 'hg-function-btn',
|
|
|
|
- buttons: '{bksp} {lock} {enter} {tab} {shift} {clear} {close}'
|
|
|
|
|
|
+ if (!keyboardEl.value) return
|
|
|
|
+
|
|
|
|
+ try {
|
|
|
|
+ // 使用安全的配置
|
|
|
|
+ const layout = isChineseMode.value ? getChineseLayoutConfig() : englishLayout
|
|
|
|
+
|
|
|
|
+ keyboard.value = new Keyboard(keyboardEl.value, {
|
|
|
|
+ onChange: input => {
|
|
|
|
+ emit('onChange', input)
|
|
},
|
|
},
|
|
- {
|
|
|
|
- class: 'hg-mode-btn',
|
|
|
|
- buttons: '{change}'
|
|
|
|
|
|
+ onKeyPress: button => {
|
|
|
|
+ handleKeyPress(button)
|
|
},
|
|
},
|
|
- {
|
|
|
|
- class: 'hg-space-btn',
|
|
|
|
- buttons: '{space}'
|
|
|
|
|
|
+ layout: layout,
|
|
|
|
+ layoutName: currentLayout.value,
|
|
|
|
+ display: displayOptions,
|
|
|
|
+ buttonTheme: [
|
|
|
|
+ {
|
|
|
|
+ class: 'hg-function-btn',
|
|
|
|
+ buttons: '{bksp} {lock} {enter} {tab} {shift} {clear} {close}'
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ class: 'hg-mode-btn',
|
|
|
|
+ buttons: '{change}'
|
|
|
|
+ },
|
|
|
|
+ {
|
|
|
|
+ class: 'hg-space-btn',
|
|
|
|
+ buttons: '{space}'
|
|
|
|
+ }
|
|
|
|
+ ],
|
|
|
|
+ mergeDisplay: true,
|
|
|
|
+ // 关键修复:只在中文模式且布局可用时启用候选词
|
|
|
|
+ enableLayoutCandidates: isChineseMode.value && !!chineseLayoutModule.value,
|
|
|
|
+ useMouseEvents: true,
|
|
|
|
+ physicalKeyboardHighlight: false,
|
|
|
|
+ syncInstanceInputs: true,
|
|
|
|
+ preventMouseDownDefault: true,
|
|
|
|
+ stopMouseDownPropagation: true
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ // 覆盖库中可能出错的方法
|
|
|
|
+ patchKeyboardMethods()
|
|
|
|
+
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('Keyboard initialization failed:', error)
|
|
|
|
+ initializeFallbackKeyboard()
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 修复库内部方法
|
|
|
|
+ function patchKeyboardMethods() {
|
|
|
|
+ if (!keyboard.value) return
|
|
|
|
+
|
|
|
|
+ // 保存原始方法
|
|
|
|
+ const originalSetInput = keyboard.value.setInput
|
|
|
|
+ const originalClearInput = keyboard.value.clearInput
|
|
|
|
+
|
|
|
|
+ // 重写 setInput 方法,添加错误处理
|
|
|
|
+ keyboard.value.setInput = function(input) {
|
|
|
|
+ try {
|
|
|
|
+ originalSetInput.call(this, input)
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.warn('setInput error:', error)
|
|
|
|
+ // 尝试安全的设置方式
|
|
|
|
+ try {
|
|
|
|
+ if (this.input && typeof this.input === 'string') {
|
|
|
|
+ this.input = input || ''
|
|
|
|
+ }
|
|
|
|
+ } catch (e) {
|
|
|
|
+ console.error('Failed to set input:', e)
|
|
}
|
|
}
|
|
- ],
|
|
|
|
- mergeDisplay: true,
|
|
|
|
- enableLayoutCandidates: true,
|
|
|
|
- useMouseEvents: true,
|
|
|
|
- physicalKeyboardHighlight: true,
|
|
|
|
- syncInstanceInputs: true
|
|
|
|
- })
|
|
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // 重写 clearInput 方法
|
|
|
|
+ keyboard.value.clearInput = function() {
|
|
|
|
+ try {
|
|
|
|
+ originalClearInput.call(this)
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.warn('clearInput error:', error)
|
|
|
|
+ try {
|
|
|
|
+ if (this.input && typeof this.input === 'string') {
|
|
|
|
+ this.input = ''
|
|
|
|
+ }
|
|
|
|
+ } catch (e) {
|
|
|
|
+ console.error('Failed to clear input:', e)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ function initializeFallbackKeyboard() {
|
|
|
|
+ try {
|
|
|
|
+ keyboard.value = new Keyboard(keyboardEl.value, {
|
|
|
|
+ onChange: input => emit('onChange', input),
|
|
|
|
+ onKeyPress: button => handleKeyPress(button),
|
|
|
|
+ layout: englishLayout,
|
|
|
|
+ display: displayOptions,
|
|
|
|
+ mergeDisplay: true,
|
|
|
|
+ enableLayoutCandidates: false // 完全禁用候选词
|
|
|
|
+ })
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('Fallback keyboard also failed:', error)
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
function handleKeyPress(button) {
|
|
function handleKeyPress(button) {
|
|
@@ -112,7 +213,7 @@
|
|
break
|
|
break
|
|
case '{clear}':
|
|
case '{clear}':
|
|
emit('onChange', '')
|
|
emit('onChange', '')
|
|
- keyboard.value?.clearInput()
|
|
|
|
|
|
+ safeClearInput()
|
|
break
|
|
break
|
|
case '{shift}':
|
|
case '{shift}':
|
|
case '{lock}':
|
|
case '{lock}':
|
|
@@ -121,25 +222,49 @@
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ function safeClearInput() {
|
|
|
|
+ if (!keyboard.value) return
|
|
|
|
+
|
|
|
|
+ try {
|
|
|
|
+ // 直接设置输入为空,避免调用可能出错的方法
|
|
|
|
+ keyboard.value.input = ''
|
|
|
|
+ emit('onChange', '')
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.warn('Error clearing input:', error)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
async function toggleChineseMode() {
|
|
async function toggleChineseMode() {
|
|
- isChineseMode.value = !isChineseMode.value
|
|
|
|
|
|
+ // 检查中文布局是否可用
|
|
|
|
+ if (!chineseLayoutModule.value) {
|
|
|
|
+ console.warn('Chinese layout not available')
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
|
|
- // 更新按钮显示文本
|
|
|
|
|
|
+ isChineseMode.value = !isChineseMode.value
|
|
displayOptions['{change}'] = isChineseMode.value ? '英/中' : '中/英'
|
|
displayOptions['{change}'] = isChineseMode.value ? '英/中' : '中/英'
|
|
|
|
|
|
await nextTick()
|
|
await nextTick()
|
|
|
|
|
|
- keyboard.value.setOptions({
|
|
|
|
- layout: isChineseMode.value ? chineseLayoutConfig : englishLayout,
|
|
|
|
- layoutCandidates: isChineseMode.value ? chineseLayout.layoutCandidates : null,
|
|
|
|
- display: displayOptions
|
|
|
|
- })
|
|
|
|
-
|
|
|
|
- // 强制刷新候选词显示
|
|
|
|
- if (isChineseMode.value && keyboard.value.input) {
|
|
|
|
- const currentInput = keyboard.value.input
|
|
|
|
- keyboard.value.setInput(currentInput + ' ')
|
|
|
|
- keyboard.value.setInput(currentInput)
|
|
|
|
|
|
+ if (!keyboard.value) return
|
|
|
|
+
|
|
|
|
+ try {
|
|
|
|
+ keyboard.value.setOptions({
|
|
|
|
+ layout: isChineseMode.value ? getChineseLayoutConfig() : englishLayout,
|
|
|
|
+ enableLayoutCandidates: isChineseMode.value,
|
|
|
|
+ display: displayOptions
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.error('Failed to toggle Chinese mode:', error)
|
|
|
|
+ // 出错时回退到英文模式
|
|
|
|
+ isChineseMode.value = false
|
|
|
|
+ displayOptions['{change}'] = '中/英'
|
|
|
|
+ keyboard.value.setOptions({
|
|
|
|
+ layout: englishLayout,
|
|
|
|
+ enableLayoutCandidates: false,
|
|
|
|
+ display: displayOptions
|
|
|
|
+ })
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
@@ -147,20 +272,46 @@
|
|
isShiftOn.value = !isShiftOn.value
|
|
isShiftOn.value = !isShiftOn.value
|
|
currentLayout.value = isShiftOn.value ? 'shift' : 'default'
|
|
currentLayout.value = isShiftOn.value ? 'shift' : 'default'
|
|
|
|
|
|
- keyboard.value.setOptions({
|
|
|
|
- layoutName: currentLayout.value
|
|
|
|
- })
|
|
|
|
|
|
+ if (keyboard.value) {
|
|
|
|
+ try {
|
|
|
|
+ keyboard.value.setOptions({
|
|
|
|
+ layoutName: currentLayout.value
|
|
|
|
+ })
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.warn('Error toggling shift:', error)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
}
|
|
}
|
|
|
|
|
|
watch(() => props.input, (newVal) => {
|
|
watch(() => props.input, (newVal) => {
|
|
if (keyboard.value && newVal !== keyboard.value.input) {
|
|
if (keyboard.value && newVal !== keyboard.value.input) {
|
|
- keyboard.value.setInput(newVal)
|
|
|
|
|
|
+ try {
|
|
|
|
+ keyboard.value.setInput(newVal)
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.warn('Error setting input from props:', error)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ onUnmounted(() => {
|
|
|
|
+ if (keyboard.value) {
|
|
|
|
+ try {
|
|
|
|
+ keyboard.value.destroy()
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.warn('Error destroying keyboard:', error)
|
|
|
|
+ }
|
|
}
|
|
}
|
|
})
|
|
})
|
|
|
|
|
|
defineExpose({
|
|
defineExpose({
|
|
setInput: (input) => {
|
|
setInput: (input) => {
|
|
- if (keyboard.value) keyboard.value.setInput(input)
|
|
|
|
|
|
+ if (keyboard.value) {
|
|
|
|
+ try {
|
|
|
|
+ keyboard.value.setInput(input)
|
|
|
|
+ } catch (error) {
|
|
|
|
+ console.warn('Error setting input:', error)
|
|
|
|
+ }
|
|
|
|
+ }
|
|
}
|
|
}
|
|
})
|
|
})
|
|
</script>
|
|
</script>
|
|
@@ -196,6 +347,9 @@
|
|
display: flex;
|
|
display: flex;
|
|
align-items: center;
|
|
align-items: center;
|
|
justify-content: center;
|
|
justify-content: center;
|
|
|
|
+ -webkit-user-select: none;
|
|
|
|
+ user-select: none;
|
|
|
|
+ touch-action: manipulation;
|
|
}
|
|
}
|
|
|
|
|
|
:deep(.hg-function-btn) {
|
|
:deep(.hg-function-btn) {
|
|
@@ -211,40 +365,4 @@
|
|
flex-grow: 1;
|
|
flex-grow: 1;
|
|
max-width: none !important;
|
|
max-width: none !important;
|
|
}
|
|
}
|
|
-
|
|
|
|
- :deep(.hg-candidate-box) {
|
|
|
|
- position: absolute;
|
|
|
|
- bottom: calc(100% + 5px);
|
|
|
|
- left: 0;
|
|
|
|
- width: 100%;
|
|
|
|
- background: white;
|
|
|
|
- border: 1px solid #ddd;
|
|
|
|
- border-radius: 5px;
|
|
|
|
- padding: 5px;
|
|
|
|
- box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- :deep(.hg-candidate-box-list) {
|
|
|
|
- display: flex;
|
|
|
|
- flex-wrap: wrap;
|
|
|
|
- justify-content: center;
|
|
|
|
- gap: 5px;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- :deep(.hg-candidate-box-list-item) {
|
|
|
|
- padding: 8px 12px;
|
|
|
|
- cursor: pointer;
|
|
|
|
- border-radius: 3px;
|
|
|
|
- background: #f5f5f5;
|
|
|
|
- transition: all 0.2s;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- :deep(.hg-candidate-box-list-item:hover) {
|
|
|
|
- background: #e0e0e0;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- :deep(.hg-candidate-box-list-item.active) {
|
|
|
|
- background: #4a8cff;
|
|
|
|
- color: white;
|
|
|
|
- }
|
|
|
|
</style>
|
|
</style>
|