NumericKeyboard.vue 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. <template>
  2. <div class="numeric-keyboard">
  3. <!-- 输入框显示 -->
  4. <div class="keyboard-input" @click="$emit('focus')">
  5. <span class="input-label">{{ label }}</span>
  6. <div class="input-value">
  7. <span class="value-text">{{ displayValue || placeholder }}</span>
  8. </div>
  9. </div>
  10. <!-- 键盘 -->
  11. <div class="keyboard-keys">
  12. <button
  13. v-for="key in keys"
  14. :key="key"
  15. class="key-btn"
  16. :class="{
  17. 'key-action': isActionKey(key),
  18. 'key-delete': key === 'DEL',
  19. 'key-confirm': key === '确认'
  20. }"
  21. @click="handleKeyClick(key)"
  22. >
  23. <template v-if="key === 'DEL'">
  24. <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
  25. <path d="M21 4H8l-7 8 7 8h13a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2z" />
  26. <line x1="18" y1="9" x2="12" y2="15" />
  27. <line x1="12" y1="9" x2="18" y2="15" />
  28. </svg>
  29. </template>
  30. <template v-else>
  31. {{ key }}
  32. </template>
  33. </button>
  34. </div>
  35. </div>
  36. </template>
  37. <script setup>
  38. import { computed } from 'vue'
  39. const props = defineProps({
  40. modelValue: {
  41. type: String,
  42. default: ''
  43. },
  44. maxLength: {
  45. type: Number,
  46. default: 11
  47. },
  48. label: {
  49. type: String,
  50. default: '请输入'
  51. },
  52. placeholder: {
  53. type: String,
  54. default: ''
  55. },
  56. type: {
  57. type: String,
  58. default: 'phone' // 'phone' | 'idcard'
  59. }
  60. })
  61. const emit = defineEmits(['update:modelValue', 'confirm', 'focus'])
  62. const keys = computed(() => {
  63. if (props.type === 'idcard') {
  64. return ['1', '2', '3', '4', '5', '6', '7', '8', '9', 'X', '0', 'DEL']
  65. }
  66. return ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'DEL']
  67. })
  68. const displayValue = computed(() => {
  69. return props.modelValue
  70. })
  71. const isActionKey = (key) => {
  72. return ['DEL'].includes(key)
  73. }
  74. const handleKeyClick = (key) => {
  75. if (key === 'DEL') {
  76. // 删除
  77. const newValue = props.modelValue.slice(0, -1)
  78. emit('update:modelValue', newValue)
  79. } else if (key === '确认') {
  80. // 确认
  81. emit('confirm', props.modelValue)
  82. } else {
  83. // 输入
  84. if (props.modelValue.length < props.maxLength) {
  85. let newValue = props.modelValue + key
  86. // X 要大写
  87. if (key === 'X') {
  88. newValue = props.modelValue + 'X'
  89. }
  90. emit('update:modelValue', newValue)
  91. // 自动触发确认(身份证号18位、手机号11位)
  92. if (newValue.length === props.maxLength) {
  93. setTimeout(() => {
  94. emit('confirm', newValue)
  95. }, 200)
  96. }
  97. }
  98. }
  99. }
  100. </script>
  101. <style scoped>
  102. .numeric-keyboard {
  103. width: 100%;
  104. max-width: 480px;
  105. margin: 0 auto;
  106. }
  107. .keyboard-input {
  108. display: flex;
  109. flex-direction: row;
  110. align-items: center;
  111. gap: 16px;
  112. padding: 0 24px;
  113. height: 108px;
  114. min-height: 108px;
  115. max-height: 108px;
  116. box-sizing: border-box;
  117. background: linear-gradient(160deg, #f0f7ff 0%, #e8f4fd 100%);
  118. border: 2px solid rgba(59, 130, 246, 0.18);
  119. border-radius: 20px;
  120. margin-bottom: 20px;
  121. cursor: pointer;
  122. transition: border-color var(--transition-fast);
  123. }
  124. .keyboard-input:focus-within {
  125. border-color: var(--primary);
  126. }
  127. .input-label {
  128. font-size: 30px;
  129. color: var(--text-muted);
  130. font-weight: 700;
  131. flex-shrink: 0;
  132. letter-spacing: 0.5px;
  133. line-height: 1;
  134. }
  135. .input-value {
  136. display: flex;
  137. align-items: center;
  138. flex: 1;
  139. height: 100%;
  140. justify-content: flex-end;
  141. }
  142. .value-text {
  143. font-size: 38px;
  144. font-weight: 700;
  145. color: var(--text-primary);
  146. letter-spacing: 4px;
  147. font-family: 'Courier New', monospace;
  148. line-height: 1;
  149. white-space: nowrap;
  150. }
  151. .placeholder {
  152. font-size: 38px;
  153. font-weight: 700;
  154. color: #b0bec5;
  155. letter-spacing: 2px;
  156. line-height: 1;
  157. white-space: nowrap;
  158. }
  159. .keyboard-keys {
  160. display: grid;
  161. grid-template-columns: repeat(3, 1fr);
  162. gap: 12px;
  163. }
  164. .key-btn {
  165. height: 72px;
  166. font-size: 28px;
  167. font-weight: 500;
  168. color: var(--text-primary);
  169. background: var(--bg-card);
  170. border: 1px solid var(--border-light);
  171. border-radius: var(--radius-lg);
  172. cursor: pointer;
  173. transition: all var(--transition-fast);
  174. display: flex;
  175. align-items: center;
  176. justify-content: center;
  177. }
  178. .key-btn:hover {
  179. background: var(--primary-soft);
  180. border-color: var(--primary);
  181. color: var(--primary);
  182. }
  183. .key-btn:active {
  184. transform: scale(0.95);
  185. background: var(--primary);
  186. color: white;
  187. }
  188. .key-btn svg {
  189. width: 28px;
  190. height: 28px;
  191. }
  192. .key-action {
  193. background: var(--bg-page);
  194. }
  195. .key-delete {
  196. color: var(--text-secondary);
  197. }
  198. .key-confirm {
  199. background: var(--primary);
  200. color: white;
  201. border-color: var(--primary);
  202. }
  203. .key-confirm:hover {
  204. background: var(--primary-dark);
  205. color: white;
  206. }
  207. </style>