H5 开发技巧总结

738 阅读7分钟

2020就做了 H5 开发,完成之后想做一个总结,可是一直处理的比较随意,就是草草了事,按理说这完全违背了开发者应有的精神。往日不可追,把握当下,做好现在需要做的事情吧!下面总结自己的开发过程中遇到的问题,如有错误望指正。

微信分享

按照官方的文档一步一步做即可。developers.weixin.qq.com/doc/offiacc… 下面是自己遇到过的坑

  1. 获取签名的时候需要给后端传当前页面的 URL,结果在没有完全熟悉官方文档的前提下,直接传当前页面的 URL,然后就遇到 invalid signature 的问题。解决方案就是给后端传递的 URL 不能包含 # 及后面的部分,而分享链接的时候可以包含 # 及后面的内容
let shareLink = window.location.href.split("#")[0]; // 注意:当前网页的 URL,不包含#及其后面的部分
const { data } = await getWechatShareSignAPI({
  params: { url: encodeURIComponent(shareLink) },
});

微信支付和支付宝支付

微信支付中次捐需要调起支付,比较复杂,参考官网教程 pay.weixin.qq.com/wiki/doc/ap… ,而签约扣款大部分是后端处理好了,前端只需跳转页面即可。支付宝支付参考官方 Demo,opensupport.alipay.com/support/hel… . 需要注意的内容如下:

  1. 获取微信 openId。如果是当前页面跳转获取 opnedId 并跳转到当前页面,在 ios 上会出现可以回退多次的情况,比较影响用户体验,自己当时在处理这个问题的时候也花费了大量的时间,尝试了主动回退抵消一次浏览记录的方法,路由守卫进行跳转处理等方法,都没有解决问题。最后的方案是在首页跳转到支付发起页面的时候获取 openId
generateUserAuthorizesUrl: debounce(async function() {
  const redirectUrl =
    process.env.NODE_ENV === "production"
      ? `https://${window.location.host}/prm/fr/mobile/index/#/donationInfo` // 不能有参数
      : "https://121419d838.goho.co/pay/#/donationInfo"; // 公众号配置的域名,我的 IP 地址被转到该域名
  const { data } = await generateUrlForOpenIdAPI({
    redirectUrl
  });
  if (data.data) {
    const userAuthorizesUrl = data.data;
    this.$store.commit("cannotGetAuthorizesurl");
    location.replace(userAuthorizesUrl);
  } else {
    this.$toast.fail(data.errmsg);
  }
}, 1000)
  1. 微信返回的支付结果不可靠,所以需要前端拿到订单 ID 后,向后端轮询支付结果。
  2. 由于需要配置支付授权目录,所以一般只在一个环境中进行测试且该域名需要进行配置。官方的解决方案是,请检查下单接口中使用的商户号是否在商户平台(pay.weixin.qq.com)配置了对应的支付目录。

image.png

  1. 从首页到捐赠页可获取 openId ,可是连续点击的话会多次调用接口,于是采用三个措施进行处理。
  • 使用 debounce 函数,刚开始可以立即执行,后来调用的时候需要延迟1秒再执行
generateUserAuthorizesUrl: debounce(async function() {},1000export const debounce = (func, wait) => {
  let timeout;
  return function() {
    let context = this;
    let args = arguments;

    if (timeout) clearTimeout(timeout);

    let callNow = !timeout;
    timeout = setTimeout(() => {
      timeout = null;
    }, wait);

    if (callNow) func.apply(context, args);
  };
};
  • 在请求的时候使用全局 loading 并使用遮罩层,这样请求过程中无法点击页面
  • 接口返回结果后,增加获取授权地址的锁,防止再次调用
this.$store.commit("cannotGetAuthorizesurl");

this.getAuthorizesUrlFlag && this.generateUserAuthorizesUrl();
  1. 使用节点处理后端表单字符串的时候,需要保证节点的唯一性,防止出现二次支付的时候生成的订单仍然是第一次支付的订单。
// 错误方案
const div = document.createElement("div");  // 需要设置成特定的 div 防止之后重复累加出现问题,出现过多次提交是同一个订单的问题
div.innerHTML = data.data.payResult;
document.body.appendChild(div);
// 修改后的完整方案
const alipayForm = document.getElementById("alipayForm");
alipayForm.innerHTML = data.data.payResult;
let queryParam = "";
Array.prototype.slice
  .call(document.querySelectorAll("input[type=hidden]"))
  .forEach(function (ele) {
    queryParam += "&" + ele.name + "=" + encodeURIComponent(ele.value);
  });
let gotoUrl =
  document.getElementsByName("punchout_form")[0].getAttribute("action") +
  queryParam;
_AP.pay(gotoUrl); // 导入文件 @/utils/ap 后提供的方法
  1. 在提示浏览器打开的页面中,通过浏览器打开页面,然后跳转到支付宝支付界面。需要熟悉官方 Demo 的处理思路
var ua = navigator.userAgent.toLowerCase();
if (ua.indexOf("micromessenger") === -1) {
  var getQueryString = function (url, name) {
    var reg = new RegExp("(^|\\?|&)" + name + "=([^&]*)(\\s|&|$)", "i");
    if (reg.test(url)) return RegExp.$2.replace(/\+/g, " ");
  };
  var param = getQueryString(location.href, "goto") || "";
  location.href = param != "" ? _AP.decode(param) : "#/alipayTips#error";
}

技巧

  1. 封面图片设置。根据图片的高宽比进行设置,图片宽度是100%,高度设置为屏幕宽度/1.875(之前的方案是使用 rem ,可是这个方案考虑到屏幕放大会出现字体无限变大的情况,所以限制获取到的最大屏幕宽度是750px,这样最终导致通过 rem 设置的图片高度在屏幕大于750px 的情况不会变大)
// 高度
this.pageCoverPicHeight = window.screen.width / 1.875;
img {
 width:100%;  /* 宽度 */
}
  1. 错误图片处理
<img
  :style="{ height: pageCoverPicHeight + 'px' }"
  :src="fundraisingContentInfo.pageCoverPic"
  :onerror="errorImg"
  alt="封面图"
/>
errorImg:'this.src="' + require("@/assets/images/n@3x.png") + '"',
  1. 金额千分位化
amount.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")
  1. 微信调试方法:微信下调试H5页面_weixin_33935777的博客-CSDN博客
  2. 微信缓存问题,在微信浏览器上打开空白,在其他浏览器上打开正常。需要清理缓存
  3. 判断当前环境是否为 IOS 端
const u = navigator.userAgent;
const isiOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/);
  1. 判断当前环境是否为微信端
const userAgent = navigator.userAgent.toLowerCase();
const isWechat = /MicroMessenger/i.test(userAgent);

兼容性问题

  1. 由于页面标题是后端接口返回的,所以需要动态设置。在 ios 中需要通过创建一个节点 ,然后移除掉的方法进行设置
let i = document.createElement("iframe");
i.src = "";
i.style.display = "none";
i.onload = function () {
  setTimeout(function () {
    i.remove();
  }, 9);
};
document.title = this.projectName;
document.body.appendChild(i);
  1. 使用原生 css 的 fixed 属性时候,底部会出现1px 的空隙问题,后来是直接使用了 vant 的组件
  2. 记录微信 ios 底部导航的情况:有路由历史的时候显示底部导航栏,往上滑动的时候隐藏底部导航栏; iPhone 前进回退不会触发任何 vue 的事件
  3. 需求是当前页面内容较少,需要填充背景色并且不能出现滚动条。由于 iPhone 会出现底部导航栏,而 document.documentElement.clientHeight 属性不会受底部栏的影响,这样页面一定会出现滚动条的情况。最后使用了 fixed 属性来解决问题。
position: fixed;
top: 0;
bottom: 0;
width: 100%;
background-color: #fff;
  1. 需求:除了首页之外(有项目参数),所有页面的 URL 都需要有项目信息的参数,这样通过浏览器打开的时候就可以通过这些参数跳转到首页并获取项目信息。初始方案是使用路由守卫(beforeRouteEnter),打开其他页面的时候给当前地址拼凑参数,可是这样会产生浏览记录,需要回退两次。后来才用了 location.replace。现在想想,跳转页面的时候直接加上参数就可以了。
  2. 问题:域名重复使用的问题,iPhone 重定向后需要回退两次的问题 后面有参数的话跳转就没有问题,如果直接是 #/home/?pid=2&code=3 就有问题 onefoundation.yeepay.com/prm/fr/mobi… 生成的链接去掉 #/ 就可以了,可能是判断后面有没有参数,有 # 就会有两次跳转 onefoundation.yeepay.com/prm/fr/mobi…
  3. iOS 需要点击两次按钮的问题,可以通过关闭弹窗的时候点击下确定按钮,来防止下次需要点击两次才出现弹窗
<div
  class="agreement-link-donate"
  :style="{ color: pageThemeColor }"
  @click="onShowDonationAuthorizationDialog"
>
  《捐赠授权协议》、
</div>

<van-dialog
  v-model="showPrivacyDialog"
  title="隐私权协议"
  :show-cancel-button="false"
  :lock-scroll="true"
  :allow-html="true"
  :message="agreementInfos.privacyAgreement"
  @confirm="clickPage"
>
</van-dialog>
 // 展示捐赠授权弹窗
onShowDonationAuthorizationDialog() {
  this.showDonationAuthorizationDialog = true;
},

// 需要点击下页面,防止 ios 下次点击无法起效果
clickPage() {
  document.getElementById("alipayForm").click();
}
  1. 解决返回顶部的问题 问题描述:点击返回顶部,ios 版微信上需要点击两次,经判断第一次没有触发事件,之后点击触发了事件
  • 方案一:fixed 属性变成 absolute 属性,可是设置 overflow:hidden 之后,还需要设置 max-height 才会将部分元素隐藏,可是这样不能获取到滚动之后的 scrollTop 值,于是这个方案排除
<div class="home-wrapper">
  <div class="home-one-wrapper" id="home-one-wrapper"></div>
  <div class="home-two-wrapper"></div>
</div>
.home-wrapper {
  position: relatvie;
  width: 100%;
  height: 100%;
  overflow: hidden;
  .home-one-wrapper {
    width: 100%;
    height: 100%;
    max-height: 627px;
    overflow-y: scroll;
  }
  .home-two-wrapper {
    position: absolute;
    bottom: 0;
    left: 0;
    width: 100%;
  }
}
  • 方案二(折中方案):让返回顶部的按钮固定显示出来
<van-icon
  v-show="true"
  :style="{ color: pageThemeColor }"
  class-prefix="iconfont icon-Top"
  class="go-top"
  name="extra"
  @click="backTop"
/>
  1. ios 中通过浏览器打开支付界面的时候,由于页面在轮询支付结果,可能会出现网络错误的情况。这时候需要取消掉所有轮询的请求
// 取消所有请求
cancalRequest() {
    let cancelArr = window.axiosCancel;
    cancelArr.forEach((ele, index) => {
      // ele.cancel("取消了请求"); // 在失败函数中返回这里自定义的错误信息
      delete window.axiosCancel[index];
    });
},

// axios 保存所有的请求
window.axiosCancel = [];

config.cancelToken = new axios.CancelToken((cancel) => {
  window.axiosCancel.push({
    cancel,
  });
});

其他问题

  1. H5 需要处理无网络的情况,弱网的情况
  2. 图标使用阿里的 iconfont,时间处理使用 moment 库
  3. iphone 会出现旋转屏幕的问题,还没有找到解决方案

教训

  1. 虽然是前端开发,可是也要花时间了解整个开发流程,特别是三方对接的时候,要熟悉所有可能出现的异常情况,及时找到对应的解决方案
  2. 最少有一台安卓机和一台苹果手机,由于苹果手机的特殊性,在微信 H5 开发中有很多坑。