1487 lines
41 KiB
JavaScript
1487 lines
41 KiB
JavaScript
import Config from './config.js'
|
||
import store from '@/store/index.js'
|
||
import Http from './http.js'
|
||
import Platform from './platform.js'
|
||
import {
|
||
Weixin
|
||
} from 'common/js/wx-jssdk.js';
|
||
|
||
//
|
||
export function decodeEntity(str, amp) {
|
||
const html4Entities = {
|
||
nbsp: ' ', iexcl: '¡', cent: '¢', pound: '£', curren: '₣', yen: '¥', brvbar: '§', sect: 'Ş', uml: '¨', copy: '©', ordf: 'ª', laquo: '«', not: '¬', shy: '®', macr: '¯', deg: '°', plusmn: '±', sup2: '²', sup3: '³', acute: '´', micro: 'µ', para: '¶', middot: '·', cedil: '¸', sup1: '¹', ordm: 'º', raquo: '»', frac14: '¼', frac12: '½', frac34: '¾', iquest: '¿', agrave: 'à', aacute: 'á', acirc: 'â', atilde: 'ã', auml: 'ä', aring: 'å', aelig: 'æ', ccedil: 'ç', egrave: 'è', eacute: 'é', ecirc: 'ê', euml: 'ë', igrave: 'ì', iacute: 'í', icirc: 'î', iuml: 'ï', eth: 'ð', ntilde: 'ñ', ograve: 'ò', oacute: 'ó', ocirc: 'ô', otilde: 'õ', ouml: 'ö', times: 'ø', ugrave: 'ù', uacute: 'ú', ucirc: 'û', uuml: 'ü', yacute: 'ý', thorn: 'þ', yuml: 'ÿ', fnof: 'ƒ', alpha: 'α', beta: 'β', gamma: 'γ', delta: 'δ', epsilon: 'ε', zeta: 'ζ', eta: 'η', theta: 'θ', iota: 'ι', kappa: 'κ', lambda: 'λ', mu: 'μ', nu: 'ν', xi: 'ξ', omicron: 'ο', pi: 'π', rho: 'ρ', sigmaf: 'σ', sigma: 'σ', tau: 'τ', upsilon: 'υ', phi: 'φ', chi: 'ψ', omega: 'ω'
|
||
}
|
||
amp = amp === undefined ? true : amp
|
||
let i = str.indexOf('&')
|
||
while (i !== -1) {
|
||
const j = str.indexOf(';', i + 3)
|
||
let code
|
||
if (j === -1) break
|
||
if (str[i + 1] === '#') {
|
||
code = parseInt((str[i + 2] === 'x' ? '0' : '') + str.substring(i + 2, j))
|
||
if (!isNaN(code)) {
|
||
str = str.substring(0, i) + String.fromCharCode(code) + str.substring(j + 1)
|
||
}
|
||
} else {
|
||
code = str.substring(i + 1, j)
|
||
if (html4Entities[code] || (code === 'amp' && amp)) {
|
||
str = str.substring(0, i) + (html4Entities[code] || '&') + str.substring(j + 1)
|
||
}
|
||
}
|
||
i = str.indexOf('&', i + 1)
|
||
}
|
||
return str
|
||
}
|
||
|
||
|
||
// 关于页面路径,使用常量导出
|
||
export const INDEX_PAGE_URL = '/pages/index/index';
|
||
export const CATEGORY_PAGE_URL = '/pages_goods/category';
|
||
export const CONTACT_PAGE_URL = '/pages_tool/contact/contact';
|
||
export const MEMBER_PAGE_URL = '/pages_tool/member/index';
|
||
export const LOGIN_PAGE_URL = '/pages_tool/login/login';
|
||
export const AI_CHAT_PAGE_URL = '/pages_tool/ai-chat/index';
|
||
|
||
// 当前最新的tabBar.list (参见pages.json 中的tabBar.list 配置)
|
||
export const systemTabBarList = [
|
||
INDEX_PAGE_URL,
|
||
CATEGORY_PAGE_URL,
|
||
CONTACT_PAGE_URL,
|
||
MEMBER_PAGE_URL
|
||
];
|
||
|
||
/**
|
||
* 适配子包路径
|
||
* @param {string} url
|
||
* @returns
|
||
*/
|
||
export const adaptSubpackageUrl = (url) => {
|
||
/**
|
||
* 特别注意:
|
||
* 由于老版本或者后台系统服务未同步更新可以作为tabBarList的最新分包路径。历史遗留问题,需要与当前最新分包机制版本保持一致。
|
||
* 系统当前的支持tarbarList(包含动态tabBar),注意这是实际分包后的路径
|
||
* 根据匹配映射关系,修改url为实际分包后的路径
|
||
*/
|
||
// 定义前缀映射表
|
||
const urlPrefixMap = {
|
||
'/pages/promotion/': '/pages_promotion/',
|
||
'/pages/order/': '/pages_order/',
|
||
'/pages/goods/': '/pages_goods/',
|
||
|
||
'/pages/contact/': '/pages_tool/contact/',
|
||
'/pages/member/': '/pages_tool/member/',
|
||
'/pages/login/': '/pages_tool/login/',
|
||
'/pages/agreement/': '/pages_tool/agreement/',
|
||
'/pages/article/': '/pages_tool/article/',
|
||
'/pages/cases/': '/pages_tool/cases/',
|
||
'/pages/contact/': '/pages_tool/contact/',
|
||
'/pages/files/': '/pages_tool/files/',
|
||
'/pages/form/': '/pages_tool/form/',
|
||
'/pages/help/': '/pages_tool/help/',
|
||
'/pages/notice/': '/pages_tool/notice/',
|
||
'/pages/pay/': '/pages_tool/pay/',
|
||
'/pages/recharge/': '/pages_tool/recharge/',
|
||
'/pages/seal/': '/pages_tool/seal/',
|
||
'/pages/storeclose/': '/pages_tool/storeclose/',
|
||
'/pages/vr/': '/pages_tool/vr/',
|
||
'/pages/weapp/': '/pages_tool/weapp/',
|
||
'/pages/webview/': '/pages_tool/webview/',
|
||
};
|
||
|
||
// 构建正则表达式
|
||
const regex = new RegExp(
|
||
Object.keys(urlPrefixMap).map(key =>
|
||
key.replace(/\//g, '\\/').replace(/\*/g, '.*')
|
||
).join('|'),
|
||
'g'
|
||
);
|
||
|
||
// 替换函数
|
||
function replacePrefix(str) {
|
||
return str.replace(regex, match => {
|
||
for (const [oldPrefix, newPrefix] of Object.entries(urlPrefixMap)) {
|
||
if (match.startsWith(oldPrefix)) {
|
||
return match.replace(oldPrefix, newPrefix);
|
||
}
|
||
}
|
||
return match; // 默认返回原匹配
|
||
});
|
||
}
|
||
|
||
return replacePrefix(url);
|
||
}
|
||
|
||
/**
|
||
* 检查tabBar是否激活
|
||
* @param {string} linkUrl 链接路径 /pages/goods/category
|
||
* @param {string} currentPageRoute 当前页面路径 /pages_goods/category
|
||
* @param {Object} tabBarList tabBar配置对象,用于动态判断
|
||
* @param {Object} packageEntryTab 追踪每个分包的入口Tab { 'pages_goods': '/pages_goods/category' }
|
||
* @returns {boolean} 是否激活
|
||
*/
|
||
export const checkTabBarActive = (linkUrl, currentPageRoute, tabBarList, packageEntryTab) => {
|
||
const adaptedLinkUrl = adaptSubpackageUrl(linkUrl);
|
||
const adaptedCurrentRoute = adaptSubpackageUrl(currentPageRoute);
|
||
|
||
// 去除查询参数,仅比较路径部分
|
||
const cleanLinkUrl = adaptedLinkUrl.split('?')[0];
|
||
const cleanCurrentRoute = adaptedCurrentRoute.split('?')[0];
|
||
|
||
const linkUrlParts = cleanLinkUrl.split('/');
|
||
const currentPageRouteParts = cleanCurrentRoute.split('/');
|
||
|
||
// 首先尝试精确匹配(整个路径匹配)
|
||
if (cleanLinkUrl === cleanCurrentRoute) {
|
||
return true;
|
||
}
|
||
|
||
// 如果没有精确匹配,且传入了 tabBarList,使用动态判断逻辑
|
||
if (tabBarList && tabBarList.list && Array.isArray(tabBarList.list)) {
|
||
// 获取所有 tab 项的链接(去除查询参数)
|
||
const tabUrls = tabBarList.list
|
||
.filter(item => item.link && item.link.wap_url)
|
||
.map(item => adaptSubpackageUrl(item.link.wap_url).split('?')[0]);
|
||
|
||
// 检查当前 linkUrl 是否是合法的 tab URL
|
||
if (!tabUrls.includes(cleanLinkUrl)) {
|
||
return false;
|
||
}
|
||
|
||
// 获取当前页面所属的分包名(parts[1])
|
||
const currentPackageName = currentPageRouteParts[1];
|
||
const linkPackageName = linkUrlParts[1];
|
||
|
||
// 如果 linkUrl 和当前页面不在同一个分包,直接返回 false
|
||
if (linkPackageName !== currentPackageName) {
|
||
return false;
|
||
}
|
||
|
||
// 获取当前页面路径的二级目录(parts[2])
|
||
const currentPageSecondPart = currentPageRouteParts[2];
|
||
|
||
// 检查当前页面路径是否匹配某个 tab 的精确路径
|
||
const currentMatchingTab = tabUrls.find(url => {
|
||
if (!url) return false;
|
||
const parts = url.split('/');
|
||
return parts[2] === currentPageSecondPart;
|
||
});
|
||
|
||
// 如果当前页面能精确匹配到某个 tab,那么只有该 tab 高亮
|
||
if (currentMatchingTab) {
|
||
return cleanLinkUrl === currentMatchingTab;
|
||
}
|
||
|
||
// 当前页面不是任何 tab 的精确匹配(是子页面)
|
||
// 使用 packageEntryTab 来确定应该高亮的 tab
|
||
if (packageEntryTab && typeof packageEntryTab === 'object') {
|
||
const entryTab = packageEntryTab[currentPackageName];
|
||
if (entryTab) {
|
||
const entryTabParts = entryTab.split('/');
|
||
return linkUrlParts[2] === entryTabParts[2];
|
||
}
|
||
}
|
||
|
||
// 基于二级路径匹配(fallback)
|
||
try {
|
||
if (linkUrlParts[2] === currentPageSecondPart) {
|
||
return true;
|
||
}
|
||
} catch (error) {
|
||
console.error('diy-bottom-nav verify error:', error);
|
||
}
|
||
|
||
return false;
|
||
}
|
||
}
|
||
|
||
export default {
|
||
/** 导出页面URL常量 */
|
||
MEMBER_PAGE_URL,
|
||
CATEGORY_PAGE_URL,
|
||
CONTACT_PAGE_URL,
|
||
INDEX_PAGE_URL,
|
||
LOGIN_PAGE_URL,
|
||
AI_CHAT_PAGE_URL,
|
||
|
||
/**
|
||
* 页面跳转
|
||
* @param {string} to 跳转链接 /pages/idnex/index
|
||
* @param {Object} param 参数 {key : value, ...}, 默认{}
|
||
* @param {string} mode 模式 navigateTo、redirectTo、reLaunch、tabbar, 默认navigateTo
|
||
*/
|
||
redirectTo(to, param = {}, mode = 'navigateTo') {
|
||
let url = to;
|
||
|
||
// 替换url中的前缀
|
||
url = adaptSubpackageUrl(url);
|
||
|
||
if (param != undefined) {
|
||
Object.keys(param).forEach(function (key) {
|
||
if (url.indexOf('?') != -1) {
|
||
url += "&" + key + "=" + param[key];
|
||
} else {
|
||
url += "?" + key + "=" + param[key];
|
||
}
|
||
});
|
||
}
|
||
|
||
// 针对tabBar.list中的路径,直接切换tabBar
|
||
for (let i = 0; i < systemTabBarList.length; i++) {
|
||
const tabBarUrl = systemTabBarList[i];
|
||
if (url.indexOf(tabBarUrl) == 0) {
|
||
// 首页特殊处理,采用switchTab, 其他页面采用redirectTo, 不能返回,不能退回
|
||
(tabBarUrl == INDEX_PAGE_URL ? uni.switchTab : uni.redirectTo)({ url });
|
||
return;
|
||
}
|
||
}
|
||
|
||
// 如果url不包含tabBarList中的路径,根据mode判断跳转方式
|
||
switch (mode) {
|
||
case 'tabbar':
|
||
// 跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面。重置页面栈,仅保留 tabBar 页面
|
||
uni.switchTab({
|
||
url
|
||
});
|
||
break;
|
||
case 'redirectTo':
|
||
// 关闭当前页面,跳转到应用内的某个页面。替换栈顶页面,长度不变(上限10层)
|
||
uni.redirectTo({
|
||
url
|
||
});
|
||
break;
|
||
case 'reLaunch':
|
||
// 关闭所有页面,打开到应用内的某个页面。清空栈后打开新页面,长度=1
|
||
uni.reLaunch({
|
||
url
|
||
});
|
||
break;
|
||
default:
|
||
// 保留当前页面,跳转到应用内的某个页面,新增页面到栈顶,长度+1(上限10层)
|
||
uni.navigateTo({
|
||
url
|
||
});
|
||
}
|
||
},
|
||
/**
|
||
* 图片路径转换
|
||
* @param {String} img_path 图片地址
|
||
* @param {Object} params 参数,针对商品、相册里面的图片区分大中小,size: big、mid、small
|
||
*/
|
||
img(img_path, params) {
|
||
var path = "";
|
||
if (img_path != undefined && img_path != "") {
|
||
if (img_path.split(',').length > 1) {
|
||
img_path = img_path.split(',')[0];
|
||
}
|
||
if (params && img_path != this.getDefaultImage().goods) {
|
||
// 过滤默认图
|
||
let arr = img_path.split(".");
|
||
let suffix = arr[arr.length - 1];
|
||
arr.pop();
|
||
arr[arr.length - 1] = arr[arr.length - 1] + "_" + params.size.toUpperCase();
|
||
arr.push(suffix);
|
||
// if(img_path.indexOf('attachment') == -1){
|
||
// img_path = arr.join(".");
|
||
// }
|
||
|
||
}
|
||
if (img_path.indexOf("http://") == -1 && img_path.indexOf("https://") == -1) {
|
||
path = Config.imgDomain + "/" + img_path;
|
||
} else {
|
||
// console.log(img_path)
|
||
path = img_path;
|
||
}
|
||
// 修复:去除 URL 中的双斜杠,避免微信 downloadFile 域名匹配失败
|
||
path = path.replace(/([^:]\/)\/+/g, "$1");
|
||
}
|
||
|
||
// path += '?t=' + parseInt(new Date().getTime() / 1000);
|
||
return path;
|
||
},
|
||
/**
|
||
* 时间戳转日期格式
|
||
* @param {Object} timeStamp
|
||
*/
|
||
timeStampTurnTime(timeStamp, type = "") {
|
||
if (timeStamp != undefined && timeStamp != "" && timeStamp > 0) {
|
||
var date = new Date();
|
||
date.setTime(timeStamp * 1000);
|
||
var y = date.getFullYear();
|
||
var m = date.getMonth() + 1;
|
||
m = m < 10 ? ('0' + m) : m;
|
||
var d = date.getDate();
|
||
d = d < 10 ? ('0' + d) : d;
|
||
var h = date.getHours();
|
||
h = h < 10 ? ('0' + h) : h;
|
||
var minute = date.getMinutes();
|
||
var second = date.getSeconds();
|
||
minute = minute < 10 ? ('0' + minute) : minute;
|
||
second = second < 10 ? ('0' + second) : second;
|
||
if (type) {
|
||
if (type == 'yearMonthDay') {
|
||
return y + '年' + m + '月' + d + '日';
|
||
}
|
||
return y + '-' + m + '-' + d;
|
||
} else {
|
||
return y + '-' + m + '-' + d + ' ' + h + ':' + minute + ':' + second;
|
||
}
|
||
} else {
|
||
return "";
|
||
}
|
||
},
|
||
/**
|
||
* 日期格式转时间戳
|
||
* @param {Object} string
|
||
*/
|
||
timeTurnTimeStamp(string) {
|
||
var f = string.split(' ', 2);
|
||
var d = (f[0] ? f[0] : '').split('-', 3);
|
||
var t = (f[1] ? f[1] : '').split(':', 3);
|
||
return (new Date(
|
||
parseInt(d[0], 10) || null,
|
||
(parseInt(d[1], 10) || 1) - 1,
|
||
parseInt(d[2], 10) || null,
|
||
parseInt(t[0], 10) || null,
|
||
parseInt(t[1], 10) || null,
|
||
parseInt(t[2], 10) || null
|
||
)).getTime() / 1000;
|
||
},
|
||
/**
|
||
* 倒计时
|
||
* @param {Object} seconds 秒
|
||
*/
|
||
countDown(seconds) {
|
||
let [day, hour, minute, second] = [0, 0, 0, 0]
|
||
if (seconds > 0) {
|
||
day = Math.floor(seconds / (60 * 60 * 24))
|
||
hour = Math.floor(seconds / (60 * 60)) - (day * 24)
|
||
minute = Math.floor(seconds / 60) - (day * 24 * 60) - (hour * 60)
|
||
second = Math.floor(seconds) - (day * 24 * 60 * 60) - (hour * 60 * 60) - (minute * 60)
|
||
}
|
||
if (day < 10) {
|
||
day = '0' + day
|
||
}
|
||
if (hour < 10) {
|
||
hour = '0' + hour
|
||
}
|
||
if (minute < 10) {
|
||
minute = '0' + minute
|
||
}
|
||
if (second < 10) {
|
||
second = '0' + second
|
||
}
|
||
return {
|
||
d: day,
|
||
h: hour,
|
||
i: minute,
|
||
s: second
|
||
};
|
||
},
|
||
/**
|
||
* 数值去重
|
||
* @param {Array} arr 数组
|
||
* @param {string} field 字段
|
||
*/
|
||
unique(arr, field) {
|
||
const res = new Map();
|
||
return arr.filter((a) => !res.has(a[field]) && res.set(a[field], 1));
|
||
},
|
||
/**
|
||
* 判断值是否在数组中
|
||
* @param {Object} elem
|
||
* @param {Object} arr
|
||
*/
|
||
inArray: function (elem, arr) {
|
||
return arr == null ? -1 : arr.indexOf(elem);
|
||
},
|
||
/**
|
||
* 获取某天日期
|
||
* @param {Object} day
|
||
*/
|
||
getDay: function (day) {
|
||
var today = new Date();
|
||
var targetday_milliseconds = today.getTime() + 1000 * 60 * 60 * 24 * day;
|
||
today.setTime(targetday_milliseconds);
|
||
|
||
const doHandleMonth = function (month) {
|
||
var m = month;
|
||
if (month.toString().length == 1) {
|
||
m = "0" + month;
|
||
}
|
||
return m
|
||
}
|
||
|
||
var tYear = today.getFullYear();
|
||
var tMonth = today.getMonth();
|
||
var tDate = today.getDate();
|
||
var tWeek = today.getDay();
|
||
var time = parseInt(today.getTime() / 1000);
|
||
tMonth = doHandleMonth(tMonth + 1);
|
||
tDate = doHandleMonth(tDate);
|
||
|
||
const week = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
|
||
return {
|
||
't': time,
|
||
'y': tYear,
|
||
'm': tMonth,
|
||
'd': tDate,
|
||
'w': week[tWeek]
|
||
};
|
||
},
|
||
/**
|
||
* 图片选择加上传
|
||
* @param num
|
||
* @param params
|
||
* @param callback
|
||
* @param url
|
||
*/
|
||
upload: function (num, params, callback, url) {
|
||
const { appType, appTypeName } = Platform.getPlatformInfo();
|
||
var data = {
|
||
token: store.state.token,
|
||
app_type: appType,
|
||
app_type_name: appTypeName
|
||
}
|
||
data = Object.assign(data, params);
|
||
|
||
var imgs_num = num;
|
||
var _self = this;
|
||
|
||
uni.chooseImage({
|
||
count: imgs_num,
|
||
sizeType: ['compressed'], //可以指定是原图还是压缩图,默认二者都有
|
||
sourceType: ['album', 'camera'], //从相册或者拍照
|
||
success: async function (res) {
|
||
const tempFilePaths = res.tempFilePaths;
|
||
var _data = data;
|
||
var imgs = [];
|
||
uni.showLoading({
|
||
title: '图片上传中'
|
||
})
|
||
for (var i = 0; i < tempFilePaths.length; i++) {
|
||
var path = await _self.upload_file_server(tempFilePaths[i], _data, params.path,
|
||
url);
|
||
imgs.push(path);
|
||
if (imgs.length == tempFilePaths.length) {
|
||
uni.hideLoading()
|
||
uni.showToast({
|
||
title: '上传成功',
|
||
icon: 'none'
|
||
})
|
||
typeof callback == 'function' && callback(imgs);
|
||
}
|
||
}
|
||
},
|
||
fail: err => {
|
||
uni.hideLoading()
|
||
uni.showToast({
|
||
title: '上传失败',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
});
|
||
},
|
||
//上传
|
||
upload_file_server(tempFilePath, data, path, url = "", callback) {
|
||
if (url) {
|
||
var uploadUrl = Config.baseUrl + url
|
||
} else {
|
||
var uploadUrl = Config.baseUrl + '/api/upload/' + path
|
||
}
|
||
return new Promise((resolve, reject) => {
|
||
uni.uploadFile({
|
||
url: uploadUrl,
|
||
filePath: tempFilePath,
|
||
name: 'file',
|
||
fileType: data.fileType || 'image',
|
||
formData: data,
|
||
success: function (res) {
|
||
var path_str = JSON.parse(res.data);
|
||
if (path_str.code >= 0) {
|
||
resolve(path_str.data.pic_path);
|
||
typeof callback == 'function' && callback(path_str.data.pic_path);
|
||
} else {
|
||
reject("error");
|
||
}
|
||
}
|
||
});
|
||
|
||
});
|
||
|
||
},
|
||
/**
|
||
* 复制
|
||
* @param {Object} value
|
||
* @param {Object} callback
|
||
*/
|
||
copy(value, callback) {
|
||
// #ifdef H5
|
||
var oInput = document.createElement('input'); //创建一个隐藏input(重要!)
|
||
oInput.value = value; //赋值
|
||
oInput.setAttribute("readonly", "readonly");
|
||
document.body.appendChild(oInput);
|
||
oInput.select(); // 选择对象
|
||
document.execCommand("Copy"); // 执行浏览器复制命令
|
||
oInput.className = 'oInput';
|
||
oInput.style.display = 'none';
|
||
uni.hideKeyboard();
|
||
this.showToast({
|
||
title: '复制成功'
|
||
});
|
||
|
||
typeof callback == 'function' && callback();
|
||
// #endif
|
||
|
||
// #ifdef MP || APP-PLUS
|
||
uni.setClipboardData({
|
||
data: value,
|
||
success: () => {
|
||
typeof callback == 'function' && callback();
|
||
}
|
||
});
|
||
// #endif
|
||
},
|
||
/**
|
||
* 是否是微信浏览器
|
||
* @deprecated 请使用 Platform.isWeiXin()
|
||
*/
|
||
isWeiXin() {
|
||
return Platform.isWeiXin();
|
||
},
|
||
/**
|
||
* 判断是否在微信小程序的web-view中打开H5
|
||
* @returns {boolean} 是否在微信小程序的web-view中
|
||
*/
|
||
isWeChatMiniProgram() {
|
||
const userAgent = navigator.userAgent.toLowerCase();
|
||
|
||
// 微信小程序的web-view User-Agent会包含以下特征之一
|
||
const miniProgramIndicators = [
|
||
'miniprogram', // 普通微信小程序
|
||
'wxwork', // 企业微信小程序
|
||
'micromessenger/[0-9]+\.[0-9]+\.[0-9]+ nettype/wifi wxwebviewtype/1'
|
||
];
|
||
|
||
return miniProgramIndicators.some(indicator => {
|
||
if (typeof indicator === 'string' && indicator.includes('/')) {
|
||
// 正则表达式匹配
|
||
const regex = new RegExp(indicator, 'i');
|
||
return regex.test(userAgent);
|
||
}
|
||
return userAgent.includes(indicator);
|
||
});
|
||
},
|
||
/**
|
||
* 判断是否在微信浏览器中打开H5(非小程序的web-view)
|
||
* @returns {boolean} 是否在微信浏览器中打开的H5
|
||
*/
|
||
isWeChatBrowser() {
|
||
return isWeiXin() && !isWeChatMiniProgram();
|
||
},
|
||
/**
|
||
* 显示消息提示框
|
||
* @param {Object} params 参数
|
||
*/
|
||
showToast(params = {}) {
|
||
params.title = params.title || "";
|
||
params.icon = params.icon || "none";
|
||
// params.position = params.position || 'bottom';
|
||
params.duration = params.duration || 1500;
|
||
uni.showToast(params);
|
||
if (params.success) params.success();
|
||
},
|
||
/**
|
||
* 检测苹果X以上的手机
|
||
*/
|
||
isIPhoneX() {
|
||
let res = this.getDeviceInfo();
|
||
if (res.model.search('iPhone X') != -1) {
|
||
return true;
|
||
}
|
||
return false;
|
||
},
|
||
//判断安卓还是iOS
|
||
isAndroid() {
|
||
let platform = this.getDeviceInfo().platform;
|
||
if (platform == 'ios') {
|
||
return false;
|
||
} else if (platform == 'android') {
|
||
return true;
|
||
}
|
||
},
|
||
/**
|
||
* 获取设备信息(包含降级处理)
|
||
*/
|
||
getDeviceInfo() {
|
||
try {
|
||
return uni.getDeviceInfo();
|
||
} catch (e) {
|
||
return uni.getSystemInfoSync();
|
||
}
|
||
},
|
||
/**
|
||
* 获取窗口信息(包含降级处理)
|
||
*/
|
||
getWindowInfo() {
|
||
try {
|
||
return uni.getWindowInfo();
|
||
} catch (e) {
|
||
return uni.getSystemInfoSync();
|
||
}
|
||
},
|
||
/**
|
||
* 深度拷贝对象
|
||
* @param {Object} obj
|
||
*/
|
||
deepClone(obj) {
|
||
const isObject = function (obj) {
|
||
return typeof obj == 'object';
|
||
}
|
||
|
||
if (!isObject(obj)) {
|
||
throw new Error('obj 不是一个对象!')
|
||
}
|
||
//判断传进来的是对象还是数组
|
||
let isArray = Array.isArray(obj)
|
||
let cloneObj = isArray ? [] : {}
|
||
//通过for...in来拷贝
|
||
for (let key in obj) {
|
||
cloneObj[key] = isObject(obj[key]) ? this.deepClone(obj[key]) : obj[key]
|
||
}
|
||
return cloneObj
|
||
},
|
||
|
||
|
||
|
||
/**
|
||
* 打开微信企业客服
|
||
* @param {Function} fallbackFunc 降级处理函数
|
||
*/
|
||
openWxWorkServiceChat(fallbackFunc) {
|
||
// #ifdef MP-WEIXIN
|
||
let wxworkConfig = store.state?.wxworkConfig
|
||
if (wxworkConfig?.enable && wxworkConfig?.contact_url) {
|
||
// 直接使用活码链接跳转
|
||
wx.navigateToMiniProgram({
|
||
appId: 'wxeb490c6f9b154ef9', // 是企业微信官方小程序的AppID(固定值, 由腾讯企业微信团队维护, 不需要修改,用于展示"添加企业微信联系人"的官方页面)
|
||
path: `pages/contacts/externalContactDetail?url=${encodeURIComponent(wxworkConfig?.contact_url)}`,
|
||
success: () => {
|
||
},
|
||
fail: (err) => {
|
||
}
|
||
});
|
||
} else {
|
||
fallbackFunc && fallbackFunc();
|
||
}
|
||
// #endif
|
||
},
|
||
|
||
/**
|
||
* 自定义模板的跳转链接
|
||
* @param {Object} link
|
||
*/
|
||
diyRedirectTo(link) {
|
||
//if (link == null || Object.keys(link).length == 1) return;
|
||
if (link.wap_url) {
|
||
link.wap_url = decodeEntity(link.wap_url);
|
||
}
|
||
|
||
// 外部链接
|
||
if (link.wap_url && link.wap_url.indexOf('http') != -1) {
|
||
// 公共参数
|
||
const params = {
|
||
title: link?.title ?? '',
|
||
enable_set_title: link?.enable_set_title ? 1 : 0
|
||
}
|
||
let url = this.normalizeUrl(decodeEntity(link.wap_url));
|
||
url = this.appendQueryParams(url, params);
|
||
|
||
// #ifdef H5
|
||
// console.log(`url ---->`, url, link)
|
||
window.location.href = url;
|
||
// #endif
|
||
|
||
// #ifdef MP
|
||
this.redirectTo('/pages_tool/webview/webview', {
|
||
src: encodeURIComponent(url)
|
||
});
|
||
// #endif
|
||
|
||
} else if (link.appid) {
|
||
// 跳转其他小程序
|
||
uni.navigateToMiniProgram({
|
||
appId: link.appid,
|
||
path: link.page
|
||
})
|
||
|
||
} else if (link.name == 'MOBILE' && !link.wap_url) {
|
||
// 拨打电话
|
||
uni.makePhoneCall({
|
||
phoneNumber: link.mobile,
|
||
success: (res) => {
|
||
},
|
||
fail: (res) => {
|
||
}
|
||
});
|
||
|
||
} else if (link.name == 'MEMBER_CONTACT') {
|
||
// 客服
|
||
let servicerConfig = store.state.servicerConfig
|
||
let config = {
|
||
type: 'none'
|
||
};
|
||
|
||
// #ifdef H5
|
||
config = servicerConfig.h5;
|
||
// #endif
|
||
|
||
// #ifdef MP-WEIXIN
|
||
config = servicerConfig.weapp;
|
||
// #endif
|
||
|
||
switch (config.type) {
|
||
case 'wxwork':
|
||
// 企业微信客服
|
||
|
||
// #ifdef H5
|
||
window.location.href = config.wxwork_url;
|
||
// #endif
|
||
|
||
// #ifdef MP-WEIXIN
|
||
this.openWxWorkServiceChat(() => {
|
||
wx.openCustomerServiceChat({
|
||
extInfo: {
|
||
url: config.wxwork_url
|
||
},
|
||
corpId: config.corpid,
|
||
showMessageCard: true,
|
||
sendMessageTitle: 'this.sendMessageTitle',
|
||
sendMessagePath: 'this.sendMessagePath',
|
||
sendMessageImg: 'this.sendMessageImg'
|
||
});
|
||
});
|
||
|
||
// #endif
|
||
break;
|
||
case 'third':
|
||
// 第三方客服
|
||
window.location.href = config.third_url;
|
||
break;
|
||
case 'niushop':
|
||
// Niushop客服
|
||
this.redirectTo('/pages_tool/chat/room');
|
||
break;
|
||
case 'weapp':
|
||
// 微信小程序,由于需要手动点击按钮触发,所以要跳转到中间页
|
||
this.redirectTo(link.wap_url);
|
||
break;
|
||
default:
|
||
// 拨打客服电话
|
||
let siteInfo = store.state.siteInfo;
|
||
if (siteInfo && siteInfo.site_tel) {
|
||
uni.makePhoneCall({
|
||
phoneNumber: siteInfo.site_tel
|
||
});
|
||
} else {
|
||
this.showToast({
|
||
title: '抱歉,商家暂无客服,请线下联系',
|
||
})
|
||
}
|
||
}
|
||
|
||
} else if (link.wap_url) {
|
||
this.redirectTo(link.wap_url);
|
||
}
|
||
},
|
||
/**
|
||
* 获取默认图
|
||
*/
|
||
getDefaultImage() {
|
||
let defaultImg = store.state.defaultImg;
|
||
defaultImg.goods = this.img(defaultImg.goods);
|
||
defaultImg.head = this.img(defaultImg.head);
|
||
defaultImg.store = this.img(defaultImg.store);
|
||
defaultImg.article = this.img(defaultImg.article);
|
||
defaultImg.kefu = this.img(defaultImg.kefu);
|
||
defaultImg.phone = this.img(defaultImg.phone);
|
||
return defaultImg;
|
||
},
|
||
/**
|
||
* 判断手机是否为iphoneX系列
|
||
*/
|
||
uniappIsIPhoneX() {
|
||
let isIphoneX = false;
|
||
let systemInfo = this.getDeviceInfo();
|
||
// #ifdef MP
|
||
if (systemInfo.model.search('iPhone X') != -1 || systemInfo.model.search('iPhone 11') != -1 || systemInfo.model.search('iPhone 12') != -1 || systemInfo.model.search('iPhone 13') != -1) {
|
||
isIphoneX = true;
|
||
}
|
||
// #endif
|
||
|
||
// #ifdef H5
|
||
var u = navigator.userAgent;
|
||
var isIOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); //ios终端
|
||
if (isIOS) {
|
||
if (systemInfo.screenWidth == 375 && systemInfo.screenHeight == 812 && systemInfo.pixelRatio == 3) {
|
||
isIphoneX = true;
|
||
} else if (systemInfo.screenWidth == 414 && systemInfo.screenHeight == 896 && systemInfo.pixelRatio == 3) {
|
||
isIphoneX = true;
|
||
} else if (systemInfo.screenWidth == 414 && systemInfo.screenHeight == 896 && systemInfo.pixelRatio == 2) {
|
||
isIphoneX = true;
|
||
}
|
||
}
|
||
// #endif
|
||
return isIphoneX;
|
||
},
|
||
/**
|
||
* 判断手机是否为iphone11系列
|
||
*/
|
||
uniappIsIPhone11() {
|
||
let isIphone11 = false;
|
||
let systemInfo = this.getDeviceInfo();
|
||
// #ifdef MP
|
||
if (systemInfo.model.search('iPhone 11') != -1) {
|
||
isIphone11 = true;
|
||
}
|
||
// #endif
|
||
return isIphone11;
|
||
},
|
||
// #ifdef H5
|
||
//判断该浏览器是否为safaria浏览器
|
||
isSafari() {
|
||
let res = this.getDeviceInfo();
|
||
var ua = navigator.userAgent.toLowerCase();
|
||
if (ua.indexOf('applewebkit') > -1 && ua.indexOf('mobile') > -1 && ua.indexOf('safari') > -1 &&
|
||
ua.indexOf('linux') === -1 && ua.indexOf('android') === -1 && ua.indexOf('chrome') === -1 &&
|
||
ua.indexOf('ios') === -1 && ua.indexOf('browser') === -1) {
|
||
return true;
|
||
} else {
|
||
return false;
|
||
}
|
||
},
|
||
// #endif
|
||
numberFixed(e, f) {
|
||
if (!f) {
|
||
f = 0;
|
||
}
|
||
return Number(e).toFixed(f);
|
||
},
|
||
/**
|
||
* 获取url参数
|
||
*/
|
||
getUrlCode(callback) {
|
||
var url = location.search;
|
||
var theRequest = new Object();
|
||
if (url.indexOf('?') != -1) {
|
||
var str = url.substr(1);
|
||
var strs = str.split('&');
|
||
for (var i = 0; i < strs.length; i++) {
|
||
theRequest[strs[i].split('=')[0]] = strs[i].split('=')[1];
|
||
}
|
||
}
|
||
typeof callback == 'function' && callback(theRequest);
|
||
},
|
||
/**
|
||
* 获取当前页面路由
|
||
*/
|
||
getCurrRoute() {
|
||
let routes = getCurrentPages(); // 获取当前打开过的页面路由数组
|
||
return routes.length ? routes[routes.length - 1].route : '';
|
||
},
|
||
goBack(backUrl = INDEX_PAGE_URL) {
|
||
if (getCurrentPages().length == 1) {
|
||
this.redirectTo(backUrl);
|
||
} else {
|
||
uni.navigateBack();
|
||
}
|
||
},
|
||
/**
|
||
* 隐藏“返回首页/小房子”按钮
|
||
* 这个函数,用到页面show, onshow 的生命周期时
|
||
*/
|
||
hideHomeButton() {
|
||
// #ifdef MP-WEIXIN
|
||
wx.hideHomeButton();
|
||
// #endif
|
||
},
|
||
/**
|
||
*
|
||
* @param val 转化时间字符串 (转化时分秒)
|
||
* @returns {string}
|
||
*/
|
||
getTimeStr(val) {
|
||
var h = parseInt(val / 3600).toString();
|
||
var m = parseInt((val % 3600) / 60).toString();
|
||
if (m.length == 1) {
|
||
m = '0' + m;
|
||
}
|
||
if (h.length == 1) {
|
||
h = '0' + h;
|
||
}
|
||
return h + ':' + m;
|
||
},
|
||
/**
|
||
* 获取定位信息
|
||
*/
|
||
getLocation(param = {}) {
|
||
/*uni.getLocation({
|
||
type: param.type ?? 'gcj02',
|
||
success: res => {
|
||
store.commit('setLocation', res);
|
||
typeof param.success == 'function' && param.success(res);
|
||
},
|
||
fail: res => {
|
||
typeof param.fail == 'function' && param.fail(res);
|
||
},
|
||
complete: res => {
|
||
typeof param.complete == 'function' && param.complete(res);
|
||
}
|
||
});*/
|
||
},
|
||
// 计算两个经纬度之间的距离
|
||
getDistance(lat1, lng1, lat2, lng2) {
|
||
var radLat1 = lat1 * Math.PI / 180.0;
|
||
var radLat2 = lat2 * Math.PI / 180.0;
|
||
var a = radLat1 - radLat2;
|
||
var b = lng1 * Math.PI / 180.0 - lng2 * Math.PI / 180.0;
|
||
var s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2) +
|
||
Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)));
|
||
s = s * 6378.137; // EARTH_RADIUS;
|
||
s = Math.round(s * 10000) / 10000;
|
||
return s;
|
||
},
|
||
//记录分享人
|
||
onSourceMember(source_member) {
|
||
Http.sendRequest({
|
||
url: '/api/Member/alterShareRelation',
|
||
data: {
|
||
share_member: source_member,
|
||
},
|
||
success: res => {
|
||
if (res.code >= 0) {
|
||
uni.removeStorage({
|
||
key: 'source_member',
|
||
success: res => {
|
||
}
|
||
})
|
||
}
|
||
}
|
||
})
|
||
},
|
||
/**
|
||
* 微信订阅消息
|
||
*/
|
||
subscribeMessage(keywords) {
|
||
let url = ""
|
||
// #ifdef MP-WEIXIN
|
||
url = '/weapp/api/weapp/messagetmplids';
|
||
// #endif
|
||
// #ifdef MP-ALIPAY
|
||
url = '/aliapp/api/aliapp/messagetmplids';
|
||
// #endif
|
||
Http.sendRequest({
|
||
url,
|
||
data: {
|
||
keywords: keywords
|
||
},
|
||
success: res => {
|
||
if (res.data.length) {
|
||
// #ifdef MP-WEIXIN
|
||
uni.requestSubscribeMessage({
|
||
tmplIds: res.data,
|
||
success: (res) => {
|
||
},
|
||
fail: (res) => {
|
||
}
|
||
})
|
||
// #endif
|
||
|
||
// #ifdef MP-ALIPAY
|
||
my.requestSubscribeMessage({
|
||
entityIds: res.data,
|
||
success: res => {
|
||
},
|
||
fail: res => {
|
||
}
|
||
});
|
||
// #endif
|
||
}
|
||
}
|
||
})
|
||
},
|
||
/**
|
||
* 获取小程序分享内容数据
|
||
*/
|
||
getMpShare(path) {
|
||
//如果没有特别指定 则获取当前页面的路由
|
||
if (!path) {
|
||
let route = this.getCurrentRoute();
|
||
path = route.path;
|
||
if (path == '/pages_tool/member/index') {
|
||
return new Promise((resolve, reject) => {
|
||
resolve({})
|
||
});
|
||
}
|
||
}
|
||
return new Promise((resolve, reject) => {
|
||
Http.sendRequest({
|
||
url: "/weapp/api/weapp/share",
|
||
data: {
|
||
path: path
|
||
},
|
||
success: res => {
|
||
if (res.code >= 0) {
|
||
let shareConfig = res.data.data;
|
||
if (shareConfig) {
|
||
//分享给好友
|
||
let appMessageData = {
|
||
title: shareConfig.title,
|
||
path: shareConfig.path,
|
||
imageUrl: shareConfig.imageUrl,
|
||
success: res => {
|
||
},
|
||
fail: res => {
|
||
}
|
||
}
|
||
//分享到朋友圈
|
||
let query = '';
|
||
if (shareConfig.path.indexOf('?') > 0) {
|
||
query = shareConfig.path.split('?')[1];
|
||
}
|
||
let timeLineData = {
|
||
title: shareConfig.title,
|
||
query: shareConfig.path,
|
||
imageUrl: shareConfig.imageUrl,
|
||
}
|
||
resolve({
|
||
appMessage: appMessageData,
|
||
timeLine: timeLineData,
|
||
})
|
||
} else {
|
||
reject(res.data);
|
||
}
|
||
} else {
|
||
reject(res.data)
|
||
}
|
||
}
|
||
});
|
||
})
|
||
},
|
||
/**
|
||
* 设置公众号分享
|
||
* @param shareData
|
||
* @param callback
|
||
*/
|
||
setPublicShare(shareData, callback) {
|
||
if (!this.isWeiXin()) return;
|
||
Http.sendRequest({
|
||
url: '/wechat/api/wechat/jssdkconfig',
|
||
data: {
|
||
url: uni.getSystemInfoSync().platform == 'ios' ? uni.getStorageSync('initUrl') : window.location
|
||
.href
|
||
},
|
||
success: res => {
|
||
if (res.code == 0) {
|
||
var wxJS = new Weixin();
|
||
wxJS.init(res.data);
|
||
wxJS.weixin.showOptionMenu();
|
||
|
||
wxJS.setShareData({
|
||
title: shareData.title ?? '',
|
||
desc: shareData.desc ?? '',
|
||
link: shareData.link ?? location.href,
|
||
imgUrl: shareData.imgUrl ? this.img(shareData.imgUrl) : ''
|
||
}, (res) => {
|
||
typeof callback == 'function' && callback(res);
|
||
})
|
||
}
|
||
}
|
||
})
|
||
},
|
||
//获取当前路由
|
||
getCurrentRoute() {
|
||
let currentRoutes = getCurrentPages(); // 获取当前打开过的页面路由数组
|
||
let currentRoute = currentRoutes[currentRoutes.length - 1].route //获取当前页面路由
|
||
let currentParam = currentRoutes[currentRoutes.length - 1].options; //获取路由参数
|
||
// 拼接参数
|
||
let param = [];
|
||
for (let key in currentParam) {
|
||
param.push(key + '=' + currentParam[key])
|
||
}
|
||
let currentPath = '/' + currentRoute;
|
||
let currentQuery = param.join('&');
|
||
if (currentQuery) currentPath += '?' + currentQuery;
|
||
|
||
return {
|
||
path: currentPath,
|
||
query: currentQuery,
|
||
}
|
||
},
|
||
//获取分享路由
|
||
getCurrentShareRoute(member_id) {
|
||
let route = this.getCurrentRoute();
|
||
//去掉原来的分享人数据
|
||
route.path = route.path.replace(/[?|&]source_member=\d+/, '');
|
||
if (member_id) {
|
||
//路径的处理
|
||
if (route.path.indexOf('?') > 0) {
|
||
route.path += '&';
|
||
} else {
|
||
route.path += '?';
|
||
}
|
||
route.path += 'source_member=' + member_id;
|
||
//参数的处理
|
||
if (route.query) {
|
||
route.query += '&';
|
||
}
|
||
route.query += 'source_member=' + member_id;
|
||
}
|
||
return route;
|
||
},
|
||
/**
|
||
* 对象转style字符串
|
||
* @param {Object} obj
|
||
*/
|
||
objToStyle(obj) {
|
||
let s = [];
|
||
for (let i in obj) {
|
||
s.push(i + ':' + obj[i]);
|
||
}
|
||
return s.join(';')
|
||
},
|
||
/**
|
||
* 颜色减值
|
||
* @param {Object} c1
|
||
* @param {Object} c2
|
||
* @param {Object} ratio
|
||
*/
|
||
colourBlend(c1, c2, ratio) {
|
||
ratio = Math.max(Math.min(Number(ratio), 1), 0)
|
||
let r1 = parseInt(c1.substring(1, 3), 16)
|
||
let g1 = parseInt(c1.substring(3, 5), 16)
|
||
let b1 = parseInt(c1.substring(5, 7), 16)
|
||
let r2 = parseInt(c2.substring(1, 3), 16)
|
||
let g2 = parseInt(c2.substring(3, 5), 16)
|
||
let b2 = parseInt(c2.substring(5, 7), 16)
|
||
let r = Math.round(r1 * (1 - ratio) + r2 * ratio)
|
||
let g = Math.round(g1 * (1 - ratio) + g2 * ratio)
|
||
let b = Math.round(b1 * (1 - ratio) + b2 * ratio)
|
||
r = ('0' + (r || 0).toString(16)).slice(-2)
|
||
g = ('0' + (g || 0).toString(16)).slice(-2)
|
||
b = ('0' + (b || 0).toString(16)).slice(-2)
|
||
return '#' + r + g + b
|
||
},
|
||
/**
|
||
* 生成贝塞尔曲线轨迹
|
||
* @param {Object} points
|
||
* @param {Object} times
|
||
*/
|
||
bezier(points, times) {
|
||
var bezier_points = [];
|
||
var points_D = [];
|
||
var points_E = [];
|
||
const DIST_AB = Math.sqrt(Math.pow(points[1]['x'] - points[0]['x'], 2) + Math.pow(points[1]['y'] - points[0][
|
||
'y'
|
||
], 2));
|
||
// 邻控制BC点间距
|
||
const DIST_BC = Math.sqrt(Math.pow(points[2]['x'] - points[1]['x'], 2) + Math.pow(points[2]['y'] - points[1][
|
||
'y'
|
||
], 2));
|
||
// D每次在AB方向上移动的距离
|
||
if (points[0]['x'] > points[2]['x']) {
|
||
var EACH_MOVE_AD = -(DIST_AB / times);
|
||
// E每次在BC方向上移动的距离
|
||
var EACH_MOVE_BE = -(DIST_BC / times);
|
||
} else {
|
||
var EACH_MOVE_AD = +(DIST_AB / times);
|
||
// E每次在BC方向上移动的距离
|
||
var EACH_MOVE_BE = +(DIST_BC / times);
|
||
}
|
||
// 点AB的正切
|
||
const TAN_AB = (points[1]['y'] - points[0]['y']) / (points[1]['x'] - points[0]['x']);
|
||
// 点BC的正切
|
||
const TAN_BC = (points[2]['y'] - points[1]['y']) / (points[2]['x'] - points[1]['x']);
|
||
// 点AB的弧度值
|
||
const RADIUS_AB = Math.atan(TAN_AB);
|
||
// 点BC的弧度值
|
||
const RADIUS_BC = Math.atan(TAN_BC);
|
||
// 每次执行
|
||
for (var i = 1; i <= times; i++) {
|
||
// AD的距离
|
||
var dist_AD = EACH_MOVE_AD * i;
|
||
// BE的距离
|
||
var dist_BE = EACH_MOVE_BE * i;
|
||
// D点的坐标
|
||
var point_D = {};
|
||
point_D['x'] = dist_AD * Math.cos(RADIUS_AB) + points[0]['x'];
|
||
point_D['y'] = dist_AD * Math.sin(RADIUS_AB) + points[0]['y'];
|
||
points_D.push(point_D);
|
||
// E点的坐标
|
||
var point_E = {};
|
||
point_E['x'] = dist_BE * Math.cos(RADIUS_BC) + points[1]['x'];
|
||
point_E['y'] = dist_BE * Math.sin(RADIUS_BC) + points[1]['y'];
|
||
points_E.push(point_E);
|
||
// 此时线段DE的正切值
|
||
var tan_DE = (point_E['y'] - point_D['y']) / (point_E['x'] - point_D['x']);
|
||
// tan_DE的弧度值
|
||
var radius_DE = Math.atan(tan_DE);
|
||
// 地市DE的间距
|
||
var dist_DE = Math.sqrt(Math.pow((point_E['x'] - point_D['x']), 2) + Math.pow((point_E['y'] - point_D['y']),
|
||
2));
|
||
// 此时DF的距离
|
||
var dist_DF = (dist_AD / DIST_AB) * dist_DE;
|
||
// 此时DF点的坐标
|
||
var point_F = {};
|
||
point_F['x'] = dist_DF * Math.cos(radius_DE) + point_D['x'];
|
||
point_F['y'] = dist_DF * Math.sin(radius_DE) + point_D['y'];
|
||
bezier_points.push(point_F);
|
||
}
|
||
return {
|
||
'bezier_points': bezier_points
|
||
};
|
||
},
|
||
/**
|
||
* 验证手机号
|
||
* @param {string} mobile 被验证的mobile
|
||
* @return {object} 验证后的结果
|
||
**/
|
||
verifyMobile(mobile) {
|
||
var parse = /^\d{11}$/.test(mobile);
|
||
return parse;
|
||
},
|
||
/**
|
||
* 判断是否为快应用环境
|
||
* @returns {boolean} 是否为快应用
|
||
*/
|
||
isQuickApp() {
|
||
// #ifndef QUICKAPP-WEBVIEW
|
||
let _isQuickApp = false;
|
||
try {
|
||
const ua = navigator?.userAgent?.toLowerCase();
|
||
_isQuickApp = ua.indexOf('quickapp') !== -1;
|
||
|
||
if (!_isQuickApp) {
|
||
// 特殊说明由于网站是嵌入到快应用的Webview组件中<web>,UA中不会包含quickapp字符串,
|
||
// 但是会包含uniacid参数,所以这里通过uniacid参数来判断是否为快应用环境
|
||
_isQuickApp = ua.indexOf('uniacid=') !== -1;
|
||
}
|
||
} catch (e) { }
|
||
|
||
if (!_isQuickApp) {
|
||
const systemInfo = this.getDeviceInfo();
|
||
_isQuickApp = systemInfo?.platform === 'quickapp' || process?.env?.UNI_PLATFORM === 'quickapp';
|
||
}
|
||
return _isQuickApp;
|
||
// #endif
|
||
|
||
return true;
|
||
},
|
||
/**
|
||
* 判断是否为华为快应用环境
|
||
* @returns {boolean} 是否为华为快应用
|
||
*/
|
||
isHuaweiQuickApp() {
|
||
if (!this.isQuickApp()) return false;
|
||
|
||
// #ifndef QUICKAPP-WEBVIEW-HUAWEI
|
||
const systemInfo = this.getDeviceInfo();
|
||
return systemInfo.brand === 'HUAWEI' || systemInfo.manufacturer === 'HUAWEI' ||
|
||
(typeof qh !== 'undefined' && qh.platform === 'huawei');
|
||
// #endif
|
||
return true;
|
||
},
|
||
|
||
/**
|
||
* 蛇形命名转驼峰命名
|
||
* @param {string} str 蛇形命名字符串
|
||
* @returns {string} 驼峰命名字符串
|
||
*/
|
||
snakeToCamel(str) {
|
||
return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
||
},
|
||
|
||
/**
|
||
* 蛇形命名转驼峰命名(递归处理对象中的所有属性名)
|
||
* @param {Object} obj 包含蛇形命名字符串属性的对象
|
||
* @returns {Object} 驼峰命名字符串属性的对象
|
||
*/
|
||
snakeToCamelForObj(obj) {
|
||
if (typeof obj !== 'object' || obj === null) return obj;
|
||
if (Array.isArray(obj)) return obj.map(this.snakeToCamelForObj.bind(this));
|
||
const newObj = {};
|
||
for (const key in obj) {
|
||
if (Object.hasOwnProperty.call(obj, key)) {
|
||
const newKey = this.snakeToCamel(key);
|
||
newObj[newKey] = this.snakeToCamelForObj(obj[key]);
|
||
}
|
||
}
|
||
return newObj;
|
||
},
|
||
|
||
/**
|
||
* 适用于 UniApp 的全平台 URL 标准化函数
|
||
* @param {string} urlString - 需要标准化的原始 URL 字符串
|
||
* @returns {string} 标准化后的 URL
|
||
*/
|
||
normalizeUrl(urlString) {
|
||
if (!urlString || typeof urlString !== 'string') return '';
|
||
|
||
let url = urlString.trim();
|
||
if (!/^https?:\/\//i.test(url)) {
|
||
url = 'https://' + url;
|
||
}
|
||
|
||
// ==================== H5 端处理 (利用原生 URL API) ====================
|
||
// #ifdef H5
|
||
try {
|
||
const urlObj = new URL(url);
|
||
|
||
urlObj.protocol = urlObj.protocol.toLowerCase();
|
||
urlObj.hostname = urlObj.hostname.toLowerCase();
|
||
|
||
if ((urlObj.protocol === 'http:' && urlObj.port === '80') ||
|
||
(urlObj.protocol === 'https:' && urlObj.port === '443')) {
|
||
urlObj.port = '';
|
||
}
|
||
|
||
const tempUrl = new URL(urlObj.pathname, urlObj.origin);
|
||
urlObj.pathname = tempUrl.pathname;
|
||
|
||
if (urlObj.search) {
|
||
const searchParams = new URLSearchParams(urlObj.search);
|
||
const sortedParams = new URLSearchParams();
|
||
Array.from(searchParams.keys()).sort().forEach(key => {
|
||
sortedParams.append(key, searchParams.get(key));
|
||
});
|
||
urlObj.search = sortedParams.toString();
|
||
}
|
||
|
||
if (urlObj.hash === '#') urlObj.hash = '';
|
||
|
||
return urlObj.toString();
|
||
} catch (e) {
|
||
console.error('H5端 URL 解析失败:', e);
|
||
return url;
|
||
}
|
||
// #endif
|
||
|
||
// ==================== 非 H5 端处理 (小程序 / App) ====================
|
||
// #ifndef H5
|
||
try {
|
||
const protocolMatch = url.match(/^(https?):\/\//i);
|
||
const protocol = protocolMatch ? protocolMatch[1].toLowerCase() + '://' : 'https://';
|
||
const withoutProtocol = url.replace(/^https?:\/\//i, '');
|
||
|
||
const hashIndex = withoutProtocol.indexOf('#');
|
||
let hash = '';
|
||
let mainPart = withoutProtocol;
|
||
if (hashIndex !== -1) {
|
||
hash = withoutProtocol.substring(hashIndex);
|
||
mainPart = withoutProtocol.substring(0, hashIndex);
|
||
}
|
||
|
||
const pathQuerySplit = mainPart.split('/');
|
||
const hostPort = pathQuerySplit.shift();
|
||
let pathname = '/' + pathQuerySplit.join('/');
|
||
|
||
const [hostname, port] = hostPort.split(':');
|
||
const lowerHostname = hostname.toLowerCase();
|
||
|
||
let finalPort = port;
|
||
if ((protocol === 'http://' && port === '80') || (protocol === 'https://' && port === '443')) {
|
||
finalPort = '';
|
||
}
|
||
|
||
let pathParts = pathname.split('/').filter(p => p !== '' && p !== '.');
|
||
const resolvedPath = [];
|
||
for (const part of pathParts) {
|
||
if (part === '..') {
|
||
resolvedPath.pop();
|
||
} else {
|
||
resolvedPath.push(part);
|
||
}
|
||
}
|
||
pathname = '/' + resolvedPath.join('/');
|
||
|
||
const queryIndex = pathname.indexOf('?');
|
||
let query = '';
|
||
if (queryIndex !== -1) {
|
||
const queryString = pathname.substring(queryIndex + 1);
|
||
pathname = pathname.substring(0, queryIndex);
|
||
|
||
const params = queryString.split('&').map(pair => {
|
||
const [k, v = ''] = pair.split('=');
|
||
return { k, v };
|
||
});
|
||
params.sort((a, b) => a.k.localeCompare(b.k));
|
||
query = '?' + params.map(p => `${p.k}=${p.v}`).join('&');
|
||
}
|
||
|
||
const portStr = finalPort ? `:${finalPort}` : '';
|
||
return `${protocol}//${lowerHostname}${portStr}${pathname}${query}${hash}`;
|
||
} catch (e) {
|
||
console.error('非H5端 URL 解析失败:', e);
|
||
return url;
|
||
}
|
||
// #endif
|
||
},
|
||
|
||
/**
|
||
* UniApp 全平台通用的 URL 追加查询参数函数(自动处理中文编码)
|
||
* @param {string} url - 原始 URL 字符串
|
||
* @param {object} params - 需要追加的参数对象,例如 { name: '张三', city: '苏州' }
|
||
* @returns {string} 追加参数后的完整 URL
|
||
*/
|
||
appendQueryParams(url, params) {
|
||
if (!url || typeof url !== 'string') return '';
|
||
|
||
// ==================== H5 端处理 (利用原生 URL API) ====================
|
||
// #ifdef H5
|
||
try {
|
||
let targetUrl = url.trim();
|
||
if (!/^https?:\/\//i.test(targetUrl)) {
|
||
targetUrl = 'https://' + targetUrl;
|
||
}
|
||
const urlObj = new URL(targetUrl);
|
||
for (const [key, value] of Object.entries(params)) {
|
||
if (value !== undefined && value !== null) {
|
||
urlObj.searchParams.append(key, String(value));
|
||
}
|
||
}
|
||
return urlObj.toString();
|
||
} catch (e) {
|
||
console.error('H5端 URL 参数追加失败:', e);
|
||
return url;
|
||
}
|
||
// #endif
|
||
|
||
// ==================== 非 H5 端处理 (小程序 / App) ====================
|
||
// #ifndef H5
|
||
try {
|
||
let targetUrl = url.trim();
|
||
// 分离 hash (#部分),避免干扰参数拼接
|
||
const hashIndex = targetUrl.indexOf('#');
|
||
let hash = '';
|
||
if (hashIndex !== -1) {
|
||
hash = targetUrl.substring(hashIndex);
|
||
targetUrl = targetUrl.substring(0, hashIndex);
|
||
}
|
||
|
||
// 判断当前 URL 是否已经包含查询参数,决定使用 ? 还是 &
|
||
const separator = targetUrl.includes('?') ? '&' : '?';
|
||
|
||
// 遍历参数对象,手动进行 encodeURIComponent 编码并拼接
|
||
const queryString = Object.entries(params)
|
||
.filter(([_, value]) => value !== undefined && value !== null)
|
||
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`)
|
||
.join('&');
|
||
|
||
// 重新拼接成完整的 URL
|
||
return `${targetUrl}${separator}${queryString}${hash}`;
|
||
} catch (e) {
|
||
console.error('非H5端 URL 参数追加失败:', e);
|
||
return url;
|
||
}
|
||
// #endif
|
||
}
|
||
} |