wd-keyboard.vue 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. <template>
  2. <wd-popup
  3. v-model="show"
  4. position="bottom"
  5. :z-index="zIndex"
  6. :safe-area-inset-bottom="safeAreaInsetBottom"
  7. :modal-style="modal ? '' : 'opacity: 0;'"
  8. :modal="hideOnClickOutside"
  9. :lockScroll="lockScroll"
  10. :root-portal="rootPortal"
  11. @click-modal="handleClose"
  12. >
  13. <view :class="`wd-keyboard ${customClass}`" :style="customStyle">
  14. <view class="wd-keyboard__header" v-if="showHeader">
  15. <slot name="title" v-if="showTitle">
  16. <text class="wd-keyboard__title">{{ title }}</text>
  17. </slot>
  18. <view class="wd-keyboard__close" hover-class="wd-keyboard__close--hover" v-if="showClose" @click="handleClose">
  19. <text>{{ closeText }}</text>
  20. </view>
  21. </view>
  22. <template v-if="mode !== 'car'">
  23. <view class="wd-keyboard__body">
  24. <view class="wd-keyboard__keys">
  25. <wd-key v-for="key in keys" :key="key.text" :text="key.text" :type="key.type" :wider="key.wider" @press="handlePress"></wd-key>
  26. </view>
  27. <view class="wd-keyboard__sidebar" v-if="mode === 'custom'">
  28. <wd-key v-if="showDeleteKey" large :text="deleteText" type="delete" @press="handlePress"></wd-key>
  29. <wd-key large :text="closeText" type="close" :loading="closeButtonLoading" @press="handlePress"></wd-key>
  30. </view>
  31. </view>
  32. </template>
  33. <template v-if="mode === 'car'">
  34. <view class="wd-keyboard-car__body">
  35. <view class="wd-keyboard-car__keys">
  36. <wd-key v-for="key in keys" :key="key.text" :text="key.text" :type="key.type" :wider="key.wider" @press="handlePress"></wd-key>
  37. </view>
  38. </view>
  39. </template>
  40. </view>
  41. </wd-popup>
  42. </template>
  43. <script lang="ts">
  44. export default {
  45. name: 'wd-keyboard',
  46. options: {
  47. virtualHost: true,
  48. addGlobalClass: true,
  49. styleIsolation: 'shared'
  50. }
  51. }
  52. </script>
  53. <script lang="ts" setup>
  54. import { computed, ref, watch, useSlots } from 'vue'
  55. import wdPopup from '../wd-popup/wd-popup.vue'
  56. import WdKey from './key/index.vue'
  57. import { keyboardProps, type Key, type CarKeyboardLang } from './types'
  58. import type { NumberKeyType } from './key/types'
  59. import { CAR_KEYBOARD_AREAS, CAR_KEYBOARD_KEYS } from './constants'
  60. const props = defineProps(keyboardProps)
  61. const emit = defineEmits(['update:visible', 'input', 'close', 'delete', 'update:modelValue', 'update:carLang'])
  62. const slots = useSlots()
  63. const show = ref(props.visible)
  64. watch(
  65. () => props.visible,
  66. (newValue) => {
  67. show.value = newValue
  68. }
  69. )
  70. const carLang = ref<CarKeyboardLang>('zh')
  71. const carKeyboardLang = computed({
  72. get: () => (props.carLang ? props.carLang : carLang.value),
  73. set: (value: CarKeyboardLang) => {
  74. carLang.value = value
  75. }
  76. })
  77. const keys = computed(() => (props.mode !== 'car' ? (props.mode === 'custom' ? genCustomKeys() : genDefaultKeys()) : genCarKeys()))
  78. const showClose = computed(() => {
  79. return props.closeText && (props.mode === 'default' || props.mode === 'car')
  80. })
  81. const showTitle = computed(() => {
  82. return !!props.title || !!slots.title
  83. })
  84. const showHeader = computed(() => {
  85. return showTitle.value || showClose.value
  86. })
  87. /**
  88. * 随机打乱数组的顺序
  89. * @param arr 要打乱顺序的数组
  90. * @returns 打乱顺序后的数组
  91. */
  92. function shuffleArray<T>(arr: T[]): T[] {
  93. const newArr = [...arr]
  94. for (let i = newArr.length - 1; i > 0; i--) {
  95. // 生成一个随机索引 j,范围是 [0, i]
  96. const j = Math.floor(Math.random() * (i + 1))
  97. // 交换索引 i 和 j 处的元素
  98. ;[newArr[i], newArr[j]] = [newArr[j], newArr[i]]
  99. }
  100. return newArr
  101. }
  102. function genBasicKeys(): Key[] {
  103. const keys = Array.from({ length: 9 }, (_, i) => ({ text: i + 1 }))
  104. // 如果需要随机顺序,则调用 shuffleArray 方法打乱数组顺序
  105. return props.randomKeyOrder ? shuffleArray(keys) : keys
  106. }
  107. function genDefaultKeys(): Key[] {
  108. return [
  109. ...genBasicKeys(),
  110. { text: props.extraKey as string, type: 'extra' },
  111. { text: 0 },
  112. {
  113. // 根据条件是否显示删除键的文本和类型
  114. text: props.showDeleteKey ? props.deleteText : '',
  115. type: props.showDeleteKey ? 'delete' : ''
  116. }
  117. ]
  118. }
  119. function genCustomKeys(): Key[] {
  120. const keys = genBasicKeys()
  121. const extraKeys = Array.isArray(props.extraKey) ? props.extraKey : [props.extraKey]
  122. if (extraKeys.length === 1) {
  123. // 如果只有一个额外按键,则添加一个宽度较大的数字0和一个额外按键
  124. keys.push({ text: 0, wider: true }, { text: extraKeys[0], type: 'extra' })
  125. } else if (extraKeys.length === 2) {
  126. // 如果有两个额外按键,则添加两个额外按键和一个数字0
  127. keys.push({ text: extraKeys[0], type: 'extra' }, { text: 0 }, { text: extraKeys[1], type: 'extra' })
  128. }
  129. return keys
  130. }
  131. // 生成车牌键盘
  132. function genCarKeys(): Array<Key> {
  133. const [keys, remainKeys] = splitCarKeys()
  134. return [
  135. ...keys,
  136. { text: carKeyboardLang.value === 'zh' ? 'ABC' : '省份', type: 'extra', wider: true },
  137. ...remainKeys,
  138. { text: props.deleteText, type: 'delete', wider: true }
  139. ]
  140. }
  141. function splitCarKeys(): Array<Array<Key>> {
  142. const keys = carKeyboardLang.value === 'zh' ? CAR_KEYBOARD_AREAS.map((key) => ({ text: key })) : CAR_KEYBOARD_KEYS.map((key) => ({ text: key }))
  143. return [keys.slice(0, 30), keys.slice(30)]
  144. }
  145. const handleClose = () => {
  146. emit('close')
  147. emit('update:visible', false)
  148. }
  149. const handlePress = (text: string, type: NumberKeyType) => {
  150. if (type === 'extra') {
  151. if (text === '') {
  152. return handleClose()
  153. } else if (text === 'ABC' || text === '省份') {
  154. const newLang = carKeyboardLang.value === 'zh' ? 'en' : 'zh'
  155. if (props.carLang) {
  156. emit('update:carLang', newLang)
  157. } else {
  158. carKeyboardLang.value = newLang
  159. }
  160. return
  161. }
  162. }
  163. const value = props.modelValue
  164. if (type === 'delete') {
  165. emit('delete')
  166. const newValue = value.slice(0, value.length - 1)
  167. emit('update:modelValue', newValue)
  168. if (props.mode === 'car' && newValue.length === 0 && props.autoSwitchLang) {
  169. carKeyboardLang.value = 'zh'
  170. }
  171. } else if (type === 'close') {
  172. handleClose()
  173. } else if (value.length < +props.maxlength) {
  174. emit('input', text)
  175. const newValue = value + text
  176. emit('update:modelValue', newValue)
  177. if (props.mode === 'car' && newValue.length === 1 && props.autoSwitchLang) {
  178. // 输入第一位(省份)后,自动切换到英文
  179. carKeyboardLang.value = 'en'
  180. }
  181. }
  182. }
  183. </script>
  184. <style lang="scss">
  185. @import './index.scss';
  186. </style>