消毒机设备
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

427 lines
10 KiB

4 weeks ago
3 weeks ago
2 months ago
3 weeks ago
4 weeks ago
4 weeks ago
3 weeks ago
4 weeks ago
3 weeks ago
2 months ago
4 weeks ago
4 weeks ago
4 weeks ago
2 months ago
4 weeks ago
4 weeks ago
4 weeks ago
3 weeks ago
4 weeks ago
4 weeks ago
2 months ago
2 months ago
4 weeks ago
3 weeks ago
3 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
3 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
3 weeks ago
3 weeks ago
3 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
4 weeks ago
2 months ago
2 months ago
4 weeks ago
2 months ago
4 weeks ago
4 weeks ago
4 weeks ago
2 months ago
  1. <script lang="ts" setup>
  2. import pinyinDict from 'libs/pinyinDict.json'
  3. import { useDeviceStore } from 'stores/deviceStore'
  4. import type { Ref } from 'vue'
  5. import { computed, onMounted, ref, watch, watchEffect } from 'vue'
  6. const props = defineProps<{
  7. modelValue: string
  8. keyboardType: 'text' | 'number'
  9. isVisible: boolean
  10. }>()
  11. const emits = defineEmits<{
  12. (e: 'update:modelValue', value: string): void
  13. (e: 'updateKeyboardVisible', value: boolean): void
  14. (e: 'confirm', value: string): void
  15. (e: 'close'): void
  16. }>()
  17. const deviceStete = useDeviceStore()
  18. const languageType = ref('en')
  19. const inputValue = ref(props.modelValue)
  20. const cnList = ref<string[]>([])
  21. const pinyinMap: Record<string, string[]> = pinyinDict
  22. const pinyinValue = ref('')
  23. // 拖动相关状态
  24. const isDragging = ref(false) // 是否正在拖动
  25. const startX = ref(0) // 触摸起始 X
  26. const startY = ref(0) // 触摸起始 Y
  27. const x = ref(0) // 容器偏移 X
  28. const y = ref(-50) // 容器偏移 Y
  29. const keyboardRef = ref() as Ref<HTMLDivElement> // 软键盘容器 DOM
  30. onMounted(() => {
  31. document.addEventListener('click', (e: any) => {
  32. if (isOpen.value && !e.target?.name) {
  33. isOpen.value = false
  34. emits('updateKeyboardVisible', false)
  35. }
  36. })
  37. })
  38. const isOpen = ref(false)
  39. watchEffect(() => {
  40. // 在焦点内二次点击时不触发EventListener,做一下延迟处理
  41. console.log('deviceStete.deviceType--11-', deviceStete.isLowCost)
  42. if (!deviceStete.isLowCost) {
  43. setTimeout(() => {
  44. isOpen.value = props.isVisible
  45. }, 100)
  46. }
  47. inputValue.value = props.modelValue
  48. })
  49. watch(
  50. () => props.isVisible,
  51. (newVal) => {
  52. console.log('deviceStete.deviceType--2-', deviceStete.isLowCost)
  53. if (!deviceStete.isLowCost) {
  54. isOpen.value = newVal
  55. }
  56. },
  57. )
  58. const activeKey = ref('')
  59. const keyboardLayout = computed(() => {
  60. if (props.keyboardType === 'number') {
  61. return [
  62. ['1', '2', '3'],
  63. ['4', '5', '6'],
  64. ['7', '8', '9'],
  65. ['.', '0', 'del'],
  66. ]
  67. }
  68. return [
  69. ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'del'],
  70. ['q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '/'],
  71. ['a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', 'enter'],
  72. ['z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '?', '.', ':'],
  73. ['close', ' ', 'en'],
  74. ]
  75. })
  76. const specialKeys = ['del', 'enter', ' ']
  77. const handleKeyCn = (cn: string) => {
  78. emits('update:modelValue', props.modelValue + cn)
  79. cnList.value = []
  80. pinyinValue.value = ''
  81. }
  82. const handleKeyPress = (key: string) => {
  83. activeKey.value = key
  84. setTimeout(() => {
  85. activeKey.value = ''
  86. }, 150)
  87. if (key === 'del') {
  88. if (props.keyboardType === 'text' && languageType.value === 'cn' && pinyinValue.value) {
  89. pinyinValue.value = pinyinValue.value.slice(0, -1)
  90. // 中文
  91. onHandlePinyinToCn(pinyinValue.value)
  92. }
  93. else {
  94. emits('update:modelValue', props.modelValue.slice(0, -1))
  95. }
  96. }
  97. else if (key === 'enter') {
  98. emits('confirm', props.modelValue)
  99. closeKeyboard()
  100. }
  101. else if (key === 'close') {
  102. closeKeyboard()
  103. }
  104. else if ((key === 'en' || key === 'cn') && props.keyboardType === 'text') {
  105. languageType.value = key === 'en' ? 'cn' : 'en'
  106. keyboardLayout.value[4][2] = key === 'en' ? 'cn' : 'en'
  107. cnList.value = []
  108. }
  109. else {
  110. if (props.keyboardType === 'text' && languageType.value === 'cn') {
  111. // 中文
  112. pinyinValue.value = pinyinValue.value + key
  113. onHandlePinyinToCn(pinyinValue.value)
  114. }
  115. else {
  116. emits('update:modelValue', props.modelValue + key)
  117. }
  118. }
  119. }
  120. const onHandlePinyinToCn = (keyValue: string) => {
  121. const cn: string[] = pinyinMap[keyValue]
  122. if (cn && cn.length) {
  123. cnList.value = cn
  124. }
  125. else {
  126. cnList.value = []
  127. }
  128. }
  129. const closeKeyboard = () => {
  130. isOpen.value = false
  131. emits('close')
  132. }
  133. // 触摸开始:记录初始位置
  134. const handleTouchStart = (e: TouchEvent) => {
  135. isDragging.value = true
  136. const touch = e.touches[0]
  137. startX.value = touch.clientX - x.value
  138. startY.value = touch.clientY - y.value
  139. }
  140. // 触摸移动:计算偏移量
  141. const handleTouchMove = (e: TouchEvent) => {
  142. if (isDragging.value) {
  143. const touch = e.touches[0]
  144. x.value = touch.clientX - startX.value
  145. y.value = touch.clientY - startY.value
  146. // const keyboardRect = keyboardRef.value.getBoundingClientRect()
  147. // // 拖动时不超出左边界
  148. // if (newX < 0) {
  149. // newX = 0
  150. // }
  151. // // 拖动时不超出右边界
  152. // else if ((newX + keyboardRect.width) > window.innerWidth) {
  153. // newX = window.innerWidth - keyboardRect.width
  154. // }
  155. // // 拖动时不超出上边界
  156. // if (keyboardRect.height + newY < 0) {
  157. // newY = keyboardRect.height - window.innerHeight + 50
  158. // }
  159. // // 拖动时不超出下边界
  160. // const maxY = keyboardRect.height - (window.innerHeight - keyboardRect.height) + 10
  161. // if (newY > maxY) {
  162. // newY = maxY
  163. // }
  164. // x.value = newX
  165. // y.value = newY
  166. }
  167. }
  168. // 触摸结束:停止拖动
  169. const handleTouchEnd = () => {
  170. isDragging.value = false
  171. }
  172. </script>
  173. <template>
  174. <div v-if="isOpen" class="soft-keyboard" :class="{ 'keyboard-open': isOpen }">
  175. <!-- <div class="keyboard-header">
  176. <button @click="closeKeyboard">
  177. 关闭键盘
  178. </button>
  179. </div> -->
  180. <div
  181. ref="keyboardRef"
  182. class="keyboard-container keyboard-body"
  183. :style="{
  184. transform: `translate(${x}px, ${y}px)`,
  185. transition: isDragging ? 'none' : 'transform 0.3s ease',
  186. width: keyboardType === 'number' ? '30vw' : '66vw',
  187. height: keyboardType === 'number' ? '46vh' : '46vh',
  188. }"
  189. @touchstart="handleTouchStart"
  190. @touchmove="handleTouchMove"
  191. @touchend="handleTouchEnd"
  192. >
  193. <div>
  194. <div v-if="keyboardType === 'text'" class="pinyin-container">
  195. <span v-if="pinyinValue" style="font-size: 12px">拼音{{ pinyinValue }}</span>
  196. <div v-if="cnList && cnList.length" class="pinyin-cn">
  197. <div
  198. v-for="(cnName, cnIndex) in cnList"
  199. :key="cnIndex"
  200. class="cn-name"
  201. @click="
  202. e => {
  203. e.stopPropagation();
  204. handleKeyCn(cnName);
  205. }
  206. "
  207. >
  208. {{ cnName }}
  209. </div>
  210. </div>
  211. </div>
  212. <div v-for="(row, index) in keyboardLayout" :key="index" class="keyboard-row">
  213. <button
  214. v-for="(key, keyIndex) in row"
  215. :key="keyIndex"
  216. :class="{
  217. 'key-space': key === ' ',
  218. 'key-special': specialKeys.includes(key),
  219. 'key-active': activeKey === key,
  220. 'key-number': keyboardType === 'number',
  221. 'key-text': key !== ' ' && keyboardType === 'text',
  222. }"
  223. :style="keyboardType === 'number' ? 'height: 10vh' : 'height:3rem;'"
  224. @click="
  225. e => {
  226. e.stopPropagation();
  227. handleKeyPress(key);
  228. }
  229. "
  230. >
  231. {{
  232. key === ' '
  233. ? '空格'
  234. : key === 'del'
  235. ? '删除'
  236. : key === 'enter'
  237. ? '确认'
  238. : key === 'close'
  239. ? '关闭'
  240. : key === 'cn'
  241. ? '英文'
  242. : key === 'en'
  243. ? '拼音'
  244. : key
  245. }}
  246. </button>
  247. </div>
  248. </div>
  249. </div>
  250. </div>
  251. </template>
  252. <style lang="scss" scoped>
  253. .soft-keyboard {
  254. bottom: -300px;
  255. left: 0;
  256. right: 0;
  257. background-color: #f5f5f5;
  258. border-top: 1px solid #ddd;
  259. padding: 10px;
  260. transition: bottom 0.3s ease;
  261. user-select: none;
  262. z-index: 9999;
  263. }
  264. .keyboard-open {
  265. bottom: 0;
  266. }
  267. .keyboard-header {
  268. margin-bottom: 10px;
  269. position: absolute;
  270. float: right;
  271. }
  272. .keyboard-header button {
  273. padding: 8px 15px;
  274. background-color: #e0e0e0;
  275. border: none;
  276. border-radius: 5px;
  277. cursor: pointer;
  278. }
  279. .keyboard-body {
  280. display: flex;
  281. flex-direction: column;
  282. gap: 8px;
  283. .pinyin-container {
  284. display: flex;
  285. width: 80%;
  286. height: 4rem;
  287. padding-left: 2rem;
  288. .pinyin-cn {
  289. color: #1890ff;
  290. padding-left: 1rem;
  291. display: flex;
  292. width: 1rem;
  293. position: relative;
  294. gap: 5px;
  295. //font-family: fangsong;
  296. .cn-name {
  297. font-size: 2.5rem;
  298. }
  299. }
  300. }
  301. }
  302. .keyboard-row {
  303. display: flex;
  304. justify-content: center;
  305. gap: 5px;
  306. }
  307. .keyboard-row button {
  308. min-width: 50px;
  309. font-size: 18px;
  310. border: none;
  311. border-radius: 5px;
  312. background-color: #fff;
  313. box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
  314. cursor: pointer;
  315. transition: all 0.2s;
  316. height: 10vh;
  317. }
  318. .keyboard-row button:hover {
  319. /* background-color: #e0e0e0; */
  320. }
  321. .keyboard-row button:active {
  322. transform: scale(0.95);
  323. }
  324. .key-space {
  325. margin-top: 3.5px;
  326. width: 75vw;
  327. }
  328. .key-special {
  329. background-color: #e0e0e0;
  330. }
  331. .key-active {
  332. background-color: #a0c4ff;
  333. transform: scale(0.95);
  334. }
  335. .key-number {
  336. width: 30vw;
  337. height: 6vh;
  338. margin: 5px;
  339. }
  340. .key-text {
  341. width: 8vw;
  342. height: 5vh;
  343. margin: 5px;
  344. }
  345. .input-w {
  346. width: 20%;
  347. height: 4rem;
  348. font-size: 2rem;
  349. margin-left: 3rem;
  350. }
  351. .keyboard-container {
  352. position: fixed;
  353. bottom: 0;
  354. left: 0;
  355. width: 90%;
  356. border-radius: 16px;
  357. box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.1);
  358. overflow: hidden;
  359. z-index: 9999;
  360. /* 让拖动更顺滑 */
  361. will-change: transform;
  362. touch-action: none; /* 禁止浏览器默认触摸行为(如滚动) */
  363. background: #c8c8c8;
  364. }
  365. .input-box {
  366. padding: 16px;
  367. background: #f5f5f5;
  368. }
  369. .input-field {
  370. width: 100%;
  371. padding: 12px;
  372. text-align: right;
  373. font-size: 18px;
  374. border: 1px solid #ddd;
  375. border-radius: 8px;
  376. background: #fff;
  377. }
  378. .keys {
  379. display: grid;
  380. grid-template-columns: repeat(3, 1fr);
  381. }
  382. .key {
  383. height: 60px;
  384. display: flex;
  385. justify-content: center;
  386. align-items: center;
  387. font-size: 20px;
  388. border: 1px solid #eee;
  389. background: #fff;
  390. touch-action: manipulation; /* 加速点击响应 */
  391. }
  392. .key:active {
  393. background: #f8f8f8;
  394. }
  395. </style>