目前h5新增一個文字轉語音的功能(但是正在完善中,勉強能用),h5新增的SpeechSynthesisUtterance實例 首先new一個SpeechSynthesisUtterance對象 使用實例對象的一些屬性,包括: text – 要合成的文字內容,字元串。 lang – 使用的語言,字元串 ...
項目背景:
公司為了實現小程式與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=
<rich-text nodes="{{detail.prizeDetailContent}}"></rich-text>替換<div class="fuwenben" v-html="detail.prizeDetailContent"></div>
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(/<view/g, "<div"); data = data.replace(/\/view>/g, "/div>"); data = data.replace(/<text/g, "<span"); data = data.replace(/\/text>/g, "/text>"); data = data.replace(/<image([^>]+)/g, ($0, $1) => "<img " + $1 + "/"); data = data.replace(/<input([^>]+)/g, ($0, $1) => "<input " + $1 + "/"); data = data.replace(/<\/image>/g, ""); data = data.replace(/<\/input>/g, ""); data = data.replace( /<icon([^>]+)/g, ($0, $1) => '<i style="display:inline-block" ' + $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(/<swiper-item/g, "<van-swipe-item"); data = data.replace(/\/swiper-item>/g, "/van-swipe-item>"); data = data.replace(/<swiper/g, "<van-swipe"); 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;