# H5 的实践记录

# 打开微信

function goWeixin() {
  window.location = 'weixin://'
  // 如果没有拉起 app 跳转应用网站
  setTimeout(() => {
    if (document.visibilityState !== 'hidden') {
      window.location.href = '//weixin.qq.com/'
    }
  }, 3000)
}

# 拨打电话

<a href="tel:18360523057">18360523057</a>

将网页内容中的手机号码不显示为拨号的超链接

<meta name="format-detection" content="telephone=no" />

# 微信分享

微信 JS-SDK 说明文档 (opens new window)

  1. 安装依赖包yarn add weixin-js-sdk -D
  2. 在微信公众平台绑定安全域名
  3. 后端接口实现 JS-SDK 配置需要的参数
  4. 页面实现 JS-SDk 中 config 的注入配置,并实现对成功和失败的处理
  5. 微信分享如果没有图标和标题,可使用微信开发者工具查看分析 sdk 是否加载成功(和绑定的域名有关)
import wx from 'weixin-js-sdk'

async function shareByWeiXin(params) {
  const { title, content: desc, url: link, image: imgUrl, success, fail, complete } = params
  const url = encodeURIComponent(location.href)
  const { data: result } = await axios.get(`/weixin/getconfig?url=${url}`)
  if (result.status !== 1) return
  wx.config({
    appId: result.data.appId, // 必填,公众号的唯一标识
    timestamp: result.data.timestamp, // 必填,生成签名的时间戳
    nonceStr: result.data.nonceStr, // 必填,生成签名的随机串
    signature: result.data.signature, // 必填,签名
    jsApiList: [
      'onMenuShareTimeline',
      'onMenuShareAppMessage',
      'updateAppMessageShareData',
      'updateTimelineShareData',
      'onMenuShareQQ',
      'onMenuShareQZone'
    ],
    debug: process.env.NODE_ENV === 'development'
  })

  // 分享成功回调
  const success = v => {
    typeof success === 'function' && success(v)
  }

  // 分享失败回调
  const fail = err => {
    typeof fail === 'function' && fail(err)
  }

  // 分享完成回调
  const complete = v => {
    typeof complete === 'function' && complete(v)
  }

  wx.ready(() => {
    const timelineOpt = {
      title,
      link,
      imgUrl,
      success,
      fail,
      complete
    }
    const messageOpt = {
      title, // 分享标题
      link, // 分享链接
      imgUrl, // 分享图片
      desc: desc && desc.substr(0, 50), // 分享描述
      success,
      fail,
      complete
    }
    if (wx.onMenuShareAppMessage) {
      // QQ
      wx.onMenuShareQQ(messageOpt)
      // 朋友圈
      wx.onMenuShareTimeline(timelineOpt)
      // 个人或群
      wx.onMenuShareAppMessage(messageOpt)
    } else {
      wx.updateAppMessageShareData(messageOpt)
      wx.updateTimelineShareData(timelineOpt)
    }
  })
  wx.error(err => {
    console.log(err)
  })
}

# 样式篇章

  1. 常用样式封装
// 设置输入框的提示符样式
@mixin setPlaceholder($color) {
  &::-webkit-input-placeholder {
    color: $color;
  }
  &::-moz-placeholder {
    color: $color;
  }
  &::-moz-placeholder {
    color: $color;
  }
  &:-ms-input-placeholder {
    color: $color;
  }
}

// 多行文字省略号
@mixin clamp($num) {
  overflow: hidden;
  text-overflow: ellipsis;
  display: -webkit-box;
  -webkit-line-clamp: $num;
  -webkit-box-orient: vertical;
}
  1. 常用样式兼容
body {
  -webkit-tap-highlight-color: transparent; // 去掉元素被点击的时候会被高亮显示(针对ios)
  -webkit-font-smoothing: antialiased; // 让页面里的字体变清晰,抗锯齿很好
  -webkit-overflow-scrolling: touch; // 让滚动条产生滚动回弹的效果
  -webkit-text-size-adjust: none; // 禁止字体大小自动调节
}

# 刘海屏适配

访问地址 (opens new window)

# 微信环境

// 判断是否为微信
export function isWeixin() {
  const ua = navigator.userAgent.toLowerCase()
  return /MicroMessenger/i.test(ua)
}

// 小程序环境
export function isMiniProgram() {
  const ua = navigator.userAgent.toLowerCase()
  return (
    (ua.match(/micromessenger/i) && ua.match(/miniprogram/i)) ||
    win.__wxjs_environment === 'miniprogram'
  )
}

# 兼容性篇章

  1. van-datetime-picker 在 iOS 上显示一片空白

问题:使用 vant 的 van-datetime-picker 在 iOS 上显示显示一片空白

方案:2020-04-10 格式改成 2020/04/10 格式

原因:ios 初始化 Date 时不支持 2020-04-10 格式,正确写法是 2020/04/10

# 自定义高度输入框

场景:聊天输入信息框,根据内容撑起高度

问题:使用 input/textarea 输入框,没法根据文字撑起高度

方案:使用 div 元素,设置其属性 contenteditable 为 true,使其内容可编辑。(contenteditable 属性指定元素内容是否可编辑)

<template>
  <div class="reply">
    <div class="txt-box">
      <div class="textarea" ref="textarea" contenteditable="true" @input="change" @click.stop></div>
      <span class="tip" v-if="!value">回复Ta:</span>
    </div>
    <van-button>发送</van-button>
  </div>
</template>
<script>
export default {
  data() {
    return {
      value: ''
    }
  },
  methods: {
    focus() {
      this.value = ''
      this.$refs.textarea.focus()
    },
    change(e) {
      this.value = e.target.innerText
    }
  }
}
</script>

# 防止用 v-html 时 xss 攻击

// main.js中
import xss from 'xss'
Vue.prototype.$xss = xss

// vue.config.js中
module.exports = {
  chainWebpack: config => {
    // 覆写html指令,防止xss攻击
    config.module
      .rule('vue')
      .use('vue-loader')
      .loader('vue-loader')
      .tap(options => {
        options.compilerOptions.directives = {
          html(node, directiveMeta) {
            ;(node.props || (node.props = [])).push({
              name: 'innerHTML',
              value: `$xss(_s(${directiveMeta.value}))`
            })
          }
        }
        return options
      })
  }
}

# 关键词高亮显示

const list = res.data || []
for (const val of list) {
  val.name_str = val.name.replace(
    new RegExp(this.keyword, 'g'),
    `<span style="color:#3A72E4">${this.keyword}</span>`
  )
}
this.searchList = list
<ul class="search-list">
  <li v-for="item in searchList" :key="item.id">
    <i class="icon-search"></i>
    <div class="name" v-html="item.name_str"></div>
  </li>
</ul>