index.vue 11.2 KB
<!--
 * @Date: 2022-09-14 14:44:30
 * @LastEditors: hookehuyr hookehuyr@gmail.com
 * @LastEditTime: 2024-11-21 10:39:09
 * @FilePath: /data-table/src/components/IdentityField/index.vue
 * @Description: 身份证输入控件
-->
<template>
  <div v-if="HideShow" class="identity-page">
    <div class="label">
      <span v-if="item.component_props.disabled_show"><van-icon name="https://cdn.ipadbiz.cn/custom_form/icon/closed-eye1.png" /></span>
      <span v-if="item.component_props.required" style="color: red">&nbsp;*</span>
      <span :class="[ReadonlyShow ? 'readonly-show' : '']">{{ item.component_props.label }}</span>
    </div>
    <!-- <div v-if="item.component_props.readonly" style="padding: 0.5rem 1rem;">{{ item.value }}</div> -->
    <!-- <van-field
      ref="fieldRef"
      v-model="item.value"
      :id="item.name"
      :name="item.name"
      :placeholder="item.component_props.placeholder"
      :rules="rules"
      :required="item.component_props.required"
      :disabled="item.component_props.readonly"
      :readonly="!edit_mode"
      right-icon="edit" @click-right-icon="clickRightIcon"
      @touchstart.stop="openKeyboard($event)"
      :border="false"
    >
    </van-field> -->
    <div v-if="item.component_props.readonly && data_type !== 'encrypt_mode'" style="padding: 0.5rem 1rem; font-size: 0.9rem;">
      {{ item.value }}
    </div>
    <van-field
      v-else
      ref="fieldRef"
      v-model="item.value"
      :id="item.name"
      :name="item.name"
      :placeholder="item.component_props.placeholder"
      :rules="rules"
      :required="item.component_props.required"
      :readonly="item.component_props.readonly"
      :border="false"
      @blur="onBlur(item)"
    >
      <template #button>
        <van-button size="small" type="primary" v-if="IdEditShow" @click="clickEdit()">编辑</van-button>
      </template>
    </van-field>
    <!-- <div v-if="gender" class="gender"><span>性别:</span>{{ gender }}</div> -->
    <van-number-keyboard
      v-model="item.value"
      :show="show"
      extra-key="X"
      close-button-text="完成"
      @blur="blurKeyboard()"
      @input="onInput"
      @delete="onDelete"
      safe-area-inset-bottom
    />
  </div>
</template>

<script setup>
import { useRoute } from "vue-router";
import $ from "jquery";
import { storeToRefs, mainStore } from "@/utils/generatePackage";
import { showSuccessToast, showFailToast, showConfirmDialog } from "vant";
// import idCard from "idcard";
import { styleColor } from "@/constant.js";
import Cookies from 'js-cookie';

const $route = useRoute();
const props = defineProps({
  item: Object,
});
const emit = defineEmits(["blur"]);

// 隐藏显示
const HideShow = computed(() => {
  return !props.item.component_props.disabled
});
// 只读显示-流程模式
const ReadonlyShow = computed(() => {
  return ($route.query.page_type === 'flow' || $route.query.page_type === 'edit') && !props.item.component_props.readonly;
});
// 是否显示编辑按钮
const IdEditShow = computed(() => {
  return ($route.query.page_type === 'edit') && data_type === 'encrypt_mode';
});
onMounted(() => {
  props.item.value = props.item.component_props.default;
})
const show = ref(false);
let content = "";

const store = mainStore();
const { fieldName } = storeToRefs(store);

// 监听字段变化
watch(
  () => fieldName.value,
  (v) => {
    // 如果不是点击本输入框
    if (v !== props.item.name) {
      // 还原border颜色
      $(`#${props.item.name}`).parent().css("border-color", "#eaeaea");
      show.value = false;
      document.getElementById("app").style.paddingBottom = "0";
    }
  }
);

const data_type = ref('');
const clickEdit = () => {
  showConfirmDialog({
    title: '温馨提示',
    message:
      '身份证号将重置后重新输入,是否确认?',
    confirmButtonColor: styleColor.baseColor
  })
    .then(() => {
      // 重置输入显示
      props.item.value = '';
      props.item.component_props.readonly = false;
      data_type.value = '';
    })
    .catch(() => {
      // on cancel
    });
}

// 监听默认值变化
watch(
  () => props.item.component_props.default,
  (v) => {
    if (v) {
      props.item.value = v;
      // 确保v是字符串类型后再调用indexOf方法
      const vStr = String(v);
      if (vStr.indexOf('*') !== -1) { // 默认值是加密模式
        props.item.component_props.readonly = true;
        data_type.value = 'encrypt_mode';
      }
    }
  }, {
    immediate: true,
  }
);

var checkProv = function (val) {
    var pattern = /^[1-9][0-9]/;
    var provs = {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:"澳门"};
    if(pattern.test(val)) {
        if(provs[val]) {
            return true;
        }
    }
    return false;
}

var checkDate = function (val) {
    var pattern = /^(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)$/;
    if(pattern.test(val)) {
        var year = val.substring(0, 4);
        var month = val.substring(4, 6);
        var date = val.substring(6, 8);
        var date2 = new Date(year+"-"+month+"-"+date);
        if(date2 && date2.getMonth() == (parseInt(month) - 1)) {
            return true;
        }
    }
    return false;
}

var checkCode = function (val) {
    var p = /^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/;
    var factor = [ 7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2 ];
    var parity = [ 1, 0, 'X', 9, 8, 7, 6, 5, 4, 3, 2 ];
    var code = val.substring(17);
    if(p.test(val)) {
        var sum = 0;
        for(var i=0;i<17;i++) {
            sum += val[i]*factor[i];
        }
        if(parity[sum % 11] == code.toUpperCase()) {
            return true;
        }
    }
    return false;
}

const idCard = {
  verify: function (val) {
      // 确保val是字符串类型
      const valStr = String(val || '');
      if (valStr.indexOf('*') !== -1 && data_type.value === 'encrypt_mode') { // 默认值是加密模式,跳过校验
        return true;
      }
      if (checkCode(valStr)) {
          var date = valStr.substring(6,14);
          if (checkDate(date)) {
              if (checkProv(valStr.substring(0,2))) {
                return true;
              }
          }
      }
      return false;
  }
}

const readonly = props.item.component_props.readonly;
const fieldRef = ref(null);
const edit_mode = ref(false);
const clickRightIcon = () => { // 编辑模式
  edit_mode.value = true;
  nextTick(() => {
    fieldRef.value?.focus();
  })
}
const openKeyboard = (e) => {
  if (readonly || e.target.className.indexOf('edit') > 0 || edit_mode.value) return false; // 如果为只读或者编辑模式,不能设置
  // // 键盘上移动
  // const target_to_view_height = window.innerHeight - e.target.getBoundingClientRect().y; // 元素到适口高度
  // const target_top = document.body.scrollHeight - $(e.target).offset().top; // 元素到正文高度
  // let scroll_height = "";
  // console.warn(target_top);
  // if (target_top < 250) {
  //   document.getElementById("app").style.paddingBottom = "250px";
  //   window.scrollTo(0, $("#app").height());
  // } else {
  //   // 向上滚动位置
  //   document.documentElement.scrollTop = (target_top > 250 ? 0 : target_top) + 250;
  // }
  // 键盘上移动
  const target_to_view_height =
    window.innerHeight - e.target.getBoundingClientRect().bottom; // 元素到适口高度
  const target_top = document.body.scrollHeight - $(e.target).offset().top; // 元素到正文高度
  let scroll_height = "";
  if (target_to_view_height <= 250) {
    document.getElementById("app").style.paddingBottom = "250px";
    // 向上滚动位置
    document.documentElement.scrollTop = $(e.target).offset().top - 244;
  }
  // 选中添加border颜色
  content = $(e.target).parent();
  // TAG: 自定义主题颜色
  content.css("border-color", "#c2915f");
  setTimeout(() => {
    show.value = true;
  }, 300);
  // 记录点击field名
  store.changeFieldName(props.item.name);
};
const blurKeyboard = () => {
  show.value = false;
  document.getElementById("app").style.paddingBottom = "0";
  // 还原border颜色
  content.css("border-color", "#eaeaea");
  // 键盘失焦检查输入和添加性别显示
  const input_val = props.item.value;
  if (required && !input_val) {
    showFailToast("身份证号码不能为空");
  } else if (input_val && !idCard.verify(input_val)) {
    showFailToast("请输入正确身份证号码");
  } else {
    // gender.value = getGenderByIdNumber(input_val)
  }
};

// 校验函数返回 true 表示校验通过,false 表示不通过
// 身份证号码为15位或者18位,15位时全为数字,18位前17位为数字,最后一位是校验位,可能为数字或字符X
const required = props.item.component_props.required;
const validator = (val) => {
  if (required && !val) {
    return false;
  } else if (val && !idCard.verify(val)) {
    return false;
  } else {
    return true;
  }
};
// 错误提示文案
const validatorMessage = (val, rule) => {
  if (required && !val) {
    return "身份证号码不能为空";
  } else if (val && !idCard.verify(val)) {
    return "请输入正确身份证号码";
  }
};
const rules = [{ validator, message: validatorMessage }];

const onInput = (value) => {};
const onDelete = () => {};

const gender = ref('');

/**
 * 按身份证号码获取性别
 * @idNumber 身份证号码
 * @return 男:male;女:female;异常(身份证号码为空或长度、格式错误):undefined
 */
const getGenderByIdNumber = (idNumber) => {
  if (idNumber) {
    let genderCode; // 性别代码
    if (idNumber.length == 18) { // 二代身份证号码长度为18位(第17位为性别代码)
      genderCode = idNumber.charAt(16);
    } else if (idNumber.length == 15) { // 一代身份证号码长度为15位(第15位为性别代码)
      genderCode = idNumber.charAt(14);
    }
    if (genderCode && !isNaN(genderCode)) {
      // 两代身份证号码的性别代码都为男奇女偶
      if (parseInt(genderCode) % 2 == 0) {
        return '女';
      }
      return '男';
    }
  }
}

// 适配cookie保存未完成表单
const onBlur = (item) => {
  const currentValue = item.value;
  const existingCookie = Cookies.get($route.query.code);

  if (existingCookie) {
    // 如果Cookie存在,更新它
    let obj = JSON.parse(existingCookie);
    obj[props.item.key] = currentValue; // 替换掉旧值
    Cookies.set($route.query.code, JSON.stringify(obj), { expires: 1 });
  } else {
    // 如果Cookie不存在,新增它
    Cookies.set($route.query.code, JSON.stringify({ [props.item.key]: currentValue }), { expires: 1 });
  }

  // 触发自定义失焦操作
  emit("blur", item);
}
</script>

<style lang="less" scoped>
.identity-page {
  .label {
    padding: 1rem 1rem 0 1rem;
    font-size: 0.9rem;
    font-weight: bold;
  }
  .gender {
    padding: 0 1rem 0 1rem;
    font-size: 0.9rem;
    span {
      font-weight: bold;
    }
  }
}

:deep(.van-field__body) {
  border: var(--border-style);
  border-radius: 0.25rem;
  padding: 0.25rem 0.5rem;
}
</style>