| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232 |
- <template>
- <div class="numeric-keyboard">
- <!-- 输入框显示 -->
- <div class="keyboard-input" @click="$emit('focus')">
- <span class="input-label">{{ label }}</span>
- <div class="input-value">
- <span class="value-text">{{ displayValue || placeholder }}</span>
- </div>
- </div>
- <!-- 键盘 -->
- <div class="keyboard-keys">
- <button
- v-for="key in keys"
- :key="key"
- class="key-btn"
- :class="{
- 'key-action': isActionKey(key),
- 'key-delete': key === 'DEL',
- 'key-confirm': key === '确认'
- }"
- @click="handleKeyClick(key)"
- >
- <template v-if="key === 'DEL'">
- <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
- <path d="M21 4H8l-7 8 7 8h13a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2z" />
- <line x1="18" y1="9" x2="12" y2="15" />
- <line x1="12" y1="9" x2="18" y2="15" />
- </svg>
- </template>
- <template v-else>
- {{ key }}
- </template>
- </button>
- </div>
- </div>
- </template>
- <script setup>
- import { computed } from 'vue'
- const props = defineProps({
- modelValue: {
- type: String,
- default: ''
- },
- maxLength: {
- type: Number,
- default: 11
- },
- label: {
- type: String,
- default: '请输入'
- },
- placeholder: {
- type: String,
- default: ''
- },
- type: {
- type: String,
- default: 'phone' // 'phone' | 'idcard'
- }
- })
- const emit = defineEmits(['update:modelValue', 'confirm', 'focus'])
- const keys = computed(() => {
- if (props.type === 'idcard') {
- return ['1', '2', '3', '4', '5', '6', '7', '8', '9', 'X', '0', 'DEL']
- }
- return ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'DEL']
- })
- const displayValue = computed(() => {
- return props.modelValue
- })
- const isActionKey = (key) => {
- return ['DEL'].includes(key)
- }
- const handleKeyClick = (key) => {
- if (key === 'DEL') {
- // 删除
- const newValue = props.modelValue.slice(0, -1)
- emit('update:modelValue', newValue)
- } else if (key === '确认') {
- // 确认
- emit('confirm', props.modelValue)
- } else {
- // 输入
- if (props.modelValue.length < props.maxLength) {
- let newValue = props.modelValue + key
- // X 要大写
- if (key === 'X') {
- newValue = props.modelValue + 'X'
- }
- emit('update:modelValue', newValue)
- // 自动触发确认(身份证号18位、手机号11位)
- if (newValue.length === props.maxLength) {
- setTimeout(() => {
- emit('confirm', newValue)
- }, 200)
- }
- }
- }
- }
- </script>
- <style scoped>
- .numeric-keyboard {
- width: 100%;
- max-width: 480px;
- margin: 0 auto;
- }
- .keyboard-input {
- display: flex;
- flex-direction: row;
- align-items: center;
- gap: 16px;
- padding: 0 24px;
- height: 108px;
- min-height: 108px;
- max-height: 108px;
- box-sizing: border-box;
- background: linear-gradient(160deg, #f0f7ff 0%, #e8f4fd 100%);
- border: 2px solid rgba(59, 130, 246, 0.18);
- border-radius: 20px;
- margin-bottom: 20px;
- cursor: pointer;
- transition: border-color var(--transition-fast);
- }
- .keyboard-input:focus-within {
- border-color: var(--primary);
- }
- .input-label {
- font-size: 30px;
- color: var(--text-muted);
- font-weight: 700;
- flex-shrink: 0;
- letter-spacing: 0.5px;
- line-height: 1;
- }
- .input-value {
- display: flex;
- align-items: center;
- flex: 1;
- height: 100%;
- justify-content: flex-end;
- }
- .value-text {
- font-size: 38px;
- font-weight: 700;
- color: var(--text-primary);
- letter-spacing: 4px;
- font-family: 'Courier New', monospace;
- line-height: 1;
- white-space: nowrap;
- }
- .placeholder {
- font-size: 38px;
- font-weight: 700;
- color: #b0bec5;
- letter-spacing: 2px;
- line-height: 1;
- white-space: nowrap;
- }
- .keyboard-keys {
- display: grid;
- grid-template-columns: repeat(3, 1fr);
- gap: 12px;
- }
- .key-btn {
- height: 72px;
- font-size: 28px;
- font-weight: 500;
- color: var(--text-primary);
- background: var(--bg-card);
- border: 1px solid var(--border-light);
- border-radius: var(--radius-lg);
- cursor: pointer;
- transition: all var(--transition-fast);
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .key-btn:hover {
- background: var(--primary-soft);
- border-color: var(--primary);
- color: var(--primary);
- }
- .key-btn:active {
- transform: scale(0.95);
- background: var(--primary);
- color: white;
- }
- .key-btn svg {
- width: 28px;
- height: 28px;
- }
- .key-action {
- background: var(--bg-page);
- }
- .key-delete {
- color: var(--text-secondary);
- }
- .key-confirm {
- background: var(--primary);
- color: white;
- border-color: var(--primary);
- }
- .key-confirm:hover {
- background: var(--primary-dark);
- color: white;
- }
- </style>
|