• 如何将原生微信小程序页面改成原生VUE框架的H5页面


    项目背景:

           公司为了实现小程序与H5页面共同覆盖,全面推广。特此想将已有的小程序进行快速改造上线(二周内),研发出H5版本。目前公司前端技术较为薄弱,现有的技术解决方案还停留在JSP。

    问题:

           如何将目前已经上线运行的原生小程序(40多个页面)快速转化为原生H5页面,实现快速上线,是采用我们较为熟悉的JSP还是引用目前最热门的前后端分离VUE框架呢???

    想法:

    1)采用熟练的JSP框架,每人一天可以改造2个页面左右,计划投入2人,风险最小;

    2)采用主流分布式Vue框架,时间未知,风险未知;

           首先自我介绍下,本人是一名JAVA开发工程师,平时喜欢研究相关主流技术和挑战自己。对此我还是比较倾向于第二种解决方案,但是第二种解决方案无疑是最复杂,最耗时,最未知,风险最大。公司内没有人愿意承接。于是我抱着学习和研究的态度以及对主流技术的向往,我找到我们领导我是这样说的:我还是比较建议公司采用第二种方案。1)这无疑是给我们进行敲门砖及学习的机会;2)这是公司提升前端技术能力与主流技术看齐的机会。最后公司同意了我建议,采用方案二,有我来承接此事,进行牵头负责。

           中间心酸过程忽略,刚接下来第一天就后悔了,VUE用都没用过,还怎么玩。于是我花了大量的时间,看了大量文献,我这里使用到是Vue 2 + Vant 2 + axios + router。

      (1)VUE官方技术文档:

      https://v2.cn.vuejs.org/v2/guide/

      https://cli.vuejs.org/zh/guide/creating-a-project.html

           (2)引用了Vant 2组件:

       https://youzan.github.io/vant/v2/#/zh-CN/toast

           (3)同时也参考很多成熟开源VUE框架:

            若依(也是从这个项目中进行借鉴与参考):http://doc.ruoyi.vip/ruoyi/

            于是我花费了3天时间终于完成了VUE项目框架搭建及第一个小程序页面转H5页面。心想如果是这样的进度,指定时间完成是不可能的了,有没有什么方法可以快速将小程序组件替换成VUE呢,报着这个想法就开干,也是网上找了很多案例都不是很齐全,没办法我只能自己进行编写工具方法,废话不多说之前上干货。

             首先要想编写工具方法必须要知道小程序和VUE有哪些特性,以及哪些是等价效果。这里我进行了简单梳理:

    页面事件替换:

    view 替换 div

    image替换img

    bindinput=替换v-on:input=

    bindtap=替换@click=

    替换

    Js替换:

    e.detail.value替换e.target.value

    app.globalData.替換this.$globalData.(globalData这里是需要在main.js定义替换的全局变量)

    wx.showLoading({title: "加载中",mask: true,});

    替代写法

    this.$toast.loading({ message: '加载中...',forbidClick: true, duration: 0,});(这里是引用了Vant 2)

    wx.getStorageSync替代写法localStorage.getItem

    wx.setStorageSync替代写法localStorage.setItem

    wx.navigateTo替换window.location.href=" "

    wx.redirectTo替换window.open();

    wx.hideLoading();替换this.$toast.clear();(这里是引用了Vant 2)

    样式替换:

    //100rpx -> 5rem
    rpx替换rem(考虑到移动端兼容引入rem计量单位)
    等等,我这里不一一列举了。其次介绍下实现步骤及原理:
    VUE各个文件目录含义说明:

     

     具体步骤如下:

    (1)VUE采用的是单页面设计思路,所有js,html,css都在一个页面内完成,对此我们需要先将小程序指定文件夹下面的内容进行粘贴过来。

     

     

     (2)进入“replace”目录,将复制完成后的代码,粘贴到“replace.vue” 点击“replaceUtil.js” 右键,在集成终端中打开,运行代码“node .\replaceUtil.js”

     

    以上两步完成绝大部分的小程序转VUE特性功能点,个别较为复杂的可能需要人工进行修改调整。(列入wx.request这些目前我们的写法是人工进行修改改为axios,当然也可以进行二次封装,减少代码改动地方,欢迎大家进行尝试。我这里这里主要是为了去wx化防止误导,选择了人工进行修改调整)

    具体后台请求接口方面我们采用的axios具体写法如下:

    1、 使用axios组件完成后台请求,第一步“service”目录下新建“.js”

     

    2、 引用js。

     

    3、调用方式

     

    以上就是我对VUE转H5相关理解和具体实现思路,欢迎大家进行评论和指导!!!

     

    相关源码如下:

    (1)核心替换代码replaceUtil.js

    复制代码
    const fs = require("fs");
    let fileName = "replace.vue"; // 需要替换字符的文件名
    
    fs.readFile(`${__dirname}/${fileName}`, "utf8", (err, data) => {
      console.log(__dirname);
      console.log(fileName);
      if (err) {
        console.log("readFile error");
        console.log(err);
      }
    
      console.log("readFile sussce");
      // 替换字符
      let rs = changeWxToVue(data, ["label", "rpx", "if", "for", "tap", "labjs"]);
      // console.log(rs)
      fs.writeFile(`${__dirname}/${fileName}`, rs, "utf8", (err) => {
        if (err) {
          console.log("writeFile error");
        }
      });
    });
    
    /**
     *
     * @param {*} data vue文件字符
     * @param {*} changeArr 数组格式,需要替换的字符,以数组格式传递;
     * ['label']:只替换view、text、block标签
     * ['label','rpx']:替换view、text、block标签 和 rpx,rpx的数值需要是基准为750px的标准设计稿,100rpx->50px
     * ['for']:for标签替换不包括key,否则正则表达式太长了
     * @returns
     */
    function changeWxToVue(data, changeArr) {
      for (let i = 0; i < changeArr.length; i++) {
        if (changeArr[i] == "label") {
          // 替换view -> div, text -> span
          data = data.replace(/<(\/(view)|view.*)>/g, (a) => {
            return a.replace(/view/g, "div");
          });
          data = data.replace(/<(\/(text)|text.*)>/g, (a) => {
            return a.replace(/text/g, "span");
          });
          data = data.replace(/<(\/(block)|block.*)>/g, (a) => {
            return a.replace(/template/g, "span");
          });
        }
        if (changeArr[i] == "rpx") {
          // 替换100rpx -> 5rem
          data = data.replace(/(\d*)rpx/g, (num) => {
            return (parseInt(num) / 20).toFixed(2).toString() + "rem";
          });
        }
        if (changeArr[i] == "if") {
          // 替换wx:if -> v-if
          data = data.replace(
            /(wx:if|wx:elif|wx:else)="{{(((?![-=]).)*)}}"/g,
            (a, b, c) => {
              console.log(a,b,c);
    
              c = c.replace(/(^\s*)|(\s*$)/g, "");
              let vue = "";
              switch (b) {
                case "wx:if":
                  vue = "v-if";
                  break;
                case "wx:elif":
                  vue = "v-else-if";
                  break;
                case "wx:else":
                  vue = "v-else";
                  break;
              }
              return `${vue}="${c}"`;
            }
          );
        }
    
        if (changeArr[i] == "for") {
          // 替换wx:for="{{list}}" wx:for-item="item1" wx:for-index="index1" -> v-for=""
          let forRegArr = [
            /\swx:for="{{(((?![-=]).)*)}}"([^key].*)wx:for-item="(((?!-).)*)"([^key].*)wx:for-index="(((?![-=]).)*)"/g,
            /\swx:for="{{(((?![-=]).)*)}}"([^key].*)wx:for-index="(((?!-).)*)"([^key].*)wx:for-item="(((?![-=]).)*)"/g,
            /\swx:for="{{(((?![-=]).)*)}}"/g,
          ];
          data = data.replace(forRegArr[0], (a, b, c, d, e, f, g, h, i, j, k) => {
            // console.log('a-',a,'\nb-',b,'\nc-',c,'\nd-',d,'\ne-',e,'\nf-',f,'\ng-',g,'\nh-',h,'\ni-',i,'\nj-',j,'\nk-',k);
            if (e) {
              if (h) {
                return ` v-for="(${e}, ${h}) in ${b}" `;
              } else {
                return ` v-for="${e} in ${b}" `;
              }
            }
          });
          data = data.replace(forRegArr[1], (a, b, c, d, e, f, g, h, i, j, k) => {
            // console.log('a-',a,'\nb-',b,'\nc-',c,'\nd-',d,'\ne-',e,'\nf-',f,'\ng-',g,'\nh-',h,'\ni-',i,'\nj-',j,'\nk-',k);
            if (h) {
              if (e) {
                return ` v-for="(${h}, ${e}) in ${b}" `;
              } else {
                return ` v-for="${h} in ${b}" `;
              }
            }
          });
          data = data.replace(forRegArr[2], (a, b) => {
            return ` v-for="item in ${b}" `;
          });
        }
        if (changeArr[i] == "tap") {
          // bindtap || catchtap -> @click
          data = data.replace(
            /(bindtap|bind:tap|catchtap)="(((?!-).)*)"/g,
            (a, b, c) => {
              return `@click="${c}"`;
            }
          );
    
          // bindinput -> v-on:input=
          data = data.replace(/(bindinput)="(((?!-).)*)"/g, (a, b, c) => {
            return `v-on:input="${c}"`;
          });
        }
        if (changeArr[i] == "data") {
          // data-index="{{index}}" -> data-index="index"
          data = data.replace(
            /data-(((?!-).)*)="{{(((?!-).)*)}}"/g,
            (a, b, c, d) => {
              return `data-${b}="${d}"`;
            }
          );
        }
        if (changeArr[i] == "labjs") {
          // e.detail.value -> e.target.value
          data = data.replace(/e.detail.value/g, "e.target.value");
          // app.globalData. -> this.$globalData.
          data = data.replace(/app.globalData./g, "this.$globalData.");
          // wx.getStorageSync -> localStorage.getItem
          data = data.replace(/wx.getStorageSync/g, "localStorage.getItem");
          // wx.setStorageSync -> localStorage.setItem
          data = data.replace(/wx.setStorageSync/g, "localStorage.setItem");
          //  wx.hideLoading -> localStorage.setItem
          data = data.replace(/wx.setStorageSync/g, "localStorage.setItem");
          //  wx.hideLoading -> this.$toast.clear
          data = data.replace(/wx.hideLoading/g, "this.$toast.clear");
    
          data = data.replace(/wx:key=\"\*this\"/g, "");
          data = data.replace(
            /wx:for=["|']\s*\{\{([^\}]+)\}\}\s*["|']/g,
            ($0, $1) => 'v-for="(item, index) in ' + $1 + '"'
          );
          data = data.replace(
            /wx:for-items=["|']\s*\{\{([^\}]+)\}\}\s*["|']/g,
            ($0, $1) => 'v-for="(item, index) in ' + $1 + '"'
          );
          data = data.replace(
            /wx:key=["|']([^"|']+)["|']/g,
            ($0, $1) => ':key="' + $1 + '"'
          );
          // if
          data = data.replace(
            /wx:if=["|']\s*\{\{([^\}]+)\}\}\s*["|']/g,
            ($0, $1) => 'v-if="' + $1 + '"'
          );
           // elif
           data = data.replace(
            /wx:elif=["|']\s*\{\{([^\}]+)\}\}\s*["|']/g,
            ($0, $1) => 'v-else-if="' + $1 + '"'
          );
           // else
           data = data.replace(/wx:else/g, "v-else");
    
    
          data = data.replace(/);
          data = data.replace(/\/view>/g, "/div>");
          data = data.replace(/);
          data = data.replace(/\/text>/g, "/text>");
          data = data.replace(/]+)/g, ($0, $1) => ");
          data = data.replace(/]+)/g, ($0, $1) => ");
    
          data = data.replace(/<\/image>/g, "");
          data = data.replace(/<\/input>/g, "");
          data = data.replace(
            /]+)/g,
            ($0, $1) => '"
          );
          data = data.replace(
            /bind([^=|\s]+)=["|']([^"|'|\s]+)["|']/g,
            ($0, $1, $2) => "@" + $1 + '="' + $2 + '"'
          );
          data = data.replace(/navigator/g, "router-link");
          data = data.replace(/\"\/images/g, '"../../assets');
          data = data.replace(/this.data./g, "this.");
          data = data.replace(/that.data./g, "this.");
          data = data.replace(/..\/..\/utils\/wxml\/common.wxml/g, "../../utils/vue/common.vue");
          data = data.replace(/..\/..\/utils\/wxss\/common.wxss/g, "../../utils/css/common.css");
    
                // 变量
          // data = data.replace(/data-([^=|\s]+)=["|']\{\{([^\}]+)\}\}["|']/g, ($0,$1,$2)=> ':data-'+$1+'="'+$2+'"')
          data = data.replace(
            /([^\s|=]+)=["|']\{\{([^\}]+)\}\}["|']/g,
            ($0, $1, $2) => ":" + $1 + '="' + $2 + '"'
          );
    
          // 变量轮播图替换
          data = data.replace(/);
          data = data.replace(/\/swiper-item>/g, "/van-swipe-item>");
          data = data.replace(/);
          data = data.replace(/\/swiper>/g, "/van-swipe>");
          data = data.replace(/:autoplay=\"autoplay\"/g, ":autoplay=\"interval\"");
    
        }
      }
      return data;
    }
    复制代码

    (2)axios.js:

    复制代码
    /**
     * 严肃声明:
     * 开源版本请务必保留此注释头信息,若删除我方将保留所有法律责任追究!
     * 本系统已申请软件著作权,受国家版权局知识产权以及国家计算机软件著作权保护!
     * 可正常分享和学习源码,不得用于违法犯罪活动,违者必究!
     * Copyright (c) 2020 陈尼克 all rights reserved.
     * 版权所有,侵权必究!
     */
    import axios from "axios";
    import { Toast } from "vant";
    import router from "../router";
    import globalData from "../utils/global.js";
    import qs from 'qs';
    
    
    axios.defaults.baseURL = '';
    axios.defaults.timeout = 30000;
    axios.defaults.withCredentials = true;
    axios.defaults.headers["X-Requested-With"] = "XMLHttpRequest";
    axios.defaults.headers["token"] = localStorage.getItem("token") || "";
    axios.defaults.headers.post["Content-Type"] =
      "application/x-www-form-urlencoded;charset=UTF-8";
    
    // 请求拦截器
    axios.interceptors.request.use(
      (config) => {
        // config 请求的所有信息
        // console.log(config);
        // 设置请求头
        if (config.method === "post" && !!config.data && config.data !== "") {
          config.headers = {
            Accept: "*/*",
            "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
          };
          if(config.url === '/pages/sri/order/uploadImg'){
            config.headers["Content-Type"] = "multipart/form-data"
          } else {
            config.data = qs.stringify(config.data, {arrayFormat:'comma'})
          }
        }
    
        // if (config.method === "put" && !!config.params && config.params !== "") {
        //   config.headers = {
        //     Accept: "*/*",
        //     "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
        //   };
        // }
    
        // if (config.method === "delete") {
        //   if (!!config.params && !!config.params.data) {
        //     config.headers = {
        //       Accept: "*/*",
        //       "Content-Type": "application/json;charset=UTF-8",
        //     };
        //     config.data = config.params;
        //     config.params = "";
        //   }
        // }
    
        return config; // 将配置完成的config对象返回出去 如果不返回 请求讲不会进行
      },
      (err) => {
        // 请求发生错误时的相关处理 抛出错误
        console.log("服务端请求异常!");
        return Promise.reject(err);
      }
    );
    
    // 响应拦截器
    axios.interceptors.response.use(
      (res) => {
        return res;
      },
      (err) => {
        console.log("服务端响应异常!");
        return Promise.reject(err);
      }
    );
    
    export default axios;
    复制代码

     

  • 相关阅读:
    botocore.exceptions.NoCredentialsError: Unable to locate credentials
    电脑自动关机是什么原因?解决方案全解析!
    【蓝桥杯】算法模板题(Floyd算法)
    SQLite 常用功能整合
    sw型材利用父子关系找最新特征
    数据结构第三课 -----线性表之双向链表
    软考高级(信息系统项目管理师)高频考点:项目质量管理
    《最新出炉》系列入门篇-Python+Playwright自动化测试-51- 字符串操作 - 上篇
    [第一章]1.4 条件概率及派生的三个公式
    js数组方法复习汇总
  • 原文地址:https://www.cnblogs.com/cbw-mango/p/16657778.html