chore: initialize qiming workspace repository
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
<template>
|
||||
</template>
|
||||
<script>
|
||||
</script>
|
||||
<style>
|
||||
</style>
|
||||
97
qiming-mobile/uni_modules/x-tools/package.json
Normal file
97
qiming-mobile/uni_modules/x-tools/package.json
Normal file
@@ -0,0 +1,97 @@
|
||||
{
|
||||
"id": "x-tools",
|
||||
"displayName": "各端常用工具函数",
|
||||
"version": "1.1.7",
|
||||
"description": "各端常用工具函数",
|
||||
"keywords": [
|
||||
"x-tools"
|
||||
],
|
||||
"repository": "",
|
||||
"engines": {
|
||||
"HBuilderX": "^3.1.0",
|
||||
"uni-app": "^3.6.5",
|
||||
"uni-app-x": ""
|
||||
},
|
||||
"dcloudext": {
|
||||
"type": "component-vue",
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
},
|
||||
"sourcecode": {
|
||||
"price": "0.00"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"qq": ""
|
||||
},
|
||||
"declaration": {
|
||||
"ads": "无",
|
||||
"data": "插件不采集任何数据",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": "",
|
||||
"darkmode": "x",
|
||||
"i18n": "x",
|
||||
"widescreen": "x"
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": [],
|
||||
"encrypt": [],
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "√",
|
||||
"aliyun": "√",
|
||||
"alipay": "√"
|
||||
},
|
||||
"client": {
|
||||
"uni-app": {
|
||||
"vue": {
|
||||
"vue2": "√",
|
||||
"vue3": "√"
|
||||
},
|
||||
"web": {
|
||||
"safari": "√",
|
||||
"chrome": "√"
|
||||
},
|
||||
"app": {
|
||||
"vue": "√",
|
||||
"nvue": "√",
|
||||
"android": "√",
|
||||
"ios": "√",
|
||||
"harmony": "√"
|
||||
},
|
||||
"mp": {
|
||||
"weixin": "√",
|
||||
"alipay": "√",
|
||||
"toutiao": "√",
|
||||
"baidu": "√",
|
||||
"kuaishou": "√",
|
||||
"jd": "√",
|
||||
"harmony": "√",
|
||||
"qq": "√",
|
||||
"lark": "√"
|
||||
},
|
||||
"quickapp": {
|
||||
"huawei": "√",
|
||||
"union": "√"
|
||||
}
|
||||
},
|
||||
"uni-app-x": {
|
||||
"web": {
|
||||
"safari": "-",
|
||||
"chrome": "-"
|
||||
},
|
||||
"app": {
|
||||
"android": "-",
|
||||
"ios": "-",
|
||||
"harmony": "-"
|
||||
},
|
||||
"mp": {
|
||||
"weixin": "-"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
59
qiming-mobile/uni_modules/x-tools/tools/appletUtils.js
Normal file
59
qiming-mobile/uni_modules/x-tools/tools/appletUtils.js
Normal file
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* 小程序工具
|
||||
*/
|
||||
import { getQueryParams } from './h5Utils.js'
|
||||
|
||||
// 跳转视频号直播
|
||||
export const toChannelsLive = (finderUserName = '') => {
|
||||
// #ifdef MP-WEIXIN
|
||||
wx.getChannelsLiveInfo({
|
||||
finderUserName,
|
||||
success: info => {
|
||||
const { feedId, nonceId } = info
|
||||
wx.openChannelsLive({
|
||||
finderUserName,
|
||||
feedId,
|
||||
nonceId,
|
||||
fail: (err) => {
|
||||
console.log(err);
|
||||
uni.showToast({
|
||||
title: err?.errMsg
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
fail: (err) => {
|
||||
console.log(err);
|
||||
uni.showToast({
|
||||
title: err?.errMsg
|
||||
})
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
// #ifndef MP-WEIXIN
|
||||
console.error('只支持在微信小程序中调用')
|
||||
// #endif
|
||||
}
|
||||
|
||||
// 获取小程序分享参数
|
||||
export const getWxShareParams = (queryParams, title, imageUrl, path) => {
|
||||
const { appName } = uni.getSystemInfoSync()
|
||||
const pages = getCurrentPages()
|
||||
const { route, options, $page } = pages[pages.length - 1]
|
||||
const params = queryParams || options || getQueryParams($page?.fullPath)
|
||||
title = title || appName
|
||||
path = path || '/' + route
|
||||
path += Object.keys(params).reduce((prev, curr) => curr ? `${prev}${curr}=${params[curr]}&` : prev, path.includes('?') ? '&' : '?').slice(0, -1)
|
||||
console.log('getWxShareParams', title, path, imageUrl);
|
||||
return {
|
||||
title,
|
||||
path,
|
||||
imageUrl
|
||||
}
|
||||
}
|
||||
|
||||
// 获取场景参数
|
||||
export const getSceneParams = () => {
|
||||
const { query } = uni.getLaunchOptionsSync()
|
||||
return getQueryParams(decodeURIComponent(query.scene))
|
||||
}
|
||||
57
qiming-mobile/uni_modules/x-tools/tools/codeUtil.js
Normal file
57
qiming-mobile/uni_modules/x-tools/tools/codeUtil.js
Normal file
@@ -0,0 +1,57 @@
|
||||
export class CodeUtil {
|
||||
|
||||
codeType = []
|
||||
|
||||
constructor(codeType) {
|
||||
if (!Array.isArray(codeType)) {
|
||||
console.error('codeType should be an array')
|
||||
return
|
||||
}
|
||||
this.codeType = codeType
|
||||
}
|
||||
|
||||
scan(options = {}) {
|
||||
return uni.scanCode(options)
|
||||
}
|
||||
|
||||
async getCodeInfo(options) {
|
||||
const { scanType, result } = await this.scan(options)
|
||||
return this.parseCode(result)
|
||||
}
|
||||
|
||||
makeCode(type, content) {
|
||||
const t = this.codeType.indexOf(type)
|
||||
|
||||
if (t == -1) {
|
||||
console.error(`codeType ${type} is undefined`, this.codeType)
|
||||
return null
|
||||
}
|
||||
|
||||
return JSON.stringify({
|
||||
t,
|
||||
c: content
|
||||
})
|
||||
}
|
||||
|
||||
parseCode(codeInfo) {
|
||||
try {
|
||||
const info = JSON.parse(codeInfo)
|
||||
|
||||
const { t, c } = info
|
||||
|
||||
const type = this.codeType[t]
|
||||
|
||||
if (type) return { type, content: c }
|
||||
|
||||
return { type: 'unknown', content: info }
|
||||
|
||||
} catch (err) {
|
||||
|
||||
console.error('parseCode error', err)
|
||||
|
||||
return { type: 'error', content: codeInfo }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const codeUtil = new CodeUtil([])
|
||||
18
qiming-mobile/uni_modules/x-tools/tools/com.js
Normal file
18
qiming-mobile/uni_modules/x-tools/tools/com.js
Normal file
@@ -0,0 +1,18 @@
|
||||
export const commonProps = {
|
||||
bgColor: {
|
||||
type: String,
|
||||
default: '#fff'
|
||||
},
|
||||
customStyle: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
}
|
||||
|
||||
export const str2px = str => {
|
||||
if (!str) return 0
|
||||
str = String(str)
|
||||
if (str.endsWith('rpx')) return uni.upx2px(parseInt(str))
|
||||
if (str.endsWith('px')) return parseInt(str)
|
||||
return parseInt(str)
|
||||
}
|
||||
227
qiming-mobile/uni_modules/x-tools/tools/h5Utils.js
Normal file
227
qiming-mobile/uni_modules/x-tools/tools/h5Utils.js
Normal file
@@ -0,0 +1,227 @@
|
||||
/**
|
||||
* H5工具
|
||||
*/
|
||||
|
||||
// 线上调试
|
||||
export const debug = async (tip = true) => {
|
||||
const erudaList = [
|
||||
'https://unpkg.com/eruda',
|
||||
'https://cdn.jsdelivr.net/npm/eruda',
|
||||
'/eruda.js',
|
||||
]
|
||||
let idx = 0
|
||||
while (erudaList[idx]) {
|
||||
try {
|
||||
await loadScript(erudaList[idx])
|
||||
eruda.init()
|
||||
if (tip) alert('debug ready')
|
||||
return Promise.resolve('加载成功')
|
||||
} catch (e) {
|
||||
idx++
|
||||
}
|
||||
}
|
||||
return Promise.reject('加载失败')
|
||||
}
|
||||
|
||||
// 获取查询参数
|
||||
export const getQueryParams = (url) => {
|
||||
url = url || window?.location?.href
|
||||
const queryString = String(url).split('?').at(-1);
|
||||
if (!queryString) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return queryString.split('&').reduce((params, param) => {
|
||||
const [key, value] = param.split('=');
|
||||
if (key && value) params[key] = decodeURIComponent(value);
|
||||
return params;
|
||||
}, {});
|
||||
}
|
||||
|
||||
// 判断浏览器
|
||||
export const judgeBrowser = () => {
|
||||
const userAgent = navigator.userAgent.toLowerCase();
|
||||
if (userAgent.includes('micromessenger')) return 'wx'
|
||||
if (userAgent.includes('alipay')) return 'ali'
|
||||
return 'unknown'
|
||||
}
|
||||
|
||||
// 加载 js script
|
||||
export const loadScript = (src) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const script = document.createElement('script');
|
||||
script.src = src;
|
||||
script.onload = () => resolve(script);
|
||||
script.onerror = (error) => reject(error);
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 微信公众号相关
|
||||
*/
|
||||
|
||||
// 微信公众号授权获取 code
|
||||
export const getWxAuthCode = (appId, state = '', scope = 'snsapi_base') => {
|
||||
/**
|
||||
* 以snsapi_base为scope发起的网页授权,是用来获取进入页面的用户的openid的,
|
||||
* 并且是静默授权并自动跳转到回调页的。用户感知的就是直接进入了回调页(往往是业务页面)
|
||||
* 以snsapi_userinfo为scope发起的网页授权,是用来获取用户的基本信息的。但这种授权需要用户手动同意,
|
||||
* 并且由于用户同意过,所以无须关注,就可在授权后获取该用户的基本信息。
|
||||
*/
|
||||
const redirect_uri = encodeURIComponent(window.location.href);
|
||||
const authUrl = `http://open.weixin.qq.com/connect/oauth2/authorize
|
||||
?appid=${appId}
|
||||
&redirect_uri=${redirect_uri}
|
||||
&response_type=code
|
||||
&scope=${scope}
|
||||
&state=${state}#wechat_redirect`;
|
||||
window.location.href = authUrl
|
||||
}
|
||||
|
||||
export const wxConfig = (params) => {
|
||||
/**
|
||||
* config信息验证后会执行ready方法,所有接口调用都必须在config接口获得结果之后,
|
||||
* config是一个客户端的异步操作,所以如果需要在页面加载时就调用相关接口,
|
||||
* 则须把相关接口放在ready函数中调用来确保正确执行。对于用户触发时才调用的接口,
|
||||
* 则可以直接调用,不需要放在ready函数中。
|
||||
*/
|
||||
jWeixin.config({
|
||||
debug: false,
|
||||
appId: params.appId,
|
||||
timestamp: params.timestamp,
|
||||
nonceStr: params.noncestr,
|
||||
signature: params.signature,
|
||||
jsApiList: [
|
||||
"updateTimelineShareData",
|
||||
"updateAppMessageShareData",
|
||||
"chooseWXPay",
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
// 微信公众号支付
|
||||
export const wxPay = (params) => {
|
||||
console.log('wxPay', params);
|
||||
wxConfig(params)
|
||||
return new Promise((resolve, reject) => {
|
||||
jWeixin.ready(function() {
|
||||
jWeixin.chooseWXPay({
|
||||
timestamp: params.timeStamp,
|
||||
nonceStr: params.nonceStr,
|
||||
package: params.package,
|
||||
signType: params.signType,
|
||||
paySign: params.paySign,
|
||||
success: function(res) {
|
||||
console.log('支付成功:', res);
|
||||
resolve(res)
|
||||
},
|
||||
cancel: function(res) {
|
||||
console.log('用户取消支付:', res);
|
||||
reject(res)
|
||||
},
|
||||
fail: function(err) {
|
||||
console.error('支付失败:', err);
|
||||
reject(err)
|
||||
}
|
||||
});
|
||||
});
|
||||
wx.error(function(res) {
|
||||
reject(res)
|
||||
})
|
||||
})
|
||||
};
|
||||
|
||||
// 微信公众号设置分享信息
|
||||
export const wxShare = (params) => {
|
||||
console.log('wxShare', params);
|
||||
//配置校验成功后执行
|
||||
jWeixin.ready(function() {
|
||||
if (!params.link) {
|
||||
let url = window.location.href;
|
||||
let index = url.indexOf("?");
|
||||
if (index != -1) {
|
||||
if (url.indexOf("#") != -1 && url.indexOf("#") > index) {
|
||||
url = url.substring(0, index) + url.substring(url.indexOf("#"));
|
||||
} else {
|
||||
url = url.substr(0, index);
|
||||
}
|
||||
}
|
||||
params.link = url;
|
||||
}
|
||||
// 自定义“分享给朋友”及“分享到QQ”按钮的分享内容
|
||||
jWeixin.updateAppMessageShareData({
|
||||
title: params.title, // 分享标题
|
||||
desc: params.desc, // 分享描述
|
||||
link: params.link, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
|
||||
imgUrl: params.imgUrl, // 分享图标
|
||||
success: function() {
|
||||
console.log('updateAppMessageShareData success');
|
||||
},
|
||||
fail: err => {
|
||||
console.log('updateAppMessageShareData fail', err);
|
||||
}
|
||||
});
|
||||
|
||||
// 自定义“分享到朋友圈”及“分享到QQ空间”按钮的分享内容
|
||||
jWeixin.updateTimelineShareData({
|
||||
title: params.title, // 分享标题
|
||||
link: params.link, // 分享链接,该链接域名或路径必须与当前页面对应的公众号JS安全域名一致
|
||||
imgUrl: params.imgUrl, // 分享图标
|
||||
success: function() {
|
||||
console.log('updateTimelineShareData success');
|
||||
},
|
||||
fail: err => {
|
||||
console.log('updateTimelineShareData fail', err);
|
||||
}
|
||||
});
|
||||
console.log('wxShare', params);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 支付宝相关
|
||||
*/
|
||||
|
||||
// 支付宝生活号授权获取 code
|
||||
export const getAliAuthCode = (appId, scope = 'auth_base') => {
|
||||
/**
|
||||
* auth_base(静默授权):静默授权,用户无需点击确认授权,默认返回 auth_code,该授权码不支持获取用户信息。
|
||||
* auth_user(主动授权):首次授权需要用户手动点击同意,用户同意后,返回 auth_code;
|
||||
* 商家需要考虑用户拒绝授权的情况并进行相应容错。如果授权关系依旧存在,下次进入页面时也会静默授权。
|
||||
*/
|
||||
return new Promise((resolve, reject) => {
|
||||
ap.getAuthCode({
|
||||
appId,
|
||||
scopes: [scope],
|
||||
}, function(res) {
|
||||
if (res.error) {
|
||||
reject(res)
|
||||
} else {
|
||||
resolve(res)
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
// 支付宝生活号支付
|
||||
export const aliPay = (tradeNO) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
console.log('aliPay', tradeNO);
|
||||
ap.tradePay({
|
||||
tradeNO,
|
||||
success: res => {
|
||||
if (res.resultCode === '9000') {
|
||||
resolve(res)
|
||||
} else {
|
||||
reject(res)
|
||||
}
|
||||
},
|
||||
fail: err => {
|
||||
reject(err)
|
||||
},
|
||||
});
|
||||
})
|
||||
}
|
||||
321
qiming-mobile/uni_modules/x-tools/tools/index.js
Normal file
321
qiming-mobile/uni_modules/x-tools/tools/index.js
Normal file
@@ -0,0 +1,321 @@
|
||||
export * as sugar from './sugar'
|
||||
export * as router from './router'
|
||||
export * as h5Utils from './h5Utils'
|
||||
export * as appletUtils from './appletUtils'
|
||||
export * from './retryAsync'
|
||||
|
||||
import pagesJson from '@/pages.json'
|
||||
|
||||
export const isPromise = (obj) => {
|
||||
return !!obj && (typeof obj === "object" || typeof obj === "function") && typeof obj.then === "function";
|
||||
}
|
||||
|
||||
let promisifyFlag = false
|
||||
export const promisify = () => {
|
||||
|
||||
if (promisifyFlag) return
|
||||
|
||||
promisifyFlag = true
|
||||
|
||||
// #ifdef VUE2
|
||||
uni.addInterceptor({
|
||||
returnValue(res) {
|
||||
if (isPromise(res)) {
|
||||
return new Promise((resolve, reject) => {
|
||||
res.then((res) => {
|
||||
if (Array.isArray(res)) {
|
||||
if (res[0]) reject(res[0])
|
||||
else resolve(res[1])
|
||||
} else {
|
||||
if (typeof res == 'object' && String(res.errMsg).includes(':ok')) {
|
||||
resolve(res)
|
||||
} else {
|
||||
reject(res)
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
return res;
|
||||
},
|
||||
});
|
||||
// #endif
|
||||
}
|
||||
|
||||
// 获取类型
|
||||
export const _typeof = val => Object.prototype.toString.call(val).slice(8, -1).toLowerCase()
|
||||
|
||||
// 休眠
|
||||
export const sleep = ms => new Promise(r => setTimeout(r, ms))
|
||||
|
||||
// 获取当前页面信息
|
||||
export const getCurrentPage = () => {
|
||||
const pages = getCurrentPages()
|
||||
return pages[pages.length - 1] || {}
|
||||
}
|
||||
|
||||
// 获取当前页面 path
|
||||
export const getCurrentPagePath = (fullPath = false) => {
|
||||
const { route, $page } = getCurrentPage()
|
||||
return fullPath ? $page.fullPath : '/' + route
|
||||
}
|
||||
|
||||
// 判断当前页或指定 path 是否是 tabbar 页面
|
||||
export const isTabBarPage = path => {
|
||||
const { tabBar } = pagesJson
|
||||
if (!tabBar) return false
|
||||
path = path || getCurrentPagePath()
|
||||
return !(!tabBar || !tabBar.list || !tabBar.list.length || tabBar.list.findIndex(i => '/' + i.pagePath == path) == -1)
|
||||
}
|
||||
|
||||
// 获取应用运行页面信息
|
||||
export const getPageInfo = () => {
|
||||
|
||||
// 获取 pages 并去重
|
||||
const pages = getCurrentPages().reduce((p, c) => {
|
||||
if (!p.find(f => f.route == c.route)) p.push(c)
|
||||
return p
|
||||
}, [])
|
||||
|
||||
// 启动页
|
||||
const startPage = pagesJson.pages[0].path
|
||||
|
||||
// tabBar
|
||||
const tabBarList = pagesJson.tabBar?.list || []
|
||||
|
||||
// 首页
|
||||
const [page] = pages
|
||||
|
||||
return {
|
||||
currentPage: pages[pages.length - 1] || {},
|
||||
startPage,
|
||||
isSharePage: pages.length == 1 && page.route != startPage && !tabBarList.some(i => i.pagePath == page.route),
|
||||
pageStackLen: pages.length,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @desc 倒计时
|
||||
* @func countDown
|
||||
* @param {String | Number} endTime 结束时间
|
||||
* @param {Function} callback 倒计时字符串更新回调函数 (info, isEnd) => {}
|
||||
* @param {String} formatStr 倒计时字符串显示格式 d=天, h=时, m=分, s=秒, 默认(hh:mm:ss)
|
||||
* @param {Number} refreshRate 更新频率 默认(100)
|
||||
* @return {Number} 定时器 id 用于结束倒计时
|
||||
*/
|
||||
export const countDown = function(endTime, callback, formatStr = 'hh:mm:ss', refreshRate = 100) {
|
||||
const targetTime = new Date(endTime).getTime();
|
||||
let cacheStr
|
||||
const intervalId = setInterval(function() {
|
||||
|
||||
const currentTime = new Date().getTime();
|
||||
|
||||
const distance = targetTime - currentTime;
|
||||
|
||||
const date = {
|
||||
d: Math.floor(distance / (1000 * 60 * 60 * 24)),
|
||||
h: Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)),
|
||||
m: Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60)),
|
||||
s: Math.floor((distance % (1000 * 60)) / 1000)
|
||||
}
|
||||
|
||||
if (!formatStr.includes('d')) {
|
||||
date.h += date.d * 24;
|
||||
date.d = 0;
|
||||
}
|
||||
|
||||
if (!formatStr.includes('h')) {
|
||||
date.m += date.h * 60;
|
||||
date.h = 0;
|
||||
}
|
||||
|
||||
if (!formatStr.includes('m')) {
|
||||
date.s += date.m * 60;
|
||||
date.m = 0;
|
||||
}
|
||||
|
||||
let str = formatStr
|
||||
|
||||
Object.keys(date).forEach(key => {
|
||||
if (date[key] < 0) date[key] = 0
|
||||
if (str.includes(key)) {
|
||||
if (str.includes(key + key)) {
|
||||
str = str.replace(key + key, date[key].toString().padStart(2, 0))
|
||||
} else {
|
||||
str = str.replace(key, date[key])
|
||||
}
|
||||
}
|
||||
})
|
||||
if (cacheStr != str) {
|
||||
cacheStr = str
|
||||
if (date.d + date.h + date.m + date.s <= 0) {
|
||||
clearInterval(intervalId);
|
||||
callback && callback({ str, ...date }, true)
|
||||
} else {
|
||||
callback && callback({ str, ...date }, false)
|
||||
}
|
||||
}
|
||||
}, refreshRate);
|
||||
return intervalId
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @desc 轮询函数(等待结果)
|
||||
* @func pollingForResult
|
||||
* @param {Function} fun 返回值为 Promise 的函数
|
||||
* @param {Array} params fun函数调用的参数列表 ( 例: [param,param] )
|
||||
* @param {Function} judgeFun 拿到 fun 函数返回结果 res, 然后调用 judgeFun(res), judgeFun函数应返回 success 或 fail , 没有返回值即忽略本次
|
||||
* @param {Number} ms 最快多长时间调用一次 ( 默认 1000ms )
|
||||
* @param {Number} timeout 超时时间 ( 默认 1min )
|
||||
* @return {Promise} judgeFun 函数返回 success 或 fail 的调用结果
|
||||
*/
|
||||
export const pollingForResult = function(fun, params, judgeFun, ms = 1000, timeout = 1000 * 60) {
|
||||
// console.time('polling-time')
|
||||
console.log('polling-params', arguments);
|
||||
let startTime = Date.now()
|
||||
let prevCallTime = 0
|
||||
if (typeof judgeFun != 'function') throw new Error('judgeFun 参数应为一个 function');
|
||||
if (!(params instanceof Array)) params = [params]
|
||||
if (ms <= 0) ms = 1
|
||||
return new Promise((resolve, reject) => {
|
||||
function fun_call() {
|
||||
if ((prevCallTime + ms) > Date.now()) {
|
||||
setTimeout(() => {
|
||||
fun_call()
|
||||
}, (prevCallTime + ms) - Date.now());
|
||||
return
|
||||
}
|
||||
if ((timeout + startTime) < Date.now()) {
|
||||
reject('Timeout')
|
||||
return
|
||||
}
|
||||
prevCallTime = Date.now()
|
||||
// console.time('fun-call-time')
|
||||
fun(...params).then(res => {
|
||||
console.log('fun-call-res', res);
|
||||
// console.timeEnd('fun-call-time')
|
||||
if (judgeFun(res) === 'success') {
|
||||
resolve(res)
|
||||
// console.timeEnd('polling-time')
|
||||
} else if (judgeFun(res) === 'fail') {
|
||||
reject(res)
|
||||
// console.timeEnd('polling-time')
|
||||
} else {
|
||||
fun_call()
|
||||
}
|
||||
}).catch(err => {
|
||||
console.log('fun-call-err', err);
|
||||
// console.timeEnd('fun-call-time')
|
||||
fun_call()
|
||||
})
|
||||
}
|
||||
fun_call()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @desc 轮询函数(获取数据)
|
||||
* @func pollingForData
|
||||
* @param {Function} fun 返回值为 Promise 的函数
|
||||
* @param {Array} params fun函数调用的参数列表 ( 例: [param,param] )
|
||||
* @param {Function} callback 每次调用的结果 ( 例: (err, res, count) => {} )
|
||||
* @param {Number} ms 最快多长时间调用一次 ( 默认 1000ms )
|
||||
* @return {Function} 结束轮询函数
|
||||
*/
|
||||
export const pollingForData = function(fun, params, callback, ms = 1000) {
|
||||
console.log('polling-params', arguments);
|
||||
let prevCallTime = 0
|
||||
let endFlag = false
|
||||
let count = 0
|
||||
if (typeof callback != 'function') throw new Error('callback 参数应为一个 function');
|
||||
if (!(params instanceof Array)) params = [params]
|
||||
if (ms <= 0) ms = 1
|
||||
|
||||
function fun_call() {
|
||||
if (endFlag) return
|
||||
if ((prevCallTime + ms) > Date.now()) {
|
||||
setTimeout(() => {
|
||||
fun_call()
|
||||
}, (prevCallTime + ms) - Date.now());
|
||||
return
|
||||
}
|
||||
prevCallTime = Date.now()
|
||||
count++
|
||||
fun(...params).then(res => {
|
||||
callback && callback(null, res, count)
|
||||
fun_call()
|
||||
}).catch(err => {
|
||||
callback && callback(err, null, count)
|
||||
fun_call()
|
||||
})
|
||||
}
|
||||
fun_call()
|
||||
return () => {
|
||||
console.log('polling-end');
|
||||
endFlag = true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @desc 深拷贝
|
||||
* @func deepClone
|
||||
* @param {Object} target 被拷贝的对象
|
||||
* @return {Object} 新对象
|
||||
*/
|
||||
export const deepClone = (target, map = new WeakMap()) => {
|
||||
if (target === null || typeof target !== 'object') {
|
||||
return target;
|
||||
}
|
||||
|
||||
if (target instanceof Date) {
|
||||
return new Date(target);
|
||||
}
|
||||
|
||||
if (target instanceof RegExp) {
|
||||
return new RegExp(target.source, target.flags);
|
||||
}
|
||||
|
||||
if (target instanceof Map) {
|
||||
const newMap = new Map();
|
||||
target.forEach((value, key) => {
|
||||
newMap.set(deepClone(key, map), deepClone(value, map));
|
||||
});
|
||||
return newMap;
|
||||
}
|
||||
|
||||
if (target instanceof Set) {
|
||||
const newSet = new Set();
|
||||
target.forEach(value => {
|
||||
newSet.add(deepClone(value, map));
|
||||
});
|
||||
return newSet;
|
||||
}
|
||||
|
||||
if (typeof target === 'function') {
|
||||
return target;
|
||||
}
|
||||
|
||||
if (map.has(target)) {
|
||||
return map.get(target);
|
||||
}
|
||||
|
||||
const cloneTarget = Array.isArray(target) ? [] : {};
|
||||
|
||||
map.set(target, cloneTarget);
|
||||
|
||||
const symbolKeys = Object.getOwnPropertySymbols(target);
|
||||
if (symbolKeys.length > 0) {
|
||||
symbolKeys.forEach(symKey => {
|
||||
cloneTarget[symKey] = deepClone(target[symKey], map);
|
||||
});
|
||||
}
|
||||
|
||||
for (let key in target) {
|
||||
if (Object.prototype.hasOwnProperty.call(target, key)) {
|
||||
cloneTarget[key] = deepClone(target[key], map);
|
||||
}
|
||||
}
|
||||
|
||||
return cloneTarget;
|
||||
}
|
||||
112
qiming-mobile/uni_modules/x-tools/tools/retryAsync.js
Normal file
112
qiming-mobile/uni_modules/x-tools/tools/retryAsync.js
Normal file
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
* 异步操作重试工具函数
|
||||
* @param {Function} asyncFn - 需要重试的异步函数
|
||||
* @param {Object} options - 重试配置选项
|
||||
* @param {number} options.maxRetries - 最大重试次数 (默认: 3)
|
||||
* @param {number} options.delay - 重试延迟时间(ms) (默认: 1000)
|
||||
* @param {string} options.backoffStrategy - 退避策略 ('fixed' | 'exponential' | 'linear') (默认: 'fixed')
|
||||
* @param {number} options.backoffFactor - 退避因子 (默认: 2)
|
||||
* @param {Function} options.shouldRetry - 自定义重试条件函数 (默认: 所有错误都重试)
|
||||
* @param {Function} options.onRetry - 重试回调函数
|
||||
* @param {number} options.timeout - 单次操作超时时间(ms)
|
||||
* @returns {Promise} 返回异步操作的结果
|
||||
*/
|
||||
export async function retryAsync(asyncFn, options = {}) {
|
||||
const {
|
||||
maxRetries = 3,
|
||||
delay = 1000,
|
||||
backoffStrategy = 'fixed',
|
||||
backoffFactor = 2,
|
||||
shouldRetry = () => true,
|
||||
onRetry = () => {},
|
||||
timeout
|
||||
} = options;
|
||||
|
||||
let lastError;
|
||||
|
||||
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
||||
try {
|
||||
// 如果设置了超时时间,包装函数添加超时控制
|
||||
const result = timeout ?
|
||||
await withTimeout(asyncFn, timeout) :
|
||||
await asyncFn();
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
|
||||
// 如果是最后一次尝试,直接抛出错误
|
||||
if (attempt === maxRetries) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// 检查是否应该重试
|
||||
if (!shouldRetry(error, attempt + 1)) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// 执行重试回调
|
||||
onRetry(error, attempt + 1, maxRetries);
|
||||
|
||||
// 计算延迟时间并等待
|
||||
const waitTime = calculateDelay(delay, attempt, backoffStrategy, backoffFactor);
|
||||
await sleep(waitTime);
|
||||
}
|
||||
}
|
||||
|
||||
throw lastError;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算重试延迟时间
|
||||
*/
|
||||
function calculateDelay(baseDelay, attempt, strategy, factor) {
|
||||
switch (strategy) {
|
||||
case 'exponential':
|
||||
return baseDelay * Math.pow(factor, attempt);
|
||||
case 'linear':
|
||||
return baseDelay * (attempt + 1);
|
||||
case 'fixed':
|
||||
default:
|
||||
return baseDelay;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 延迟函数
|
||||
*/
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
/**
|
||||
* 为异步函数添加超时控制
|
||||
*/
|
||||
function withTimeout(asyncFn, timeoutMs) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let isResolved = false;
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
if (!isResolved) {
|
||||
isResolved = true;
|
||||
reject(new Error(`Operation timed out after ${timeoutMs}ms`));
|
||||
}
|
||||
}, timeoutMs);
|
||||
|
||||
asyncFn()
|
||||
.then(result => {
|
||||
if (!isResolved) {
|
||||
isResolved = true;
|
||||
clearTimeout(timer);
|
||||
resolve(result);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
if (!isResolved) {
|
||||
isResolved = true;
|
||||
clearTimeout(timer);
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
91
qiming-mobile/uni_modules/x-tools/tools/router.js
Normal file
91
qiming-mobile/uni_modules/x-tools/tools/router.js
Normal file
@@ -0,0 +1,91 @@
|
||||
import { deepClone } from './index.js'
|
||||
|
||||
const delayCall = (fun, timeout) => {
|
||||
if (timeout) setTimeout(fun, timeout)
|
||||
else fun()
|
||||
}
|
||||
|
||||
export const navBackAndEvent = (eventChannel, backData, timeout = 0, delta = 1) => {
|
||||
delayCall(() => {
|
||||
eventChannel.emit('backData', backData)
|
||||
uni.navigateBack({
|
||||
delta
|
||||
})
|
||||
}, timeout)
|
||||
}
|
||||
|
||||
export const navToAndEvent = (url, data, acceptCallback, timeout = 0) => {
|
||||
return new Promise(resolve => {
|
||||
delayCall(() => {
|
||||
uni.navigateTo({
|
||||
url,
|
||||
events: {
|
||||
backData: data => {
|
||||
acceptCallback && acceptCallback(data)
|
||||
resolve(data)
|
||||
}
|
||||
},
|
||||
success: ({ eventChannel }) => {
|
||||
if (eventChannel && eventChannel.emit) {
|
||||
eventChannel.emit('navData', deepClone(data))
|
||||
}
|
||||
},
|
||||
fail: console.log,
|
||||
})
|
||||
}, timeout)
|
||||
})
|
||||
}
|
||||
|
||||
export const navTo = (url, options, timeout = 0) => {
|
||||
delayCall(() => {
|
||||
uni.navigateTo({
|
||||
url,
|
||||
fail: console.log,
|
||||
...options
|
||||
})
|
||||
}, timeout)
|
||||
}
|
||||
|
||||
export const redTo = (url, timeout = 0) => {
|
||||
delayCall(() => {
|
||||
uni.redirectTo({
|
||||
url,
|
||||
fail: console.log
|
||||
})
|
||||
}, timeout)
|
||||
}
|
||||
|
||||
export const clearTo = (url, timeout = 0) => {
|
||||
delayCall(() => {
|
||||
uni.reLaunch({
|
||||
url,
|
||||
fail: console.log
|
||||
})
|
||||
}, timeout)
|
||||
}
|
||||
|
||||
export const tabTo = (url, timeout = 0) => {
|
||||
delayCall(() => {
|
||||
uni.switchTab({
|
||||
url,
|
||||
fail: console.log
|
||||
})
|
||||
}, timeout)
|
||||
}
|
||||
|
||||
export const shTab = (url, timeout = 0) => {
|
||||
delayCall(() => {
|
||||
uni.switchTab({
|
||||
url,
|
||||
fail: console.log
|
||||
})
|
||||
}, timeout)
|
||||
}
|
||||
|
||||
export const navBack = (timeout = 0, delta = 1) => {
|
||||
delayCall(() => {
|
||||
uni.navigateBack({
|
||||
delta
|
||||
})
|
||||
}, timeout)
|
||||
}
|
||||
213
qiming-mobile/uni_modules/x-tools/tools/sugar.js
Normal file
213
qiming-mobile/uni_modules/x-tools/tools/sugar.js
Normal file
@@ -0,0 +1,213 @@
|
||||
export const log = console.log
|
||||
import { t } from '@/utils/i18n'
|
||||
|
||||
export const upx2px = val => uni.upx2px(parseInt(val))
|
||||
|
||||
export const toast = (title, options = { duration: 2000, icon: 'none' }) => uni.showToast({ title, fail: console.log, ...options })
|
||||
|
||||
export const makePhoneCall = phoneNumber => uni.makePhoneCall({ phoneNumber, fail: console.log });
|
||||
|
||||
export const previewImage = (urls, current, options) => uni.previewImage({ urls, current, fail: console.log, ...options });
|
||||
|
||||
export const copy = (str, options) => uni.setClipboardData({ data: String(str), fail: console.log, ...options })
|
||||
|
||||
export const openMap = (lng, lat, name, address, options) => uni.openLocation({ longitude: parseFloat(lng), latitude: parseFloat(lat), name, address, fail: console.log, ...options })
|
||||
|
||||
export const setStorage = (key, value) => uni.setStorageSync(key, value)
|
||||
|
||||
// 判断空值
|
||||
export const isEmpty = value => {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return true;
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
return value.length === 0;
|
||||
}
|
||||
if (typeof value === 'object') {
|
||||
return Object.keys(value).length === 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 返回对象属性为空的 property path
|
||||
const getEmptyValuePropertyPath = (obj, excludedKeys = []) => {
|
||||
for (let key in obj) {
|
||||
if (obj.hasOwnProperty(key) && !excludedKeys.includes(key)) {
|
||||
let value = obj[key];
|
||||
if (Array.isArray(value)) {
|
||||
// 判断是否为数组对象
|
||||
for (let item of value) {
|
||||
const childrenKey = getEmptyValuePropertyPath(item, excludedKeys)
|
||||
if (item instanceof Object && childrenKey) {
|
||||
return key + '.' + childrenKey;
|
||||
}
|
||||
}
|
||||
if (value.length === 0) {
|
||||
return key;
|
||||
}
|
||||
} else if (value instanceof Object) {
|
||||
const childrenKey = getEmptyValuePropertyPath(value, excludedKeys)
|
||||
if (childrenKey) {
|
||||
return key + '.' + childrenKey;
|
||||
}
|
||||
} else if (isEmpty(value)) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 有无空字段
|
||||
export const hasEmptyField = (obj, excludedKeys = []) => getEmptyValuePropertyPath(obj, excludedKeys)
|
||||
|
||||
// 跳转企业微信客服
|
||||
export const toCustomerService = (corpId, url) => {
|
||||
// #ifdef MP-WEIXIN
|
||||
wx.openCustomerServiceChat({
|
||||
extInfo: {
|
||||
url
|
||||
},
|
||||
corpId,
|
||||
success(res) {},
|
||||
fail: (err) => {
|
||||
console.log(err);
|
||||
uni.showToast({
|
||||
title: err?.errMsg
|
||||
})
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
|
||||
// #ifdef APP
|
||||
plus.share.getServices(services => {
|
||||
const sweixin = services.find(i => i.id === 'weixin')
|
||||
if (sweixin) {
|
||||
sweixin.openCustomerServiceChat({
|
||||
corpid: corpId,
|
||||
url,
|
||||
}, res => {
|
||||
console.log("success", JSON.stringify(res))
|
||||
}, err => {
|
||||
console.log("error", JSON.stringify(err))
|
||||
})
|
||||
} else {
|
||||
plus.nativeUI.alert(t('Mobile.ThirdParty.XTools.wechatUnsupported'))
|
||||
}
|
||||
}, function() {
|
||||
uni.showToast({
|
||||
title: t('Mobile.ThirdParty.XTools.getServiceFailed'),
|
||||
icon: 'error'
|
||||
})
|
||||
})
|
||||
// #endif
|
||||
|
||||
// #ifdef H5
|
||||
window.location.href = url
|
||||
// #endif
|
||||
}
|
||||
|
||||
// 保存图片
|
||||
export const saveImage = async (url, tips = true) => {
|
||||
try {
|
||||
console.log('saveImage', url);
|
||||
// #ifdef H5
|
||||
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = url.substring(url.lastIndexOf('/') + 1);
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
|
||||
// #endif
|
||||
|
||||
// #ifndef H5
|
||||
|
||||
if (url.startsWith('http')) {
|
||||
const { statusCode, tempFilePath: filePath } = await uni.downloadFile({
|
||||
url
|
||||
})
|
||||
|
||||
if (statusCode == 200) {
|
||||
await uni.saveImageToPhotosAlbum({
|
||||
filePath
|
||||
})
|
||||
} else {
|
||||
throw Error('图片下载失败')
|
||||
}
|
||||
|
||||
} else {
|
||||
await uni.saveImageToPhotosAlbum({
|
||||
filePath: url
|
||||
})
|
||||
}
|
||||
|
||||
// #endif
|
||||
|
||||
tips && uni.showToast({
|
||||
title: t('Mobile.ThirdParty.XTools.saveSuccess'),
|
||||
icon: 'success'
|
||||
})
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
|
||||
tips && uni.showToast({
|
||||
title: t('Mobile.ThirdParty.XTools.saveFailed'),
|
||||
icon: 'error'
|
||||
})
|
||||
}
|
||||
}
|
||||
// 获取页面通信管道(支持 vue2 需绑定this)
|
||||
export const pageEvent = function() {
|
||||
// #ifdef VUE2
|
||||
return this.getOpenerEventChannel()
|
||||
// #endif
|
||||
// #ifdef VUE3
|
||||
console.error('vue3 不支持这种使用方式')
|
||||
// #endif
|
||||
}
|
||||
|
||||
// 对象转查询字符串
|
||||
export const object2queryStr = (obj) => {
|
||||
if (!obj) return obj
|
||||
return Object.keys(obj).reduce((val, key) => {
|
||||
return `${val}${key}=${obj[key]}&`
|
||||
}, '?').slice(0, -1)
|
||||
}
|
||||
|
||||
// html 转纯文本
|
||||
export const html2text = (html) => {
|
||||
return String(html).replace(/<[^>]+>|&[^;]+;/g, '').replace(/\s+/g, ' ').trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* @Func dataMask
|
||||
* @Desc 数据脱敏
|
||||
* @param {String} str 字符串
|
||||
* @param {Number} head 头部保留位数
|
||||
* @param {Number} tail 尾部保留位数
|
||||
* @return {String} 脱敏后的数据
|
||||
*/
|
||||
export const dataMask = (str, head, tail) => {
|
||||
if (!str) return str
|
||||
const arr = String(str).split('')
|
||||
tail = arr.length - tail
|
||||
if (head < 0) head = 0
|
||||
if (head > tail) return str
|
||||
for (let i = head; i < tail; i++) {
|
||||
arr[i] = '*'
|
||||
}
|
||||
return arr.join('')
|
||||
}
|
||||
|
||||
// 查询元素信息
|
||||
export const queryElementRect = function(selector) {
|
||||
if (Array.isArray(selector)) return Promise.all(selector.map(i => queryElementRect.call(this, i)))
|
||||
return new Promise(resolve => {
|
||||
uni.createSelectorQuery()
|
||||
.in(this).select(selector)
|
||||
.boundingClientRect(resolve)
|
||||
.exec();
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user