智慧教务系统
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

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
}