|
|
<!-- components/IDCardInput.vue -->
|
|
|
<template>
|
|
|
<view class="idcard-input-wrapper">
|
|
|
<input v-model="inputValue" class="idcard-input" placeholder="请输入18位身份证号码" type="text" maxlength="18"
|
|
|
@input="handleInput" @blur="handleBlur" />
|
|
|
<text v-if="showClear && inputValue" class="clear-btn" @click="clearInput">×</text>
|
|
|
</view>
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
import {
|
|
|
ref,
|
|
|
watch
|
|
|
} from 'vue';
|
|
|
|
|
|
const props = defineProps({
|
|
|
modelValue: String,
|
|
|
showClear: {
|
|
|
type: Boolean,
|
|
|
default: true
|
|
|
}
|
|
|
});
|
|
|
|
|
|
const emit = defineEmits(['update:modelValue', 'change']);
|
|
|
|
|
|
const inputValue = ref(props.modelValue || '');
|
|
|
|
|
|
// 处理输入
|
|
|
const handleInput = (e) => {
|
|
|
const value = e.detail.value.toUpperCase();
|
|
|
inputValue.value = value;
|
|
|
emit('update:modelValue', value);
|
|
|
|
|
|
// 验证身份证格式
|
|
|
const isValid = validateIDCard(value);
|
|
|
emit('change', value, isValid);
|
|
|
};
|
|
|
|
|
|
// 失焦验证
|
|
|
const handleBlur = () => {
|
|
|
const isValid = validateIDCard(inputValue.value);
|
|
|
emit('change', inputValue.value, isValid);
|
|
|
};
|
|
|
|
|
|
// 清空输入
|
|
|
const clearInput = () => {
|
|
|
inputValue.value = '';
|
|
|
emit('update:modelValue', '');
|
|
|
emit('change', '', false);
|
|
|
};
|
|
|
|
|
|
// 身份证验证函数
|
|
|
const validateIDCard = (idCard) => {
|
|
|
if (!idCard) return false;
|
|
|
|
|
|
// 基本格式验证
|
|
|
const reg = /^[1-9]\d{5}(19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/;
|
|
|
if (!reg.test(idCard)) return false;
|
|
|
|
|
|
// 校验码验证
|
|
|
const idCardArray = idCard.split('');
|
|
|
const factor = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
|
|
|
const parity = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];
|
|
|
|
|
|
let sum = 0;
|
|
|
for (let i = 0; i < 17; i++) {
|
|
|
sum += parseInt(idCardArray[i]) * factor[i];
|
|
|
}
|
|
|
|
|
|
const mod = sum % 11;
|
|
|
return parity[mod] === idCardArray[17].toUpperCase();
|
|
|
};
|
|
|
|
|
|
// 监听外部值变化
|
|
|
watch(() => props.modelValue, (newVal) => {
|
|
|
if (newVal !== inputValue.value) {
|
|
|
inputValue.value = newVal || '';
|
|
|
}
|
|
|
});
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
.idcard-input-wrapper {
|
|
|
position: relative;
|
|
|
}
|
|
|
|
|
|
.idcard-input {
|
|
|
width: 100%;
|
|
|
height: 88rpx;
|
|
|
border: 2rpx solid #e0e0e0;
|
|
|
border-radius: 12rpx;
|
|
|
padding: 0 60rpx 0 24rpx;
|
|
|
font-size: 28rpx;
|
|
|
background: #fafafa;
|
|
|
box-sizing: border-box;
|
|
|
font-family: monospace;
|
|
|
}
|
|
|
|
|
|
.idcard-input:focus {
|
|
|
border-color: #007AFF;
|
|
|
background: white;
|
|
|
}
|
|
|
|
|
|
.clear-btn {
|
|
|
position: absolute;
|
|
|
right: 24rpx;
|
|
|
top: 50%;
|
|
|
transform: translateY(-50%);
|
|
|
font-size: 36rpx;
|
|
|
color: #999;
|
|
|
width: 40rpx;
|
|
|
height: 40rpx;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
border-radius: 50%;
|
|
|
background: rgba(0, 0, 0, 0.1);
|
|
|
}
|
|
|
</style> |