528 lines
22 KiB
JavaScript
528 lines
22 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
|
||
|
||
export default {
|
||
// 用直观的色彩,代替 uview 的命名方式:
|
||
RED: 'error',
|
||
GREEN: 'success',
|
||
BLUE: 'primary',
|
||
YELLOW: 'warning',
|
||
GREY: 'info',
|
||
BLACK_TOAST: 'default',
|
||
WHITE_BUTTON: 'default',
|
||
|
||
// internal consts used by unitool
|
||
RESPONSIVE_TABBAR_AUTOHIDE: false,
|
||
RESPONSIVE_TABBAR_AUTOHIDE_WIDTH_THRESHOLD: 768.768,
|
||
RESPONSIVE_TABBAR_ALWAYSHIDE: false,
|
||
BACKEND: 'SERVER', // 通过变量来动态切换后台类型:服务器 SERVER 或云服务 UNICLOUD. 应当根据实际需要,在前端所用的 unitool 里覆盖。
|
||
|
||
thisPage() {
|
||
return this.constructor.name==='VueComponent' ? this // 对于组件内定义的 i18nText,要使用 this 来获得组建内的 i18nText,而不是 getCurrentPages[...] 去访问全局页面的 i18nText。
|
||
: ( getCurrentPages()[getCurrentPages().length - 1] // [20220401] 发现在 topWindow 里, getCurrentPages 是 undefined。
|
||
|| {} ) // 用 {} 做备份是为了在 App.vue 中使用时,getCurrentPages() 有可能获取不到。
|
||
},
|
||
|
||
// 输出命令行提示,可用来取代 console.log/info/warn/error
|
||
cclog(...args) {
|
||
if (process.env.NODE_ENV === 'development') {
|
||
const pageName = this.thisPage()?.route || 'VueApp'
|
||
console.log('%c '+JSON.stringify({time:new Date().toJSON(), page:pageName}), 'color:blue', ...args)
|
||
}
|
||
},
|
||
ccinfo(...args) {
|
||
if (process.env.NODE_ENV === 'development') {
|
||
const pageName = this.thisPage()?.route || 'VueApp'
|
||
console.info('%c '+JSON.stringify({time:new Date().toJSON(), page:pageName}), 'color:green', ...args)
|
||
}
|
||
},
|
||
ccwarn(...args) {
|
||
if (process.env.NODE_ENV === 'development') {
|
||
const pageName = this.thisPage().route || 'VueApp'
|
||
console.warn('%c '+JSON.stringify({time:new Date().toJSON(), page:pageName}), 'color:orange', ...args)
|
||
}
|
||
},
|
||
ccerr(...args) {
|
||
if (process.env.NODE_ENV === 'development') {
|
||
const pageName = this.thisPage()?.route || 'App'
|
||
console.error('%c '+JSON.stringify({time:new Date().toJSON(), page:pageName}), 'color:red', ...args)
|
||
}
|
||
},
|
||
ccdebug(...args) {
|
||
if (process.env.NODE_ENV === 'development') {
|
||
const pageName = this.thisPage()?.route || 'App'
|
||
console.debug('%c '+JSON.stringify({time:new Date().toJSON(), page:pageName}), 'color:cyan', ...args)
|
||
}
|
||
},
|
||
|
||
localizeText(i18nText) {
|
||
i18nText = i18nText?.constructor?.name==='VueComponent' ? this.i18nText // 如果挂载到具体页面的 computed { lote: wo.localizeText } 那么 this 就是当前页面,直接取用 this.i18nText 即可。
|
||
: ( i18nText // 如果传入i18n参数 ({zhCN:'...', enUS:'...'})
|
||
|| this.thisPage()?.i18nText) // 如果不是挂载到 Vue.prototype 而是 挂载到 wo 下调用,那么 this.i18nText 就报错了。因此通过 thisPage().i18nText 访问。
|
||
const mylang = getApp().$store.state.i18n.mylang // this.thisPage() 有可能为空(例如在 topWindow 里,或者在 App.vue 里),所以用 getApp().$store 更安全
|
||
return i18nText?.[mylang] || i18nText || ''
|
||
},
|
||
|
||
localeText() { // 专供绑定到 computed { lote: wo.localeText } 使用,这时 this 就是当前页面。
|
||
return this.i18nText?.[getApp().$store.state.i18n.mylang] || {}
|
||
},
|
||
|
||
setBarTitles ({ windowTitle, pageTitle, pagesJson=wo?.pagesJson } = {}) {
|
||
const mylang = getApp().$store.state.i18n.mylang
|
||
const pageNow = getCurrentPages()[getCurrentPages().length - 1]
|
||
|
||
// #ifdef H5
|
||
document.title = windowTitle || pagesJson?.appInfo?.i18nText?.[mylang] // 必须放在 setNavigationBarTitle 之后,否则会被其覆盖掉。
|
||
// #endif
|
||
|
||
uni.setNavigationBarTitle({
|
||
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 的页面配置里
|
||
})
|
||
|
||
// 必须要在有 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
|
||
if (uni.getSystemInfoSync().model==='PC') {
|
||
if (this.RESPONSIVE_TABBAR_AUTOHIDE) {
|
||
if (window.screen.width > this.RESPONSIVE_TABBAR_AUTOHIDE_WIDTH_THRESHOLD) {
|
||
uni.hideTabBar()
|
||
}
|
||
uni.onWindowResize(({size})=>{
|
||
if (size.windowWidth > this.RESPONSIVE_TABBAR_AUTOHIDE_WIDTH_THRESHOLD) {
|
||
uni.hideTabBar()
|
||
}else{
|
||
uni.showTabBar()
|
||
}
|
||
})
|
||
}else if (this.RESPONSIVE_TABBAR_ALWAYSHIDE) {
|
||
uni.hideTabBar()
|
||
}
|
||
}
|
||
// #endif
|
||
},
|
||
|
||
makeServerUrl(route = '') {
|
||
if (typeof route !== 'string') route = '' // 防止 route 为 null, undefined 等由于后台数据库默认值而造成的异常。
|
||
|
||
route = route.replace('\\', '/')
|
||
if (/^https?:\/\//.test(route)) {
|
||
return route
|
||
}
|
||
|
||
let port = this.SERVER_PORT
|
||
// #ifdef H5
|
||
|| window.location.port
|
||
// #endif
|
||
let hostname
|
||
let protocol
|
||
if (process.env.NODE_ENV === 'production') {
|
||
hostname = this.SERVER_HOSTNAME
|
||
// #ifdef H5
|
||
|| window.location.hostname
|
||
// #endif
|
||
protocol = this.SERVER_PROTOCOL || 'https'
|
||
} else {
|
||
hostname =
|
||
this.SERVER_HOSTNAME_DEV // 在本机的手机模拟器里可以,在虚拟机的浏览器里也可以,但是运行到连接的iPhone里就无法连接,不知为何
|
||
// #ifdef H5
|
||
|| window.location.hostname
|
||
// #endif
|
||
protocol = this.SERVER_PROTOCOL_DEV || 'http'
|
||
}
|
||
return `${protocol}://${hostname}:${port}/${route.replace(/^\//, '')}`
|
||
},
|
||
|
||
makeBgUrl(path) {
|
||
if (path) {
|
||
return `url(${this.makeServerUrl(path)})`
|
||
}
|
||
return ''
|
||
},
|
||
|
||
/** 统一 uni.request 和 uniCloud.callFunction 的调用方法,提供统一、透明的后台调用
|
||
* 返回值:{ _state, 成功结果或错误结果 },其中 _state 除了后台返回的,还可以是
|
||
* - CLIENT_BACKEND_BROKEN: 前端发现后台断线
|
||
* - CLIENT_BACKEND_TIMEOUT: 前端发现后台超时
|
||
* - CLINET_BACKEND_EXCEPTION: 前端发现后台异常
|
||
**/
|
||
async callBackend({ backend = this.BACKEND, httpMethod = 'POST', apiVersion = 'api', apiWho, apiTodo, apiWhat = {} }) {
|
||
const thisRoute = this.thisPage()?.route || 'VueApp' // 立刻保存 this.thisPage().route,因为在调用后台后,可能已切换到了其他页面。
|
||
const startTime = new Date().toJSON()
|
||
let result = {}
|
||
if (backend === 'UNICLOUD') {
|
||
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)) {
|
||
// 后台云服务无法连接
|
||
result = { _state: 'CLIENT_BACKEND_BROKEN', error }
|
||
} else {
|
||
// 后台云服务返回异常
|
||
result = { _state: 'CLIENT_BACKEND_EXCEPTION', error }
|
||
}
|
||
})
|
||
result = resultCloud
|
||
} else {
|
||
if (httpMethod === 'GET') {
|
||
// 如果不是 POST 方法,要额外把参数JSON化
|
||
for (let key in apiWhat) {
|
||
apiWhat[key] = JSON.stringify(apiWhat[key])
|
||
}
|
||
}
|
||
let [error, { statusCode, header, errMsg, data: resultServer = {} } = {}] = await uni.request({
|
||
method: httpMethod,
|
||
url: this.makeServerUrl(`${apiVersion}/${apiWho}/${apiTodo}`),
|
||
header: { _passtoken: uni.getStorageSync('_passtoken') },
|
||
data: apiWhat,
|
||
})
|
||
if (error) {
|
||
if (error.errMsg === 'request:fail') {
|
||
// 后台服务器无法连接
|
||
result = { _state: 'CLIENT_BACKEND_BROKEN', error }
|
||
} else if (error.errMsg === 'request:fail timeout') {
|
||
// 后台服务器超时
|
||
result = { _state: 'CLIENT_BACKEND_TIMEOUT', error }
|
||
} else {
|
||
// 后台服务器返回异常
|
||
result = { _state: 'CLIENT_BACKEND_EXCEPTION', error }
|
||
}
|
||
} else {
|
||
result = resultServer
|
||
}
|
||
}
|
||
// 注意1,resultServer 和 resultCloud 推荐遵循同样的格式 { _state, error | data },这样方便前端做统一判断。
|
||
// 注意2,虽然预设了 resultServer 和 resultCloud = {},但如果后台返回了 null,那么 resultServer/resultCloud 也是 null。
|
||
if (process.env.NODE_ENV === 'development') {
|
||
console.log(
|
||
'%c '+JSON.stringify({startTime:startTime, page:thisRoute}) +
|
||
' %c '+ JSON.stringify({ backend, apiWho, apiTodo, apiWhat }) +
|
||
' %c '+ JSON.stringify(result) +
|
||
' %c '+ JSON.stringify({endTime:new Date().toJSON()}),
|
||
'color:blue', 'background:skyblue', 'background:magenta', 'color:magenta') // 不知为何,直接用 result 会输出一个奇怪的对象,要主动添加 JSON.stringify 才按照期望输出。
|
||
}
|
||
return result
|
||
},
|
||
|
||
async pickupFile2Server({
|
||
mediaType = 'image',
|
||
count = 1,
|
||
sizeType = ['original', 'compressed'],
|
||
sourceType = ['album', 'camera'],
|
||
url = 'api/FileTransfer/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 },
|
||
// 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 === '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) {
|
||
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({
|
||
backend = this.BACKEND,
|
||
mediaType = 'image', count = 1, sizeType = ['original', 'compressed'], sourceType = ['album', 'camera'], maxDuration,
|
||
url, header = {}, formData = {}, name = 'file',
|
||
}) {
|
||
if (backend==='UNICLOUD'){
|
||
const resultCloud = await this.pickupFile2Cloud({mediaType, count, sizeType, sourceType, maxDuration })
|
||
return resultCloud
|
||
}else if (backend==='SERVER'){
|
||
const resultServer = await this.pickupFile2Server({mediaType, count, sizeType, sourceType, maxDuration, url, header, formData, name})
|
||
return resultServer
|
||
}else {
|
||
return { _state: 'CLEINT_FAIL_UNKNOWN_BACKEND_TYPE', backend }
|
||
}
|
||
},
|
||
|
||
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 }) {
|
||
const pageNow = this.thisPage()
|
||
if (tool === 'uni' || ! 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 {
|
||
// 根据 html 中不同的组件 <ucToast/> 或 <toast/> 而不同。
|
||
pageNow.$refs.toast.show({ type, title, duration, ...rest })
|
||
}
|
||
},
|
||
|
||
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
|
||
},
|
||
|
||
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)
|
||
},
|
||
|
||
}
|