You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
599 lines
16 KiB
599 lines
16 KiB
import {img_domian,Api_url} from "./config";
|
|
import marketApi from '@/api/apiRoute.js';
|
|
|
|
function formatTime(time) {
|
|
if (typeof time !== 'number' || time < 0) {
|
|
return time
|
|
}
|
|
|
|
var hour = parseInt(time / 3600)
|
|
time = time % 3600
|
|
var minute = parseInt(time / 60)
|
|
time = time % 60
|
|
var second = time
|
|
|
|
return ([hour, minute, second]).map(function(n) {
|
|
n = n.toString()
|
|
return n[1] ? n : '0' + n
|
|
}).join(':')
|
|
}
|
|
|
|
function formatDateTime(date, fmt = 'yyyy-MM-dd hh:mm:ss') {
|
|
if(!date) {
|
|
return ''
|
|
}
|
|
if (typeof (date) === 'number') {
|
|
date = new Date(date * 1000)
|
|
}
|
|
var o = {
|
|
"M+": date.getMonth() + 1, //月份
|
|
"d+": date.getDate(), //日
|
|
"h+": date.getHours(), //小时
|
|
"m+": date.getMinutes(), //分
|
|
"s+": date.getSeconds(), //秒
|
|
"q+": Math.floor((date.getMonth() + 3) / 3), //季度
|
|
"S": date.getMilliseconds() //毫秒
|
|
}
|
|
if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length))
|
|
for (var k in o)
|
|
if (new RegExp("(" + k + ")").test(fmt))
|
|
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)))
|
|
return fmt
|
|
}
|
|
|
|
function formatLocation(longitude, latitude) {
|
|
if (typeof longitude === 'string' && typeof latitude === 'string') {
|
|
longitude = parseFloat(longitude)
|
|
latitude = parseFloat(latitude)
|
|
}
|
|
|
|
longitude = longitude.toFixed(2)
|
|
latitude = latitude.toFixed(2)
|
|
|
|
return {
|
|
longitude: longitude.toString().split('.'),
|
|
latitude: latitude.toString().split('.')
|
|
}
|
|
}
|
|
|
|
var dateUtils = {
|
|
UNITS: {
|
|
'年': 31557600000,
|
|
'月': 2629800000,
|
|
'天': 86400000,
|
|
'小时': 3600000,
|
|
'分钟': 60000,
|
|
'秒': 1000
|
|
},
|
|
humanize: function(milliseconds) {
|
|
var humanize = '';
|
|
for (var key in this.UNITS) {
|
|
if (milliseconds >= this.UNITS[key]) {
|
|
humanize = Math.floor(milliseconds / this.UNITS[key]) + key + '前';
|
|
break;
|
|
}
|
|
}
|
|
return humanize || '刚刚';
|
|
},
|
|
format: function(dateStr) {
|
|
var date = this.parse(dateStr)
|
|
var diff = Date.now() - date.getTime();
|
|
if (diff < this.UNITS['天']) {
|
|
return this.humanize(diff);
|
|
}
|
|
var _format = function(number) {
|
|
return (number < 10 ? ('0' + number) : number);
|
|
};
|
|
return date.getFullYear() + '/' + _format(date.getMonth() + 1) + '/' + _format(date.getDate()) + '-' +
|
|
_format(date.getHours()) + ':' + _format(date.getMinutes());
|
|
},
|
|
parse: function(str) { //将"yyyy-mm-dd HH:MM:ss"格式的字符串,转化为一个Date对象
|
|
var a = str.split(/[^0-9]/);
|
|
return new Date(a[0], a[1] - 1, a[2], a[3], a[4], a[5]);
|
|
}
|
|
};
|
|
|
|
const hexToRgba = (hex, opacity) => { //16进制颜色转rgba
|
|
return "rgba(" + parseInt("0x" + hex.slice(1, 3)) + "," + parseInt("0x" + hex.slice(3, 5)) + "," + parseInt("0x" + hex.slice(5, 7)) + "," + opacity + ")"
|
|
}
|
|
|
|
/**
|
|
* 图片路径转换
|
|
* @param {String} img_path 图片地址
|
|
* @param {Object} params 参数,针对商品、相册里面的图片区分大中小,size: big、mid、small
|
|
*/
|
|
function 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 != 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);
|
|
img_path = arr.join(".");
|
|
}
|
|
|
|
// 处理静态资源路径的迁移
|
|
if (img_path.startsWith('/static/')) {
|
|
// 将旧的 /static/ 路径转换为新的后端静态资源路径
|
|
img_path = img_path.replace('/static/', 'static/resource/uniapp/');
|
|
}
|
|
|
|
if (img_path.indexOf("http://") == -1 && img_path.indexOf("https://") == -1) {
|
|
path = img_domian + "/" + img_path;
|
|
} else {
|
|
path = img_path;
|
|
}
|
|
if(img_domian.indexOf('https://') != -1){
|
|
path = path.replace('http://', 'https://');
|
|
}
|
|
}
|
|
// path += '?t=' + parseInt(new Date().getTime() / 1000);
|
|
return path;
|
|
}
|
|
/**
|
|
* 获取默认图
|
|
*/
|
|
function getDefaultImage() {
|
|
let defaultImg = store.state.defaultImg;
|
|
defaultImg.goods = img(defaultImg.goods);
|
|
defaultImg.head = img(defaultImg.head);
|
|
defaultImg.store = img(defaultImg.store);
|
|
defaultImg.article = img(defaultImg.article);
|
|
return defaultImg;
|
|
}
|
|
|
|
/**
|
|
* 时间格式转换 (iOS兼容版本)
|
|
* @param dateTime 如 2024-05-01 01:10:21
|
|
* @param fmt 可选参数[Y-m-d H:i:s,Y-m-d,Y-m-d H,Y-m-d H:i,H:i:s,H:i]
|
|
* @returns {string}
|
|
*/
|
|
function formatToDateTime(dateTime, fmt = 'Y-m-d H:i:s') {
|
|
if (!dateTime) return ''; // 如果为空,返回空字符串
|
|
|
|
// iOS兼容性处理:将 "2025-07-29 09:49:27" 格式转换为 "2025/07/29 09:49:27"
|
|
let processedDateTime = dateTime;
|
|
if (typeof dateTime === 'string') {
|
|
// 检测是否为 "YYYY-MM-DD HH:mm:ss" 格式(iOS不支持)
|
|
if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(dateTime)) {
|
|
// 将中间的 "-" 替换为 "/",但保留时间部分的格式
|
|
processedDateTime = dateTime.replace(/^(\d{4})-(\d{2})-(\d{2})/, '$1/$2/$3');
|
|
}
|
|
// 检测是否为 "YYYY-MM-DD HH:mm" 格式
|
|
else if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/.test(dateTime)) {
|
|
processedDateTime = dateTime.replace(/^(\d{4})-(\d{2})-(\d{2})/, '$1/$2/$3');
|
|
}
|
|
// 检测是否为 "YYYY-MM-DD HH" 格式
|
|
else if (/^\d{4}-\d{2}-\d{2} \d{2}$/.test(dateTime)) {
|
|
processedDateTime = dateTime.replace(/^(\d{4})-(\d{2})-(\d{2})/, '$1/$2/$3');
|
|
}
|
|
}
|
|
|
|
const date = new Date(processedDateTime);
|
|
|
|
// 检查日期是否有效
|
|
if (isNaN(date.getTime())) {
|
|
console.warn('formatToDateTime: 无效的日期格式:', dateTime);
|
|
return ''; // 返回空字符串而不是错误
|
|
}
|
|
|
|
// 定义格式化规则
|
|
const o = {
|
|
'Y+': date.getFullYear(), // 年
|
|
'm+': String(date.getMonth() + 1).padStart(2, '0'), // 月
|
|
'd+': String(date.getDate()).padStart(2, '0'), // 日
|
|
'H+': String(date.getHours()).padStart(2, '0'), // 小时
|
|
'i+': String(date.getMinutes()).padStart(2, '0'), // 分钟
|
|
's+': String(date.getSeconds()).padStart(2, '0'), // 秒
|
|
};
|
|
|
|
// 替换格式模板中的占位符
|
|
for (const k in o) {
|
|
if (new RegExp(`(${k})`).test(fmt)) {
|
|
fmt = fmt.replace(RegExp.$1, o[k]);
|
|
}
|
|
}
|
|
|
|
return fmt;
|
|
}
|
|
|
|
|
|
|
|
//跳转首页
|
|
function openHomeView() {
|
|
//获取用户类型缓存
|
|
let userType = uni.getStorageSync('userType')
|
|
let url_path = ''
|
|
switch (String(userType)) {
|
|
case '1': //教练
|
|
url_path = '/pages/coach/home/index'
|
|
break;
|
|
case '2': //销售
|
|
url_path = '/pages/market/index/index'
|
|
break;
|
|
case '3': //学员
|
|
url_path = '/pages/student/index/index'
|
|
break;
|
|
default:
|
|
uni.showToast({
|
|
title: '用户类型错误',
|
|
icon: 'none'
|
|
})
|
|
url_path = '/pages/student/login/login'
|
|
return;
|
|
}
|
|
uni.navigateTo({
|
|
url: url_path
|
|
})
|
|
}
|
|
|
|
//退出登陆-清空缓存数据
|
|
function loginOut() {
|
|
//清除token
|
|
uni.removeStorageSync('token')
|
|
//清除用户信息
|
|
uni.removeStorageSync('userInfo')
|
|
//清除用户类型
|
|
uni.removeStorageSync('userType')
|
|
//清除用户角色
|
|
uni.removeStorageSync('userRoles')
|
|
//清除过期时间
|
|
uni.removeStorageSync('expires_time')
|
|
//底部菜单选中
|
|
uni.removeStorageSync('tabBerIndex')
|
|
|
|
// 重置Vuex中的用户信息和登录状态
|
|
// 注意:在非组件环境中无法直接访问store,需要在组件中调用
|
|
// 这里不做store的更新,而是在组件中使用时更新
|
|
|
|
// 直接跳转到登录页,不显示任何提示信息
|
|
uni.reLaunch({
|
|
url: '/pages/student/login/login'
|
|
})
|
|
}
|
|
|
|
// 全局内存字典缓存
|
|
// 使用模块级的变量保存缓存,在整个应用生命周期内都有效
|
|
const dictMemoryCache = {};
|
|
|
|
/**
|
|
* 获取字典值,带内存和存储双重缓存
|
|
* @param {String} dictKey 字典key
|
|
* @returns {Promise<Array|Object>}
|
|
*/
|
|
async function getDict(dictKey) {
|
|
console.log(`getDict - 开始获取字典: ${dictKey}`);
|
|
|
|
// 先从内存缓存中获取
|
|
const now = Date.now();
|
|
if (dictMemoryCache[dictKey] && dictMemoryCache[dictKey].expire > now) {
|
|
console.log(`getDict - 使用内存缓存: ${dictKey}`);
|
|
return dictMemoryCache[dictKey].data;
|
|
}
|
|
|
|
// 内存缓存不存在或已过期,尝试从存储中获取
|
|
const cacheKey = `dict_${dictKey}`;
|
|
try {
|
|
const storageCache = uni.getStorageSync(cacheKey);
|
|
if (storageCache && storageCache.data && storageCache.expire > now) {
|
|
console.log(`getDict - 使用存储缓存: ${dictKey}`);
|
|
// 更新内存缓存
|
|
dictMemoryCache[dictKey] = storageCache;
|
|
return storageCache.data;
|
|
}
|
|
} catch (e) {
|
|
console.error(`getDict - 读取存储缓存异常: ${dictKey}`, e);
|
|
}
|
|
|
|
console.log(`getDict - 缓存无效,请求接口: ${dictKey}`);
|
|
|
|
// 缓存不存在或已过期,从服务器获取
|
|
try {
|
|
// 添加超时处理和重试机制
|
|
let retryCount = 0;
|
|
const maxRetries = 2;
|
|
const fetchWithRetry = async () => {
|
|
try {
|
|
// 超时控制
|
|
const timeoutPromise = new Promise((_, reject) => {
|
|
setTimeout(() => reject(new Error(`字典请求超时: ${dictKey}`)), 3000);
|
|
});
|
|
|
|
// 实际API请求
|
|
const apiPromise = marketApi.common_Dictionary({ key: dictKey });
|
|
|
|
// 使用Promise.race来实现超时控制
|
|
return await Promise.race([apiPromise, timeoutPromise]);
|
|
} catch (error) {
|
|
if (retryCount < maxRetries) {
|
|
retryCount++;
|
|
console.log(`getDict - 重试第${retryCount}次: ${dictKey}`);
|
|
return await fetchWithRetry();
|
|
}
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
const res = await fetchWithRetry();
|
|
|
|
if (res && res.code === 1) {
|
|
// 处理接口返回的数据
|
|
const formattedData = Array.isArray(res.data) ? res.data : [];
|
|
|
|
if (formattedData.length > 0) {
|
|
// 设置缓存过期时间,默认1小时
|
|
const cacheData = {
|
|
data: formattedData,
|
|
expire: now + 3600 * 1000
|
|
};
|
|
|
|
// 同时更新内存缓存和存储缓存
|
|
dictMemoryCache[dictKey] = cacheData;
|
|
|
|
try {
|
|
uni.setStorageSync(cacheKey, cacheData);
|
|
} catch (e) {
|
|
console.error(`getDict - 存储缓存异常: ${dictKey}`, e);
|
|
}
|
|
|
|
console.log(`getDict - 字典获取成功: ${dictKey}, 条数: ${formattedData.length}`);
|
|
} else {
|
|
console.warn(`getDict - 字典数据为空: ${dictKey}`);
|
|
}
|
|
|
|
return formattedData;
|
|
} else {
|
|
console.error(`getDict - API请求失败: ${dictKey}`, res);
|
|
return [];
|
|
}
|
|
} catch (error) {
|
|
console.error(`getDict - 异常: ${dictKey}`, error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 上传文件通用方法
|
|
* @param {string} filePath 文件路径
|
|
* @param {Function} successCallback 成功回调
|
|
* @param {Function} errorCallback 失败回调
|
|
*/
|
|
export function uploadFile(filePath, successCallback, errorCallback) {
|
|
const token = uni.getStorageSync('token') || '';
|
|
|
|
uni.uploadFile({
|
|
url: Api_url + '/file/image', // 上传地址
|
|
filePath: filePath,
|
|
name: 'file',
|
|
header: {
|
|
'token': token
|
|
},
|
|
success: (res) => {
|
|
let response;
|
|
try {
|
|
// 去除 BOM 字符并解析 JSON
|
|
response = JSON.parse(res.data.replace(/\ufeff/g, '') || '{}');
|
|
} catch (e) {
|
|
uni.showToast({ title: '响应格式错误', icon: 'none' });
|
|
if (errorCallback) errorCallback(e);
|
|
return;
|
|
}
|
|
|
|
if (response.code === 1) {
|
|
const fileData = {
|
|
url: response.data.url,
|
|
extname: response.data.ext,
|
|
name: response.data.name
|
|
};
|
|
|
|
if (successCallback) {
|
|
successCallback(fileData);
|
|
}
|
|
} else if (response.code === 401) {
|
|
uni.showToast({ title: response.msg, icon: 'none' });
|
|
setTimeout(() => {
|
|
uni.navigateTo({ url: '/pages/student/login/login' });
|
|
}, 1000);
|
|
} else {
|
|
uni.showToast({ title: response.msg || '上传失败', icon: 'none' });
|
|
if (errorCallback) errorCallback(response);
|
|
}
|
|
},
|
|
fail: (err) => {
|
|
uni.showToast({ title: err.errMsg || '网络异常', icon: 'none' });
|
|
if (errorCallback) errorCallback(err);
|
|
}
|
|
});
|
|
}
|
|
/**
|
|
* 获取服务器上的资源完整 url
|
|
* @return {string} 完整的资源 URL
|
|
*/
|
|
function getResourceUrl(resource) {
|
|
//如果没有 http 协议,则加上 http 协议+服务域名
|
|
return resource.indexOf('http') === -1 ? 'https://' + img_domian + resource : resource;
|
|
}
|
|
|
|
/**
|
|
* 安全访问对象属性的方法,优化性能
|
|
* @param {Object} obj 目标对象
|
|
* @param {String} path 属性路径,如 'a.b.c'
|
|
* @param {*} defaultValue 默认值
|
|
* @returns {*} 属性值或默认值
|
|
*/
|
|
function safeGet(obj, path, defaultValue = '') {
|
|
if (!obj) return defaultValue
|
|
|
|
// 使用缓存来提高性能
|
|
if (!safeGet._pathCache) safeGet._pathCache = {}
|
|
|
|
// 使用路径作为缓存键
|
|
const cacheKey = path
|
|
|
|
// 如果这个路径没有缓存过分割结果,则计算并缓存
|
|
if (!safeGet._pathCache[cacheKey]) {
|
|
safeGet._pathCache[cacheKey] = path.split('.')
|
|
}
|
|
|
|
const keys = safeGet._pathCache[cacheKey]
|
|
let result = obj
|
|
|
|
// 使用for循环而不是for...of,更高效
|
|
for (let i = 0; i < keys.length; i++) {
|
|
const key = keys[i]
|
|
if (result === null || result === undefined || !result.hasOwnProperty(key)) {
|
|
return defaultValue
|
|
}
|
|
result = result[key]
|
|
}
|
|
|
|
return result || defaultValue
|
|
}
|
|
|
|
/**
|
|
* 格式化文件大小
|
|
* @param {Number} bytes 字节数
|
|
* @returns {String} 格式化后的大小
|
|
*/
|
|
function formatFileSize(bytes) {
|
|
if (bytes === 0) return '0 B'
|
|
const k = 1024
|
|
const sizes = ['B', 'KB', 'MB', 'GB']
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
|
|
}
|
|
|
|
/**
|
|
* 获取当前日期 YYYY-MM-DD 格式
|
|
* @returns {String} 当前日期字符串
|
|
*/
|
|
function getCurrentDate() {
|
|
const now = new Date()
|
|
const year = now.getFullYear()
|
|
const month = String(now.getMonth() + 1).padStart(2, '0')
|
|
const day = String(now.getDate()).padStart(2, '0')
|
|
return `${year}-${month}-${day}`
|
|
}
|
|
|
|
/**
|
|
* 格式化年龄显示
|
|
* @param {Number} age 年龄(小数格式,如9.05表示9岁5个月)
|
|
* @returns {String} 格式化后的年龄字符串
|
|
*/
|
|
function formatAge(age) {
|
|
if (!age) return '未知年龄'
|
|
const years = Math.floor(age)
|
|
const months = Math.round((age - years) * 12)
|
|
if (months === 0) {
|
|
return `${years}岁`
|
|
}
|
|
return `${years}岁${months}个月`
|
|
}
|
|
|
|
/**
|
|
* 格式化性别显示
|
|
* @param {Number} gender 性别值(1男,2女)
|
|
* @returns {String} 性别字符串
|
|
*/
|
|
function formatGender(gender) {
|
|
switch (gender) {
|
|
case 1: return '男'
|
|
case 2: return '女'
|
|
default: return '未知'
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 拨打电话的通用方法
|
|
* @param {String} phoneNumber 电话号码
|
|
* @param {Function} successCallback 成功回调
|
|
* @param {Function} failCallback 失败回调
|
|
*/
|
|
function makePhoneCall(phoneNumber, successCallback, failCallback) {
|
|
if (!phoneNumber) {
|
|
uni.showToast({
|
|
title: '电话号码为空',
|
|
icon: 'none'
|
|
})
|
|
return
|
|
}
|
|
|
|
uni.makePhoneCall({
|
|
phoneNumber: phoneNumber,
|
|
success: (res) => {
|
|
console.log('拨打电话成功')
|
|
if (successCallback) successCallback(res)
|
|
},
|
|
fail: (err) => {
|
|
console.error('拨打电话失败:', err)
|
|
uni.showToast({
|
|
title: '拨打电话失败',
|
|
icon: 'none'
|
|
})
|
|
if (failCallback) failCallback(err)
|
|
}
|
|
})
|
|
}
|
|
|
|
/**
|
|
* 页面跳转的通用方法
|
|
* @param {String} url 跳转路径
|
|
* @param {Object} params 跳转参数对象
|
|
*/
|
|
function navigateToPage(url, params = {}) {
|
|
let queryString = ''
|
|
|
|
// 将参数对象转换为查询字符串
|
|
if (Object.keys(params).length > 0) {
|
|
const paramArray = []
|
|
for (const key in params) {
|
|
if (params[key] !== null && params[key] !== undefined && params[key] !== '') {
|
|
paramArray.push(`${key}=${encodeURIComponent(params[key])}`)
|
|
}
|
|
}
|
|
if (paramArray.length > 0) {
|
|
queryString = '?' + paramArray.join('&')
|
|
}
|
|
}
|
|
|
|
const fullUrl = url + queryString
|
|
|
|
uni.navigateTo({
|
|
url: fullUrl,
|
|
fail: (err) => {
|
|
console.error('页面跳转失败:', err)
|
|
uni.showToast({
|
|
title: '页面跳转失败',
|
|
icon: 'none'
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
module.exports = {
|
|
loginOut,
|
|
openHomeView,
|
|
formatTime,
|
|
formatDateTime,
|
|
formatLocation,
|
|
dateUtils,
|
|
hexToRgba,
|
|
img,
|
|
formatToDateTime,
|
|
getDict,
|
|
uploadFile,
|
|
getResourceUrl,
|
|
safeGet,
|
|
formatFileSize,
|
|
getCurrentDate,
|
|
formatAge,
|
|
formatGender,
|
|
makePhoneCall,
|
|
navigateToPage
|
|
}
|
|
|