消毒机设备
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.

403 lines
9.6 KiB

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