做完小程式專案、老闆給我加了6k薪資~
大家好,我是蘇南(South·Su),今天要給大家分享的是最近公司做的一個小程式專案,過程中的一些好的總結和遇到的坑,希望能給其他攻城獅帶來些許便利,更希望能像標題所說,做完老闆給你加薪~
今天是中秋節的第一天,假日的清晨莫名的醒的特別早,不知道為什麼,也許是因為,昨晚公司上線的專案回來的路上,發現了個小bug,心裡有些忐忑吧~~
以上純為扯淡,現在開始一本正經的裝逼,請繫好安全帶,中間過程有可能會開車,請注意安全!!!!!
最近個專案跟團隊小夥伴溝通後用的是wepy框架,沒有直接用原生的,小程式原生就……,大家都懂的,用wepy框架,給自己帶來了便利,也帶來了不少坑,但縱是如此,我還是懷著:“ ofollow,noindex">縱你虐我千百遍,我仍待你如初戀 ”的心態去認真把專案做好。
toast 的封裝
- toast 的封裝,大家都知道,官方的api wx.showToast 是滿足不了我們的需求的,因為它只支援 "success", "loading"兩種狀態,同時“ title 文字最多顯示 7 個漢字長度”,這是官方原話,有圖有真相哦,樣式巨醜~
wx.showToast({ title: '成功', icon: 'success', duration: 2000 }) wx.showModal({ title: '提示', content: '這是一個模態彈窗', success: function(res) { if (res.confirm) { console.log('使用者點選確定') } else if (res.cancel) { console.log('使用者點選取消') } } }) 複製程式碼
wx.showModal 的content的文字是不會居中的(現在不確定有沒有擴充套件,可以設定),依稀記得有一次因為問題差點跟產品經理吵起來,讓文字居中,我說最少要兩小時,當時產品就炸了,什麼鬼???讓文字居中一下要兩小時??兩小時??兩小時??呵呵~走了,後來就下決定自己封裝了一個屬於自己的toast元件, 以下為部分核心程式碼
<template lang="wxml"> <view class="ui-toast{{ className }}" hidden="{{ !visible }}"> <view class="ui-toast_bd"> <icon wx:if="{{ options.icon}}" type="{{ options.icon }}" size="40" color="{{ options.color }}" class="ui-toast_icon" /> <view class="ui-toast_text">{{ options.text }}</view> </view> </view> </template> <script> import wepy from 'wepy'; const __timer__ =1900; //方法以 : __XX__ 命名,因併入其他元件中後,可能引起方法名重複 class Toast extends wepy.component { /** * 預設資料 */ data={ list:[ { type: `success`, icon: `success`, className: `ui-toast-success`, }, { type: `cancel`, icon: `cancel`, className: `ui-toast-cancel`, }, { type: `forbidden`, icon: `warn`, className: `ui-toast-forbidden`, }, { type: `text`, icon: ``, className: `ui-toast-text`, }, ], timer:null, scope: `$ui.toast`, animateCss:'animateCss', className:'', visible:!1, options:{ type: ``, timer: __timer__, color: `#fff`, text: `已完成`, } } /** * 預設引數 */ __setDefaults__() { return { type: `success`, timer: __timer__, color: `#fff`, text: `已完成`, success() {}, } } /** * 設定元素顯示 */ __setVisible__(className = `ui-animate-fade-in`) { this.className = `${this.animateCss} ${className}`; this.visible = !0; this.$apply(); } /** * 設定元素隱藏 */ __setHidden__(className = `ui-animate-fade-out`, timer = 300) { this.className = `${this.animateCss} ${className}`; this.$apply(); setTimeout(() => { this.visible = !1; this.$apply(); }, timer) } /** * 顯示toast元件 * @param {Object} opts 配置項 * @param {String} opts.type 提示型別 * @param {Number} opts.timer 提示延遲時間 * @param {String} opts.color 圖示顏色 * @param {String} opts.text 提示文字 * @param {Function} opts.success 關閉後的回撥函式 */ __show__(opts = {}) { let options = Object.assign({}, this.__setDefaults__(), opts) const TOAST_TYPES = this.list; TOAST_TYPES.forEach((value, key) => { if (value.type === opts.type) { options.icon = value.icon; options.className = value.className } }) this.options = options; if(!this.options.text){ return ; }; clearTimeout(this.timer); this.__setVisible__(); this.$apply(); this.timer = setTimeout(() => { this.__setHidden__() options.success&&options.success(); }, options.timer); } __info__(args=[]){ let [ message, callback, duration ]= args; this.__show__({ type: 'text', timer: (duration||__timer__), color: '#fff', text: message, success: () => {callback&&callback()} }); } __success__(args=[]){ let [ message, callback, duration ]= args; this.__show__({ type: 'success', timer: (duration||__timer__), color: '#fff', text: message, success: () => {callback&&callback()} }); } __warning__(args){ let [ message, callback, duration ]= args; this.__show__({ type: 'forbidden', timer: (duration||__timer__), color: '#fff', text: message, success: () => {callback&&callback()} }); } __error__(args){ let [ message, callback, duration ]= args; this.__show__({ type: 'cancel', timer: (duration||__timer__), color: '#fff', text: message, success: () => {callback&&callback()} }); } __showLoading__(options){ wx.showLoading({ title: (options&&options.title||"載入中"), }); } __hideLoading__(){ wx.hideLoading(); } onLoad(){ this.$apply() } } export default Toast; </script>複製程式碼
呼叫示例:
<template> <view class="demo-page"> <Toast /> <Modals /> </view> </template> <script> import wepy from 'wepy' import Toast from '../components/ui/Toast' import Modals from '../components/ui/Modals' import {fetchJson} from '../utils/fetch'; export default class Index extends wepy.page { config = { navigationBarBackgroundColor: "#0ECE8D", navigationBarTextStyle:"white", navigationBarTitleText: '' } components = { Toast: Toast, Modals: Modals } methods = { tapToast(){ this.$invoke("Toast","__success__",[`您已提交成功,感謝您的支援與配合`]); } } } </script>複製程式碼
Storage (資料 儲存)
- Storage (儲存)在前端我們儲存的方式,
cookie
、localStorage
、sessionStorage
等這些,特性就不一一說明了,小程式裡大家都知道,資料儲存只能呼叫 wx.setStorage、wx.setStorageSync,相當於h5的localStorage
,而localStorage
是不會過期的,這個大家都知道,而且在很多的面試中,面試官都會問到這個問題,怎麼讓localStorage像cookie
一樣,只存兩小時、兩天、甚至只存兩分鐘呢?今天帶你解惑,讓你在職場面試中又減少一個難題,這也是我們專案中一直在用的方式,小程式中也同樣實用:
class storage { constructor(props) { this.props = props || {} this.source =wx||this.props.source; } get(key) { const data = this.source, timeout = (data.getStorageSync(`${key}__expires__`)||0) // 過期失效 if (Date.now() >= timeout) { this.remove(key) return; } const value = data.getStorageSync(key) return value } // 設定快取 // timeout:過期時間(分鐘) set(key, value, timeout) { let data = this.source let _timeout = timeout||120; data.setStorageSync(key,(value)); data.setStorageSync(`${key}__expires__`,(Date.now() + 1000*60*_timeout)); return value; } remove(key) { let data = this.source data.removeStorageSync(key) data.removeStorageSync(`${key}__expires__`) return undefined; } } module.exports = new storage(); 複製程式碼
其實很簡單,大家看了之後就都 “哦,原來還可以這樣” 懂了,只是一時沒想到而已,就是個小技巧,每次在儲存的時候同時也存入一個時效時間戳,而在獲取資料前,先與當前時間比較,如果小於當前時間則過期了,直接返回空的資料。
介面API的維護
- 介面API的維護, 在沒有
nodejs
之前,前端好像一直都在為處理不同環境下呼叫對應的API而煩惱,做的更多的就是用域名來進行判斷,當然也有些高階一點的做法,後端在頁面渲染的時候,存一個變數到cookie
裡或者在頁面輸出一個全域性的api變數(建立在沒有前後端分離的基礎上),到了小程式同樣也是如此,每次都要手動改環境,那麼一個專案可能有不同的業務,要呼叫不同域名api,又有不同的環境區分,怎麼維護會比較好呢??
env/dev.js //本地環境 module.exports = { wabApi:{ host:"https://dev-ali.southsu.com/XX/api/**", }, questionApi:{ host:"https://dev-ali.bin.com/question/api/**/question", }, mockApi:{ host:"https://easy.com/mock/594635**c/miniPrograms" }, inWelApi: { host: "https://dev.**.com/Wab/api/escene/v2" } }; 複製程式碼
import dev from './env/dev'; //本地或開發 import uat from './env/pre'; //體驗環境 import prd from './env/prd'; //線上 var ENV = "prd"; //'dev | uat | prd'; let _base_ = { dev, uat, prd }[ENV]; var config = { ENV, baseAPI:{..._base_, env : ENV }, appID:"wx*****b625e", //公司賬號(指數)appid isAuthorization:true, 'logId': 'gVDSMH****HAas4qSSOTb-gzGzoHsz', 'logKey': 'pxFOg****Jn3JyjOVr', questionnaireNo:'z**Insu' // 問卷調查編號 }; export const __DEBUG__ = (ENV!="prd"); export defaultconfig; 複製程式碼
請求呼叫api處理的示例 import wepy from 'wepy' import _login_ from './login'; import config,{__DEBUG__} from './config'; import 'wepy-async-function'; export constfetchJson = (options)=>{ /* *請求前的公共資料處理 * @ param {String} url 請求的地址 * @ param {String} Type 請求型別 * @ param {String} sessionId 使用者userToken * @ param {Boolean} openLoad 開啟載入提示,預設開啟,true-開,false-關 * @ param {function} StaticToast 靜態提示方法 ,詳細說明請參考 components/ui/Toast * @ param {Object} header 重置請求頭 * @ param {Boolean} isMandatory 是否強制使用者授權,獲取使用者資訊 */ StaticToast = getCurrentPages()[getCurrentPages().length - 1]; let { url,openLoad=true, type, data={},header={}, ...others } = options||{}; let sessionId = (Storage.get(__login__.server+'_userToken')||""); /*Start */ var regExp = /\/(.*?)\//, hostkey = url.match(regExp)[1]; let baseUrl = config.baseAPI[hostkey].host; url = url.replace(regExp, '/'); /*End */ __DEBUG__&&console.log('#--baseUrl:', baseUrl); __DEBUG__&&console.log('#--請求地址:', `${baseUrl}${url}`); __DEBUG__&&console.log('----------分割線---------------'); openLoad&&StaticToast.__showLoading__(); return new Promise((resolve, reject) => { return wepy.request({ url:`${baseUrl}${url}`, method:(type || 'POST'), data, header:{ "t9oken":sessionId, 'content-type': 'application/json', // 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', ...header }, success:(res)=>{ StaticToast.__hideLoading__(); return resolve(resHandler(res,options)); }, error:(err,status)=>{ StaticToast.__hideLoading__(); return reject(errorHandler(err,options,err.statusCode)); } }); }) 複製程式碼
業務呼叫示例
fetchJson({ type:"post", // url:"/mockApi/service/XXX", 最後請求得到的地址是 https://easy.com/mock/594635**c/miniPrograms/service/XXX (域名不同環境不一樣,在config裡的 ENV baseAPI控制) data:{ name:"蘇南" }, success:res=>{ console.log(`大家好,我是蘇南`,res) } }) 複製程式碼
填坑時間了
- 填坑時間了 ,
wepy
框架中每個元件內的生命週期回撥 onload,只要是引入了元件,不管你檢視有沒有渲染,他都會執行,導致某些業務邏輯用不上它的時候也執行了產生異常(當然為個鍋< 小程式 >肯定說我不背~^~ ),詳細看連結: github.com/Tencent/wep… , https://github.com/Tencent/wepy/issues/1386 ,不知道後面有沒有人解決。
rich-text元件
- rich-text, 小程式的一個元件,雖然有那麼一點點用處,但又不得不說到底要它何用啊?其它的我就忍了,
a
標籤,a
標籤啊,屬性沒有,那還要它何用啊,你都不要我跳轉,我還要用你嗎?b、i、span、em……哪個我不能用?不知道設計這個元件的人是不是腦被驢踢了(願老天保佑,我在這罵他,可千萬別被看到了,哈哈~),又是業務需求後臺配置的內容有連結,沒辦法,來吧,搞吧,往死裡搞吧,一切的推脫都是你技術low的藉口( 你看,你看,別人的怎麼可以跳轉啊,別人怎麼做到的?給我一刀,我能把產品砍成渣 ),所以有了後面的填坑:
<template> <view class="test-page"> <button @tap="cutting">點選解析html</button> <view wx:if="{{result.length>0}}" class="parse-list"> <viewclass="parse-view" wx:for="{{result}}" wx:key="unique" wx:for-index="index" wx:for-item="items"> <block wx:if="{{items.children&&items.children.length}}"> <block wx:for="{{items.children}}" wx:for-item="child" > <text wx:if="{{child.type == 'link'}}" class="parse-link" @tap="goToTap({{child.link}})">{{child.value}}</text> <text class="parse-text" wx:else>{{child.value}}</text> </block> </block> <text class="parse-text" wx:else>{{items.value}}</text> </view> </view> <Toast /> <Modals /> </view> </template> <script> import wepy from 'wepy' import { connect } from 'wepy-redux' import Toast from '../components/ui/Toast' import Modals from '../components/ui/Modals' import {fetchJson} from '../utils/fetch'; import Storage from "../utils/storage"; function wxHtmlParse(htmlStr=''){ if(!htmlStr){ return [] }; const httpExp=/(http:\/\/|https:\/\/)((\w|=|\?|\.|\/|\&|-)+)/g;//提取網址正則 const aExp=/<a.[^>]*?>([(^<a|\s\S)]*?)<\/a>/ig; //a標籤分割正則 let cuttingArr = htmlStr.split(/[\n]/); let result = []; //有a標籤的html處理 let itemParse = (itemHtml='')=>{ let itemCutting = itemHtml.split(aExp)||[]; let itemResult = []; for(var i = 0;i<itemCutting.length;i++){ let _html = itemCutting[i]; if(_html!==''){ let itemData = { value:_html, type:'text', class:"parse-text" }; let matchTag = itemHtml.match(aExp)||[]; //再次匹配有 a 標籤的 if(matchTag.length){ let itemIndex = matchTag.findIndex((k,v)=>(k.indexOf(_html)!==-1)); if(itemIndex>=0){ let link = matchTag[itemIndex].match(httpExp)[0]; itemData.type = 'link'; itemData.link = link; itemData.class = "parse-link"; }; }; itemResult.push(itemData) } }; return itemResult; }; cuttingArr.map((k,v)=>{ let itemData = {type : "view",class:"parse-view"}; let isATag = k.match(aExp); if(isATag){ itemData.children = itemParse(k); }else{ itemData.value = k; }; result.push(itemData); return k; }) ; return result; }; export default class Index extends wepy.page { config = { navigationBarBackgroundColor: "#0ECE8D", navigationBarTextStyle:"white", navigationBarTitleText: '小程式解析資料中的a標籤' } components = { Toast: Toast, Modals: Modals } data = { html:'大家好,我是蘇南(South·Su),\n職業:前端攻城獅(俗稱切圖崽),\n身高:176cm,\n性別:男,\n性取向:女,\n公司:目前就職於由騰訊、阿里、平安三巨頭合資的一家網際網路金融公司深圳分公司某事業部、,\n簡介:寶劍鋒從磨礪出 梅花香自苦寒來,認真做自己!,gitHub:https://github.com/meibin08/,\n興趣:跑步、羽毛球、爬上、音樂、看書、分享自己的微薄知識幫助他人……,\n其他:想了解更多嗎?可以加入<a href="https://segmentfault.com/a/1190000010017486/">386485473交流群</a>,也可以給我電話<a href="https://github.com/meibin08/">134XX852xx5</a> ,開玩笑啦', result:[] } methods = { cutting(e){ this.result = wxHtmlParse(this.html); console.log(`result`,this.result); this.$apply(); }, } } </script>複製程式碼
PS:如有需要完整程式碼的同學,可以留言,待我整理後會貼上鍊接~
今天的分享就到這裡,寫了蠻久,最近才在開始嘗試寫部落格,新手上路中,如果文章中有不對之處,煩請各位大神斧正。如果你覺得這篇文章對你有所幫助,打個賞,讓我有更大的動力去創作。