支付宝小程序开发准备工作
在正式开发支付宝小程序之前,我们要做一些准备工作,比如说封装一下config文件、request请求、api接口集中定义,自定义的工具脚本等。文末还有彩蛋哈~
第一步:在支付宝开发者工具中新增支付宝小程序。
第二步:在小程序文件的根目录创建utils目录。
创建自定义的相关js文件(或者直接从以前定义好的项目中拷贝过来),如apis.js、config.js、tools.js、http.js、tools.sjs
1.小程序中调用接口的集中展示,加上备注信息,这样在后台查看的时候方便一些吧。
//apis.js //此处为封装的请求方法 import { getJSON } from './http' const apis = { // 用户登录 userLogin(params, method = 'GET') { return getJSON('/api/login?r=' + new Date().getTime(), params, method) }, } module.exports = apis
2.配置文件,开发的时候难免遇到接口地址切换开发环境、测试环境、生产环境等各种环境,我们事先配置好文件,在切换的时候直接修改环境参数即可。
// 后台不同环境 const ENV = { SIT: 'sit', UAT: 'uat', PRD: 'prd' }; //后台环境对应的接口目录 const BUILD_INFO = { sit: { api: 'http://127.0.0.1:8080', domain: '' }, uat: { api: 'https://**.com', domain: 'https://**.com' }, prd: { api: 'https://**.com', domain: 'https://**.com' } } // 小程序APP_id const APP_ID = { sit: '', uat: '', prd: '' } // 不同环境的版本 const VERSION = { sit: '1.2.1', uat: '1.0.1', prd: '1.0.16' } //实际使用的后台环境 const env = ENV.PRD; const config = { ENV: env, API_URL: BUILD_INFO[env].api, APP_ID: APP_ID[env], DOMAIN_URL: BUILD_INFO[env].domain, VERSION: VERSION[env] }; module.exports = config;
3.封装一些常用的工具函数以便我们提升开发效率。比如小程序中的页面跳转一般情况下要绑定事件,然后定义事件方法,在其中定义跳转的页面,此处我封装了一个go2page的方法,这样就不用在小程序页面的脚本中定义各种跳转事件了。不过就是需要在引用go2page的地方添加参数到data中。
在使用go2page方法进行页面跳转后,我们需要从query中提取参数。使用下面的curPagePath方法即可。
const tools = { isObject(obj) { if (Object.prototype.toString.call(obj) === '[object Object]') { return true } return false }, isDefine(str) { if (str === 0) { return true } if (str == '' || (!str)) { return false } return true }, isIDCard(str) { const reg = /^(^[1-9]\d{7}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}$)|(^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])((\d{4})|\d{3}[Xx])$)$/ return reg.test(str) }, isPhone(str) { const myreg = /^1[3|4|5|6|7|8|9][0-9]\d{8}$/ return myreg.test(str) }, isFullName(str) { const reg = /^[\u4E00-\u9FA5\uf900-\ufa2d·s]{2,20}$/ return reg.test(str) }, getUrlParam(href) { const indexWen = href.indexOf('?') const indexJing = href.indexOf('#') const theRequest = {} if (indexWen === -1) { return theRequest } if (indexJing < indexWen) { // 如果#号在问好前面 href = href.substring(indexWen + 1) } else { href = href.substring(indexWen + 1, indexJing) } const strArr = href.split('&') for (let i = 0; i < strArr.length; i++) { theRequest[strArr[i].split('=')[0]] = unescape(strArr[i].split('=')[1]) } return theRequest }, stringifyParam(param = {}) { let paramString = '' for (let key in param) { if (paramString === '') { paramString += key + '=' + param[key] } else { paramString += '&' + key + '=' + param[key] } } return paramString }, // 生成随机码 getRandom(num) { const char = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' const result = [] for (let i = 0; i < num - 1; i++) { const index = Math.random() * char.length - 1 const value = char.charAt(index) result.push(value) } return result.join('') }, desensitizing(str = '', from = 0, length = 0, replaceStr = '*') { // 隐藏某些字符串 if (str.length === 0) { return str } // 变成数组 const strArr = str.split('') // 更改数组 strArr.splice(from, length, replaceStr.repeat(length)) return strArr.join('') }, htmlEncode(str = '') { if (str === '') { return str } let s = str.replace(/&/g, '&') s = s.replace(/</g, '<') s = s.replace(/>/g, '>') s = s.replace(/\'/g, ''') s = s.replace(/\"/g, '"') return s }, getDay(day) { var today = new Date(); var targetday_milliseconds = today.getTime() + 1000 * 60 * 60 * 24 * day; today.setTime(targetday_milliseconds); //注意,这行是关键代码 var tYear = today.getFullYear(); var tMonth = today.getMonth(); var tDate = today.getDate(); tMonth = this.doHandleMonth(tMonth + 1); tDate = this.doHandleMonth(tDate); return tYear + "-" + tMonth + "-" + tDate; }, doHandleMonth(month) { var m = month; if (month.toString().length == 1) { m = "0" + month; } return m; }, getWeek(dateString) { var dateArray = dateString.split("-"); var date = new Date(dateArray[0], parseInt(dateArray[1] - 1), dateArray[2]); return "周" + "日一二三四五六".charAt(date.getDay()); }, getUserInfo() { my.getStorage({ key: 'userInfo', success: function (res) { console.log('获取缓存中用户信息', res) return res.data }, fail: function (res) { console.log('获取缓存中用户信息失败', res) return {} } }); }, checkToken(callback, param = {}) { let that = this var app = getApp() if (app.globalData.userId) { callback.call(this, param); } else { setTimeout(function () { param = Object.assign(param, app.globalData) that.checkToken(callback, param); }, 200); } }, go2page(e) { const { dataset } = e.target let params = [] for (let key in dataset) { if (key != 'url') { params.push(key + '=' + dataset[key]) } } if (!this.isDefine(e.target.dataset.url)) { my.showToast({ type: 'success', content: '建设中,敬请期待', duration: 3000, success: () => { }, }); } else { let url = e.target.dataset.url if (params.length > 0) { url = url + '?' + params.join('&') } my.navigateTo({ url: url }); } }, round(str) { if(!str){return ''} return str.toFixed(2) }, curPagePath(query){ let pages = getCurrentPages(); let currPage = null; // console.log(pages) 的到一个数组 if (pages.length) { // 获取当前页面的对象(上边所获得的数组中最后一项就是当前页面的对象) currPage = pages[pages.length - 1]; } // 获取当前页面的路由 let route = currPage.route let params = [] if(query){ for (let key in query) { params.push(key + '=' + query[key]) } } if(params.length>0){ route += '?' + params.join('&') } console.log('当前页面路径==>',route) }, getTimeList(start, end, step,restStart,restEnd,selectDate) { var curDate = new Date(), curFullYear = curDate.getFullYear(), curMonth = curDate.getMonth() + 1, curDay = curDate.getDate() curMonth = curMonth < 10 ? '0' + curMonth : curMonth curDay = curDay < 10 ? '0' + curDay : curDay var ymd = curFullYear + '/' + curMonth + '/' + curDay selectDate = selectDate.replace(/-/g,'/') console.log('selectDate',selectDate) var startTime = new Date(ymd + ' ' + start).getTime(), endTime = new Date(ymd + ' ' + end).getTime(), restStartTime = new Date(ymd + ' ' + restStart).getTime(), restEndTime = new Date(ymd + ' ' + restEnd).getTime() var returnArr = [] while (startTime + step * 60 * 1000 <= endTime) { // 中途休息的时间阶段 if(startTime + step * 60 * 1000>restStartTime && startTime + step * 60 * 1000<=restEndTime){ startTime += step * 60 * 1000 continue } // 如果时间小于当前时间则跳过显示 if(startTime + step * 60 * 1000<new Date().getTime() && ymd == selectDate){ startTime += step * 60 * 1000 continue } returnArr.push(this.getTime(startTime) + '-' + this.getTime(startTime+step*60*1000)) startTime += step * 60 * 1000 } return returnArr }, getTime(timestamp) { var date = new Date(timestamp);//时间戳为10位需*1000,时间戳为13位的话不需乘1000 var Y = date.getFullYear() + '-'; var M = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1) + '-'; var D = (date.getDate() < 10 ? '0' + date.getDate() : date.getDate()) + ' '; var h = (date.getHours() < 10 ? '0' + date.getHours() : date.getHours()) + ':'; var m = (date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()) + ''; var s = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds(); return h + m; }, isIphoneX(){ var u = navigator.userAgent; var isIOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); if (isIOS) { if (screen.height == 812 && screen.width == 375) { return true; } else { return false; } } } } module.exports = tools;
4.常用的请求方法,根据实际项目中约定的接口规范进行修改。
import config from './config'; function isApiSuccess(result) { const status = result.success || result.stat || result.status || ''; // 接口状态字段 if (status == true || status == '000' || status === 'ok' || status === 'success' || status === 'succeed') { return true; } else { return false; } } export function getJSON(url, params, method = 'GET', mockSetting = {}) { let path = ''; const { on = false, mockData = {} } = mockSetting; if (url.indexOf('http') > -1 || url.indexOf('https') > -1) { path = url; } else { path = config.API_URL + url; } return new Promise((resolve, reject) => { my.showLoading(); if (on) { console.log('-------返回 mock 数据-------', url, params, mockData); if (isApiSuccess(mockData)) { resolve(mockData); } else { reject(mockData); } my.hideLoading(); return; } let token = params.token let headers = { "Content-Type": "application/json", } if (params.headers) { headers = Object.assign(headers, params.headers) delete params.headers } if (token) { headers['Authorization'] = 'Bearer ' + token delete params.token } if (method.toLocaleLowerCase() != 'get') { headers['content-type'] = 'application/json' } if (method.toLocaleLowerCase() == 'delete') { headers['content-type'] = 'application/x-www-form-urlencoded' } my.request({ url: path, method: method, data: params, dataType: 'json', headers: headers, success: (result) => { // console.log(`--返回数据--接口地址=>${url},结果=>${JSON.stringify(result)}`); my.hideLoading(); if (isApiSuccess(result.data)) { resolve(result.data); } else { reject(result.data); } }, fail: (err) => { console.log('-------返回错误-------', url, err); my.hideLoading(); if (err.status == 401) { console.log('未登录权限') } my.showToast({ type: 'fail', content: '请求异常,请稍后再试!' }); reject(err); }, }); }); }
5.在页面中引用方法时需要用到sjs文件。
const desensitizing = (str,from,length,replaceStr = '*')=>{ if (str.length === 0) { return str } // 变成数组 const strArr = str.split('') let replace_str = '' for(var i=0;i<length;i++){ replace_str += replaceStr } // 更改数组 strArr.splice(from, length, replace_str) return strArr.join('') } const repeat = (length,repeatStr='*') =>{ var str = '' for(let i=0;i<length;i++){ str += repeatStr } return str } const desensitizing2 = (str,type,isdes=false,replaceStr = '*')=>{ if(!isdes){ return str } if(str == '' || !str){ return '' } switch(type){ case 'name': str = str.replace(getRegExp('·','g'),replaceStr); if(str.length == 1){ return replaceStr } if(str.length>=2){ if(str.length>=5){ return repeat(4,replaceStr) + str[str.length-1] } return desensitizing(str,0,str.length-1) } return replaceStr; break; case 'sfz': return desensitizing(str,1,str.length-2) break; case 'phone': return desensitizing(str,3,6) break; } } const substring = (str,from,length=0)=>{ if(str.length == 0){ return '' } return str.substring(from,from+length) } const isDefine = (str)=>{ if (str === '' || !str) { return false } return true } const round = (str,len=2)=>{ if(!str){return ''} let point_index = str.lastIndexOf('.') if(len == 0){ return str.substring(0,1) } if(str.length > len + point_index + 1){ return str.substring(0,len + point_index + 1) } return str } const str2date = (str,separater='-') => { if(str === '' || !str || str.length<8) { return '' } return str.substring(0,4) + separater + str.substring(4,6) + separater + str.substring(6,8) } export default { desensitizing, substring, isDefine, round, desensitizing2, str2date }
以上准备工作完成之后,需要引用一下支付宝小程序的ui组件,我用的是mini-ali-ui。
这下终于可以开发愉快地写页面了吧。此时此刻你在想什么呢。
作为一个经常使用mac、linux系统的程序猿,想借助于开发工具中的控制台进行页面创建,怎么办呢?
这时候要用到node的功能了,在根目录下添加package.json
{ "dependencies": { "iconv-lite": "^0.6.3" }, "scripts": { "page": "node scripts/page" } }
然后在根目录添加scripts目录,在scripts目录中添加page.js
示例代码如下:此程序仅支持 “pages/分组名称/页面名称”这种结构的页面创建,若需要减小层级或增加层级自行修改哈。
/** * pages页面快速生成脚本 * 用法:npm run tep `文件名` * npm run page product/ProductClass */ const fs = require('fs'); var iconv = require('iconv-lite'); const dirName = process.argv[2]; const cover = process.argv[3]; console.log(dirName) const dirNameArr = dirName.split('/') const folder = dirNameArr[0] const fileName = dirNameArr[1] const capPirName = dirName.substring(0, 1).toUpperCase() + dirName.substring(1); console.log(folder, fileName, capPirName, __dirname) if (!dirName) { console.log('文件夹名称不能为空!'); console.log('示例:npm run page product/ProductClas'); process.exit(0); } var curPath = __dirname.substring(0, __dirname.lastIndexOf('/')) console.log('curPath==', curPath) function loadjson(filepath) { var data; try { var jsondata = iconv.decode(fs.readFileSync(curPath + '/' + filepath, "binary"), "utf8"); data = JSON.parse(jsondata); } catch (err) { console.log(err); } return data; } function savejson(filepath, data) { var datastr = JSON.stringify(data, null, 4); if (datastr) { try { fs.writeFileSync(curPath + '/' + filepath, datastr); } catch (err) { } } } //model模板 const wxmlTemp = ` <view> </view> ` const jsTemp = ` import tools from '/utils/tools' import { getJSON } from '/utils/http' import apis from '/utils/apis' const app = getApp() Page({ data: { }, onLoad: function (options) { tools.curPagePath(options) }, onShow: function () { }, go2page(e){ tools.go2page(e) } }) ` const wxssTemp = ` ` const jsonTemp = ` { "usingComponents": {} } ` if (!fs.existsSync(`pages/${folder}`)) { fs.mkdirSync(`pages/${folder}`) } if (!fs.existsSync(`pages/${folder}/${fileName}`)) { fs.mkdirSync(`pages/${folder}/${fileName}`) } else { // 如果文件夹存在则说明页面已创建,不能再操作了,否则会覆盖已有页面 if (cover && cover != 1) { process.exit(0); } } process.chdir(`pages/${folder}/${fileName}`); // cd $1 fs.writeFileSync(`${fileName}.axml`, wxmlTemp) fs.writeFileSync(`${fileName}.json`, jsonTemp) fs.writeFileSync(`${fileName}.acss`, wxssTemp) fs.writeFileSync(`${fileName}.js`, jsTemp) var jsonPath = 'app.json' var appJson = loadjson(jsonPath) if (appJson) { var pages = appJson.pages if (pages.indexOf(`pages/${folder}/${fileName}/${fileName}`) == -1) { pages.push(`pages/${folder}/${fileName}/${fileName}`) } savejson(jsonPath, appJson) } process.exit(0);
终于写完了,以上是我个人在开发支付宝小程序时的经验总结,仅供参考。不喜可以直接跳过~~~