/** * 判断是否有值 */ const hasVal = v => { return typeof v !== 'undefined' && v !== null } /** * 判断是否是字符串 */ const isStr = v => { return typeof v === 'string' } /** * 判断是否是函数 */ const isFn = v => { return typeof v === 'function' } /** * 判断是否是数组 */ const isArr = v => { return Array.isArray ? Array.isArray(v) : Object.prototype.toString.call([]) === '[object Array]' } /** * 判断是否是对象 */ const isObj = v => { return v !== null && typeof v === 'object' } /** * 判断是否是空对象 */ const isEmptyObj = v => { if (v !== null && typeof v === 'object') { for (const i in v) return false return true } return true } /** * 对个位数前补零 */ const formatNumber = n => { n = n.toString() return n[1] ? n : '0' + n } /** * 日期时间格式化 * 2018-01-01 23:59:59 */ const formatDateTime = (date, { dateStr = '-', medianStr = ' ', timeStr = ':' } = {}) => { const year = date.getFullYear() const month = date.getMonth() + 1 const day = date.getDate() const hour = date.getHours() const minute = date.getMinutes() const second = date.getSeconds() return [year, month, day].map(formatNumber).join(dateStr) + medianStr + [hour, minute, second].map(formatNumber).join(timeStr) } /** * 日期格式化 * 2018-01-01 */ const formatDate = (date, dateStr = '-') => { const year = date.getFullYear() const month = date.getMonth() + 1 const day = date.getDate() return [year, month, day].map(formatNumber).join(dateStr) } /** * 时间格式化 * 23:59:59 */ const formatTime = (date, timeStr = ':') => { const hour = date.getHours() const minute = date.getMinutes() const second = date.getSeconds() return [hour, minute, second].map(formatNumber).join(timeStr) } /** * 路径格式化 */ const formatUrl = (url, obj) => { const arr = [] if (isObj(obj)) { for (let i in obj) { arr.push(`${i}=${obj[i]}`) } return url.indexOf('?') < 0 ? url + '?' + arr.join('&') : url + '&' + arr.join('&') } return url } /** * 判断当前月份的总天数 */ const thisMonthLenth = ({ year, month }) => { let len = 0 if (month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12) { len = 31 } else if (month == 4 || month == 6 || month == 9 || month == 11) { len = 30 } else if (month == 2) { if (((year % 4) == 0) && ((year % 100) != 0) || ((year % 400) == 0)) { len = 29 } else { len = 28 } } return len } /** * 节流 * fn是我们需要包装的事件回调, interval是时间间隔的阈值 */ const throttle = (fn, interval = 800) => { // last为上一次触发回调的时间 let last = 0 // 将throttle处理结果当作函数返回 return function () { // 保留调用时的this上下文 let context = this // 保留调用时传入的参数 let args = arguments // 记录本次触发回调的时间 let now = +new Date() // 判断上次触发的时间和本次触发的时间差是否小于时间间隔的阈值 if (now - last >= interval) { // 如果时间间隔大于我们设定的时间间隔阈值,则执行回调 last = now; fn.apply(context, args); } } } /** * 防抖 * fn是我们需要包装的事件回调, delay是每次推迟执行的等待时间 */ const debounce = (fn, delay = 800) => { // 定时器 let timer = null // 将debounce处理结果当作函数返回 return function () { // 保留调用时的this上下文 let context = this // 保留调用时传入的参数 let args = arguments // 每次事件被触发时,都去清除之前的旧定时器 if (timer) { clearTimeout(timer) } // 设立新定时器 timer = setTimeout(function () { fn.apply(context, args) }, delay) } } /** * 加强版节流 * fn是我们需要包装的事件回调, delay是时间间隔的阈值 */ const throttleDebounce = (fn, delay = 800) => { // last为上一次触发回调的时间, timer是定时器 let last = 0, timer = null // 将throttle处理结果当作函数返回 return function () { // 保留调用时的this上下文 let context = this // 保留调用时传入的参数 let args = arguments // 记录本次触发回调的时间 let now = +new Date() // 判断上次触发的时间和本次触发的时间差是否小于时间间隔的阈值 if (now - last < delay) { // 如果时间间隔小于我们设定的时间间隔阈值,则为本次触发操作设立一个新的定时器 clearTimeout(timer) timer = setTimeout(function () { last = now fn.apply(context, args) }, delay) } else { // 如果时间间隔超出了我们设定的时间间隔阈值,那就不等了,无论如何要反馈给用户一次响应 last = now fn.apply(context, args) } } } /** * 微信授权接口封装 */ const wxSetting = ({ scope, text }) => { return new Promise((resolve, reject) => { wx.getSetting({ success: res => { if (isEmptyObj(res.authSetting)) { resolve(Object.assign(res, { type: 1, text: `请手动点击${text}` })) } else if (res.authSetting[scope] === true) { resolve(Object.assign(res, { type: 2, text: `默认${text}成功` })) } else if (res.authSetting[scope] === false) { wx.showModal({ content: `检测到您没打开${text},是否去设置打开?`, confirmText: "确认", cancelText: "取消", success: res => { if (res.confirm) { wx.openSetting({ success: res => { if (res.authSetting[scope]) { resolve(Object.assign(res, { type: 3, text: `手动设置${text}成功` })) } else { reject(Object.assign(res, { type: 5, text: `没有手动设置${text}` })) } }, fail: res => { reject(Object.assign(res, { type: 4, text: `打开手动设置${text}失败` })) } }) } else { reject(Object.assign(res, { type: 3, text: `拒绝打开手动设置${text}` })) } }, fail: res => { reject(Object.assign(res, { type: 2, text: `弹出${text}提示框失败` })) } }) } else { wx.authorize({ scope, success: res => { resolve(Object.assign(res, { type: 4, text: `自动获取${text}成功` })) }, fail: res => { reject(Object.assign(res, { type: 6, text: `自动获取${text}失败` })) } }) } }, fail: res => { reject(res, { type: 1, text: `获取${text}失败` }) } }) }) } /** * 获取微信用户信息授权 */ const getWxUserInfoSetting = cb => { wxSetting({ scope: 'scope.userInfo', text: '微信用户信息授权' }).then((res) => { wx.setStorageSync('hasUserInfo', 1) if (isFn(cb)) cb(1, res) }).catch((res) => { wx.setStorageSync('hasUserInfo', 0) if (res.type != 6) { wx.showToast({ title: res.text, icon: 'none' }) } setTimeout(() => { wx.navigateBack({ delta: 1 }) }, 1200) if (isFn(cb)) cb(0, res) }) } /** * 获取微信定位授权 */ const getWxLocationSetting = cb => { wxSetting({ scope: 'scope.userLocation', text: '微信定位授权' }).then((res) => { wx.setStorageSync('hasPosition', 1) if (isFn(cb)) cb(1, res) }).catch((res) => { wx.setStorageSync('hasPosition', 0) wx.showToast({ title: res.text, icon: 'none' }) setTimeout(() => { wx.navigateBack({ delta: 1 }) }, 1200) if (isFn(cb)) cb(0, res) }) } /** * 获取微信相机授权 */ const getWxCameraSetting = cb => { wxSetting({ scope: 'scope.camera', text: '微信相机授权' }).then((res) => { wx.setStorageSync('hasCamera', 1) if (isFn(cb)) cb(1, res) }).catch((res) => { wx.setStorageSync('hasCamera', 0) wx.showToast({ title: res.text, icon: 'none' }) setTimeout(() => { wx.navigateBack({ delta: 1 }) }, 1200) if (isFn(cb)) cb(0, res) }) } /** * 获取系统信息 */ const systemInfo = wx.getSystemInfoSync() /** * 路由跳转 */ const routers = () => { const fn = (method) => { return (e) => { const { paras = null, url = null, msg = '暂未开放', delta = 1 } = e && e.currentTarget && e.currentTarget.dataset ? e.currentTarget.dataset : e && !e.currentTarget ? e : {} switch (method) { case 'navigateBack': wx.navigateBack({ delta }) break default: if (!url) { wx.showToast({ title: msg, icon: 'none' }) return } wx[method]({ url: `/${formatUrl(url, method === 'switchTab' ? null : paras)}`, success: res => { }, fail: res => { wx.showToast({ title: '跳转失败', icon: 'none' }) } }) } } } const arr = ['reLaunch', 'navigateTo', 'redirectTo', 'switchTab', 'navigateBack'] const obj = {} for (const v of arr) { obj[v] = throttleDebounce(fn(v)) } return obj } /** * 查看图片 */ const viewImage = throttleDebounce((e, type) => { const { images, index, baseurl, url = 'imgUrl' } = type ? e : e.currentTarget.dataset const arr = [] if (isObj(images)) { for (const v of images) { arr.push(`${baseurl}${v[url] ? v[url] : v}`) } } wx.previewImage({ current: arr[index], urls: arr }) }) /** * 分享控制层 */ const sharePage = ({ title = '私塾家', path = 'pages/index/index', imageUrl = null, msg = '转发成功' } = {}) => { return { title, path, imageUrl, success: res => { wx.showToast({ title: msg, icon: 'success' }) } } } /** * 获取全局值 */ const getGlobalVal = (e1, e2 = e1) => hasVal(getApp().globalData[e1]) ? getApp().globalData[e1] : hasVal(wx.getStorageSync(e2)) ? wx.getStorageSync(e2) : null /** * 统一调用微信支付 */ const wxPayment = ({ timeStamp = '', nonceStr = '', packageValue = '', signType = '', paySign = '' }) => { return new Promise((resolve, reject) => { wx.requestPayment({ timeStamp, nonceStr, package: packageValue, signType, paySign, success: res => { if (res.errMsg == 'requestPayment:ok') { wx.showToast({ title: '支付成功', icon: 'success' }) resolve(res) } else { wx.showToast({ title: '支付失败', icon: 'none' }) reject(res) } }, fail: res => { wx.showToast({ title: '支付取消', icon: 'none' }) reject(res) } }) }) } /** * 获取最新版本 */ const getUpdateManager = () => { const updateManager = wx.getUpdateManager() updateManager.onCheckForUpdate(res => { const { hasUpdate } = res if (hasUpdate) { wx.showToast({ title: '已发现新版本哦', icon: 'none' }) } }) updateManager.onUpdateReady(() => { wx.showModal({ title: '更新提示', content: '新版本已经准备好,是否重启应用?', success(res) { if (res.confirm) { updateManager.applyUpdate() } } }) }) updateManager.onUpdateFailed(() => { wx.showToast({ title: '新版本下载失败', icon: 'none' }) }) } /** * fetch 网络请求封装 * @param url{String} url【请求后台路径,默认必穿项,否则报错】 * @param paras{Object} paras【请求后台路径拼接的参数,默认为空】 * @param method{String} method【网络请求方法,默认POST】 * @param data{Object} data【自定义传的参数数据,默认必传项,可为空对象】 * @param continuousFn{Object} continuousFn: { fn: Function, param: Object } 续弦函数:当接口报登录失效401的时候 记录失效的当前状态,等待自动登录成功后接着运行 */ const fetch = ({ url = null, paras = {}, method = 'POST', data = {}, continuousFn = null }) => { const { host, loginTime, wechatsys, phoneimei, phonesys, phonetype, version, platform, build, errorText = '出了点小问题' } = getApp().globalData const accessToken = getGlobalVal('accessToken') || '' const header = { wechatsys, phoneimei, phonesys, phonetype, version, platform, build, "Content-Type": "application/json" } const showToast = (text) => { wx.showToast({ title: text || errorText, icon: 'none' }) } url = formatUrl(host + url, Object.assign({}, paras, { accessToken })) return new Promise((resolve, reject) => { wx.request({ url, data, method, header, success: e => { const { statusCode, data } = e const { code, msg } = data switch (~~statusCode) { case 200: switch (~~code) { case 999: resolve(data) break default: if (url.indexOf('/user/checkToken') < 0) { showToast(msg) } reject(e) } if (loginTime > 0) { getApp().globalData.loginTime = 0 } break case 401: getApp().globalData.loginTime = loginTime + 1 if (loginTime < 2) { getApp().globalData.continuousFn = continuousFn getApp().login() } else { showToast('登录失效,稍后再试') reject(e) } break default: showToast() reject(e) } }, fail: e => { showToast() reject(e) }, complete: e => { getApp().globalData.isPending = true console.log({ url, request: data, response: e.data, globalData: getApp().globalData }) } }) }) } /** * uploadFile 上传文件封装 * @param url{String} url【请求后台路径,默认必穿项,否则报错】 * @param paras{Object} paras【请求后台路径拼接的参数,默认为空】 * @param file{String} file【微信小程序生成的临时文件资源路径】 * @param data{Object} data【自定义传的参数数据,默认必传项,可为空对象】 * @param continuousFn{Object} continuousFn: { fn: Function, param: Object } 续弦函数:当接口报登录失效401的时候 记录失效的当前状态,等待自动登录成功后接着运行 */ const uploadFile = ({ url = null, paras = {}, file = null, data = {}, continuousFn = null }) => { const { host, loginTime, wechatsys, phoneimei, phonesys, phonetype, version, platform, build, errorText = '出了点小问题' } = getApp().globalData const accessToken = getGlobalVal('accessToken') || '' const header = { wechatsys, phoneimei, phonesys, phonetype, version, platform, build, "Content-Type": "application/json" } const showToast = (text) => { wx.showToast({ title: text || errorText, icon: 'none' }) } url = formatUrl(host + url, Object.assign({}, paras, { accessToken })) return new Promise((resolve, reject) => { const files = isArr(file) ? file : [file] const len = files.length const arr = [] let i = 0 const Fn = (f) => { const uploadTask = wx.uploadFile({ url, filePath: f, name: 'files', formData: data, header, success: e => { const { statusCode } = e const { code, msg, data } = JSON.parse(e.data) switch (~~statusCode) { case 200: switch (~~code) { case 999: arr.push(data.pics[0].filePath) if (i < len - 1) { ++i Fn(files[i]) } else { resolve(arr) } break default: showToast(msg) reject(e) } if (loginTime > 0) { getApp().globalData.loginTime = 0 } break case 401: getApp().globalData.loginTime = loginTime + 1 if (loginTime < 2) { getApp().globalData.continuousFn = continuousFn getApp().login() } else { showToast('登录失效,稍后再试') reject(e) } break default: showToast() reject(e) } }, fail: e => { showToast() reject(e) }, complete: e => { getApp().globalData.isPending = true uploadTask.abort() wx.hideLoading() console.log({ url, request: data, response: e.data, globalData: getApp().globalData }) } }) uploadTask.onProgressUpdate((res) => { progress && progress(res) const { progress } = res const progressNumber = ~~(progress / len + 100 / len * i) wx.showLoading({ title: `已上传(${progressNumber}%)` }) if (progressNumber == 100) { wx.hideLoading() } }) } Fn(files[i]) }) } export { hasVal, isStr, isFn, isArr, isObj, isEmptyObj, formatNumber, formatDateTime, formatDate, formatTime, formatUrl, thisMonthLenth, throttle, debounce, throttleDebounce, wxSetting, getWxUserInfoSetting, getWxLocationSetting, getWxCameraSetting, systemInfo, routers, viewImage, sharePage, getGlobalVal, wxPayment, getUpdateManager, fetch, uploadFile, }