// uniapp people side tools // #ifdef H5 // import device from 'current-device' // https://github.com/matthewhudson/current-device // if (device.mobile()){ // }else if (device.desktop()){ // }else if (device.tablet()){ // } // #endif import './ican-H5Api.js' // 对齐H5Api: https://ext.dcloud.net.cn/plugin?id=415 // 注意要取消默认自带的 showToast https://uniapp.dcloud.io/api/system/clipboard?id=%e6%b3%a8%e6%84%8f module.exports = { RED: 'error', GREEN: 'success', BLUE: 'primary', YELLOW: 'warning', GREY: 'info', BLACK_TOAST: 'default', WHITE_BUTTON: 'default', BACKEND: 'SERVER', clog(...message) { console.log( '【【【【【【【【【【', getCurrentPages().length > 0 ? getCurrentPages().pop().route : 'pages/Welcome', // 在首页时,getApp() 或 getCurrentPages() 有可能获取不到。 ...message, '】】】】】】】】】】】' ) }, async request({ method = 'POST', url, header = {}, data = {} }) { url = this.makeUrl(url) header._passtoken = uni.getStorageSync('_passtoken') if (method === 'GET') { // 如果不是 POST 方法,要额外把参数JSON化 for (let key in data) { data[key] = JSON.stringify(data[key]) } } console.log('👇 👇 👇 👇 < Request > 👇 👇 👇 👇 ', { method, url, header, data }, '👆 👆 👆 👆 < /Request > 👆 👆 👆 👆') let [error, response] = await uni.request({ method, url, header, data }) console.log('⬇️ ⬇️ ⬇️ ⬇️ < Response > ⬇️ ⬇️ ⬇️ ⬇️ ', response, '⬆️ ⬆️ ⬆️ ⬆️ < /Response > ⬆️ ⬆️ ⬆️ ⬆️') return [error, response] }, async callBackend({ backend = this.BACKEND, method = 'POST', apiVersion = 'api', apiWho, apiHow, apiWhat = {} }) { console.log('👇 < BackendRequest > ', { apiWho, apiHow, apiWhat }, ' < /BackendRequest > 👇') let result = {} if (backend === 'CLOUD') { let { /* success, header, requestedId, */ result: resultCloud = {} } = await uniCloud .callFunction({ name: apiWho, data: { apiHow, apiWhat, _passtoken: uni.getStorageSync('_passtoken'), // uniIdToken // uniCloud自动getStorageSync('uni_id_token')并传递为 uniIdToken;也可自行传入 uniIdToken }, }) .catch((exp) => { // 断网或云端返回异常 {errMsg, stack} = error if (/request:fail/.test(exp.errMsg)) { return { result: { _state: 'CLIENT_BACKEND_BROKEN' } } } else { return { result: { _state: 'CLIENT_BACKEND_EXCEPTION' } } } }) result = resultCloud } else { if (method === 'GET') { // 如果不是 POST 方法,要额外把参数JSON化 for (let key in apiWhat) { apiWhat[key] = JSON.stringify(apiWhat[key]) } } let [error, { data: resultServer = {}, statusCode, header, errMsg } = {}] = await uni.request({ method, url: this.makeUrl(`${apiVersion}/${apiWho}/${apiHow}`), header: { _passtoken: uni.getStorageSync('_passtoken') }, data: apiWhat, }) if (error) { if (error.errMsg === 'request:fail') { // 后台断线 result = { _state: 'CLIENT_BACKEND_BROKEN' } } else if (error.errMsg === 'request:fail timeout') { // 后台异常而超时返回 result = { _state: 'CLIENT_BACKEND_TIMEOUT' } } else { result = { _state: 'CLIENT_BACKEND_EXCEPTION' } } } else { result = resultServer } } console.log('👆 < BackendResult > ️', result, '< /BackendResult > 👆') return result }, async pickupFile({ mediaType = 'image', count = 1, sizeType = ['original', 'compressed'], sourceType = ['album', 'camera'], url, header = {}, formData = {}, name = 'file', } = {}) { if (uni.getStorageSync('_passtoken')) { header._passtoken = uni.getStorageSync('_passtoken') } else { return [{ _ERROR: 'USER_OFFLINE', errMsg: 'offline user cannot upload files' }, null] } let filePath if (mediaType === 'image') { let [errorChoose, { tempFilePaths, tempFiles } = {}] = await uni.chooseImage({ count, sizeType, sourceType }) filePath = tempFilePaths[0] } else if (mediaType === 'video') { let [errorChoose, { tempFilePath }] = await uni.chooseVideo({ sourceType }) filePath = tempFilePath } else { return [{ _ERROR: 'UNKNOWN_MEDIATYPE' }, null] } if (filePath) { for (let key in formData) { // multer 不会自动处理 JSON 数据,必须前后端配合处理 formData[key] = JSON.stringify(formData[key]) } uni.showLoading() let [errorUpload, response] = await uni.uploadFile({ url: this.makeUrl(url), filePath, name, header, formData }) uni.hideLoading() if (response && response.data) { try { response.data = JSON.parse(response.data) } catch (exception) {} } return [errorUpload, response] } return [{ _ERROR: 'USER_CANCELED' }, null] }, async pickupFile2Cloud({ mediaType = 'image', count = 1, sizeType = ['original', 'compressed'], sourceType = ['album', 'camera'], maxDuration } = {}) { if (!uni.getStorageSync('_passtoken')) { return { _state: 'USER_OFFLINE', errMsg: 'offline user cannot upload files' } } let filePath, cloudPath, systemInfo = this.getSystemInfo() if (mediaType === 'image') { let [errorChoose, { tempFilePaths, tempFiles } = {}] = await uni.chooseImage({ count, sizeType, sourceType }) // uni.showModal({ title: 'tempFilePaths[0]=' + tempFilePaths[0] }) filePath = tempFilePaths[0] // 在 H5 上并不是文件路径名,而是类似 "blob:http://localhost:8080/f0d3e54d-0694-4803-8097-641d76a10b0d“。 // #ifndef H5 // let [errorGetImageInfo, { path, width, height, orientation, type }] = await uni.getImageInfo({ src: filePath }) // cloudPath = path // 完整路径,包含后缀名。形如 file:///var/mobile/Containers/Data/Application/55A76332-44F5-4D5F-A9F6-3F857D584883/Documents/Pandora/apps/D064A425A8BEC13F9D8F741B98B37BC5/doc/uniapp_temp_1598593902955/compressed/1598593925815.png cloudPath = `APP_${systemInfo.platform}__${filePath}` // 在 iOS 上是 "_doc/uniapp_temp_1598593902955/compressed/1598593925815.png", 有时还包含从 file:/// 开始的完整路径名 // #endif // #ifdef H5 cloudPath = `H5_${systemInfo.platform}_${systemInfo.browser}__${tempFiles[0].name}` // name is available in H5 only. 只包含文件名和后缀名,不包含路径。 // #endif } else if (mediaType === 'video') { let [errorChoose, { tempFilePath, tempFile, duration, size, width, height, name }] = await uni.chooseVideo({ sourceType, maxDuration }) // uni.showModal({ title: 'tempFilePath=' + tempFilePath }) filePath = tempFilePath // 在 iOS 上形如 "file:///var/mobile/Containers/Data/Application/55A76332-44F5-4D5F-A9F6-3F857D584883/Documents/Pandora/apps/26B43CD2F587D37FC6799108434A6F84/doc/uniapp_temp_1598596171580/gallery/IMG_3082.MOV" // #ifndef H5 cloudPath = `APP_${systemInfo.platform}_dur${duration}__${filePath}` // #endif // #ifdef H5 cloudPath = `H5_${systemInfo.platform}_${systemInfo.browser}_dur${duration}__${name}` // tempFile and name are H5 only // #endif // iOS 上测试,filePath 为 *.MOV,而阿里云只允许 *.mp4, 所以添加 .mp4 后缀。参见 https://uniapp.dcloud.net.cn/uniCloud/storage?id=clouduploadfile // 20200915测试,阿里云支持上传 *.mov 了。 if (!/\.(mp4|mov)$/i.test(cloudPath)) cloudPath = cloudPath + '.mp4' } else { return { _state: 'UNKNOWN_MEDIA_TYPE' } } if (process.env.NODE_ENV === 'development') { cloudPath = 'dev_' + cloudPath } if (filePath) { uni.showLoading() const { fileID, requestId } = await uniCloud.uploadFile({ filePath: filePath, cloudPath: cloudPath, // 关键是要具有文件格式后缀名,这样可以保持阿里云下载链接也用这个后缀名。 fileType: mediaType, // = image, video, audio onUploadProgress: function (progressEvent) { // console.log(progressEvent) var percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total) }, }) uni.hideLoading() console.log('文件上传结果:', { fileID, requestId }) if (fileID) { return { _state: 'SUCCESS', fileID, requestId } } } return { _state: 'FAIL' } }, openUrl(url) { // #ifdef APP-PLUS plus.runtime.openURL(url) // #endif // #ifdef H5 window.open(url, '_blank') // #endif }, getSystemInfo() { let systemInfo = uni.getSystemInfoSync() // model=PC|iPhone|iPad|Nexus 6|..., // platform=ios|android|mac|windows|linux|other, // system=iOS 11.0|Android 4.0|Other 0|... 等等 // #ifdef H5 systemInfo.environment = 'h5' let userAgent = window.navigator.userAgent.toLowerCase() systemInfo.browser = /msie/.test(userAgent) && !/opera/.test(userAgent) ? 'msie' : /opera/.test(userAgent) ? 'opera' : /version.*safari/.test(userAgent) ? 'safari' : /chrome/.test(userAgent) ? 'chrome' : /gecko/.test(userAgent) && !/webkit/.test(userAgent) ? 'firefox' : /micromessenger/.test(userAgent) ? 'wechat' : 'unknown' // #endif // #ifdef APP-PLUS || APP-PLUS-NVUE systemInfo.environment = 'app' // #endif // #ifdef MP systemInfo.environment = 'mp' // 细分成 WEIXIN, ... // #endif return systemInfo }, /* * uni.showToast({ icon=success (by default)/loading/none, position(app only):center|top|bottom, success, fail, complete // 函数调用后立刻发生,不是在toast之后 }) * ucToast.show({ type=info (by default)/success/error/warning|loading, position:top/bottom }) * u-toast.show({ type=default (by default)/primary/success/error/warning/info, position:center/top/bottom, callback // 发生在 toast 之后 }) */ showToast({ tool, type, image, title, duration = 2000, ...rest }) { let pageNow = this.$store ? this : getCurrentPages().pop() if (tool === 'uni' || !(pageNow.$refs && pageNow.$refs.toast)) { // #ifdef APP-PLUS uni.showToast({ icon: 'none', title, duration, ...rest }) // #endif // #ifdef H5 uni.showToast({ icon: 'none', image, title, duration, ...rest }) // #endif } else { pageNow.$refs.toast.show({ type, title, duration, ...rest }) } }, // setBarTitles({ windowTitle, pageTitle } = {}) { // let page = this.$store ? this : getCurrentPages()[getCurrentPages().length - 1] // uni.setNavigationBarTitle({ title: pageTitle || page.i18nText[page.$store.state.i18n.mylang].tPageTitle }) // // #ifdef H5 // document.title = windowTitle || page.$store.getters['i18n/getAppName'] // 必须放在 setNavigationBarTitle 之后,否则会被其覆盖掉。 // // #endif // if (page.$store._mutations['i18n/setTabbar']) page.$store.commit('i18n/setTabbar') // 必须要在有 tab 的页面里重置才有效果 // }, localizeText(i18nText) { return i18nText[this.$store.state.i18n.mylang] }, localeText() { let page = this.$store ? this : getCurrentPages()[getCurrentPages().length - 1] return page.i18nText[page.$store.state.i18n.mylang] }, formatMoney(value, decimal) { return Number(value || 0).toFixed(decimal || 2) // Number(undefined)===NaN }, formatPercent(value, decimal) { return Number(value * 100 || 0).toFixed(decimal || 2) }, formatDate(date, format) { if (!(date instanceof Date)) { if (typeof date === 'string' && /^\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d\d\d$/.test(date)) { // 这是从 typeorm 数据库得到的Date类型的值 date = date.replace(/-/g, '/') // safari 不支持 yyyy-mm-dd,必须改成 yyyy/mm/dd } date = new Date(date) } if (!date.toJSON()) { date = new Date() } format = format && typeof format === 'string' ? format : 'yyyy-mm-dd HH:MM:SS' let o = { 'm+': date.getMonth() + 1, //月份 'q+': Math.floor((date.getMonth() + 3) / 3), //季度 'd+': date.getDate(), //日 'H+': date.getHours(), //小时 'M+': date.getMinutes(), //分 'S+': date.getSeconds(), //秒 s: date.getMilliseconds(), //毫秒 } if (/(y+)/.test(format)) format = format.replace(RegExp.$1, `${date.getFullYear()}`.substr(4 - RegExp.$1.length)) for (var k in o) { if (new RegExp(`(${k})`).test(format)) format = format.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : `00${o[k]}`.substr(`${o[k]}`.length)) } return format }, hash(data, { hasher = 'sha256', salt, input = 'utf8', output = 'hex' } = {}) { if (typeof data !== 'string' && !(data instanceof Buffer) && !(data instanceof DataView)) data = JSON.stringify(data) if (salt && typeof salt === 'string') data = data + salt let inputEncoding = input // my.INPUT_LIST.indexOf(option.input)>=0?option.input:my.INPUT // 'utf8', 'ascii' or 'latin1' for string data, default to utf8 if not specified; ignored for Buffer, TypedArray, or DataView. let outputEncoding = output === 'buf' ? undefined : output // (my.OUTPUT_LIST.indexOf(output)>=0?output:my.OUTPUT) // option.output: 留空=》默认输出hex格式;或者手动指定 'buf', hex', 'latin1' or 'base64' return require('crypto').createHash(hasher).update(data, inputEncoding).digest(outputEncoding) }, regcode2aiid(code) { if (typeof code === 'string' && /^[a-zA-Z0-9]+$/.test(code)) { const alphabet = 'e5fcdg3hqa4b1n0pij2rstuv67mwx89klyz' const base = 16367 code = code.toLowerCase() let len = code.length let num = 0 for (let i = 0; i < len; i++) { num += alphabet.indexOf(code[i]) * Math.pow(alphabet.length, i) } let aiid = num / (base - alphabet.length) - base if (aiid >= 0 && Number.isInteger(aiid)) { // 允许 aiid===0:当第一个用户(aiid==1)登录时,需要一个系统默认的邀请码。 return aiid } } return null }, }