587 lines
25 KiB
JavaScript
587 lines
25 KiB
JavaScript
// uniapp people side tools
|
||
|
||
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
|
||
const BASE_TYPE_DEFAULT = 'SERVER' // one of { SERVER: 服务器, UNICLOUD_FUNC: 云函数, UNICLOUD_OBJECT: 云对象 }
|
||
|
||
export default {
|
||
// 用直观的色彩,代替 uview 的命名方式:
|
||
RED: 'error',
|
||
GREEN: 'success',
|
||
BLUE: 'primary',
|
||
YELLOW: 'warning',
|
||
GREY: 'info',
|
||
BLACK_TOAST: 'default',
|
||
WHITE_BUTTON: 'default',
|
||
|
||
thisPage () {
|
||
return this.__page__
|
||
? this // 1) constructor.name==='VueComponent' 只在 development 环境有用,在 production 环境会被简化成 'o'。2)对于组件内定义的 i18nText,要使用 this 来获得组建内的 i18nText,而不是 getCurrentPages[...] 去访问全局页面的 i18nText。
|
||
: getCurrentPages()[getCurrentPages().length - 1] || {} // [20220401] 发现在 topWindow 里, getCurrentPages 是 undefined。 // 在 App.vue 中调用 getCurrentPages() 返回的是空数组 [],因此在这里默认 {} 做保护。
|
||
},
|
||
|
||
// 输出命令行提示,可用来取代 console.log/info/warn/error
|
||
cclog (...args) {
|
||
const pageName = this.thisPage()?.route || 'VueApp'
|
||
console.log('%c ' + JSON.stringify({ time: new Date().toJSON(), page: pageName }), 'color:blue;background:lightgrey', ...args)
|
||
},
|
||
ccinfo (...args) {
|
||
const pageName = this.thisPage()?.route || 'VueApp'
|
||
console.info('%c ' + JSON.stringify({ time: new Date().toJSON(), page: pageName }), 'color:green;background:lightgrey', ...args)
|
||
},
|
||
ccwarn (...args) {
|
||
const pageName = this.thisPage().route || 'VueApp'
|
||
console.warn('%c ' + JSON.stringify({ time: new Date().toJSON(), page: pageName }), 'color:orange;background:lightgrey', ...args)
|
||
},
|
||
ccerror (...args) {
|
||
const pageName = this.thisPage()?.route || 'VueApp'
|
||
console.error('%c ' + JSON.stringify({ time: new Date().toJSON(), page: pageName }), 'color:red;background:lightgrey', ...args)
|
||
},
|
||
ccdebug (...args) {
|
||
if (process.env.NODE_ENV !== 'production') {
|
||
const pageName = this.thisPage()?.route || 'VueApp'
|
||
console.debug('%c ' + JSON.stringify({ time: new Date().toJSON(), page: pageName }), 'color:cyan;background:lightgrey', ...args)
|
||
}
|
||
},
|
||
cctitle (...args) {
|
||
const pageName = this.thisPage().route || 'VueApp'
|
||
console.debug('%c ' + JSON.stringify({ time: new Date().toJSON(), page: pageName }), 'color:cyan;background:lightgrey', ...args)
|
||
},
|
||
|
||
localizeText (i18nText) {
|
||
i18nText = i18nText?.__page__
|
||
? this.i18nText // 如果挂载到具体页面的 computed { lote: wo.localizeText } 那么 this 就是当前页面,直接取用 this.i18nText 即可。
|
||
: i18nText || this.thisPage()?.i18nText // 如果传入i18n参数 ({zhCN:'...', enUS:'...'}) // 如果不是挂载到 Vue.prototype 而是 挂载到 wo 下调用,那么 this.i18nText 就报错了。因此通过 thisPage().i18nText 访问。
|
||
const mylang = getApp().$store.state.i18n.mylang // this.thisPage() 有可能为空(例如在 topWindow 里,或者在 App.vue 里),所以用 getApp().$store 更安全
|
||
return i18nText?.[mylang] || (typeof i18nText === 'string' ? i18nText : '') // 必须检测是否string,如果直接返回 i18nText 可能返回{}等,导致依赖于返回空值的前端出错
|
||
},
|
||
|
||
localeText () {
|
||
// 专供绑定到 computed { lote: wo.localeText } 使用,这时 this 就是当前页面。
|
||
return this.i18nText?.[getApp().$store.state.i18n.mylang] || {}
|
||
},
|
||
|
||
setBarTitles ({ windowTitle, pageTitle, pagesJson = this.pagesJson || wo?.pagesJson, envar = this.envar || wo?.envar } = {}) {
|
||
const mylang = getApp()?.$store?.state?.i18n?.mylang // 不要用 pageNow.$store,防止在 App.vue 里无法获取当前页面。
|
||
const pageNow = this.thisPage() // 需要兼顾在 App.vue 时无法获取当前页面的情况,因为如果在 topWindow 里调用本函数,getApp() 和 getCurrentPages()[getCurrentPages().length-1] 就是 undefined。
|
||
|
||
uni.setNavigationBarTitle({
|
||
// 也会被用于浏览器的标签标题,因此要用 document.title 去覆盖
|
||
title:
|
||
pageTitle ||
|
||
pageNow?.i18nText?.[mylang]?.tPageTitle || // 页面.vue 的 i18nText 对象
|
||
pageNow?.i18nPageTitle?.[mylang] || // 页面.vue 的 i18nPageTitle 变量
|
||
pagesJson?.pages?.find((page) => page.path === pageNow?.route)?.i18nPageTitle?.[mylang], // pages.json 的页面配置里
|
||
})
|
||
|
||
// #ifdef H5
|
||
document.title = windowTitle || wo?.envar?.callname?.[mylang] || pagesJson?.appInfo?.i18nText?.[mylang] || pagesJson?.globalStyle?.navigationBarTitleText // 必须放在 setNavigationBarTitle 之后,否则会被其覆盖掉。
|
||
// #endif
|
||
|
||
// 必须要在有 tab 的页面里 setTabBarItem 才有效果
|
||
//const midIndex = parseInt(pagesJson?.tabBar?.list?.length/2) // 如果存在midButton,实际上tabBar.list.length必须为偶数。不过为了心安,再parseInt一下。
|
||
pagesJson?.tabBar?.list?.forEach((tab, tabIndex) => {
|
||
if (tab.i18nText && tab.i18nText[mylang]) {
|
||
uni.setTabBarItem({
|
||
// #ifdef H5
|
||
index: tabIndex, // + ((pagesJson?.tabBar?.midButton?.iconPath && tabIndex >= midIndex)?1:0), // H5 里,如果使用了 midButton,tabBarItem的index出现错位,需hack调整。推测,在H5里 midButton 作为一个普通tab被插入到 tabBar 里,导致 tabBar 的 index 和 pagesJson.tabBar.list 的 index 错位了。[20211031] 注意到,从 HBuilderX 3.2.12.20211029 起,在 H5 里也没有错位了。
|
||
// #endif
|
||
// #ifndef H5
|
||
index: tabIndex,
|
||
// #endif
|
||
text: tab.i18nText[mylang],
|
||
})
|
||
}
|
||
})
|
||
// uni.showTabBar({})
|
||
|
||
// #ifdef H5
|
||
// 响应式方案:仅仅根据当前设备类型,如果是 PC 大屏幕,则始终显示 topWindow 并且隐藏顶部 navibar 和底部 tabBar。
|
||
if (pagesJson?.topWindow || !envar?.showBarsOnPC) {
|
||
// 如果页头不是通过 pagesJson.topWindow 而是作为组件来引入个别页面,那么定义配置参数 showBarsOnPC 来控制。
|
||
if (uni.getSystemInfoSync().model === 'PC') {
|
||
if (window.innerWidth > (pagesJson?.topWindow?.matchMedia?.minWidth || 0)) {
|
||
uni.hideTabBar()
|
||
// 不知为何,同一个二级页面,如果第二次进入,就仍然会显示 navibar, 必须通过 setTimeout 执行才能彻底隐藏。
|
||
setTimeout(() => {
|
||
document.getElementsByTagName('uni-page-head')?.[0]?.remove()
|
||
}, 0)
|
||
}
|
||
} else {
|
||
document.getElementsByTagName('uni-top-window')?.[0]?.remove() // 强制隐藏 topWindow,否则在手机浏览器里,topWindow 会遮挡掉 navibar。
|
||
}
|
||
}
|
||
// #endif
|
||
},
|
||
|
||
makeServerUrl (route = '') {
|
||
const envar = this.envar || wo?.envar || {}
|
||
|
||
if (typeof route !== 'string') route = '' // 防止 route 为 null, undefined 等由于后台数据库默认值而造成的异常。
|
||
route = route.replace('\\', '/')
|
||
if (/^https?:\/\//.test(route)) {
|
||
return route
|
||
}
|
||
|
||
let hostname = envar.servHostname
|
||
let port = envar.servPort
|
||
// #ifdef H5
|
||
if (!hostname) {
|
||
hostname = window?.location?.hostname
|
||
}
|
||
if (!port) {
|
||
port = window?.location?.port?.replace(':', '')
|
||
}
|
||
// #endif
|
||
if (!hostname) {
|
||
// 如果没有配置 envar.servHostname,又不是在 H5 环境,则最后的默认值是 localhost
|
||
hostname = 'localhost'
|
||
}
|
||
|
||
const protocol = hostname === 'localhost' ? 'http' : envar.servProtocol || (process.env.NODE_ENV === 'production' ? 'https' : 'http')
|
||
|
||
return `${protocol}://${hostname}:${port}/${route.replace(/^\//, '')}`
|
||
},
|
||
|
||
makeBgUrl (path) {
|
||
if (path) {
|
||
return `url(${this.makeServerUrl(path)})`
|
||
}
|
||
return ''
|
||
},
|
||
|
||
/** 统一 uni.request 和 uniCloud.callFunction 的调用方法,提供统一、透明的后台调用
|
||
* 返回值:{ _state, 成功结果或错误结果 },其中 _state 除了后台返回的,还可以是
|
||
* - CLIENT_WOBASE_BROKEN: 前端发现后台断线
|
||
* - CLIENT_WOBASE_TIMEOUT: 前端发现后台超时
|
||
* - CLINET_WOBASE_EXCEPTION: 前端发现后台异常
|
||
**/
|
||
async callBase ({
|
||
baseType = this.envar?.baseTypeDefault || wo?.envar?.baseTypeDefault || BASE_TYPE_DEFAULT,
|
||
httpMethod = 'POST',
|
||
apiVersion = 'api',
|
||
apiWho,
|
||
apiTodo,
|
||
apiWhat = {},
|
||
}) {
|
||
const thisRoute = this.thisPage()?.route || 'VueApp' // 立刻保存 this.thisPage().route,因为在调用后台后,可能已切换到了其他页面。
|
||
const startTime = new Date().toJSON()
|
||
let url = undefined
|
||
let result = {}
|
||
if (baseType === 'UNICLOUD_OBJECT') {
|
||
const uniObj = uniCloud.importObject(apiWho)
|
||
try {
|
||
result = await uniObj[apiTodo](apiWhat)
|
||
} catch (error) {
|
||
result = { _state: 'CLINET_WOBASE_EXCEPTION', error }
|
||
}
|
||
} else if (baseType === 'UNICLOUD_FUNC') {
|
||
let { /* success, header, requestedId, */ result: resultCloud = {} } = await uniCloud
|
||
.callFunction({
|
||
name: apiWho,
|
||
data: {
|
||
apiTodo,
|
||
apiWhat,
|
||
_passtoken: uni.getStorageSync('_passtoken'),
|
||
// uniIdToken // uniCloud自动getStorageSync('uni_id_token')并传递为 uniIdToken;也可自行组装传入 uniIdToken
|
||
},
|
||
})
|
||
.catch((error) => {
|
||
// {errMsg, stack} = error
|
||
if (/request:fail/.test(error.errMsg)) {
|
||
// 后台云服务无法连接
|
||
return { _state: 'CLIENT_WOBASE_BROKEN', error }
|
||
} else {
|
||
// 后台云服务返回异常
|
||
return { _state: 'CLIENT_WOBASE_EXCEPTION', error }
|
||
}
|
||
})
|
||
result = resultCloud
|
||
} else if (baseType === 'SERVER') {
|
||
if (httpMethod === 'GET') {
|
||
// 如果不是 POST 方法,要额外把参数JSON化
|
||
for (let key in apiWhat) {
|
||
apiWhat[key] = JSON.stringify(apiWhat[key])
|
||
}
|
||
}
|
||
url = this.makeServerUrl(`${apiVersion}/${apiWho}/${apiTodo}`)
|
||
let [error, { statusCode, header, errMsg, data: resultServer = {} } = {}] = await uni.request({
|
||
method: httpMethod,
|
||
url: url,
|
||
header: { _passtoken: uni.getStorageSync('_passtoken') },
|
||
data: apiWhat,
|
||
})
|
||
if (error) {
|
||
if (error.errMsg === 'request:fail') {
|
||
// 后台服务器无法连接
|
||
result = { _state: 'CLIENT_WOBASE_BROKEN', error }
|
||
} else if (error.errMsg === 'request:fail timeout') {
|
||
// 后台服务器超时
|
||
result = { _state: 'CLIENT_WOBASE_TIMEOUT', error }
|
||
} else {
|
||
// 后台服务器返回异常
|
||
result = { _state: 'CLIENT_WOBASE_EXCEPTION', error }
|
||
}
|
||
} else {
|
||
result = resultServer
|
||
}
|
||
} else {
|
||
result = { _state: 'CLIENT_WOBASE_TYPE_UNKNOWN' }
|
||
}
|
||
// 注意1,resultServer 和 resultCloud 推荐遵循同样的格式 { _state, error | data },这样方便前端做统一判断。
|
||
// 注意2,虽然预设了 resultServer 和 resultCloud = {},但如果后台返回了 null,那么 resultServer/resultCloud 也是 null。
|
||
if (process.env.NODE_ENV !== 'production') {
|
||
console.log(
|
||
'%c ' +
|
||
JSON.stringify({ startTime: startTime, page: thisRoute, endTime: new Date().toJSON() }) +
|
||
' %c ' +
|
||
JSON.stringify({ baseType, apiWho, apiTodo, apiWhat, url }) +
|
||
' %c ' +
|
||
JSON.stringify(result),
|
||
'color:blue;background:lightgrey',
|
||
'background:skyblue',
|
||
'background:magenta'
|
||
) // 不知为何,直接用 result 会输出一个奇怪的对象,要主动添加 JSON.stringify 才按照期望输出。
|
||
}
|
||
return result
|
||
},
|
||
|
||
async pickupFile2Server ({
|
||
mediaType = 'image',
|
||
count = 1,
|
||
sizeType = ['original', 'compressed'],
|
||
sourceType = ['album', 'camera'],
|
||
url = 'api/Fileloader/receiveFile', // 默认后台用这个接口来接受文件
|
||
header = {},
|
||
formData = {},
|
||
name = 'file',
|
||
} = {}) {
|
||
// 有的管理后台不需要登录就允许上传,例如 cmctoy。因此不要在这里依赖登录状态。
|
||
// 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 { _state: 'UNKNOWN_MEDIATYPE' }
|
||
}
|
||
|
||
if (filePath) {
|
||
for (let key in formData) {
|
||
// multer 不会自动处理 JSON 数据,必须前后端配合处理
|
||
formData[key] = JSON.stringify(formData[key])
|
||
}
|
||
header._passtoken = uni.getStorageSync('_passtoken')
|
||
|
||
uni.showLoading()
|
||
let [errorUpload, { data, statusCode } = {}] = await uni.uploadFile({ url: this.makeServerUrl(url), filePath, name, header, formData })
|
||
// 后台 Multer 处理 req.file = { destination, filename, originalname, path, mimetype, size }, 其中 path 包括了 destination 和 filename 的文件完整路径。
|
||
// url 所在方法进一步处理后,通过 uploadFile 存在 data 里返回结果
|
||
uni.hideLoading()
|
||
|
||
if (typeof data === 'string') {
|
||
// 不知为何,uni.uploadFile返回的 data 是字符串而不是对象
|
||
try {
|
||
data = JSON.parse(data)
|
||
} catch (exp) {
|
||
return { _state: 'CLIENT_FAIL_RESPONSE_JSON_MALFORMED' }
|
||
}
|
||
}
|
||
|
||
if (data?._state === 'SUCCESS' && data?.path) {
|
||
return { _state: 'SUCCESS', fileUrl: this.makeServerUrl(data.path), filePath: data.path, ...data }
|
||
} else {
|
||
return { _state: 'CLIENT_FAIL_UPLOAD_FILE', errorUpload }
|
||
}
|
||
} else {
|
||
return { _state: 'CLIENT_FAIL_CHOOSE_FILE' }
|
||
}
|
||
},
|
||
|
||
async pickupFile2Cloud ({ mediaType = 'image', count = 1, sizeType = ['original', 'compressed'], sourceType = ['album', 'camera'], maxDuration } = {}) {
|
||
// 有的管理后台不需要登录就允许上传,例如 cmctoy。因此不要在这里依赖登录状态。
|
||
// 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: 'CLIENT_FAIL_UNKNOWN_MEDIA_TYPE' }
|
||
}
|
||
|
||
if (process.env.NODE_ENV !== 'production') {
|
||
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) {
|
||
var percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total)
|
||
},
|
||
})
|
||
uni.hideLoading()
|
||
|
||
if (fileID) {
|
||
return { _state: 'SUCCESS', fileUrl: fileID, requestId }
|
||
}
|
||
}
|
||
return { _state: 'CLIENT_FAIL_CHOOSE_FILE' }
|
||
},
|
||
|
||
async pickupFile ({
|
||
baseType = this.envar?.baseTypeDefault || wo?.envar?.baseTypeDefault || BASE_TYPE_DEFAULT,
|
||
mediaType = 'image',
|
||
count = 1,
|
||
sizeType = ['original', 'compressed'],
|
||
sourceType = ['album', 'camera'],
|
||
maxDuration,
|
||
url,
|
||
header = {},
|
||
formData = {},
|
||
name = 'file',
|
||
} = {}) {
|
||
if (/^UNICLOUD/.test(baseType)) {
|
||
const resultCloud = await this.pickupFile2Cloud({ mediaType, count, sizeType, sourceType, maxDuration })
|
||
return resultCloud
|
||
} else if (baseType === 'SERVER') {
|
||
const resultServer = await this.pickupFile2Server({ mediaType, count, sizeType, sourceType, maxDuration, url, header, formData, name })
|
||
return resultServer
|
||
} else {
|
||
return { _state: 'CLEINT_FAIL_UNKNOWN_WOBASE_TYPE', baseType }
|
||
}
|
||
},
|
||
|
||
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
|
||
})
|
||
* uToptips.show({
|
||
type=default (by default)/primary/success/error/warning/info,
|
||
position:center/top/bottom,
|
||
callback // 发生在 toast 之后
|
||
})
|
||
*/
|
||
showToast ({ tool, type, image, title, duration = 2000, ...rest }) {
|
||
if (tool !== 'uni') {
|
||
// 来自 <ucToast> 或 <u-toast> 或 <u-top-tips>
|
||
const toast = this.thisPage()?.$refs?.toast || getApp().globalData.toast || wo.toast
|
||
const popup = this.thisPage()?.$refs?.popup || getApp().globalData.popup || wo.popup
|
||
if (toast) {
|
||
toast.show({ type, title, duration, ...rest })
|
||
return
|
||
} else if (popup) {
|
||
wo.ss.popMessage = title
|
||
wo.ss.popType = type || 'success'
|
||
wo.ss.popDuration = duration || 2000
|
||
popup.open()
|
||
return
|
||
}
|
||
}
|
||
// #ifdef APP-PLUS
|
||
uni.showToast({ icon: 'none', title, duration, ...rest })
|
||
// #endif
|
||
// #ifdef H5
|
||
uni.showToast({ icon: 'none', image, title, duration, ...rest })
|
||
// #endif
|
||
},
|
||
|
||
formatMoney (value, precision = 2) {
|
||
return Number(value || 0).toFixed(precision) // Number(undefined)===NaN
|
||
},
|
||
|
||
formatPercent (value, precision = 2) {
|
||
return Number(value * 100 || 0).toFixed(precision)
|
||
},
|
||
|
||
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
|
||
},
|
||
|
||
getUserEndLanIp (callback) {
|
||
let recode = {}
|
||
let RTCPeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection
|
||
// 如果不存在则使用一个iframe绕过
|
||
if (!RTCPeerConnection) {
|
||
// 因为这里用到了iframe,所以在调用这个方法的script上必须有一个iframe标签
|
||
// <iframe id="iframe" sandbox="allow-same-origin" style="display:none;"></iframe>
|
||
let win = iframe.contentWindow
|
||
RTCPeerConnection = win.RTCPeerConnection || win.mozRTCPeerConnection || win.webkitRTCPeerConnection
|
||
}
|
||
//创建实例,生成连接
|
||
let pc = new RTCPeerConnection()
|
||
// 匹配字符串中符合ip地址的字段
|
||
function handleCandidate (candidate) {
|
||
let ip_regexp = /([0-9]{1,3}(\.[0-9]{1,3}){3}|([a-f0-9]{1,4}((:[a-f0-9]{1,4}){7}|:+[a-f0-9]{1,4}){6}))/
|
||
let ip_isMatch = candidate.match(ip_regexp)[1]
|
||
if (!recode[ip_isMatch]) {
|
||
callback(ip_isMatch)
|
||
recode[ip_isMatch] = true
|
||
}
|
||
}
|
||
//监听icecandidate事件
|
||
pc.onicecandidate = (ice) => {
|
||
if (ice.candidate) {
|
||
handleCandidate(ice.candidate.candidate)
|
||
}
|
||
}
|
||
//建立一个伪数据的通道
|
||
pc.createDataChannel('')
|
||
pc.createOffer(
|
||
(res) => {
|
||
pc.setLocalDescription(res)
|
||
},
|
||
() => {}
|
||
)
|
||
//延迟,让一切都能完成
|
||
setTimeout(() => {
|
||
let lines = pc.localDescription.sdp.split('\n')
|
||
lines.forEach((item) => {
|
||
if (item.indexOf('a=candidate:') === 0) {
|
||
handleCandidate(item)
|
||
}
|
||
})
|
||
}, 1000)
|
||
},
|
||
|
||
goto_page (pageName, { forget = false, pagesJson = this.pagesJson || wo?.pagesJson } = {}) {
|
||
if (pageName) {
|
||
if (pagesJson?.tabBar?.list?.find((item) => item?.pagePath?.substr(6) === pageName)) {
|
||
// 注意,即使在 PC 上 topWindow 代替了 tabBar 时,从标签页转化而来的菜单页,也是用 switchTab 跳转。
|
||
uni.switchTab({ url: pageName })
|
||
} else if (forget) {
|
||
uni.navigateTo({ url: pageName })
|
||
} else {
|
||
uni.redirectTo({ url: pageName })
|
||
}
|
||
} else {
|
||
uni.navigateBack()
|
||
}
|
||
},
|
||
|
||
next_focus (currentFocus, focusList = this.thisPage().focusList) {
|
||
if (focusList) {
|
||
for (let n in focusList) {
|
||
focusList[n] = false
|
||
}
|
||
setTimeout(() => {
|
||
focusList[(parseInt(currentFocus) + 1) % Object.keys(focusList).length] = true
|
||
}, 200) // 如果没有 setTimeout 至少 200ms, 焦点短暂跳到下一个后,又会消失
|
||
}
|
||
},
|
||
}
|