377 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			377 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| // uniapp people side tools
 | ||
| 
 | ||
| // #ifdef H5
 | ||
| // import device from 'current-device' // https://github.com/matthewhudson/current-device
 | ||
| // #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,
 | ||
|       '】】】】】】】】】】】'
 | ||
|     )
 | ||
|   },
 | ||
| 
 | ||
|   sleep: (ms) => new Promise((resolve, reject) => setTimeout(resolve, ms)),
 | ||
| 
 | ||
|   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 (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() // 包含 platform (ios|android|other), model (iPhone, iPad, Nexus 6, ...), system (iOS 11.0, Android 4.0, Other 0, ...) 等等
 | ||
| 
 | ||
|     // #ifdef H5
 | ||
|     systemInfo.environment = 'h5'
 | ||
| 
 | ||
|     if (['ios', 'android'].indexOf(systemInfo.platform) >= 0) {
 | ||
|       systemInfo.device = 'mobile'
 | ||
|     } else {
 | ||
|       systemInfo.device = 'desktop'
 | ||
|     }
 | ||
|     // if (device.mobile()){
 | ||
|     //   systemInfo.device = 'mobile'
 | ||
|     // }else if (device.desktop()){
 | ||
|     //   systemInfo.device = 'desktop'
 | ||
|     // }else if (device.tablet()){
 | ||
|     //   systemInfo.device = 'tablet'
 | ||
|     // }
 | ||
| 
 | ||
|     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 的页面里重置才有效果
 | ||
|   },
 | ||
| 
 | ||
|   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
 | ||
|   },
 | ||
| }
 |