业务功能·

微信小程序-接入电子健康卡

在数字化医疗的浪潮中,电子健康卡的接入成为提升医疗服务效率和便捷性的关键一环。对于前端开发者而言,实现电子健康卡的接入不仅需要对相关技术有深入理解,还需遵循严格的规范和流程。本文将介绍电子健康卡的接入方法。

流程介绍

流程图

实现思路

  1. 所有场景统一跳转到授权页面获取wechatcode
  2. 所有回调页面统一处理
  • 展示腾讯电子健康卡接口返回的h5页面
  • 完善信息页面
  • 人脸识别页面
  • 验证成功页面
  • 验证失败页面
  • 结果处理页(中转页面,用于记录流程结束回调的参数,然后去触发互联网医院业务)
  1. 用卡上报场景统一处理

实现代码

电子健康卡相关工具方法

import { Emitter } from '../Emitter';
import { HEALTH_CARD_TYPE, genderEnum } from './constant';
import dayjs from 'dayjs';
import { reportHisData } from '@/apis/healthCard';

// 提示
const _toast = (title, complete) => {
  uni.showToast({
    title,
    icon: 'none',
    mask: true,
    duration: 1500,
    complete: () => {
      // 延迟触发
      setTimeout(() => {
        complete && complete();
      }, 1500);
    },
  });
};

// 加载
const _showLoading = (title = '加载中') => {
  uni.showToast({
    title,
    duration: 1000 * 60 * 60 * 24,
    icon: 'loading',
    mask: true,
  });
};

const _hideLoading = delayTime => {
  // 延迟关闭
  if (delayTime && typeof delayTime === 'number') {
    setTimeout(() => {
      uni.hideToast();
    }, delayTime);
  } else {
    uni.hideToast();
  }
};

// 日志
const _log = (...args) => {
  console.log(...args);
};

class HealthCardUtil {
  // 更新参数
  patientId = '';
  // 绑定参数
  patientInfo = '';
  // 授权验证
  scene = '';
  formPath = '';
  verifyOrderId = '';
  // 回调匹配的路由
  matchRoute = '';
  // 临时存储健康卡信息
  healthCardInfo = null;
  constructor() {
    this.emitter = new Emitter();
    this.showLoad = _showLoading;
    this.hideLoad = _hideLoading;
    this.toast = _toast;
    this.log = _log;
  }
  create() {
    this.recordFormPath();
    uni.navigateTo({
      url: `/patientPages/healthCard/auth/index?type=${HEALTH_CARD_TYPE.CREATE}`,
    });
  }
  update(patientId) {
    this.recordFormPath();
    this.patientId = patientId;
    uni.navigateTo({
      url: `/patientPages/healthCard/auth/index?type=${HEALTH_CARD_TYPE.UPDATE}`,
    });
  }
  // 验证
  async verify({ scene, patientInfo, success, fail }) {
    this.showLoad('跳转中');
    const { healthCardId } = patientInfo;
    // 非电子健康卡直接跳过
    if (!healthCardId) {
      success && success();
      return;
    }
    this.patientInfo = patientInfo;
    this.recordFormPath();
    // 注册监听事件
    this.onVerifySuccess(success);
    this.onVerifyFail(fail);
    this.scene = scene;

    // 进行上报
    await this.eventData({
      qrCodeText: healthCardId,
      scene,
    });
    setTimeout(() => {
      this.hideLoad();
      uni.navigateTo({
        url: `/patientPages/healthCard/auth/index?type=${HEALTH_CARD_TYPE.SEARCH_VERIFY}`,
      });
    }, 500);
  }
  // 注册验证成功事件
  onVerifySuccess(success) {
    _log('【onVerifySuccess】', success);
    // 防止重复注册
    this.emitter.remove('verifySuccess');
    this.emitter.once('verifySuccess', e => {
      success && success(e);
    });
  }
  emitVerifySuccess(e) {
    this.emitter.emit('verifySuccess', e);
  }
  // 注册验证失败事件
  onVerifyFail(fail) {
    _log('【onVerifyFail】', fail);
    // 防止重复注册
    this.emitter.remove('verifyFail');
    this.emitter.once('verifyFail', e => {
      fail && fail(e);
    });
  }
  emitVerifyFail(e) {
    this.emitter.emit('verifyFail', e);
  }
  // 验证成功后
  verifySuccess() {
    this.toast('验证通过', () => {
      // 返回来源路由
      this.goBack(this.formPath, () => {
        // 延迟触发
        setTimeout(() => {
          this.emitVerifySuccess();
        }, 200);
      });
    });
  }
  // 验证失败后
  verifyFail() {
    this.toast('验证失败', () => {
      this.goBack(this.formPath, () => {
        // 延迟触发
        setTimeout(() => {
          this.emitVerifyFail();
        }, 200);
      });
    });
  }
  // 记录来源路由
  recordFormPath() {
    // 来源路由
    const curPages = getCurrentPages();

    const curPage = curPages[curPages.length - 1];
    this.formPath = curPage.route;
    console.log('【formPath】', this.formPath);
  }
  // 匹配重定向的路径,返回到对应的页面
  goBack(route = this.formPath, callback) {
    // 返回到就诊人列表页
    const curPages = getCurrentPages();
    const pageLen = curPages.length;

    let delta = 1;
    while (delta <= pageLen - 1) {
      if (curPages[pageLen - delta - 1].route === route) {
        this.hideLoad();
        uni.navigateBack({
          delta: delta,
          complete: () => {
            if (callback) {
              callback();
            }
          },
        });
        break;
      }
      delta++;
    }
  }
  // 记录verifyOrderId,用于校验
  recordVerifyOrderId(verifyOrderId) {
    this.verifyOrderId = verifyOrderId;
  }
  // 健康卡信息转患者信息
  transferInfoByHealthCard(healthCardInfo) {
    try {
      const {
        name,
        birthday,
        idType,
        idNumber,
        gender,
        address,
        healthCardId,
        patid,
        phone1,
        nation,
        relation,
        relationship,
        isSelf,
      } = healthCardInfo;
      const genderInfo = genderEnum.find(it => it.name === gender);

      let addressObj = null,
        provinceInfo,
        cityInfo,
        districtInfo;
      // 验证地址是否符合要求
      const verifyResult = this.verifyAddress(address);
      if (verifyResult) {
        addressObj = JSON.parse(address);
        provinceInfo = addressObj.city[0];
        cityInfo = addressObj.city[1];
        districtInfo = addressObj.city[2];
      }

      const _birthday =
        birthday || idNumber.substr(6, 8).replace(/(.{4})(.{2})/, '$1-$2-');
      const _sex = parseInt(idNumber.substr(16, 1)) % 2 == 1 ? 1 : 2;
      return {
        name,
        birthday: new Date(_birthday).getTime(),
        certType: Number(idType),
        certId: idNumber,
        sex: genderInfo ? genderInfo.value : _sex,
        address: addressObj
          ? {
              province: provinceInfo?.text,
              provinceCode: provinceInfo?.value,
              city: cityInfo?.text,
              cityCode: cityInfo?.value,
              district: districtInfo?.text,
              districtCode: districtInfo?.value,
              address: addressObj.address,
            }
          : null,
        nationality: '中国',
        nationalityCode: 'ZG',
        healthCardId,
        patid,
        phone: phone1,
        nation: nation || '汉族',
        relation: relation || relationship || isSelf ? '0' : '',
      };
    } catch (err) {
      this.toast('健康卡数据异常,请解绑后重新添加');
      throw new Error('健康卡数据异常,请解绑后重新添加');
    }
  }
  // 患者信息转健康卡信息
  transferInfoByPatientInfo(patientInfo) {
    const {
      name,
      birthday,
      certType,
      certId,
      sex,
      phone,
      currentAddress,
      relation,
    } = patientInfo;

    let city = [];
    let address = '';
    if (currentAddress && currentAddress.province) {
      city = [
        { text: currentAddress.province, value: currentAddress.provinceCode },
        { text: currentAddress.city, value: currentAddress.cityCode },
        { text: currentAddress.district, value: currentAddress.districtCode },
      ];
      address = currentAddress.address;
    } else {
      // 默认地址
      city = [
        { text: '四川省', value: '510000' },
        { text: '泸州市', value: '510500' },
        { text: '江阳区', value: '510502' },
      ];
    }
    return {
      ...patientInfo,
      name,
      birthday: dayjs(new Date(birthday)).format('YYYY-MM-DD'),
      idType: certType >= 10 ? String(certType) : '0' + String(certType),
      idNumber: certId,
      gender: sex,
      phone,
      city,
      address,
      relation,
    };
  }

  // 用卡证件 10    就诊卡、11  电子健康卡
  getCardType(healthCardId) {
    return healthCardId ? '11' : '10';
  }
  // 根据名称匹配上报场景
  getScene(title) {
    const scene = {
      change_patient: '0101087', // 基本信息
      unbind: '01010871', // 解绑
      medical_record: '010108', // 就医记录,
      check_report: '0101081', // 检查报告
      inspection_report: '0101082', // 检验报告
      digital_image: '02010913', // 数字影像
      queuing_up: '0101021', // 排队叫号
      pay: '010105', // 缴费
      outpatient_pay: '0101051', // 门诊缴费
      outpatient_record: '0101052', // 门诊记录
      appointment_register: '0101011', // 预约挂号
      today_register: '0101012', // 当日挂号
    };
    return scene[title];
  }
  // 数据上报
  eventData(param) {
    return new Promise(resolve => {
      _log('reportHisData 参数】', param);
      const { scene, qrCodeText, department } = param;

      if (!qrCodeText) {
        resolve();
        return;
      }
      const params = {
        qrCodeText,
        scene,
        cardType: '11',
        cardChannel: '0402',
        time: dayjs().format('YYYY-MM-DD'),
      };
      if (department) {
        params.department = department;
        params.cardCostTypes = '0100';
      }
      reportHisData(params)
        .then(res => {
          _log('reportHisData 结果】', res);
          resolve(res);
        })
        .catch(err => {
          _log('reportHisData 结果】', err);
          resolve();
        });
    });
  }
  /**
   * 手机号脱敏处理
   * @param {string} phone - 手机号字符串
   * @returns {string} 脱敏后的手机号
   */
  maskPhone(phone) {
    if (!phone) return '--';
    // 去除非数字字符
    const cleanPhone = phone.replace(/\D/g, '');
    // 验证是否为11位数字
    if (!/^\d{11}$/.test(cleanPhone)) {
      return phone; // 非标准手机号不做处理
    }
    // 格式化:(中间4位用*替换)
    return cleanPhone.replace(/(\d{3})(\d{4})(\d{4})/, '$1****$3');
  }

  /**
   * 身份证号脱敏处理
   * @param {string} idCard - 身份证号字符串
   * @returns {string} 脱敏后的身份证号
   */
  maskIdCard(idCard) {
    if (!idCard) return '--';
    // 去除非数字和字母字符(支持15位和18位身份证号)
    const cleanIdCard = idCard.replace(/[^\dXx]/g, '').toUpperCase();
    // 验证15位或18位身份证号
    const isValid15 = /^\d{15}$/.test(cleanIdCard);
    const isValid18 = /^\d{17}[\dX]$/.test(cleanIdCard);
    if (!isValid15 && !isValid18) {
      return idCard; // 非标准身份证号不做处理
    }

    // 格式化:中间10位用*替换
    if (isValid15) {
      return cleanIdCard.replace(/(\d{1})(\d{10})(\d{4})/, '$1**********$3');
    } else {
      return cleanIdCard.replace(
        /^(\d{4})(\d{10})(\d{3}[\dX])$/,
        '$1**********$3',
      );
    }
  }
  // 验证地址是否正确
  verifyAddress(address) {
    if (!address) return false;
    try {
      const obj = JSON.parse(address);
      // 验证数据结构
      if (obj.city && obj.address) {
        return true;
      } else {
        return false;
      }
    } catch (error) {
      // 数据格式不正确
      return false;
    }
  }
}
export default new HealthCardUtil();

电子健康卡业务逻辑相关代码

import { Emitter } from '@/utils/Emitter';
import {
  registerHealthCardPreAuth,
  registerHealthCardPreFill,
  healthCardByHealthCode,
  getRegInfoByCode,
  getOrderInfoByOrderId,
  registerRealPersonAuthOrder,
  registerUniformVerifyOrder,
  checkUniformVerifyResult,
  getDynamicQrCode,
} from '@/apis/healthCard';
import { addHealthCard, updateHealthCard } from '@/apis/patient';
import healthCardUtil from '@/utils/healthCard/index';
import { cloneDeep } from 'lodash-es';

const plugin = requirePlugin('healthCardPlugins');

/* 绑卡回调地址 */
const BindSuccessRedirectUrl =
  'mini:/patientPages/healthCard/redirectUrl/index?healthCode=${healthCode}';

const BindFailRedirectUrl =
  'mini:/patientPages/healthCard/redirectUrl/index?regInfoCode=${regInfoCode}';

const BindUserFormPageUrl =
  'mini:/patientPages/healthCard/useFrom/index?authCode=${authCode}';

// 人脸识别
const FaceRedirectUrl = '/patientPages/healthCard/faceUrl/index';

const VerifySuccessRedirectUrl =
  'mini:/patientPages/healthCard/verifyUrl/index?registerOrderId=${registerOrderId}';

// 验证失败
const VerifyFailRedirectUrl = 'mini:/patientPages/healthCard/failUrl/index';

// 腾讯电子健康卡
class ElectronicHealthCard {
  // 授权
  authEmitter;
  // 微信用户唯一标识,需通过uni.login获取
  wechatCode = '';
  // 建档地址
  createURL = '';
  // 升级地址
  updateURL = '';
  // 绑定验证地址
  bindVerifyURL = '';
  // 查询验证地址
  searchVerifyURL = '';
  // 结果
  healthCode = '';
  regInfoCode = '';
  // 记录健康卡信息
  healthCardInfo = {};
  constructor() {
    this.authEmitter = new Emitter();
  }
  loadWechatCode() {
    return new Promise((resolve, reject) => {
      plugin.login(
        (isok, res) => {
          healthCardUtil.log('【loadWechatCode 结果】 ', isok, res.result);
          if (!isok && res.result.toLogin) {
            reject(res);
          } else {
            // 用户在微信授权过,可直接获取登录信息,处理后续业务
            resolve(res.result.wechatCode);
          }
        },
        {
          wechatCode: true,
          // healthCode: true,
          // ecardNo: '',
        },
      );
    });
  }

  // 获取建档地址
  beforeCreateCard({ wechatCode }) {
    return new Promise((resolve, reject) => {
      healthCardUtil.log('【beforeCreateCard】');
      if (!wechatCode) {
        healthCardUtil.toast('wechatCode 不存在');
        return reject(new Error('wechatCode 不存在'));
      }
      // 入参
      const params = {
        wechatCode,
        patientType: 0,
        successRedirectUrl: BindSuccessRedirectUrl,
        failRedirectUrl: BindFailRedirectUrl,
        userFormPageUrl: BindUserFormPageUrl,
        verifyFailRedirectUrl: VerifyFailRedirectUrl,
        faceUrl: FaceRedirectUrl,
      };
      healthCardUtil.log('【registerHealthCardPreAuth 参数】', params);
      registerHealthCardPreAuth(params)
        .then(res => {
          healthCardUtil.log('【registerHealthCardPreAuth 结果】', res);
          if (res.data) {
            this.createURL = res.data;
            resolve && resolve(this.createURL);
          } else {
            healthCardUtil.toast('地址不存在');
            reject(new Error('地址不存在'));
          }
        })
        .catch(err => {
          healthCardUtil.log('【registerHealthCardPreAuth 结果】', err);
          reject(err);
        });
    });
  }
  // 获取升级地址
  beforeUpdateCard({ wechatCode, patientId }) {
    return new Promise((resolve, reject) => {
      healthCardUtil.log('【beforeUpdateCard】');
      if (!wechatCode) {
        healthCardUtil.toast('wechatCode 不能为空');
        return reject(new Error(`wechatCode 不能为空`));
      }
      if (!patientId) {
        healthCardUtil.toast('patientId 不能为空');
        return reject(new Error(`patientId 不能为空`));
      }
      // 入参
      const params = {
        wechatCode,
        patientType: 1, // 老患者升级
        successRedirectUrl: this.generatePatientURL(
          BindSuccessRedirectUrl,
          patientId,
        ),
        failRedirectUrl: this.generatePatientURL(
          BindFailRedirectUrl,
          patientId,
        ),
        userFormPageUrl: this.generatePatientURL(
          BindUserFormPageUrl,
          patientId,
        ),
        verifyFailRedirectUrl: this.generatePatientURL(
          VerifyFailRedirectUrl,
          patientId,
        ),
        faceUrl: this.generatePatientURL(FaceRedirectUrl, patientId),
      };
      healthCardUtil.log('【registerHealthCardPreAuth 参数】', params);
      registerHealthCardPreAuth(params)
        .then(res => {
          healthCardUtil.log('【registerHealthCardPreAuth 结果】', res);
          this.updateURL = res.data;
          resolve(this.updateURL);
        })
        .catch(err => {
          healthCardUtil.log('【registerHealthCardPreAuth 结果】', err);
          reject(err);
        });
    });
  }
  // 填写建档信息
  createCard({ param, authCode, code, patientId }) {
    return new Promise((resolve, reject) => {
      healthCardUtil.log('【createCard】');
      // param 对应的参数请参考:https://open.tengmed.com/openAccess/docs/develop#113

      const params = {
        code,
        businessDataBody: {
          ...param,
          authCode,
          successRedirectUrl: this.generatePatientURL(
            BindSuccessRedirectUrl,
            patientId,
          ),
          failRedirectUrl: this.generatePatientURL(
            BindFailRedirectUrl,
            patientId,
          ),
          verifyFailRedirectUrl: this.generatePatientURL(
            VerifyFailRedirectUrl,
            patientId,
          ),
          faceUrl: this.generatePatientURL(FaceRedirectUrl, patientId),
        },
      };
      healthCardUtil.log('【registerHealthCardPreFill 参数】', params);
      registerHealthCardPreFill(params)
        .then(res => {
          healthCardUtil.log('【registerHealthCardPreFill 结果】', res);
          this.bindVerifyURL = res.data;
          resolve(this.bindVerifyURL);
        })
        .catch(err => {
          healthCardUtil.log('【registerHealthCardPreFill 结果】', err);
          reject(err);
        });
    });
  }

  // 获取验证地址
  beforeGenerateOrderIdVerify(param) {
    return new Promise((resolve, reject) => {
      healthCardUtil.log('registerUniformVerifyOrder 参数】', param);
      const params = {
        ...param,
        verifySuccessRedirectUrl: VerifySuccessRedirectUrl,
        verifyFailRedirectUrl: VerifyFailRedirectUrl,
        faceUrl: FaceRedirectUrl,
      };
      registerUniformVerifyOrder(params)
        .then(res => {
          healthCardUtil.log('registerUniformVerifyOrder 结果】', res);
          resolve(res);
        })
        .catch(err => {
          healthCardUtil.log('registerUniformVerifyOrder 结果】', err);
          reject(err);
        });
    });
  }

  checkVerify({ verifyOrderId, registerOrderId }) {
    return new Promise((resolve, reject) => {
      healthCardUtil.log('checkUniformVerifyResult 参数】', param);
      const param = {
        verifyOrderId,
        verifyResult: registerOrderId,
      };
      checkUniformVerifyResult(param)
        .then(res => {
          healthCardUtil.log('checkUniformVerifyResult 结果】', res);
          const { suc } = res.data;
          if (suc) {
            healthCardUtil.toast('验证成功');
            resolve(suc);
          } else {
            healthCardUtil.toast('验证失败');
            reject(new Error('验证失败'));
          }
        })
        .catch(err => {
          healthCardUtil.log('checkUniformVerifyResult 结果】', err);
          healthCardUtil.toast('验证失败');
          reject(err);
        });
    });
  }

  setHealthCode(healthCode) {
    this.healthCode = healthCode;
  }
  clearHealthCode() {
    this.healthCode = null;
  }

  setRegInfoCode(regInfoCode) {
    this.regInfoCode = regInfoCode;
  }
  clearRegInfoCode() {
    this.regInfoCode = null;
  }

  hasResult() {
    return this.healthCode || this.regInfoCode;
  }

  // 通过healthCode获取健康卡信息
  getInfoByHealthCode() {
    return new Promise((resolve, reject) => {
      if (!this.healthCode) {
        healthCardUtil.toast('healthCode 不存在');
        return reject(new Error('healthCode 不存在'));
      }
      const param = {
        healthCode: this.healthCode,
      };
      healthCardUtil.log('【healthCardByHealthCode 参数】', param);
      healthCardByHealthCode(param)
        .then(res => {
          healthCardUtil.log('【healthCardByHealthCode 结果】', res);
          this.clearHealthCode();
          resolve(res);
        })
        .catch(err => {
          this.clearHealthCode();
          healthCardUtil.log('【healthCardByHealthCode 结果】', err);
          reject(err);
        });
    });
  }
  // 通过code获取健康卡信息
  getInfoByCode() {
    return new Promise((resolve, reject) => {
      if (!this.regInfoCode) {
        healthCardUtil.toast('regInfoCode 不存在');
        return reject(new Error('regInfoCode 不存在'));
      }
      const param = {
        code: this.regInfoCode,
      };
      healthCardUtil.log('getRegInfoByCode 参数】', param);
      getRegInfoByCode(param)
        .then(res => {
          healthCardUtil.log('getRegInfoByCode 结果】', res);
          this.clearRegInfoCode();
          resolve(res);
        })
        .catch(err => {
          this.clearRegInfoCode();
          healthCardUtil.log('getRegInfoByCode 结果】', err);
          reject(err);
        });
    });
  }
  // 获取健康卡信息
  getInfo() {
    if (this.healthCode) {
      return this.getInfoByHealthCode();
    } else if (this.regInfoCode) {
      return this.getInfoByCode();
    } else {
      return new Promise(resolve => {
        resolve('');
      });
    }
  }
  clearInfoCode() {
    this.clearHealthCode();
    this.clearRegInfoCode();
  }

  // 拼接患者id
  generatePatientURL(defaultUrl, patientId) {
    if (patientId) {
      if (defaultUrl.indexOf('?') >= 0) {
        return `${defaultUrl}&patientId=${patientId}`;
      } else {
        return `${defaultUrl}?patientId=${patientId}`;
      }
    }
    return defaultUrl;
  }

  // 人脸识别-设备检测
  checkIsSupportFacialRecognition() {
    return new Promise(resolve => {
      uni.checkIsSupportFacialRecognition({
        checkAliveType: 2, // 优先屏幕闪烁,不支持则读数字
        success: res => {
          console.log(res, '设备检测');
          const systemInfo = uni.getSystemInfoSync();
          if (systemInfo.platform === 'android') {
            const isSupport = res.errCode === 0;
            resolve({
              support: isSupport,
              message: isSupport ? '支持' : '设备不支持',
            });
          } else {
            resolve({
              support: true,
              message: '支持',
            });
          }
        },
        fail: err => {
          console.log(err, '设备检测异常');
          const { errCode } = err;
          const messageEnum = {
            10001: '不支持人脸采集:设备没有前置摄像头',
            10002: '不支持人脸采集:没有下载到必要模型',
            10003: '不支持人脸采集:后台控制不支持',
          };
          resolve({
            support: false,
            message: `${messageEnum[errCode] || '检测失败'}`,
            details: err,
          });
        },
        complete: () => {},
      });
    });
  }
  // 人脸识别
  startFacialRecognitionVerify({ name, idCardNumber }) {
    return new Promise(resolve => {
      uni.startFacialRecognitionVerify({
        name,
        idCardNumber,
        success: resolve,
        fail: err => {
          const { verifyResult } = err;
          resolve({ errCode: -1, verifyResult });
        },
      });
    });
  }
  // 人脸识别-校验
  async verifyFlow({ orderId, verifyType }) {
    return new Promise(async (resolve, reject) => {
      try {
        if (!orderId) {
          healthCardUtil.toast('orderId 不能为空');
          reject(new Error(`orderId 不能为空`));
          return;
        }
        if (!verifyType) {
          healthCardUtil.toast('verifyType 不能为空');
          reject(new Error(`verifyType 不能为空`));
          return;
        }
        // 检测设备
        const supportResult = await this.checkIsSupportFacialRecognition();
        if (!supportResult.support) {
          healthCardUtil.toast(supportResult.message);
          reject(new Error(supportResult.message));
          return;
        }
        healthCardUtil.log('【getOrderInfoByOrderId 参数】', {
          orderId,
          verifyType,
        });
        // 1.获取用户信息
        const healthCardInfoRes = await getOrderInfoByOrderId({
          orderId,
          verifyType,
        });
        healthCardUtil.log('【getOrderInfoByOrderId 结果】', healthCardInfoRes);

        const { name, idCard, cardType } = healthCardInfoRes.data;

        // 2.拉起人脸识别
        const verifyRes = await this.startFacialRecognitionVerify({
          name,
          idCardNumber: idCard,
        });
        healthCardUtil.log('【人脸识别 结果】', verifyRes);
        const { errCode } = verifyRes;
        // 3.获取wechatCode
        const wechatCode = await this.loadWechatCode();
        const param = {
          orderId,
          verifyType,
          result: errCode === 0 ? '01' : '-1',
          name,
          idCard,
          cardType,
          wechatCode,
        };
        healthCardUtil.log('【registerRealPersonAuthOrder 参数】', param);

        // 4.人脸识别验证结果
        const res = await registerRealPersonAuthOrder(param);
        healthCardUtil.log('【registerRealPersonAuthOrder 结果】', res);
        resolve(res.data);
      } catch (err) {
        healthCardUtil.log('【verifyFlow 结果】', err);
        reject(err);
      }
    });
  }

  // 生成二维码
  generateQrCode(healthCardInfo) {
    return new Promise((resolve, reject) => {
      const { idType, idNumber, healthCardId } = healthCardInfo;
      const param = {
        healthCardId,
        idType,
        idNumber,
        codeType: 1, // 静态
      };
      healthCardUtil.log('getDynamicQrCode 参数】', param);
      getDynamicQrCode(param)
        .then(res => {
          healthCardUtil.log('getDynamicQrCode 结果】', res);
          const { qrCodeImg } = res.data;
          if (qrCodeImg) {
            resolve(qrCodeImg);
          } else {
            healthCardUtil.toast('二维码不存在');
            reject(new Error('二维码不存在'));
          }
        })
        .catch(err => {
          healthCardUtil.log('getDynamicQrCode 结果】', err);
          reject(err);
        });
    });
  }

  // 新增-根据健康卡建档
  createByHealthCard(healthCardInfo) {
    return new Promise(async (resolve, reject) => {
      try {
        const param = healthCardUtil.transferInfoByHealthCard(healthCardInfo);
        // 校验地址,未通过去完善
        if (!param.address) {
          // 记录健康卡信息
          this.healthCardInfo = healthCardInfo;
          uni.navigateTo({
            url: '/patientPages/patient/healthAddress/index',
          });
          return reject(new Error('地址不存在'));
        }
        const qrCode = await this.generateQrCode(healthCardInfo);
        console.log('【二维码】', qrCode);
        const params = {
          ...param,
          type: 1, // 地址类型 1-就诊人现住址2-就诊人户籍住址3-物流配送地址
          syncHis: 1,
          qrCodeText: qrCode,
        };
        // 院内建档
        console.log('【院内建档 参数】', params);
        const resAdd = await addHealthCard(params);
        console.log('【院内建档 结果】', resAdd);
        resolve(resAdd);
      } catch (error) {
        reject(error);
      }
    });
  }
  // 更新-根据健康卡建档
  updateByHealthCard(healthCardInfo, patientId) {
    return new Promise(async (resolve, reject) => {
      try {
        if (!patientId) {
          return reject(new Error('patientId 不存在'));
        }
        const param = healthCardUtil.transferInfoByHealthCard(healthCardInfo);
        // 校验地址,未通过去完善
        if (!param.address) {
          // 记录健康卡信息
          this.healthCardInfo = healthCardInfo;
          uni.navigateTo({
            url: `/patientPages/patient/healthAddress/index?patientId=${patientId}`,
          });
          return reject(new Error('地址不存在'));
        }

        const qrCode = await this.generateQrCode(healthCardInfo);
        console.log('【二维码】', qrCode);

        // 已建档更新
        const { relation, phone, nation, address, healthCardId } = param;
        // 更新建档
        const params = {
          platPatientId: patientId,
          qrCodeText: qrCode,
          nation,
          phone,
          address,
          healthCardId,
          type: 1,
          syncHis: 1,
        };
        if (relation) {
          params.relation = relation;
        }
        console.log('【更新建档 参数】', params);
        const resUpdate = await updateHealthCard(params);

        console.log('【更新建档 结果】', resUpdate);
        resolve(resUpdate);
      } catch (error) {
        reject(error);
      }
    });
  }
  // 使用健康卡信息
  getHealthCardInfo() {
    const healthCardInfo = cloneDeep(this.healthCardInfo);
    this.healthCardInfo = null;
    return healthCardInfo;
  }
}

export default new ElectronicHealthCard();

使用介绍

  1. 所有场景统一通过utils/healthCard/index方法触发
  • HealthCardUtil.create: 新增场景
  • HealthCardUtil.update: 老患者升级场景
  • HealthCardUtil.verify: 查询场景

经过patientPages/healthCard/auth/index页面授权获取wechatcode

  1. 所有回调页面统一处理
  • patientPages/healthCard/webview/index展示腾讯电子健康卡接口返回的h5页面
<!-- 腾讯电子健康卡 -->
<template>
  <web-view :src="pageUrl" />
</template>

<script setup>
import { onLoad } from "@dcloudio/uni-app";
import { ref } from "vue";
import healthCard from "../static/ElectronicHealthCard";
import healthCardUtil from "@/utils/healthCard/index";
import { HEALTH_CARD_TYPE } from "@/utils/healthCard/constant";

const pageUrl = ref("");

onLoad((options) => {
  init(options);
});

async function init(options) {
  console.log("【options】", options);
  const { type, wechatCode } = options;
  switch (type) {
    // 建档
    case HEALTH_CARD_TYPE.CREATE:
      handleCreate(wechatCode);
      break;
    // 升级
    case HEALTH_CARD_TYPE.UPDATE:
      handleUpdate(wechatCode);
      break;
    // 绑定验证
    case HEALTH_CARD_TYPE.BIND_VERIFY:
      handleBind();
      break;
    // 查询验证
    case HEALTH_CARD_TYPE.SEARCH_VERIFY:
      handleSearchVerify(wechatCode);
      break;
    default:
      break;
  }
}
// 新增
const handleCreate = async (wechatCode) => {
  try {
    healthCardUtil.showLoad();
    await healthCard.beforeCreateCard({ wechatCode });
    pageUrl.value = healthCard.createURL;
    healthCardUtil.hideLoad();
    console.log("【pageUrl】", pageUrl.value);
  } catch (err) {
    healthCardUtil.hideLoad(1500);
  }
};
// 升级
const handleUpdate = async (wechatCode) => {
  try {
    healthCardUtil.showLoad();
    await healthCard.beforeUpdateCard({
      wechatCode,
      patientId: healthCardUtil.patientId,
    });
    pageUrl.value = healthCard.updateURL;
    healthCardUtil.hideLoad();
    console.log("【pageUrl】", pageUrl.value);
  } catch (err) {
    healthCardUtil.hideLoad(1500);
  }
};
// 绑定
const handleBind = () => {
  healthCardUtil.showLoad();
  pageUrl.value = healthCard.bindVerifyURL;
  healthCardUtil.hideLoad();
};
// 校验
const handleSearchVerify = async (wechatCode) => {
  try {
    healthCardUtil.showLoad();
    const { name, idType, idNumber } = healthCardUtil.transferInfoByPatientInfo(
      healthCardUtil.patientInfo
    );

    const { data } = await healthCard.beforeGenerateOrderIdVerify({
      wechatCode,
      name,
      cardType: idType,
      idCard: idNumber,
      scene: healthCardUtil.scene,
      useCardType: healthCardUtil.getCardType(
        healthCardUtil.patientInfo.healthCardId
      ),
    });
    const { verifyUrl, verifyOrderId } = data;
    // 记录校验数据
    healthCardUtil.recordVerifyOrderId(verifyOrderId);
    pageUrl.value = verifyUrl;
    healthCardUtil.hideLoad();
    console.log("【pageUrl】", pageUrl.value);
  } catch (err) {
    healthCardUtil.hideLoad(1500);
  }
};
</script>

  • patientPages/healthCard/useFrom/index完善信息页面
  • patientPages/healthCard/faceUrl/index人脸识别页面
<template>
    <web-view :src="pageUrl" />
</template>

<script setup>
  import { onLoad } from '@dcloudio/uni-app';
  import { ref } from 'vue';
  import healthCard from '../static/ElectronicHealthCard';
  import healthCardUtil from '@/utils/healthCard/index';

  const pageUrl = ref('');
  const redirectURL = ref('');

  onLoad(options => {
    init(options);
  });

  async function init(options) {
    try {
      console.log('faceUrl【options】', options);
      healthCardUtil.showLoad();
      const { redirectUrl, orderId, verifyType } = options;
      redirectURL.value = redirectUrl;
      // 发起微信人脸识别
      const verifyOrderId = await healthCard.verifyFlow({
        orderId,
        verifyType,
      });
      healthCardUtil.hideLoad();
      console.log('faceUrl success', verifyOrderId);
      pageUrl.value = `${decodeURIComponent(redirectUrl)}&verify_order_id=${
        verifyOrderId || -1
      }`;
      console.log('pageUrl', pageUrl.value);
    } catch (err) {
      console.log('faceUrl fail', err);
      pageUrl.value = `${decodeURIComponent(
        redirectURL.value,
      )}&verify_order_id=-1`;
      console.log('pageUrl', pageUrl.value);
      healthCardUtil.hideLoad(1500);
    }
  }
</script>

  • patientPages/healthCard/verifyUrl/index验证成功页面
<template>
  <view></view>
</template>

<script setup>
  import { onLoad } from '@dcloudio/uni-app';
  import healthCard from '../static/ElectronicHealthCard';
  import healthCardUtil from '@/utils/healthCard/index';

  onLoad(options => {
    init(options);
  });

  async function init(options) {
    console.log('redirectUrl【options】', options);
    const { registerOrderId } = options;
    if (registerOrderId) {
      // 直接验证
      uni.setNavigationBarTitle({ title: '授权验证中' });
      healthCardUtil.showLoad('授权验证中');
      const isVerify = await healthCard.checkVerify({
        registerOrderId,
        verifyOrderId: healthCardUtil.verifyOrderId,
      });
      if (isVerify) {
        healthCardUtil.verifySuccess();
      } else {
        healthCardUtil.verifyFail();
      }
    }
  }
</script>

  • patientPages/healthCard/failUrl/index验证失败页面
<template>
  <view></view>
</template>

<script setup>
  import { onLoad } from '@dcloudio/uni-app';
  import healthCardUtil from '@/utils/healthCard/index';

  onLoad(options => {
    init(options);
  });

  function init() {
    healthCardUtil.showLoad('跳转中');
    // 返回到起始页
    healthCardUtil.goBack();
  }
</script>

  • patientPages/healthCard/redirectUrl/index 结果处理页(中转页面,用于记录流程结束回调的参数,然后去触发互联网医院业务)
 <template>
 <view></view>
</template>

<script setup>
 import { onLoad } from '@dcloudio/uni-app';
 import healthCard from '../static/ElectronicHealthCard';
 import healthCardUtil from '@/utils/healthCard/index';

 onLoad(options => {
   init(options);
 });

 async function init(options) {
   console.log('redirectUrl【options】', options);
   const { healthCode, regInfoCode } = options;
   healthCardUtil.showLoad('跳转中');
   // 记录参数
   if (healthCode) {
     healthCard.setHealthCode(healthCode);
   } else if (regInfoCode) {
     healthCard.setRegInfoCode(regInfoCode);
   }
   // 返回到起始页,根据记录的参数进行业务处理
   healthCardUtil.goBack();
 }
</script>

  1. 用卡上报场景统一使用utils/healthCard/index中的方法
  • eventData 上报数据
  • getScene 获取(统一定义)上报场景

完整代码地址

代码仓库地址

参考文档

腾讯电子健康卡官网

微信电子健康卡插件

微信人脸识别