你们好,我是@IT·平头哥联盟的首席填坑官——苏南(South·Su),今天要给你们分享的是最近公司作的一个小程序项目,过程当中的一些好的总结和遇到的坑,但愿能给其余攻城狮带来些许便利,更但愿能作完以后老板给你加薪~html
今天是中秋节的第一天,假日的清晨莫名的醒的特别早,不知道为何,也许是由于,昨晚公司上线的项目回来的路上,发现了个小bug,内心有些忐忑,一会偷偷先改了,让领导发现这个月绩效就没了~~前端
以上纯为扯淡,如今开始一本正经的装逼,请系好安全带,中间过程有可能会开车,请注意安全!!!!!node
最近个项目跟团队小伙伴沟通后用的是wepy框架,没有直接用原生的,小程序原生就……,你们都懂的,用wepy框架,给本身带来了便利,也带来了很多坑,但纵是如此,我仍是怀着:“纵你虐我千百遍,我仍待你如初恋”的心态去认真把项目作好git
一、 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组件,如下为部分核心代码github
<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(存储)在前端咱们存储的方式,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的维护,在没有 nodejs以前,前端好像一直都在为处理不一样环境下调用对应的API而烦恼,作的更多的就是用域名来进行判断,固然也有些高级一点的作法,后端在页面渲染的时候,存一个变量到cookie里或者在页面输出一个全局的api变量(创建在没有先后端分离的基础上),到了小程序一样也是如此,每次都要手动改环境,那么一个项目可能有不一样的业务,要调用不一样域名 api,又有不一样的环境区分,怎么维护会比较好呢??
//env/dev.js 其余test、uat、prd结构相同 //本地环境 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 default config;
请求调用api处理的示例 import wepy from 'wepy' import _login_ from './login'; import config,{__DEBUG__} from './config'; import 'wepy-async-function'; export const fetchJson = (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)); } }); })
业务调用示例:
//最后请求获得的地址是 https://easy.com/mock/594635**c/miniPrograms/service/XXX (域名不一样环境不同,在config里的 ENV baseAPI控制) fetchJson({ type:"post", // url:"/mockApi/service/XXX", data:{ name:"苏南" }, success:res=>{ console.log(`你们好,我是苏南`,res) } });
四、填坑时间了,wepy框架中每一个组件内的生命周期回调 onload,只要是引入了组件,无论你视图有没有渲染,他都会执行,致使某些业务逻辑用不上它的时候也执行了产生异常(固然为个锅< 小程序 >确定说我不背~^~ ),详细看连接: https://github.com/Tencent/we... , https://github.com/Tencent/we... ,不知道后面有没有人解决
五、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"> <view class="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:完整示例源码 来啦~,以为不错记得 Star、Star
、Watch
哦,感谢!json
今天的分享就到这里,写了蛮久,最近才在开始尝试写博客,新手上路中,若是文章中有不对之处,烦请各位大神斧正。若是你以为这篇文章对你有所帮助,请记得点赞哦~,如以为不错,记得关注咱们的公众号
哦!redux
面试踩过的坑,都在这里了~
你应该作的前端性能优化之总结大全!
如何给localStorage设置一个过时时间?
手把手教你如何绘制一辆会跑车
如何用CSS3画出懂你的3D魔方?
SVG Sprites Icon的使用技巧
immutability因React官方出镜之使用总结分享!小程序
做者:苏南 - 首席填坑官
交流群:912594095,公众号:honeyBadger8
本文原创,著做权归做者全部。商业转载请联系@IT·平头哥联盟
得到受权,非商业转载请注明原连接及出处。