Files
code-bang/frontend/miniapp/utils/util.js
T
2026-10-19 15:46:10 +08:00

634 lines
14 KiB
JavaScript

import dayjs from 'dayjs';
var api = require('../config/api.js');
const formatTime = (date, template) => dayjs(date).format(template);
const formatNumber = n => {
n = n.toString()
return n[1] ? n : '0' + n
}
function formatTimeNum(number, format) {
var formateArr = ['Y', 'M', 'D', 'h', 'm', 's'];
var returnArr = [];
var date = new Date(number * 1000);
returnArr.push(date.getFullYear());
returnArr.push(formatNumber(date.getMonth() + 1));
returnArr.push(formatNumber(date.getDate()));
returnArr.push(formatNumber(date.getHours()));
returnArr.push(formatNumber(date.getMinutes()));
returnArr.push(formatNumber(date.getSeconds()));
for (var i in returnArr) {
format = format.replace(formateArr[i], returnArr[i]);
}
return format;
}
function testMobile(num) {
console.log
var myreg = /^(((13[0-9]{1})|(15[0-9]{1})|(18[0-9]{1})|(17[0-9]{1})|(16[0-9]{1})|(19[0-9]{1}))+\d{8})$/;
if (num.length == 0) {
wx.showToast({
title: '手机号为空',
image: '/images/icon/icon_error.png',
})
return false;
} else if (num.length < 11) {
wx.showToast({
title: '手机号长度有误!',
image: '/images/icon/icon_error.png',
})
return false;
} else if (!myreg.test(num)) {
wx.showToast({
title: '手机号有误!',
image: '/images/icon/icon_error.png',
})
return false;
} else {
return true;
}
}
const REQUEST_TIMEOUT = 15000;
let tokenCache = '';
let tokenLoaded = false;
let reloginTask = null;
function getAppSafe() {
try {
return getApp();
} catch (err) {
return null;
}
}
/** 内存缓存 token,避免每次请求都读 Storage */
function getToken() {
if (!tokenLoaded) {
tokenCache = wx.getStorageSync('token') || '';
tokenLoaded = true;
}
return tokenCache;
}
function setToken(token) {
tokenCache = token || '';
tokenLoaded = true;
if (tokenCache) {
wx.setStorageSync('token', tokenCache);
} else {
wx.removeStorageSync('token');
}
}
/**
* 本地还没有 token 时,等冷启动登录结束再发需要鉴权的请求
* 已有 token 则直接发,过期由 401 重登处理
*/
function ensureLogin() {
if (getToken()) {
return Promise.resolve();
}
const app = getAppSafe();
if (app && app.loginReady) {
return app.loginReady;
}
return Promise.resolve();
}
function isUnauthorized(statusCode, body) {
return statusCode === 401 || !!(body && body.code === 401);
}
/** 登录失效后重新换 token,进行中的重登合并成一次 */
function relogin() {
const app = getAppSafe();
if (app && app.loggingIn && app.loginReady) {
return app.loginReady.then(() => !!getToken());
}
if (reloginTask) {
return reloginTask;
}
reloginTask = login()
.then((wxRes) => request(api.AuthLoginByWeixin, { code: wxRes.code }, 'POST', { auth: false }))
.then((res) => {
if (res && res.code === 1 && res.data && res.data.token) {
setToken(res.data.token);
if (res.data.userinfo) {
wx.setStorageSync('userInfo', res.data.userinfo);
}
return true;
}
return false;
})
.catch((err) => {
console.error('relogin failed:', err);
return false;
})
.then((ok) => {
reloginTask = null;
return ok;
});
return reloginTask;
}
/**
* 统一请求
* @param {string} url
* @param {object} data
* @param {string} method
* @param {{auth?: boolean, timeout?: number, retried?: boolean}} options
* auth 默认 true:没有 token 时等待登录,401 时重登并重试一次
* auth 为 false:免登录接口(分类、首页配置等)立即发送,不等待登录
*/
function request(url, data = {}, method = 'GET', options = {}) {
const auth = options.auth !== false;
const timeout = options.timeout || REQUEST_TIMEOUT;
const retried = !!options.retried;
const send = () => new Promise((resolve, reject) => {
const header = { 'Content-Type': 'application/json' };
const token = getToken();
if (token) {
header.token = token;
}
wx.request({
url,
data,
method,
timeout,
header,
success(res) {
const body = res.data && typeof res.data === 'object'
? res.data
: { code: 0, msg: '网络异常', data: null };
if (res.statusCode === 200 || isUnauthorized(res.statusCode, body)) {
resolve({ unauthorized: isUnauthorized(res.statusCode, body), body });
return;
}
reject((body && body.msg) || res.errMsg || '网络异常');
},
fail(err) {
reject(err);
},
});
});
const dispatch = () => send().then((result) => {
if (result.unauthorized && auth && !retried) {
return relogin().then((ok) => {
if (!ok) {
return result.body;
}
return request(url, data, method, { auth: true, timeout, retried: true });
});
}
return result.body;
});
if (!auth) {
return dispatch();
}
return ensureLogin().then(dispatch);
}
/**
* 检查微信会话是否过期
*/
function checkSession() {
return new Promise(function (resolve, reject) {
wx.checkSession({
success: function () {
resolve(true);
},
fail: function () {
reject(false);
}
})
});
}
/**
* 调用微信登录
*/
function login() {
return new Promise(function (resolve, reject) {
wx.login({
success: function (res) {
if (res.code) {
//登录远程服务器
resolve(res);
} else {
reject(res);
}
},
fail: function (err) {
reject(err);
}
});
});
}
function getUserInfo() {
return new Promise(function (resolve, reject) {
wx.getUserInfo({
withCredentials: true,
success: function (res) {
resolve(res);
},
fail: function (err) {
reject(err);
}
})
});
}
function redirect(url) {
//判断页面是否需要登录
if (false) {
wx.redirectTo({
url: '/pages/auth/login/login'
});
return false;
} else {
wx.redirectTo({
url: url
});
}
}
function showErrorToast(msg) {
wx.showToast({
title: msg,
icon: 'none',
})
}
function showSuccessToast(msg) {
wx.showToast({
title: msg,
icon: 'success',
})
}
function loginNow() {
let userInfo = wx.getStorageSync('userInfo');
if (userInfo == '') {
wx.login({
success: (res) => {
request(api.AuthLoginByWeixin, {
code: res.code
}, 'POST', { auth: false }).then(function (res) {
if (res.code === 1) {
let userInfo = res.data.userInfo;
setToken(res.data.token);
wx.setStorageSync('userInfo', userInfo);
}
});
},
});
} else {
return true;
}
}
function getTextLength(str, full) {
let len = 0;
for (let i = 0; i < str.length; i++) {
let c = str.charCodeAt(i);
//单字节加1
if ((c >= 0x0001 && c <= 0x007e) || (0xff60 <= c && c <= 0xff9f)) {
len++;
} else {
len += (full ? 2 : 1);
}
}
return len;
}
/**
* rgba(255, 255, 255, 1) => #ffffff
* @param {String} color
*/
function transferColor(color = '') {
let res = '#';
color = color.replace(/^rgba?\(/, '').replace(/\)$/, '');
color = color.split(', ');
color.length > 3 ? color.length = 3 : '';
for (let item of color) {
item = parseInt(item || 0);
if (item < 10) {
res += ('0' + item)
} else {
res += (item.toString(16))
}
}
return res;
}
function transferBorder(border = '') {
let res = border.match(/(\w+)px\s(\w+)\s(.*)/);
let obj = {};
if (res) {
obj = {
width: +res[1],
style: res[2],
color: res[3]
}
}
return res ? obj : null;
}
/**
* 内边距,依次为上右下左
* @param {*} padding
*/
function transferPadding(padding = '0 0 0 0') {
padding = padding.split(' ');
for (let i = 0, len = padding.length; i < len; i++) {
padding[i] = +padding[i].replace('px', '');
}
return padding;
}
/**
* type1: 0, 25, 17, rgba(0, 0, 0, 0.3)
* type2: rgba(0, 0, 0, 0.3) 0px 25px 17px 0px => (0, 25, 17, rgba(0, 0, 0, 0.3))
* @param {*} shadow
*/
function transferBoxShadow(shadow = '', type) {
if (!shadow || shadow === 'none') return;
let color;
let split;
split = shadow.match(/(\w+)\s(\w+)\s(\w+)\s(rgb.*)/);
if (split) {
split.shift();
shadow = split;
color = split[3] || '#ffffff';
} else {
split = shadow.split(') ');
color = split[0] + ')'
shadow = split[1].split('px ');
}
return {
offsetX: +shadow[0] || 0,
offsetY: +shadow[1] || 0,
blur: +shadow[2] || 0,
color
}
}
function getUid(prefix) {
prefix = prefix || '';
return (
prefix +
'xxyxxyxx'.replace(/[xy]/g, c => {
let r = (Math.random() * 16) | 0;
let v = c === 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
})
);
}
/**
* 格式化金额为字符串,单位为元
* @param price 金额,单位元
* @param fill 小数位数 0-不填充 1-一位 2-两位
*/
function priceFormat(price, fill = 0) {
if (isNaN(price) || price === null || price === Infinity) {
return price;
}
const rounded = Math.round(parseFloat(`${price}`) * 100) / 100;
let priceFormatValue = `${rounded}`;
if (fill > 0) {
if (priceFormatValue.indexOf('.') === -1) {
priceFormatValue = `${priceFormatValue}.`;
}
const decimal = priceFormatValue.split('.')[1] || '';
const n = fill - decimal.length;
for (let i = 0; i < n; i++) {
priceFormatValue = `${priceFormatValue}0`;
}
}
return priceFormatValue;
}
/**
* 获取cdn裁剪后链接
*
* @param {string} url 基础链接
* @param {number} width 宽度,单位px
* @param {number} [height] 可选,高度,不填时与width同值
*/
const cosThumb = (url, width, height = width) => {
if (url.indexOf('?') > -1) {
return url;
}
if (url.indexOf('http://') === 0) {
url = url.replace('http://', 'https://');
}
return `${url}?imageMogr2/thumbnail/${~~width}x${~~height}`;
};
const get = (source, paths, defaultValue) => {
if (typeof paths === 'string') {
paths = paths
.replace(/\[/g, '.')
.replace(/\]/g, '')
.split('.')
.filter(Boolean);
}
const {
length
} = paths;
let index = 0;
while (source != null && index < length) {
source = source[paths[index++]];
}
return source === undefined || index === 0 ? defaultValue : source;
};
let systemWidth = 0;
/** 获取系统宽度,为了减少启动消耗所以在函数里边做初始化 */
export const loadSystemWidth = () => {
if (systemWidth) {
return systemWidth;
}
try {
({
screenWidth: systemWidth,
pixelRatio
} = wx.getSystemInfoSync());
} catch (e) {
systemWidth = 0;
}
return systemWidth;
};
/**
* 转换rpx为px
*
* @description
* 什么时候用?
* - 布局(width: 172rpx)已经写好, 某些组件只接受px作为style或者prop指定
*
*/
const rpx2px = (rpx, round = false) => {
loadSystemWidth();
// px / systemWidth = rpx / 750
const result = (rpx * systemWidth) / 750;
if (round) {
return Math.floor(result);
}
return result;
};
/**
* 手机号码*加密函数
* @param {string} phone 电话号
* @returns
*/
const phoneEncryption = (phone) => {
return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');
};
// 内置手机号正则字符串
const innerPhoneReg =
'^1(?:3\\d|4[4-9]|5[0-35-9]|6[67]|7[0-8]|8\\d|9\\d)\\d{8}$';
/**
* 手机号正则校验
* @param phone 手机号
* @param phoneReg 正则字符串
* @returns true - 校验通过 false - 校验失败
*/
const phoneRegCheck = (phone) => {
const phoneRegExp = new RegExp(innerPhoneReg);
return phoneRegExp.test(phone);
};
const obj2Params = (obj = {}, encode = false) => {
const result = [];
Object.keys(obj).forEach((key) =>
result.push(`${key}=${encode ? encodeURIComponent(obj[key]) : obj[key]}`),
);
return result.join('&');
};
/**
* 标记某条需求需要在列表页刷新状态
* 用 globalData 传递,不依赖页面栈深度(backAndRefresh 的 delta 在多层跳转后容易算错上一页)
* @param {number|string} demandId 需求ID
*/
function markDemandRefresh(demandId) {
const app = getApp();
if (app && app.globalData) {
app.globalData.refreshDemandId = Number(demandId) || 0;
}
}
function backAndRefresh(delta = 1, freshField = 'freshPage') {
const pages = getCurrentPages(); // 获取页面栈
const prevPage = pages[pages.length - (delta + 1)]; // 上一页实例
if (prevPage) {
prevPage.setData({ [freshField]: true });
}
wx.navigateBack({ delta }); // 返回上一页
}
function checkSubscriptionsSetting() {
wx.getSetting({
withSubscriptions: true,
success(res) {
console.log('getSetting:', res);
const itemSettings = res.subscriptionsSetting.itemSettings || {};
let hasReject = false;
if (res.subscriptionsSetting.mainSwitch === false) {
hasReject = true;
}
for (const id in itemSettings) {
if (itemSettings[id] === 'reject') {
hasReject = true;
break;
}
}
if (hasReject) {
wx.showToast({
title: '请授权订阅消息,用于接收重要消息',
icon: 'none',
duration: 3000
});
wx.openSetting({
withSubscriptions: true,
success(openRes) {
console.log('openSetting:', openRes);
},
fail(err) {
console.error('打开设置页失败:', err);
}
});
} else {
console.log('所有消息均未被拒绝');
}
},
fail(err) {
console.error('获取设置失败:', err);
}
});
}
module.exports = {
formatTime,
priceFormat,
cosThumb,
get,
rpx2px,
phoneEncryption,
phoneRegCheck,
formatTimeNum: formatTimeNum,
request,
getToken,
setToken,
redirect,
showErrorToast,
showSuccessToast,
checkSession,
login,
getUserInfo,
testMobile,
loginNow,
getTextLength,
transferBorder,
transferColor,
transferPadding,
transferBoxShadow,
obj2Params,
backAndRefresh,
markDemandRefresh,
checkSubscriptionsSetting,
getUid
};