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} */ 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 }