import moment from 'moment' import get from 'lodash/get' import mapValues from 'lodash/mapValues' import defaultConfig from './config.default.js' import globalConfig from '@/config.js' import md5 from '@/common/md5.js' /** * 通用工具方法、全局数据 * 以下定义的所有方法、计算属性,均会被挂载到所有 .vue 页面的 this 上 * * 注意:小程序端,计算属性会以数据对象的形式挂载到所有页面的 Page 对象中 * 因此,数据量较大的情况下,为优化性能请使用调用方法动态获取的形式,避免占用太多内存 * (例如,GLOBAL 全局对象现在已经移除,改为使用 GET_GLOBAL(key) 来获取全局对象) */ export default { // 【App 平台】点击返回键后移除 loading,防止某个页面出错按返回键后还有 loading // #ifdef APP-VUE onBackPress() { uni.hideLoading() }, // #endif methods: { // 暂存一个跨页面变量 (与 this.GET_PARAM 成对使用) SET_PARAM(val) { this.SET_GLOBAL('pageParam', val) }, // 获取之前暂存的跨页面变量 (与 this.SET_PARAM 成对使用) GET_PARAM() { return this.GET_GLOBAL('pageParam') }, // 进入某个页面 NAV_TO(url, param, usePageParam) { uni.navigateTo({ url: this.handleNav(url, param, usePageParam) }) }, // 返回上个页面 // delta 参数为返回的次数 NAV_BACK(delta = 1) { uni.navigateBack({ delta }) }, // 跳转到某个页面,跳转后无法返回 JUMP_TO(url, param, usePageParam) { uni.redirectTo({ url: this.handleNav(url, param, usePageParam) }) }, // 转到某个 Tab 页 TAB_TO(url, param) { uni.switchTab({ url: this.handleNav(url, param, true) }) }, // 重启应用,跳转到指定页面 RELAUNCH_TO(url) { uni.reLaunch({ url }) }, // 获取一个全局变量 // key 为键名 GET_GLOBAL(key) { return this.$store.state[key] }, // 设置一个全局变量 // key 为键名 // val 为值 SET_GLOBAL(key, val) { this.$store.commit(key, val) }, // 清空全局变量 CLEAR_GLOBAL() { this.$store.commit('clear') uni.removeStorageSync('token') }, // 将数据写入本地缓存 SET_STORAGE(key, data) { if (data === null) { uni.removeStorageSync(key) } else { uni.setStorageSync(key, data) } }, // 获取之前写入本地缓存的数据 GET_STORAGE(key) { return uni.getStorageSync(key) }, // 清空本地缓存 CLEAR_STORAGE() { uni.clearStorageSync() }, // 展示提示框 // title 为提示文字 // mask 为是否禁止点击 // icon 为显示图标,可以改为 'success' TOAST(title, icon = 'none', mask = false) { uni.showToast({ title, icon, mask }) }, // 停止展示 toast 提示框 HIDE_TOAST() { uni.hideToast() }, // 显示 loading 提示框 // title 为提示文字 // mask 为是否禁止点击 LOADING(title, mask = true) { uni.showLoading({ title, mask }) }, // 停止展示 loading 提示框 HIDE_LOADING() { uni.hideLoading() }, // 展示确认提示框 // title 为标题 // content 为提示框文字内容 // showCancel 为是否显示取消按钮 // 返回一个结果,true 或 false,表示用户是否确认 async CONFIRM(title, content, showCancel = false) { return new Promise(res => { uni.showModal({ title, content, showCancel, success: ({ confirm }) => { res(confirm) }, fail: () => { res(false) } }) }) }, // 设置页面标题 SET_TITLE(title) { uni.setNavigationBarTitle({ title }) }, // 从 /config.js 中获取某条配置项 // 如果在 /config.js 中获取不到,则前往 /common/config.default.js 中获取默认值 // path 为配置项的访问路径 // 举例: // 以下语句获取是否全局开启圆形头像 // this.CONFIG('pageConfig.roundAvatar') CONFIG(path) { return get(globalConfig, path, get(defaultConfig, path)) }, // 监听一个全局事件 ON(name, func) { uni.$on(name, func) }, // 仅单次监听一个全局事件 ONCE(name, func) { uni.$once(name, func) }, // 触发一个全局事件 EMIT(name, data) { uni.$emit(name, data) }, // 移除全局事件监听器 OFF(name, func) { uni.$off(name, func) }, // 拉取指定 code 值的数据源数据 async FETCH_DATASOURCE(code) { if (!code) { return [] } return await this.HTTP_GET('learun/adms/datasource/map', { code, ver: '' }) }, // 拉取指定规则编号的表单编码数据 async FETCH_ENCODE(rulecode) { if (!rulecode) { return '' } return await this.HTTP_GET('learun/adms/coderule/code', rulecode) }, // 拉取指定 id 的文件信息 async FETCH_FILEINFO(fileId) { if (!fileId) { return null } return await this.HTTP_GET('learun/adms/annexes/wxfileinfo', fileId) }, // 封装的 GET 请求,集成了验证信息 // 返回请求结果或 null // 对网络错误、返回错误码、登录状态失效等情况做了相应处理 // url 为请求地址 // data 为请求附带的提交数据 async HTTP_GET(url, data, showTips) { const [err, res] = await this.requestBase(url, data, null, 'GET') return this.handleResult(err, res, showTips) }, // 封装的 POST 请求,集成了验证信息 // 返回请求结果或 null // 对网络错误、返回错误码、登录状态失效等情况做了相应处理 // url 为请求地址 // data 为请求附带的提交数据 async HTTP_POST(url, data, showTips) { const [err, res] = await this.requestBase(url, data, null, 'POST') return this.handleResult(err, res, showTips) }, // 封装的文件上传,集成了验证信息 // 返回接口返回值或 null // 对网络错误、返回错误码、登录状态失效等情况做了相应处理 // url 为请求地址 // filePath 为临时文件的路径 // formData 为请求附带的提交数据 async HTTP_UPLOAD(filePath, formData) { const [err, res] = await this.UPLOAD('/annexes/wxupload', filePath, formData) return this.handleResult(err, res) }, // 封装的文件下载,集成了验证信息 // 返回临时文件路径或 null // 对网络错误、返回错误码、登录状态失效等情况做了相应处理 // url 为请求地址 // formData 为请求附带的提交数据 async HTTP_DOWNLOAD(formData) { const [err, res] = await this.DOWNLOAD('/annexes/wxdown', formData) return this.handleResult(err, res) }, // 发起一个 GET 请求,封装了身份验证 // url 为请求地址 // data 为请求附带的提交数据 // 返回结果是一个数组: [error, result] // error 表示错误,一般是网络错误,请求很可能根本没有发出 // result 包含 { statusCode, headers, data } 分别表示状态码、响应头、数据 async GET(url, data, header) { return await this.requestBase(url, data, header, 'GET') }, // 发起一个 POST 请求,封装了身份验证 // url 为请求地址 // data 为请求附带的提交数据 // 返回结果是一个数组: [error, result] // error 表示错误,一般是网络错误,请求很可能根本没有发出 // result 包含 { statusCode, headers, data } 分别表示状态码、响应头、数据 async POST(url, data, header) { return await this.requestBase(url, data, header, 'POST') }, // 上传一个文件 (本地临时文件),封装了身份验证 // url 为提交地址 // filePath 为本地临时文件路径 // formData 为上传时附带的参数 // 返回结果是一个数组: [error, result] // error 表示错误,一般是网络错误,请求很可能根本没有发出 // result 包含 { statusCode, data } 分别表示状态码、接口返回的数据 async UPLOAD(url, filePath, formData) { const uploadUrl = this.handleUrl(url) const query = { loginMark: this.getLoginMark(), token: this.GET_GLOBAL('token') } if (formData && typeof formData === 'object') { Object.assign(query, formData) } else if (typeof formData === 'string') { Object.assign(query, { data: formData }) } // #ifdef MP-DINGTALK // return new Promise((res, rej) => { // dd.uploadFile({ // url: uploadUrl, // filePath, // fileName: 'file', // fileType: 'image', // formData: query, // success: dt => { // dt.data = JSON.parse(dt.data) // res([null, dt]) // }, // fail: rs => { // rej([rs, null]) // } // }) // }) // #endif // #ifndef MP-DINGTALK return uni.uploadFile({ url: uploadUrl, filePath:filePath, name: 'file', fileType: 'image', formData: query }).then(([err, result]) => { if (!err) { result.data = JSON.parse(result.data) return [null, result] } else { return [err, null] } }) // #endif }, // 下载一个文件(下载后为临时文件),封装了身份验证 // url 为请求的地址 // formData 为请求时附带的参数 // 返回结果是一个数组: [error, result] // error 表示错误,一般是网络错误,请求很可能根本没有发出 // result 包含 { statusCode, tempFilePath } 分别表示状态码、下载后的临时文件路径 async DOWNLOAD(url, formData) { let downloadUrl = this.handleUrl(url) const query = {} if (formData && typeof formData === 'object') { Object.assign(query, formData) } else if (typeof formData === 'string') { Object.assign(query, { data: formData }) } downloadUrl = downloadUrl + '?' + this.URL_QUERY(query, true) return uni.downloadFile({ url: downloadUrl }).then(([err, result]) => { if (!err) { result.data = { data: result.tempFilePath } result.statusCode = 200 } return [err, result] }) }, // 拉取客户端全局数据,直接写入全局变量 // 目前包括了:公司、部门、人员、数据字典 async FETCH_CLIENTDATA() { await Promise.all([ this.HTTP_GET('learun/adms/company/map').then(res => this.SET_GLOBAL('company', res.data || {})), this.HTTP_GET('learun/adms/department/map').then(res => this.SET_GLOBAL('department', res.data || {})), this.HTTP_GET('learun/adms/user/map').then(res => this.SET_GLOBAL('user', res.data || {})), this.HTTP_GET('learun/adms/dataitem/map').then(res => this.SET_GLOBAL('dataDictionary', res.data || {})) ]) }, // 使用 JSON 序列化的方式克隆一个对象或数组 COPY(val) { return JSON.parse(JSON.stringify(val)) }, // 生成一个32位 GUID 随机字符串 // joinChar 为分割符,默认为下划线 GUID(joinChar = '_') { return `xxxxxxxx${joinChar}xxxx${joinChar}4xxx${joinChar}yxxx${joinChar}xxxxxxxxxxxx`.replace(/[xy]/g, c => { const r = Math.random() * 16 | 0; const v = c === 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }) }, // 获取指定字符串的 MD5 码 MD5(val = '') { return md5(val) }, // 将时间日期转化为特定格式 (一般用于 ) // datetimeString 为要格式化的日期时间字符串 // 返回值是一个数组,它也可以当做字符串直接使用 // 举例: // 如果日期是当天,则返回 ['今天 17:32'] 或者 '今天 17:32' // 如果日期是今年,则返回 ['6月8日', '17:32'] 或者 '6月8日 17:32' // 如果日期不是今年,返回 ['2018-06-08'] 或者 '2018-06-08' TABLEITEM_DATEFORMAT(datetimeString) { const dt = moment(datetimeString) let result = [] if (!dt.isValid()) { result.toString = () => '' return result } const now = moment() if (dt.isSame(now, 'day')) { result = [`今天 ${dt.format('HH:mm')}`] result.toString = () => `今天 ${dt.format('HH:mm')}` } else if (dt.isSame(now, 'year')) { result = [dt.format('M月D日'), dt.format('HH:mm')] result.toString = () => dt.format('M月D日') + ' ' + dt.format('HH:mm') } else { result = [dt.format('YYYY-MM-DD')] result.toString = () => dt.format('YYYY-MM-DD') } return result }, // 将一个对象编码并转化为 url 查询字符串 // obj 为要转换的对象,值为空则会被忽略,值为对象会被转为 JSON 字符串 // auth 为是否编入身份验证信息 URL_QUERY(obj, auth = false) { let queryObject = obj || {} if (typeof obj === 'string') { queryObject = { data: obj } } if (auth) { Object.assign(queryObject, { loginMark: this.getLoginMark(), token: this.GET_GLOBAL('token') }) } return Object.entries(queryObject) .filter(([k, v]) => k && v) .map(([k, v]) => encodeURIComponent(k) + '=' + encodeURIComponent(typeof v === 'object' ? JSON.stringify(v) : v)) .join('&') }, // 将字符串转化为 HTML 格式 (处理表单页面的 HTML) CONVERT_HTML(str) { if (!str) { return '' } return str .replace(/{@zuojian@}|{@youjian@}|{@and@}/g, tag => ({ '{@zuojian@}': '<', '{@youjian@}': '>', '{@and@}': '&' })[tag] || tag) .replace(/&|<|>|'|"/g, tag => ({ '&': '&', '<': '<', '>': '>', ''': "'", '"': '"' })[tag] || tag) }, // 用于编码小程序分享消息的 url 查询字符串 (开发环境下还会打印出来) // pageParam 为点击分享消息跳转时携带的 pageParam // query 为点击分享消息跳转时携带的 query // pagePath 点击分享消息跳转到小程序的页面 (默认为当前页) // 返回编码好的查询字符串 MP_SHARE_ENCODE(pageParam, query, pagePath) { const shareObj = { fromUser: this.GET_GLOBAL('loginUser').userId, fromPlatform: this.PLATFORM, timestamp: new Date().valueOf(), pagePath: pagePath || this.pagePath, query: query, pageParam, learun: this.APP_VERSION } const result = this.URL_QUERY(shareObj) if (this.DEV) { console.log('【您正在分享力软小程序页面】') console.log('====分享对象:====') console.log(shareObj) console.log('====启动路径:====') console.log('/pages/home') console.log('====启动参数:====') console.log(result) console.log('====(以上消息仅开发模式可见)====') } return result }, // 解析小程序分享字符串 (会自动适配微信小程序的 url 编码) MP_SHARE_DECODE(info) { // 微信小程序中获取的分享信息是被 uri 编码过的,需要解码 // 支付宝/钉钉小程序不需要解码 // #ifdef MP-WEIXIN const shareInfo = mapValues(info, decodeURIComponent) // #endif shareInfo.pageParam = shareInfo.pageParam ? JSON.parse(shareInfo.pageParam) : undefined shareInfo.query = shareInfo.query ? this.URL_QUERY(JSON.parse(shareInfo.query)) : undefined if (this.DEV) { console.log('【您通过小程序消息分享启动了力软小程序】') console.log('====小程序分享对象:====') console.log(shareInfo) console.log('====即将转入页面:====') console.log(shareInfo.pagePath) console.log('====设置的 url query:====') console.log(shareInfo.query) console.log('====设置的 pageParam:====') console.log(shareInfo.pageParam) console.log('====(以上消息仅开发模式可见)====') } this.SET_GLOBAL('pageParam', shareInfo.pageParam) uni.navigateTo({ url: `${shareInfo.pagePath}?${this.URL_QUERY(shareInfo.query)}` }) }, // 【内部方法】处理页面跳转 url 和参数 handleNav(url, param, usePageParam) { let query = '' if (param && usePageParam) { this.SET_PARAM(param) } else if (param && !usePageParam) { query += '?' + Object.entries(param).filter(([k, v]) => k && v).map(([k, v]) => k + '=' + v).join('&') } return url + query }, // 【内部方法】从全局变量和缓存中获取 loginMark 设备标识,获取不到则会重新生成一个 getLoginMark() { if (this.GET_GLOBAL('loginMark')) { return this.GET_GLOBAL('loginMark') } const storageData = uni.getStorageSync('loginMark') if (storageData && storageData !== 'null' && storageData !== 'undefined') { this.SET_GLOBAL('loginMark', storageData) return storageData } const newLoginMark = this.GUID() this.SET_GLOBAL('loginMark', newLoginMark) uni.setStorageSync('loginMark', newLoginMark) return newLoginMark }, // 【内部方法】处理 url,判断是否需要添加后台地址前缀 handleUrl(url) { let result = url if (result.startsWith('http://') || result.startsWith('https://')) { return result } if (!result.startsWith(this.API)) { result = this.API + result } return result }, // 【内部方法】HTTP 请求基础方法 async requestBase(url, data, header, method = 'GET') { const requestUrl = this.handleUrl(url) const requestHeader = header || {} let requestData = { loginMark: this.getLoginMark(), token: this.GET_GLOBAL('token') || '' } if (data && typeof data === 'object') { requestData.data = JSON.stringify(data) } else if (data) { Object.assign(requestData, { data }) } return uni.request({ url: requestUrl, method, header: { 'content-type': 'application/x-www-form-urlencoded', ...requestHeader }, data: requestData }) }, // 【内部方法】处理网络请求方法的返回结果 handleResult(err, result, tips) { // 出现错误,一般是网络连接错误 if (err || !result) { uni.hideLoading() uni.showToast({ title: '网络请求失败,请检查您的网络连接', icon: 'none' }) return null } // 状态码为 410,登录状态失效 if (result.statusCode === 410 || (result.data && result.data.code === 410)) { uni.hideLoading() uni.showToast({ title: '登录状态无效,正在跳转到登录页…', icon: 'none' }) this.CLEAR_GLOBAL() uni.reLaunch({ url: '/pages/login' }) return null uni.hideLoading() if (tips) { const errInfo = (result.data && result.data.info) || '(未知原因)' const errTips = typeof tips === 'string' ? tips : '请求数据时发生错误' uni.showToast({ title: `${errTips}: ${errInfo}`, icon: 'none' }) } return null } //dyy 2020-03-09 14:50 //请求成功后,返回数据状态码不等于200,请求数据失败 if(result.data && result.data.code !== 200){ uni.hideLoading() if (tips) { const errInfo = (result.data && result.data.info) || '(未知原因)' const errTips = typeof tips === 'string' ? tips : '请求数据时发生错误' uni.showToast({ title: `${errTips}: ${errInfo}`, icon: 'none' }) } return null } return result.data.data }, // 【即将废弃】请使用 this.CONFIG() 来替代 config(path) { return get(globalConfig, path, get(defaultConfig, path)) }, // 【即将废弃】请使用 this.SET_PARAM() 来代替 setPageParam(val) { this.$store.commit('pageParam', val) }, // 【即将废弃】请使用 this.GET_PARAM() 来代替 getPageParam() { return this.$store.state.pageParam } }, computed: { // 请求后台接口的地址 API() { return this.$store.state.apiRoot || this.CONFIG('apiHost')[this.DEV ? this.CONFIG('devApiHostIndex') : this.CONFIG('prodApiHostIndex')] }, // 当前页面的路径 // 举例:登录页为 '/pages/login' PATH() { if (!getCurrentPages) { return '' } const pages = getCurrentPages() return pages ? '/' + pages.slice(-1)[0].route : '' }, // 当前是否为开发环境 DEV() { return process.env.NODE_ENV === 'development' }, // 【仅开发模式】获取当前全局变量 // 生产环境、正式发行时无效,因为小程序端全局变量会挂载到每个页面,影响性能 DEV_ONLY_GLOBAL() { return process.env.NODE_ENV === 'development' && this.$store.state }, // 获取当前移动端版本号 (定义在 config.js) APP_VERSION() { return this.CONFIG('appVersion') }, // 当前运行平台 // 取值 'alipay'/'weixin'/'dingtalk'/'h5'/'app'/'unknow' PLATFORM() { let result = 'unknow' // #ifdef MP-ALIPAY // #ifndef MP-DINGTALK result = 'alipay' // #endif // #ifdef MP-DINGTALK result = 'dingtalk' // #endif // #endif // #ifdef MP-WEIXIN result = 'weixin' // #endif // #ifdef H5 result = 'h5' // #endif // #ifdef APP-VUE result = 'app' // #endif return result }, // 获取当前运行平台的中文全称 // 取值 '支付宝小程序'/'微信小程序'/'钉钉小程序'/'移动 H5 '/'手机 App '/'(未知)' PLATFORM_TEXT() { let result = '(未知)' // #ifdef MP-ALIPAY // #ifndef MP-DINGTALK result = '支付宝小程序' // #endif // #ifdef MP-DINGTALK result = '钉钉小程序' // #endif // #endif // #ifdef MP-WEIXIN result = '微信小程序' // #endif // #ifdef H5 result = '移动 H5 ' // #endif // #ifdef APP-VUE result = '手机 App ' // #endif return result }, // 【即将废弃】请使用 this.PATH 来代替 pagePath() { if (!getCurrentPages) { return '' } const pages = getCurrentPages() return pages ? '/' + pages.slice(-1)[0].route : '' }, // 【即将废弃】请使用 this.API 来代替 apiRoot() { return this.$store.state.apiRoot || this.CONFIG('apiHost')[this.DEV ? this.CONFIG('devApiHostIndex') : this.CONFIG('prodApiHostIndex')] }, } }