This commit is contained in:
2026-09-24 16:25:22 +08:00
commit 7428184f01
1198 changed files with 314515 additions and 0 deletions
@@ -0,0 +1,130 @@
export function validateIdCard(idCard: string):boolean {
let vcity = new Map<number,string>([
[11, "北京"], [12, "天津"], [13, "河北"], [14, "山西"], [15, "内蒙古"],
[21, "辽宁"], [22, "吉林"], [23, "黑龙江"], [31, "上海"], [32, "江苏"],
[33, "浙江"], [34, "安徽"], [35, "福建"], [36, "江西"], [37, "山东"], [41, "河南"],
[42, "湖北"], [43, "湖南"], [44, "广东"], [45, "广西"], [46, "海南"], [50, "重庆"],
[51, "四川"], [52, "贵州"], [53, "云南"], [54, "西藏"], [61, "陕西"], [62, "甘肃"],
[63, "青海"], [64, "宁夏"], [65, "新疆"], [71, "台湾"], [81, "香港"], [82, "澳门"], [91, "国外"]
]);;
//是否为空
if (idCard === '') {
return false;
}
//校验长度,类型
if (isCardNo(idCard) === false) {
return false;
}
//检查省份
if (checkProvince(idCard, vcity) === false) {
return false;
}
//校验生日
if (checkBirthday(idCard) === false) {
return false;
}
//检验位的检测
if (checkParity(idCard) === false) {
return false;
}
return true;
}
function isCardNo(card:string):boolean {
//身份证号码为15位或者18位,15位时全为数字,18位前17位为数字,最后一位是校验位,可能为数字或字符X
let reg = /(^\d{15}$)|(^\d{17}(\d|X|x)$)/;
if (reg.test(card) === false) {
return false;
}
return true;
}
function checkProvince(card:string, vcity:Map<number,string>):boolean {
let province = card.substring(0,2)
if (!vcity.has(parseInt(province))) {
return false;
}
return true;
};
function checkBirthday(card:string):boolean {
let len = card.length;
//身份证15位时,次序为省(3位)市(3位)年(2位)月(2位)日(2位)校验位(3位),皆为数字
if (len == 15) {
let re_fifteen = /^(\d{6})(\d{2})(\d{2})(\d{2})(\d{3})$/;
let arr_data = card.match(re_fifteen);
if(arr_data==null) return false
let year = arr_data[2];
let month = arr_data[3];
let day = arr_data[4];
if(year==null||month==null||day==null) return false
let birthday = new Date('19' + year + '/' + month + '/' + day);
return verifyBirthday(parseInt('19' + year), parseInt(month), parseInt(day), birthday);
}
//身份证18位时,次序为省(3位)市(3位)年(4位)月(2位)日(2位)校验位(4位),校验位末尾可能为X
if (len == 18) {
let re_eighteen = /^(\d{6})(\d{4})(\d{2})(\d{2})(\d{3})([0-9]|X|x)$/;
let arr_data = card.match(re_eighteen);
if(arr_data==null) return false
let year = arr_data[2];
let month = arr_data[3];
let day = arr_data[4];
if(year==null||month==null||day==null) return false
let birthday = new Date(year + '/' + month + '/' + day);
return verifyBirthday(parseInt(year), parseInt(month), parseInt(day), birthday);
}
return false;
};
function verifyBirthday(year:number, month:number, day:number, birthday:Date):boolean {
let now = new Date();
let now_year = now.getFullYear();
//年月日是否合理
if (birthday.getFullYear() == year && (birthday.getMonth() + 1) == month && birthday.getDate() == day) {
//判断年份的范围(0岁到100岁之间)
let time = now_year - year;
if (time >= 0 && time <= 100) {
return true;
}
return false;
}
return false;
}
function checkParity(card:string):boolean {
//15位转18位
card = changeFivteenToEighteen(card);
let len = card.length;
if (len == 18) {
let arrInt = new Array(7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2);
let arrCh = new Array('1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2');
let cardTemp = 0
let i =0;
let valnum = '';
for (i = 0; i < 17; i++) {
cardTemp += parseInt(card.charAt(i)) * arrInt[i];
}
valnum = arrCh[cardTemp % 11];
if (valnum == card.substring(17).toLocaleUpperCase()) {
return true;
}
return false;
}
return false;
}
function changeFivteenToEighteen(card:string):string {
if (card.length == 15) {
let arrInt = new Array(7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2);
let arrCh = new Array('1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2');
let cardTemp = 0
let i =0;
card = card.substring(0, 6) + '19' + card.substring(6, card.length - 6);
for (i = 0; i < 17; i++) {
cardTemp += parseInt(card.substring(i, 1)) * arrInt[i];
}
card += arrCh[cardTemp % 11];
return card;
}
return card;
}
@@ -0,0 +1,356 @@
<script lang="ts" setup>
import { ref, computed, watch, onMounted, onBeforeUnmount } from "vue"
import { getDefaultColor } from "../../core/util/xCoreColorUtil.uts"
import { xConfig, xProvitae } from "../../config/xConfig.uts"
import { checkIsCssUnit, getUnit } from "../../core/util/xCoreUtil.uts"
import { validateIdCard } from "./idcard.uts"
/**
* @name 身份证键盘 xKeyboardIdcard
* @description 自动解析第二代身份证号,验证用户输入的是否正确,事件中能得到当前输入值及校验值。
* @page /pages/index/keyboard
* @category 表单组件
* @constant 平台兼容
* | Harmony | H5 | andriod | IOS | 小程序 | UTS | UNIAPP-X SDK | version |
| --- | --- | --- | --- | --- | --- | --- | --- |
| ☑ | ☑ | ☑️ | ☑️ | ☑️ | ☑️ | 4.76+ | 1.1.18 |
*/
defineOptions({name:"xKeyboardIdcard"})
const i18n = xConfig.i18n
defineSlots<{
default(props:{show:boolean}): any
}>()
const emits = defineEmits([
/**
* 值变化时触发
* @param {string} value - 当前值
*/
'change',
/**
* 变量控制打开状态
* 等同v-model:model-show
*/
'update:modelShow',
/**
* 确认时触发
* @param {string} value - 当前值
* @param {boolean} pass - 是否验证通过
*/
'confirm',
/**
* 关闭取消时触发
* @param {string} value - 当前值
*/
'cancel',
'update:modelValue'
])
type xKeyboardIdcardPropsType = {
/**
* 当前输入的值
*/
modelValue: string,
/**
* 最大长度
*/
maxLen: number,
/**
* 当前打开的状态。
* 等同v-model:model-show
*/
modelShow: boolean,
/**
* 顶部标题,默认:安全键盘请放心输入
*/
title: string,
/**
* 主按钮色,空值取全局主题
*/
color: string,
/**
* 按钮背景,暗黑时会取二级inputDarkbg灰
*/
btnColor: string,
/**
* 键盘背景
*/
bgColor: string,
/**
* 文字颜色,暗黑是会取白。
*/
fontColor: string,
/**
* 点击确认是否保持键盘不收起
*/
hold: boolean
}
const props = withDefaults(defineProps<xKeyboardIdcardPropsType>(), {
modelValue: "",
maxLen: 18,
modelShow: false,
title: "",
color: "",
btnColor: 'white',
bgColor: 'info',
fontColor: '#3b3b3b',
hold: false
})
// 响应式数据
const show = ref(false)
const nowValue = ref("")
const numbList = ref([
['1', '2', '3'],
['4', '5', '6'],
['7', '8', '9'],
["00", "0", "x"]
])
const tid = ref(0)
// 计算属性
const _hold = computed((): boolean => props.hold)
const _color = computed((): string => {
if (props.color == '') return getDefaultColor(xConfig.color)
return getDefaultColor(props.color)
})
const _btnColor = computed((): string => {
if (xConfig.dark == 'dark') return xConfig.inputDarkColor
return getDefaultColor(props.btnColor)
})
const _bgColor = computed((): string => {
return getDefaultColor(props.bgColor)
})
const _fontColor = computed((): string => {
if (xConfig.dark == 'dark') return "#ffffff"
return getDefaultColor(props.fontColor)
})
const _title = computed((): string => {
if(props.title=='') return i18n.t('tmui4x.keyboard.placeholder')
return props.title
})
const _isPass = computed((): boolean => {
return validateIdCard(nowValue.value)
})
// 方法
function getFontSize(k: string): string {
return checkIsCssUnit(k, xConfig.unit)
}
function openShow(): void {
show.value = true;
/**
* 变量控制打开状态
* 等同v-model:model-show
*/
emits('update:modelShow', true)
}
function onCancel(): void {
/**
* 关闭取消时触发
*/
emits('cancel', nowValue.value);
}
function onClose(): void {
emits('update:modelShow', false)
}
function ok(): void {
/**
* 点击确认时触发
*/
emits('confirm', nowValue.value, validateIdCard(nowValue.value));
if(!_hold.value){
show.value = false;
emits('update:modelShow', false)
}
}
function del(): void {
if (nowValue.value.split('').length == 0) return;
let stp = nowValue.value.split('');
stp = stp.slice(0, stp.length - 1)
nowValue.value = stp.join("")
/**
* 等同v-model
*/
emits('update:modelValue', nowValue.value);
/**
* 值变化时触发
* @paramt {string} value
*/
emits('change', nowValue.value);
}
function itemClick(value: string): void {
let isDem = nowValue.value.lastIndexOf('.') > -1;
let isMaxvalu = nowValue.value.split('').length >= props.maxLen;
if (isMaxvalu) {
uni.showToast({ title: '最多输入' + props.maxLen.toString() + '位数', icon: 'error' })
return;
}
if (isDem && value == 'x') return;
if ((nowValue.value.split('').length == 0 && value == '00') || (nowValue.value.split('').length == 0 && value == 'x')) return
nowValue.value = nowValue.value + value;
/**
* 等同v-model
*/
emits('update:modelValue', nowValue.value);
/**
* 值变化时触发
* @paramt {string} value
*/
emits('change', nowValue.value);
}
// 监听器
watch((): string => props.modelValue, (newvalue: string) => {
if (newvalue == nowValue.value) return;
nowValue.value = newvalue
})
watch((): boolean => props.modelShow, (newValue: boolean) => {
if (newValue == show.value) return;
show.value = newValue
})
// 生命周期
onMounted(() => {
nowValue.value = props.modelValue
if (props.modelShow) {
tid.value = setTimeout(function () {
show.value = props.modelShow
}, 200);
}
})
onBeforeUnmount(() => {
clearTimeout(tid.value)
})
</script>
<template>
<view @click="openShow">
<!--
@slot 插槽,默认触发打开选择器。你的默认布局可以放置在这里。
@prop {boolean} show - 控制打开关闭状态
-->
<slot :show="show"></slot>
</view>
<x-drawer @close="onClose" :widthCoverCenter="true" :disabled-scroll="true" :bgColor="_bgColor" size="auto" overflayBgColor="rgba(0,0,0,0)" :title="title"
@cancel="onCancel" v-model:show="show" :show-close="true">
<template v-slot:title >
<view style="height: 44px;display: flex;justify-content: center;align-items: center;flex-direction: row;">
<x-icon style="margin-right: 5px;" v-if="_isPass" name="checkbox-circle-fill" color="success"></x-icon>
<x-icon style="margin-right: 5px;" v-if="!_isPass&&nowValue.length>0" color="warn" name="error-warning-fill"></x-icon>
<text :style="{fontWeight:'bold',fontSize:nowValue.split('').length>0?getFontSize('16'):getFontSize('12'),color:_isPass?'rgb(9, 204, 84)':_fontColor}">{{nowValue.split('').length>0?nowValue:_title}}</text>
</view>
</template>
<template v-slot:default>
<view class="xKeyboardNumber">
<view class="xKeyboardLeft">
<view v-for="(item,index) in numbList" :key="index" class="xKeyboardLeftLine">
<view @click="itemClick(item2)" v-for="(item2,index2) in item" :key="index2"
class="xKeyboardItem" :hover-start-time="20" :hover-stay-time="250"
hover-class="xKeyboardHover" :style="{backgroundColor:_btnColor}">
<text :style="{color:_fontColor}" class="xKeyboardText">{{item2}}</text>
</view>
</view>
</view>
<view class="xKeyboardRight">
<view :style="{backgroundColor:_btnColor,height:'50px'}" @click="del" class="xKeyboardItemDel xKeyboardItemNoright"
hover-class="xKeyboardHover" :hover-start-time="10" :hover-stay-time="250">
<x-icon :color="_fontColor" name="delete-back-2-line" font-size="24"></x-icon>
</view>
<view @click="ok" :style="{backgroundColor:_color}" class="xKeyboardItem xKeyboardItemNoright"
hover-class="xKeyboardHover" :hover-start-time="10" :hover-stay-time="250">
<!-- <x-icon name="check-line" font-size="38" color="white"></x-icon> -->
<text style="color: white;font-size: 16px;">
<!-- 确认 -->
{{i18n.t('tmui4x.keyboard.confirm')}}
</text>
</view>
</view>
</view>
<view style="height:12px"></view>
</template>
</x-drawer>
</template>
<style scoped>
.xKeyboardText {
font-weight: bold;
font-size: 20px;
}
.xKeyboardHover {
opacity: 0.2;
}
.xKeyboardLeftLine {
display: flex;
flex-direction: row;
}
.xKeyboardNumber {
display: flex;
flex-direction: row;
}
.xKeyboardLeft {
flex: 1;
}
.xKeyboardItem {
flex: 1;
height: 50px;
/* background-color: white; */
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
margin-right: 5px;
margin-bottom: 5px;
border-radius: 6px;
}
.xKeyboardItemDel {
height: 50px;
/* background-color: white; */
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
margin-right: 5px;
margin-bottom: 5px;
border-radius: 6px;
}
.xKeyboardItemNoright {
margin-right: 0px;
}
.xKeyboardRight {
width: 140rpx;
display: flex;
flex-direction: column;
}
</style>