完善生产查询与登录权限配置

This commit is contained in:
2026-09-03 19:56:19 +08:00
parent 793f140a45
commit 02c8fb7021
438 changed files with 1595 additions and 573 deletions
@@ -0,0 +1,24 @@
// 获取父组件的参数,因为支付宝小程序不支持provide/inject的写法
// this.$parent在非H5中,可以准确获取到父组件,但是在H5中,需要多次this.$parent.$parent.xxx
// 这里默认值等于undefined有它的含义,因为最顶层元素(组件)的$parent就是undefined,意味着不传name
// 值(默认为undefined),就是查找最顶层的$parent
import { type ComponentInternalInstance, getCurrentInstance } from 'vue';
export default function $parent(
componentName?: string,
_instance: ComponentInternalInstance | null | undefined = null
) {
const instance: ComponentInternalInstance | null | undefined = _instance || getCurrentInstance();
let parent = instance && (instance.parent as ComponentInternalInstance | null | undefined);
if (!componentName) return parent;
while (parent) {
const name = (parent.type as any)?.name as string | undefined;
if (name === componentName) {
return parent;
}
parent = parent.parent;
}
return null;
}
@@ -0,0 +1,50 @@
import validation from './test';
/**
* 添加单位,支持以下场景:
* 1. 单值:如果有rpx、%、px等单位结尾或者值为auto,直接返回,否则加上rpx单位结尾
* 2. 多值(空格分隔):分别处理每个值,如 "10 20" => "10rpx 20rpx"
*
* @example
* ```ts
* addUnit(10) => "10rpx"
* addUnit('10px') => "10px"
* addUnit('auto') => "auto"
* addUnit('10 20') => "10rpx 20rpx"
* addUnit('10rpx 20') => "10rpx 20rpx"
* ```
*
* @param value 输入值,可以为字符串或数字,默认'auto'。支持空格分隔的多值(用于 padding、margin 等)
* @param unit 单位,默认'rpx'
* @returns 添加单位后的字符串
*/
export default function addUnit(value: string | number = 'auto', unit: string = 'rpx'): string {
// 若传入 number 类型,转为 string
const strValue = String(value);
// 如果是空值,直接返回
if (!strValue) return '';
// auto 直接返回
if (strValue === 'auto') return strValue;
// 检查是否包含空格(多值场景)
if (strValue.includes(' ')) {
// 分割每个值并单独处理
return strValue
.split(' ')
.map(s => {
// 如果当前值已有单位或为 auto,直接返回
if (s === 'auto' || /^-?\d*\.?\d+(%|px|rpx|em|rem|vh|vw)$/.test(s)) return s;
// 数字类型的值添加单位
return validation.number(s) ? `${s}${unit}` : s;
})
.join(' ');
}
// 单值场景:如果已有单位或为 auto,直接返回
if (/^-?\d*\.?\d+(%|px|rpx|em|rem|vh|vw)$/.test(strValue)) return strValue;
// 用 uView 内置验证规则判断是否为数值,是则加上单位
return validation.number(strValue) ? `${strValue}${unit}` : strValue;
}
@@ -0,0 +1,73 @@
function H5Copy(text: string, config: TClipboardOptions) {
const success = (result: string) => {
if (config.showToast) {
uni.showToast({
title: '复制成功',
icon: 'none'
});
}
config.success?.(result);
config.complete?.(result);
};
const fail = (err: string) => {
if (config.showToast) {
uni.showToast({
title: '复制失败',
icon: 'none'
});
}
config.fail?.(err);
config.complete?.(err);
};
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.readOnly = true;
textarea.style.position = 'absolute';
textarea.style.left = '-9999px';
document.body.appendChild(textarea);
textarea.select();
textarea.setSelectionRange(0, text.length);
try {
const result = document.execCommand('copy');
if (result) {
success('复制成功');
} else {
// console.error(`复制失败,可能不是用户主动触发点击的方式调用,因web安全性,不能js直接调用!`);
fail('复制失败,可能不是用户主动触发点击的方式调用,因browser安全性,不能js直接调用!');
}
} catch (err) {
// console.error('【Clipboard Error】:', err);
fail(String(err));
} finally {
document.body.removeChild(textarea);
}
}
function UniCopy(text: string, config: TClipboardOptions) {
const opt = Object.assign({ data: text }, config);
uni.setClipboardData(opt);
}
type TClipboardOptions = Omit<UniNamespace.SetClipboardDataOptions, 'data'>;
export function clipboard(content: string, options?: TClipboardOptions) {
const text = String(content);
const defaultOpt = {
showToast: true,
success: () => {},
fail: () => {},
complete: () => {}
};
const config = Object.assign(defaultOpt, options);
// #ifdef H5
H5Copy(text, config);
// #endif
// #ifndef H5
UniCopy(text, config);
// #endif
}
@@ -0,0 +1,28 @@
import type { ColorType, ThemeColor } from '../../types/global';
import { color } from '../config/color';
import { lightPalette } from '../config/theme-tokens';
import configProvider from '../util/config-provider';
/**
* 获取颜色值(响应式)
* 优先从 configProvider 获取当前主题颜色,否则返回默认值
* @param name 颜色名称
* @returns 颜色值
*/
export function getColor(name: ColorType): string {
// 从响应式 color 对象获取(会被 configProvider 更新)
if (color[name]) {
return color[name] as string;
}
// 兜底返回默认值
return lightPalette[name] || '';
}
export function setColor(theme: Partial<ThemeColor> | undefined) {
if (theme) {
configProvider.setThemeColor(theme);
}
}
export default color;
@@ -0,0 +1,139 @@
/**
* 求两个颜色之间的渐变值
* @param startColor 开始的颜色
* @param endColor 结束的颜色
* @param step 颜色等分的份额
* @returns 渐变色数组
*/
function colorGradient(
startColor: string = 'rgb(0, 0, 0)',
endColor: string = 'rgb(255, 255, 255)',
step: number = 10
): string[] {
const startRGB = hexToRgb(startColor, false) as [number, number, number]; // 转换为rgb数组模式
const [startR, startG, startB] = startRGB;
const endRGB = hexToRgb(endColor, false) as [number, number, number];
const [endR, endG, endB] = endRGB;
const sR = (endR - startR) / step; // 总差值
const sG = (endG - startG) / step;
const sB = (endB - startB) / step;
const colorArr: string[] = [];
for (let i = 0; i < step; i++) {
// 计算每一步的hex值
const hex = rgbToHex(
`rgb(${Math.round(sR * i + startR)},${Math.round(sG * i + startG)},${Math.round(sB * i + startB)})`
);
colorArr.push(hex as string);
}
return colorArr;
}
/**
* 将hex表示方式转换为rgb表示方式(返回rgb数组或字符串)
* @param sColor hex或rgb字符串
* @param str 是否返回字符串
* @returns rgb数组或字符串
*/
function hexToRgb(sColor: string, str: boolean = true): [number, number, number] | string {
const reg = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
sColor = sColor.toLowerCase();
if (sColor && reg.test(sColor)) {
if (sColor.length === 4) {
let sColorNew = '#';
for (let i = 1; i < 4; i += 1) {
sColorNew += sColor.slice(i, i + 1).concat(sColor.slice(i, i + 1));
}
sColor = sColorNew;
}
// 处理六位的颜色值
const sColorChange: [number, number, number] = [
parseInt('0x' + sColor.slice(1, 3)),
parseInt('0x' + sColor.slice(3, 5)),
parseInt('0x' + sColor.slice(5, 7))
];
if (!str) {
return sColorChange;
} else {
return `rgb(${sColorChange[0]},${sColorChange[1]},${sColorChange[2]})`;
}
} else if (/^(rgb|RGB)/.test(sColor)) {
const arr = sColor.replace(/(?:\(|\)|rgb|RGB)*/g, '').split(',');
return arr.map(val => Number(val)) as [number, number, number];
} else {
return sColor;
}
}
/**
* rgb转hex
* @param rgb rgb字符串或hex字符串
* @returns hex字符串
*/
function rgbToHex(rgb: string): string | undefined {
const reg = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
if (/^(rgb|RGB)/.test(rgb)) {
const aColor = rgb.replace(/(?:\(|\)|rgb|RGB)*/g, '').split(',');
let strHex = '#';
for (let i = 0; i < aColor.length; i++) {
let hex = Number(aColor[i]).toString(16);
hex = hex.length == 1 ? '0' + hex : hex; // 保证每个rgb的值为2位
strHex += hex;
}
if (strHex.length !== 7) {
strHex = rgb;
}
return strHex;
} else if (reg.test(rgb)) {
const aNum = rgb.replace(/#/, '').split('');
if (aNum.length === 6) {
return rgb;
} else if (aNum.length === 3) {
let numHex = '#';
for (let i = 0; i < aNum.length; i += 1) {
numHex += aNum[i] + aNum[i];
}
return numHex;
}
} else {
return rgb;
}
// 默认返回原始值
return rgb;
}
/**
* JS颜色十六进制转换为rgb或rgba,返回的格式为 rgba(255,255,255,0.5)字符串
* @param color 十六进制色值或rgb字符串
* @param alpha 透明度
* @returns rgba字符串
*/
function colorToRgba(color: string, alpha: number = 0.3): string {
color = rgbToHex(color) as string; // 确保是hex格式
const reg = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;
let sColor = color.toLowerCase();
if (sColor && reg.test(sColor)) {
if (sColor.length === 4) {
let sColorNew = '#';
for (let i = 1; i < 4; i += 1) {
sColorNew += sColor.slice(i, i + 1).concat(sColor.slice(i, i + 1));
}
sColor = sColorNew;
}
const sColorChange: [number, number, number] = [
parseInt('0x' + sColor.slice(1, 3)),
parseInt('0x' + sColor.slice(3, 5)),
parseInt('0x' + sColor.slice(5, 7))
];
return `rgba(${sColorChange.join(',')},${alpha})`;
} else {
return sColor;
}
}
export default {
colorGradient,
hexToRgb,
rgbToHex,
colorToRgba
};
@@ -0,0 +1,28 @@
let timeout: ReturnType<typeof setTimeout> | null = null;
/**
* 防抖原理:一定时间内,只有最后一次操作,再过wait毫秒后才执行函数
* @param func 要执行的回调函数
* @param wait 延时的时间,单位ms,默认500
* @param immediate 是否立即执行,默认false
* @returns void
*/
function debounce(func: () => void, wait: number = 500, immediate: boolean = false): void {
// 清除定时器
if (timeout !== null) clearTimeout(timeout);
// 立即执行,此类情况一般用不到
if (immediate) {
const callNow = !timeout;
timeout = setTimeout(() => {
timeout = null;
}, wait);
if (callNow) typeof func === 'function' && func();
} else {
// 设置定时器,当最后一次操作后,timeout不会再被清除,所以在延时wait毫秒后执行func回调方法
timeout = setTimeout(() => {
typeof func === 'function' && func();
}, wait);
}
}
export default debounce;
@@ -0,0 +1,39 @@
// 判断arr是否为一个数组,返回一个bool值
function isArray(arr: any): arr is any[] {
return Object.prototype.toString.call(arr) === '[object Array]';
}
/**
* 深度克隆
* @param obj 需要克隆的对象
* @param cache 用于处理循环引用的 WeakMap
* @returns 克隆后的对象
*/
function deepClone<T>(obj: T, cache: WeakMap<any, any> = new WeakMap()): T {
if (obj === null || typeof obj !== 'object') return obj;
if (cache.has(obj)) return cache.get(obj);
let clone: any;
if (obj instanceof Date) {
clone = new Date(obj.getTime());
} else if (obj instanceof RegExp) {
clone = new RegExp(obj);
} else if (obj instanceof Map) {
clone = new Map(Array.from(obj, ([key, value]) => [key, deepClone(value, cache)]));
} else if (obj instanceof Set) {
clone = new Set(Array.from(obj, value => deepClone(value, cache)));
} else if (Array.isArray(obj)) {
clone = obj.map(value => deepClone(value, cache));
} else if (Object.prototype.toString.call(obj) === '[object Object]') {
clone = Object.create(Object.getPrototypeOf(obj));
cache.set(obj, clone);
for (const [key, value] of Object.entries(obj)) {
clone[key] = deepClone(value, cache);
}
} else {
clone = Object.assign({}, obj);
}
cache.set(obj, clone);
return clone;
}
export default deepClone;
@@ -0,0 +1,35 @@
import deepClone from './deepClone';
/**
* JS对象深度合并
* @param target 目标对象
* @param source 源对象
* @returns 合并后的对象
*/
function deepMerge<T extends object, S extends object>(target: T = {} as T, source: S = {} as S): T & S {
target = deepClone(target);
if (typeof target !== 'object' || target === null || typeof source !== 'object' || source === null)
return target as T & S;
const merged: any = Array.isArray(target) ? target.slice() : Object.assign({}, target);
for (const prop in source) {
if (!Object.prototype.hasOwnProperty.call(source, prop)) continue;
const sourceValue = (source as any)[prop];
const targetValue = merged[prop];
if (sourceValue instanceof Date) {
merged[prop] = new Date(sourceValue);
} else if (sourceValue instanceof RegExp) {
merged[prop] = new RegExp(sourceValue);
} else if (sourceValue instanceof Map) {
merged[prop] = new Map(sourceValue);
} else if (sourceValue instanceof Set) {
merged[prop] = new Set(sourceValue);
} else if (typeof sourceValue === 'object' && sourceValue !== null) {
merged[prop] = deepMerge(targetValue, sourceValue);
} else {
merged[prop] = sourceValue;
}
}
return merged as T & S;
}
export default deepMerge;
@@ -0,0 +1,63 @@
// 获取父组件的参数,因为支付宝小程序不支持provide/inject的写法
// this.$parent在非H5中,可以准确获取到父组件,但是在H5中,需要多次this.$parent.$parent.xxx
interface VueInstance {
$parent?: VueInstance;
$options?: { name?: string };
[key: string]: any;
}
/**
* 获取父组件参数
* @param this 当前组件实例
* @param name 父组件name
* @param keys 需要获取的参数名数组或对象
* @returns 父组件参数对象
*/
export default function getParent(
this: VueInstance,
name: string,
keys: string[] | Record<string, any>
): Record<string, any> {
let parent = this.$parent;
// 通过while历遍,这里主要是为了H5需要多层解析的问题
while (parent) {
// 父组件
if (parent.$options?.name !== name) {
// 如果组件的name不相等,继续上一级寻找
parent = parent.$parent;
} else {
const data: Record<string, any> = {};
// 判断keys是否数组,如果传过来的是一个数组,那么直接使用数组元素值当做键值去父组件寻找
if (Array.isArray(keys)) {
keys.forEach(val => {
data[val] = parent?.[val] ? parent[val] : '';
});
} else {
// 历遍传过来的对象参数
for (const i in keys) {
// 如果子组件有此值则用,无此值则用父组件的值
// 判断是否空数组,如果是,则用父组件的值,否则用子组件的值
if (Array.isArray(keys[i])) {
if (keys[i].length) {
data[i] = keys[i];
} else {
data[i] = parent[i];
}
} else if (keys[i] && keys[i].constructor === Object) {
// 判断是否对象,如果是对象,且有属性,那么使用子组件的值,否则使用父组件的值
if (Object.keys(keys[i]).length) {
data[i] = keys[i];
} else {
data[i] = parent[i];
}
} else {
// 只要子组件有传值,即使是false值,也是“传值”了,也需要覆盖父组件的同名参数
data[i] = keys[i] || keys[i] === false ? keys[i] : parent[i];
}
}
}
return data;
}
}
return {};
}
@@ -0,0 +1,26 @@
/**
* 获取元素的位置信息
* @param {any} selector 选择器
* @param {boolean} all 是否获取所有匹配元素
* @returns {Promise<any>} 返回一个 Promise,解析为元素的位置信息
*/
import { getCurrentInstance } from 'vue';
export default function (selector: any, _instance: any = null, all: boolean = false): Promise<any> {
const instance = _instance || getCurrentInstance();
return new Promise(resolve => {
uni.createSelectorQuery()
.in(instance?.proxy)
[all ? 'selectAll' : 'select'](selector)
.boundingClientRect((rect: any) => {
if (all && Array.isArray(rect) && rect.length) {
resolve(rect);
}
if (!all && rect) {
resolve(rect);
}
})
.exec();
});
}
@@ -0,0 +1,42 @@
/**
* 本算法来源于简书开源代码,详见:https://www.jianshu.com/p/fdbf293d0a85
* 全局唯一标识符(uuid,Globally Unique Identifier),也称作 uuid(Universally Unique IDentifier)
* 一般用于多个组件之间,给它一个唯一的标识符,或者v-for循环的时候,如果使用数组的index可能会导致更新列表出现问题
* 最可能的情况是左滑删除item或者对某条信息流"不喜欢"并去掉它的时候,会导致组件内的数据可能出现错乱
* v-for的时候,推荐使用后端返回的id而不是循环的index
* @param len uuid的长度,默认32
* @param firstU 将返回的首字母置为"u",默认true
* @param radix 生成uuid的基数(意味着返回的字符串都是这个基数),2-二进制,8-八进制,10-十进制,16-十六进制
* @returns 生成的uuid字符串
*/
function guid(len: number = 32, firstU: boolean = true, radix?: number): string {
const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
const uuid: string[] = [];
const base = radix || chars.length;
if (len) {
// 如果指定uuid长度,只是取随机的字符,0|x为位运算,能去掉x的小数位,返回整数位
for (let i = 0; i < len; i++) uuid[i] = chars[0 | (Math.random() * base)];
} else {
let r: number;
// rfc4122标准要求返回的uuid中,某些位为固定的字符
uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
uuid[14] = '4';
for (let i = 0; i < 36; i++) {
if (!uuid[i]) {
r = 0 | (Math.random() * 16);
uuid[i] = chars[i == 19 ? (r & 0x3) | 0x8 : r];
}
}
}
// 移除第一个字符,并用u替代,因为第一个字符为数值时,该guid不能用作id或者class
if (firstU) {
uuid.shift();
return 'u' + uuid.join('');
} else {
return uuid.join('');
}
}
export default guid;
@@ -0,0 +1,403 @@
/*
* A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
* Digest Algorithm, as defined in RFC 1321.
* Version 2.2 Copyright (C) Paul Johnston 1999 - 2009
* Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
* Distributed under the BSD License
* See http://pajhome.org.uk/crypt/md5 for more info.
*/
/*
* Configurable variables. You may need to tweak these to be compatible with
* the server-side, but the defaults work in most cases.
*/
let hexcase: number = 0; /* hex output format. 0 - lowercase; 1 - uppercase */
let b64pad: string = ''; /* base-64 pad character. "=" for strict RFC compliance */
/**
* 这些是通常需要调用的函数
* @param s 输入字符串
* @returns 十六进制/BASE64/任意编码的MD5字符串
*/
function hex_md5(s: string): string {
return rstr2hex(rstr_md5(str2rstr_utf8(s)));
}
function b64_md5(s: string): string {
return rstr2b64(rstr_md5(str2rstr_utf8(s)));
}
function any_md5(s: string, e: string): string {
return rstr2any(rstr_md5(str2rstr_utf8(s)), e);
}
function hex_hmac_md5(k: string, d: string): string {
return rstr2hex(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d)));
}
function b64_hmac_md5(k: string, d: string): string {
return rstr2b64(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d)));
}
function any_hmac_md5(k: string, d: string, e: string): string {
return rstr2any(rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d)), e);
}
/**
* 执行简单自测,判断 VM 是否正常
* @returns 是否通过测试
*/
function md5_vm_test(): boolean {
return hex_md5('abc').toLowerCase() == '900150983cd24fb0d6963f7d28e17f72';
}
/**
* 计算原始字符串的 MD5
* @param s 原始字符串
* @returns MD5 结果字符串
*/
function rstr_md5(s: string): string {
return binl2rstr(binl_md5(rstr2binl(s), s.length * 8));
}
/**
* 计算 HMAC-MD5
* @param key 密钥
* @param data 数据
* @returns HMAC-MD5 结果字符串
*/
function rstr_hmac_md5(key: string, data: string): string {
let bkey: number[] = rstr2binl(key);
if (bkey.length > 16) bkey = binl_md5(bkey, key.length * 8);
const ipad: number[] = Array(16),
opad: number[] = Array(16);
for (let i = 0; i < 16; i++) {
ipad[i] = bkey[i] ^ 0x36363636;
opad[i] = bkey[i] ^ 0x5c5c5c5c;
}
const hash: number[] = binl_md5(ipad.concat(rstr2binl(data)), 512 + data.length * 8);
return binl2rstr(binl_md5(opad.concat(hash), 512 + 128));
}
/**
* 原始字符串转十六进制字符串
* @param input 输入字符串
* @returns 十六进制字符串
*/
function rstr2hex(input: string): string {
try {
hexcase;
} catch (e) {
hexcase = 0;
}
const hex_tab: string = hexcase ? '0123456789ABCDEF' : '0123456789abcdef';
let output = '';
let x: number;
for (let i = 0; i < input.length; i++) {
x = input.charCodeAt(i);
output += hex_tab.charAt((x >>> 4) & 0x0f) + hex_tab.charAt(x & 0x0f);
}
return output;
}
/**
* 原始字符串转 BASE64 字符串
* @param input 输入字符串
* @returns BASE64 字符串
*/
function rstr2b64(input: string): string {
try {
b64pad;
} catch (e) {
b64pad = '';
}
const tab: string = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
let output = '';
const len: number = input.length;
for (let i = 0; i < len; i += 3) {
const triplet: number =
(input.charCodeAt(i) << 16) |
(i + 1 < len ? input.charCodeAt(i + 1) << 8 : 0) |
(i + 2 < len ? input.charCodeAt(i + 2) : 0);
for (let j = 0; j < 4; j++) {
if (i * 8 + j * 6 > input.length * 8) output += b64pad;
else output += tab.charAt((triplet >>> (6 * (3 - j))) & 0x3f);
}
}
return output;
}
/**
* 原始字符串转任意编码字符串
* @param input 输入字符串
* @param encoding 编码表
* @returns 编码后的字符串
*/
function rstr2any(input: string, encoding: string): string {
const divisor: number = encoding.length;
let i: number, j: number, q: number, x: number, quotient: number[];
/* Convert to an array of 16-bit big-endian values, forming the dividend */
let dividend: number[] = Array(Math.ceil(input.length / 2));
for (i = 0; i < dividend.length; i++) {
dividend[i] = (input.charCodeAt(i * 2) << 8) | input.charCodeAt(i * 2 + 1);
}
/*
* Repeatedly perform a long division. The binary array forms the dividend,
* the length of the encoding is the divisor. Once computed, the quotient
* forms the dividend for the next step. All remainders are stored for later
* use.
*/
const full_length: number = Math.ceil((input.length * 8) / (Math.log(encoding.length) / Math.log(2)));
const remainders: number[] = Array(full_length);
for (j = 0; j < full_length; j++) {
quotient = [];
x = 0;
for (i = 0; i < dividend.length; i++) {
x = (x << 16) + dividend[i];
q = Math.floor(x / divisor);
x -= q * divisor;
if (quotient.length > 0 || q > 0) quotient[quotient.length] = q;
}
remainders[j] = x;
dividend = quotient;
}
/* Convert the remainders to the output string */
let output = '';
for (i = remainders.length - 1; i >= 0; i--) output += encoding.charAt(remainders[i]);
return output;
}
/**
* 字符串转 UTF-8 编码
* @param input 输入字符串
* @returns UTF-8 编码字符串
*/
function str2rstr_utf8(input: string): string {
let output = '';
let i = -1;
let x: number, y: number;
while (++i < input.length) {
/* Decode utf-16 surrogate pairs */
x = input.charCodeAt(i);
y = i + 1 < input.length ? input.charCodeAt(i + 1) : 0;
if (0xd800 <= x && x <= 0xdbff && 0xdc00 <= y && y <= 0xdfff) {
x = 0x10000 + ((x & 0x03ff) << 10) + (y & 0x03ff);
i++;
}
/* Encode output as utf-8 */
if (x <= 0x7f) output += String.fromCharCode(x);
else if (x <= 0x7ff) output += String.fromCharCode(0xc0 | ((x >>> 6) & 0x1f), 0x80 | (x & 0x3f));
else if (x <= 0xffff)
output += String.fromCharCode(0xe0 | ((x >>> 12) & 0x0f), 0x80 | ((x >>> 6) & 0x3f), 0x80 | (x & 0x3f));
else if (x <= 0x1fffff)
output += String.fromCharCode(
0xf0 | ((x >>> 18) & 0x07),
0x80 | ((x >>> 12) & 0x3f),
0x80 | ((x >>> 6) & 0x3f),
0x80 | (x & 0x3f)
);
}
return output;
}
/**
* 字符串转 UTF-16LE 编码
* @param input 输入字符串
* @returns UTF-16LE 编码字符串
*/
function str2rstr_utf16le(input: string): string {
let output = '';
for (let i = 0; i < input.length; i++)
output += String.fromCharCode(input.charCodeAt(i) & 0xff, (input.charCodeAt(i) >>> 8) & 0xff);
return output;
}
/**
* 字符串转 UTF-16BE 编码
* @param input 输入字符串
* @returns UTF-16BE 编码字符串
*/
function str2rstr_utf16be(input: string): string {
let output = '';
for (let i = 0; i < input.length; i++)
output += String.fromCharCode((input.charCodeAt(i) >>> 8) & 0xff, input.charCodeAt(i) & 0xff);
return output;
}
/**
* 原始字符串转小端字数组
* @param input 输入字符串
* @returns 小端字数组
*/
function rstr2binl(input: string): number[] {
const output: number[] = Array(input.length >> 2);
for (let i = 0; i < output.length; i++) output[i] = 0;
for (let i = 0; i < input.length * 8; i += 8) output[i >> 5] |= (input.charCodeAt(i / 8) & 0xff) << i % 32;
return output;
}
/**
* 小端字数组转字符串
* @param input 小端字数组
* @returns 字符串
*/
function binl2rstr(input: number[]): string {
let output = '';
for (let i = 0; i < input.length * 32; i += 8) output += String.fromCharCode((input[i >> 5] >>> i % 32) & 0xff);
return output;
}
/**
* 计算小端字数组的 MD5
* @param x 小端字数组
* @param len 位长度
* @returns MD5 结果数组
*/
function binl_md5(x: number[], len: number): number[] {
/* append padding */
x[len >> 5] |= 0x80 << len % 32;
x[(((len + 64) >>> 9) << 4) + 14] = len;
let a = 1732584193;
let b = -271733879;
let c = -1732584194;
let d = 271733878;
for (let i = 0; i < x.length; i += 16) {
const olda = a;
const oldb = b;
const oldc = c;
const oldd = d;
a = md5_ff(a, b, c, d, x[i + 0], 7, -680876936);
d = md5_ff(d, a, b, c, x[i + 1], 12, -389564586);
c = md5_ff(c, d, a, b, x[i + 2], 17, 606105819);
b = md5_ff(b, c, d, a, x[i + 3], 22, -1044525330);
a = md5_ff(a, b, c, d, x[i + 4], 7, -176418897);
d = md5_ff(d, a, b, c, x[i + 5], 12, 1200080426);
c = md5_ff(c, d, a, b, x[i + 6], 17, -1473231341);
b = md5_ff(b, c, d, a, x[i + 7], 22, -45705983);
a = md5_ff(a, b, c, d, x[i + 8], 7, 1770035416);
d = md5_ff(d, a, b, c, x[i + 9], 12, -1958414417);
c = md5_ff(c, d, a, b, x[i + 10], 17, -42063);
b = md5_ff(b, c, d, a, x[i + 11], 22, -1990404162);
a = md5_ff(a, b, c, d, x[i + 12], 7, 1804603682);
d = md5_ff(d, a, b, c, x[i + 13], 12, -40341101);
c = md5_ff(c, d, a, b, x[i + 14], 17, -1502002290);
b = md5_ff(b, c, d, a, x[i + 15], 22, 1236535329);
a = md5_gg(a, b, c, d, x[i + 1], 5, -165796510);
d = md5_gg(d, a, b, c, x[i + 6], 9, -1069501632);
c = md5_gg(c, d, a, b, x[i + 11], 14, 643717713);
b = md5_gg(b, c, d, a, x[i + 0], 20, -373897302);
a = md5_gg(a, b, c, d, x[i + 5], 5, -701558691);
d = md5_gg(d, a, b, c, x[i + 10], 9, 38016083);
c = md5_gg(c, d, a, b, x[i + 15], 14, -660478335);
b = md5_gg(b, c, d, a, x[i + 4], 20, -405537848);
a = md5_gg(a, b, c, d, x[i + 9], 5, 568446438);
d = md5_gg(d, a, b, c, x[i + 14], 9, -1019803690);
c = md5_gg(c, d, a, b, x[i + 3], 14, -187363961);
b = md5_gg(b, c, d, a, x[i + 8], 20, 1163531501);
a = md5_gg(a, b, c, d, x[i + 13], 5, -1444681467);
d = md5_gg(d, a, b, c, x[i + 2], 9, -51403784);
c = md5_gg(c, d, a, b, x[i + 7], 14, 1735328473);
b = md5_gg(b, c, d, a, x[i + 12], 20, -1926607734);
a = md5_hh(a, b, c, d, x[i + 5], 4, -378558);
d = md5_hh(d, a, b, c, x[i + 8], 11, -2022574463);
c = md5_hh(c, d, a, b, x[i + 11], 16, 1839030562);
b = md5_hh(b, c, d, a, x[i + 14], 23, -35309556);
a = md5_hh(a, b, c, d, x[i + 1], 4, -1530992060);
d = md5_hh(d, a, b, c, x[i + 4], 11, 1272893353);
c = md5_hh(c, d, a, b, x[i + 7], 16, -155497632);
b = md5_hh(b, c, d, a, x[i + 10], 23, -1094730640);
a = md5_hh(a, b, c, d, x[i + 13], 4, 681279174);
d = md5_hh(d, a, b, c, x[i + 0], 11, -358537222);
c = md5_hh(c, d, a, b, x[i + 3], 16, -722521979);
b = md5_hh(b, c, d, a, x[i + 6], 23, 76029189);
a = md5_hh(a, b, c, d, x[i + 9], 4, -640364487);
d = md5_hh(d, a, b, c, x[i + 12], 11, -421815835);
c = md5_hh(c, d, a, b, x[i + 15], 16, 530742520);
b = md5_hh(b, c, d, a, x[i + 2], 23, -995338651);
a = md5_ii(a, b, c, d, x[i + 0], 6, -198630844);
d = md5_ii(d, a, b, c, x[i + 7], 10, 1126891415);
c = md5_ii(c, d, a, b, x[i + 14], 15, -1416354905);
b = md5_ii(b, c, d, a, x[i + 5], 21, -57434055);
a = md5_ii(a, b, c, d, x[i + 12], 6, 1700485571);
d = md5_ii(d, a, b, c, x[i + 3], 10, -1894986606);
c = md5_ii(c, d, a, b, x[i + 10], 15, -1051523);
b = md5_ii(b, c, d, a, x[i + 1], 21, -2054922799);
a = md5_ii(a, b, c, d, x[i + 8], 6, 1873313359);
d = md5_ii(d, a, b, c, x[i + 15], 10, -30611744);
c = md5_ii(c, d, a, b, x[i + 6], 15, -1560198380);
b = md5_ii(b, c, d, a, x[i + 13], 21, 1309151649);
a = md5_ii(a, b, c, d, x[i + 4], 6, -145523070);
d = md5_ii(d, a, b, c, x[i + 11], 10, -1120210379);
c = md5_ii(c, d, a, b, x[i + 2], 15, 718787259);
b = md5_ii(b, c, d, a, x[i + 9], 21, -343485551);
a = safe_add(a, olda);
b = safe_add(b, oldb);
c = safe_add(c, oldc);
d = safe_add(d, oldd);
}
return [a, b, c, d];
}
/**
* 四种基本操作
*/
function md5_cmn(q: number, a: number, b: number, x: number, s: number, t: number): number {
return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s), b);
}
function md5_ff(a: number, b: number, c: number, d: number, x: number, s: number, t: number): number {
return md5_cmn((b & c) | (~b & d), a, b, x, s, t);
}
function md5_gg(a: number, b: number, c: number, d: number, x: number, s: number, t: number): number {
return md5_cmn((b & d) | (c & ~d), a, b, x, s, t);
}
function md5_hh(a: number, b: number, c: number, d: number, x: number, s: number, t: number): number {
return md5_cmn(b ^ c ^ d, a, b, x, s, t);
}
function md5_ii(a: number, b: number, c: number, d: number, x: number, s: number, t: number): number {
return md5_cmn(c ^ (b | ~d), a, b, x, s, t);
}
/**
* 安全整数加法,防止溢出
* @param x 整数
* @param y 整数
* @returns 加法结果
*/
function safe_add(x: number, y: number): number {
const lsw: number = (x & 0xffff) + (y & 0xffff);
const msw: number = (x >> 16) + (y >> 16) + (lsw >> 16);
return (msw << 16) | (lsw & 0xffff);
}
/**
* 左移位操作
* @param num 数字
* @param cnt 位数
* @returns 结果
*/
function bit_rol(num: number, cnt: number): number {
return (num << cnt) | (num >>> (32 - cnt));
}
/**
* 计算字符串的 MD5 值
* @param str 输入字符串
* @returns MD5 十六进制字符串
*/
function md5(str: string): string {
return hex_md5(str);
}
export default {
md5
};
@@ -0,0 +1,21 @@
import { type ComponentInternalInstance, getCurrentInstance } from 'vue';
export function parent(componentName?: string, _instance: ComponentInternalInstance | null | undefined = null) {
const instance = _instance || getCurrentInstance();
let parent = instance && (instance.parent as ComponentInternalInstance | null | undefined);
while (parent) {
const name = (parent.type as any)?.name as string | undefined;
if (name === componentName) {
return parent;
}
parent = parent.parent;
}
return null;
}
export function parentData(componentName?: string, _instance: ComponentInternalInstance | null | undefined = null) {
const instance = _instance || getCurrentInstance();
const findParent = parent(componentName, instance);
return findParent ? findParent.exposed : null;
}
@@ -0,0 +1,64 @@
/**
* 对象转url参数
* @param data 对象参数
* @param isPrefix 是否自动加上"?",默认true
* @param arrayFormat 数组参数格式,indices/brackets/repeat/comma,默认brackets
* @returns url参数字符串
*/
function queryParams(
data: Record<string, any> = {},
isPrefix: boolean = true,
arrayFormat: 'indices' | 'brackets' | 'repeat' | 'comma' = 'brackets'
): string {
const prefix = isPrefix ? '?' : '';
const _result: string[] = [];
if (!['indices', 'brackets', 'repeat', 'comma'].includes(arrayFormat)) arrayFormat = 'brackets';
for (const key in data) {
const value = data[key];
// 去掉为空的参数
if (['', undefined, null].includes(value)) {
continue;
}
// 如果值为数组,另行处理
if (Array.isArray(value)) {
// e.g. {ids: [1, 2, 3]}
switch (arrayFormat) {
case 'indices':
// 结果: ids[0]=1&ids[1]=2&ids[2]=3
for (let i = 0; i < value.length; i++) {
_result.push(`${key}[${i}]=${value[i]}`);
}
break;
case 'brackets':
// 结果: ids[]=1&ids[]=2&ids[]=3
value.forEach((_value: any) => {
_result.push(`${key}[]=${_value}`);
});
break;
case 'repeat':
// 结果: ids=1&ids=2&ids=3
value.forEach((_value: any) => {
_result.push(`${key}=${_value}`);
});
break;
case 'comma':
// 结果: ids=1,2,3
let commaStr = '';
value.forEach((_value: any) => {
commaStr += (commaStr ? ',' : '') + _value;
});
_result.push(`${key}=${commaStr}`);
break;
default:
value.forEach((_value: any) => {
_result.push(`${key}[]=${_value}`);
});
}
} else {
_result.push(`${key}=${value}`);
}
}
return _result.length ? prefix + _result.join('&') : '';
}
export default queryParams;
@@ -0,0 +1,16 @@
/**
* 生成指定范围的随机整数
* @param min 最小值(包含)
* @param max 最大值(包含)
* @returns 随机整数
*/
function random(min: number, max: number): number {
if (min >= 0 && max > 0 && max >= min) {
const gab = max - min + 1;
return Math.floor(Math.random() * gab + min);
} else {
return 0;
}
}
export default random;
@@ -0,0 +1,11 @@
/**
* 打乱数组顺序
* @param array 需要打乱的数组
* @returns 打乱后的新数组
*/
function randomArray<T>(array: T[] = []): T[] {
// 原理是sort排序,Math.random()产生0<= x < 1之间的数,会导致x-0.5大于或者小于0
return array.sort(() => Math.random() - 0.5);
}
export default randomArray;
@@ -0,0 +1,118 @@
/**
* 路由跳转方法,该方法相对于直接使用uni.xxx的好处是使用更加简单快捷
* 并且带有路由拦截功能
*/
interface RouterConfig {
type?: string;
url?: string;
delta?: number;
params?: Record<string, any>;
animationType?: string;
animationDuration?: number;
intercept?: boolean;
}
declare const uni: any; // 声明uni对象,避免ts报错
class Router {
config: RouterConfig;
// route: (options?: string | RouterConfig, params?: Record<string, any>) => Promise<void>;
constructor() {
// 原始属性定义
this.config = {
type: 'navigateTo',
url: '',
delta: 1, // navigateBack页面后退时,回退的层数
params: {}, // 传递的参数
animationType: 'pop-in', // 窗口动画,只在APP有效
animationDuration: 300, // 窗口动画持续时间,单位毫秒,只在APP有效
intercept: false // 是否需要拦截
};
// 因为route方法是需要对外赋值给另外的对象使用,同时route内部有使用this,会导致route失去上下文
// 这里在构造函数中进行this绑定
this.route = this.route.bind(this);
}
// 判断url前面是否有"/",如果没有则加上,否则无法跳转
addRootPath(url: string): string {
return url[0] === '/' ? url : `/${url}`;
}
// 整合路由参数
mixinParam(url: string, params: Record<string, any>): string {
url = url && this.addRootPath(url);
// 使用正则匹配,主要依据是判断是否有"/","?","="等,如“/page/index/index?name=mary"
// 如果有url中有get参数,转换后无需带上"?"
let query = '';
if (/.*\/.*\?.*=.*/.test(url)) {
// object对象转为get类型的参数
query = uni.$u.queryParams(params, false);
// 因为已有get参数,所以后面拼接的参数需要带上"&"隔开
return url + '&' + query;
} else {
// 直接拼接参数,因为此处url中没有后面的query参数,也就没有"?/&"之类的符号
query = uni.$u.queryParams(params);
return url + query;
}
}
/**
* 路由跳转主方法
* @param options 跳转配置或url字符串
* @param params 跳转参数
*/
async route(options: string | RouterConfig = {}, params: Record<string, any> = {}): Promise<void> {
let mergeConfig: RouterConfig = {};
if (typeof options === 'string') {
// 如果options为字符串,则为route(url, params)的形式
mergeConfig.url = this.mixinParam(options, params);
mergeConfig.type = 'navigateTo';
} else {
mergeConfig = uni.$u.deepMerge(this.config, options);
// 否则正常使用mergeConfig中的url和params进行拼接
mergeConfig.url = this.mixinParam(options.url || '', options.params || {});
}
if (params.intercept) {
this.config.intercept = params.intercept;
}
// params参数也带给拦截器
mergeConfig.params = params;
// 合并内外部参数
mergeConfig = uni.$u.deepMerge(this.config, mergeConfig);
// 判断用户是否定义了拦截器
if (uni.$u.routeIntercept && typeof uni.$u.routeIntercept === 'function') {
// 定一个promise,根据用户执行resolve(true)或者resolve(false)来决定是否进行路由跳转
const isNext = await new Promise<boolean>(resolve => {
uni.$u.routeIntercept(mergeConfig, resolve);
});
// 如果isNext为true,则执行路由跳转
isNext && this.openPage(mergeConfig);
} else {
this.openPage(mergeConfig);
}
}
// 执行路由跳转
openPage(config: RouterConfig): void {
const { url = '', type = '', delta = 1, animationDuration = 300 } = config;
if (type == 'navigateTo' || type == 'to') {
uni.navigateTo({ url, animationDuration });
}
if (type == 'redirectTo' || type == 'redirect') {
uni.redirectTo({ url });
}
if (type == 'switchTab' || type == 'tab') {
uni.switchTab({ url });
}
if (type == 'reLaunch' || type == 'launch') {
uni.reLaunch({ url });
}
if (type == 'navigateBack' || type == 'back') {
uni.navigateBack({ delta });
}
}
}
export default new Router().route;
@@ -0,0 +1,83 @@
import type { CSSProperties } from 'vue';
import trim from './trim';
import test from './test';
function isValidValue(value: any): boolean {
if (test.isEmpty(value)) return false;
if (typeof value === 'string') {
const trimmed = trim(value).toLowerCase();
return trimmed !== 'null' && trimmed !== 'undefined' && trimmed !== '';
}
return true;
}
/**
* 将 CSS 字符串解析为对象
*/
function cssStrToObj(str: string): object {
const result = {};
if (!isValidValue(str)) return result;
const styleStr = trim(String(str));
if (!isValidValue(styleStr)) return result;
const declarations = styleStr.split(';');
declarations.forEach(decl => {
const [prop, ...values] = decl.split(':').map(s => s.trim());
const value = values.join(':');
if (prop && value) {
const camelProp = prop.replace(/-([a-z])/g, (match, letter) => letter.toUpperCase());
(result as Record<string, string>)[camelProp] = value;
}
});
return result;
}
/**
* 将 CSS 对象转换为 CSS 字符串
*/
function cssObjToStr(obj: object): string {
if (!isValidValue(obj) || typeof obj !== 'object') return '';
return (
Object.entries(obj)
.map(([key, value]) => {
if (!isValidValue(value)) return '';
// 驼峰转短横线
const kebab = key.replace(/([A-Z])/g, '-$1').toLowerCase();
return `${kebab}: ${value}`;
})
.filter(Boolean)
.join('; ') + ';'
);
}
/**
* 合并多个 CSS 输入(对象或字符串),返回一个 CSS 对象
*/
export function mergeStyles(...args: (object | string)[]): CSSProperties {
const result = {} as Record<string, any>;
for (let i = 0; i < args.length; i++) {
const arg = args[i];
if (!isValidValue(arg)) return result as CSSProperties;
if (typeof arg === 'string') {
Object.assign(result, cssStrToObj(arg));
} else if (test.object(arg)) {
const cleanedObj: Record<string, any> = {};
Object.keys(arg).forEach(key => {
const value = (arg as Record<string, any>)[key];
if (isValidValue(value)) {
// 有效
cleanedObj[key] = value;
}
});
Object.assign(result, cleanedObj);
}
}
return result;
}
/**
* 合并为 CSS 字符串
*/
export function mergeCssStrings(...args: (object | string)[]): string {
const obj = mergeStyles(...args);
return cssObjToStr(obj);
}
@@ -0,0 +1,15 @@
/**
* 获取当前操作系统平台
* @returns 平台字符串,如 'ios'、'android'、'windows' 等
*/
export function os(): string {
return uni.getSystemInfoSync().platform;
}
/**
* 获取系统信息
* @returns uniapp 系统信息对象
*/
export function sys(): UniApp.GetSystemInfoResult {
return uni.getSystemInfoSync();
}
@@ -0,0 +1,289 @@
/**
* 验证电子邮箱格式
*/
function email(value: string): boolean {
return /[\w!#$%&'*+/=?^_`{|}~-]+(?:\.[\w!#$%&'*+/=?^_`{|}~-]+)*@(?:[\w](?:[\w-]*[\w])?\.)+[\w](?:[\w-]*[\w])?/.test(
value
);
}
/**
* 验证手机格式
*/
function mobile(value: string): boolean {
return /^1[3-9]\d{9}$/.test(value);
}
/**
* 验证URL格式
*/
function url(value: string): boolean {
return /http(s)?:\/\/([\w-]+\.)+[\w-]+(\/[\w-.\/?%&=]*)?/.test(value);
}
/**
* 验证日期格式
*/
function date(value: string): boolean {
return !/Invalid|NaN/.test(new Date(value).toString());
}
/**
* 验证ISO类型的日期格式
*/
function dateISO(value: string): boolean {
return /^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/.test(value);
}
/**
* 验证十进制数字
*/
function number(value: string): boolean {
return /^[\+-]?(\d+\.?\d*|\.\d+|\d\.\d+e\+\d+)$/.test(value);
}
/**
* 验证整数
*/
function digits(value: string): boolean {
return /^\d+$/.test(value);
}
/**
* 验证身份证号码
*/
function idCard(value: string): boolean {
return /^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X)$/.test(value);
}
/**
* 是否车牌号
*/
function carNo(value: string): boolean {
// 新能源车牌
const xreg =
/^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}(([0-9]{5}[DF]$)|([DF][A-HJ-NP-Z0-9][0-9]{4}$))/;
// 旧车牌
const creg =
/^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}[A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳]{1}$/;
if (value.length === 7) {
return creg.test(value);
} else if (value.length === 8) {
return xreg.test(value);
} else {
return false;
}
}
/**
* 金额,只允许2位小数
*/
function amount(value: string): boolean {
//金额,只允许保留两位小数
return /^[1-9]\d*(,\d{3})*(\.\d{1,2})?$|^0\.\d{1,2}$/.test(value);
}
/**
* 中文
*/
function chinese(value: string): boolean {
let reg = /^[\u4e00-\u9fa5]+$/gi;
return reg.test(value);
}
/**
* 只能输入字母
*/
function letter(value: string): boolean {
return /^[a-zA-Z]*$/.test(value);
}
/**
* 只能是字母或者数字
*/
function enOrNum(value: string): boolean {
//英文或者数字
let reg = /^[0-9a-zA-Z]*$/g;
return reg.test(value);
}
/**
* 验证是否包含某个值
*/
function contains(value: string, param: string): boolean {
return value.indexOf(param) >= 0;
}
/**
* 验证一个值范围[min, max]
*/
function range(value: number, param: [number, number]): boolean {
return value >= param[0] && value <= param[1];
}
/**
* 验证一个长度范围[min, max]
*/
function rangeLength(value: string, param: [number, number]): boolean {
return value.length >= param[0] && value.length <= param[1];
}
/**
* 是否固定电话
*/
function landline(value: string): boolean {
let reg = /^\d{3,4}-\d{7,8}(-\d{3,4})?$/;
return reg.test(value);
}
/**
* 判断是否为空
*/
function empty(value: any): boolean {
switch (typeof value) {
case 'undefined':
return true;
case 'string':
if (value.replace(/(^[ \t\n\r]*)|([ \t\n\r]*$)/g, '').length == 0) return true;
break;
case 'boolean':
if (!value) return true;
break;
case 'number':
if (0 === value || isNaN(value)) return true;
break;
case 'object':
if (null === value || value.length === 0) return true;
for (var i in value) {
return false;
}
return true;
}
return false;
}
/**
* 是否json字符串
*/
function jsonString(value: string): boolean {
if (typeof value == 'string') {
try {
var obj = JSON.parse(value);
if (typeof obj == 'object' && obj) {
return true;
} else {
return false;
}
} catch (e) {
return false;
}
}
return false;
}
/**
* 是否数组
*/
function array(value: any): boolean {
if (typeof Array.isArray === 'function') {
return Array.isArray(value);
} else {
return Object.prototype.toString.call(value) === '[object Array]';
}
}
/**
* 是否对象
*/
function object(value: any): boolean {
return Object.prototype.toString.call(value) === '[object Object]';
}
/**
* 是否短信验证码
*/
function code(value: string, len: number = 6): boolean {
return new RegExp(`^\\d{${len}}$`).test(value);
}
/**
* 是否函数方法
* @param {Object} value
*/
function func(value: any) {
return typeof value === 'function';
}
/**
* 是否promise对象
* @param {Object} value
*/
function promise(value: any) {
return object(value) && func(value.then) && func(value.catch);
}
/** 是否图片格式
* @param {Object} value
*/
function image(value: any) {
const newValue = value.split('?')[0];
const IMAGE_REGEXP = /\.(jpeg|jpg|gif|png|svg|webp|jfif|bmp|dpg)/i;
return IMAGE_REGEXP.test(newValue);
}
/**
* 是否视频格式
* @param {Object} value
*/
function video(value: any) {
const VIDEO_REGEXP = /\.(mp4|mpg|mpeg|dat|asf|avi|rm|rmvb|mov|wmv|flv|mkv|m3u8)/i;
return VIDEO_REGEXP.test(value);
}
/**
* 是否为正则对象
* @param {Object}
* @return {Boolean}
*/
function regExp(o: any) {
return o && Object.prototype.toString.call(o) === '[object RegExp]';
}
/**
* 验证字符串
*/
function string(value: any) {
return typeof value === 'string';
}
export default {
email,
mobile,
url,
date,
dateISO,
number,
digits,
idCard,
carNo,
amount,
chinese,
letter,
enOrNum,
contains,
range,
rangeLength,
empty,
isEmpty: empty,
jsonString,
landline,
object,
array,
code,
func,
promise,
video,
image,
regExp,
string
};
@@ -0,0 +1,31 @@
let timer: ReturnType<typeof setTimeout> | undefined;
let flag: boolean | undefined;
/**
* 节流原理:在一定时间内,只能触发一次
* @param func 要执行的回调函数
* @param wait 延时的时间,单位ms,默认500
* @param immediate 是否立即执行,默认true
* @returns void
*/
function throttle(func: () => void, wait: number = 500, immediate: boolean = true): void {
if (immediate) {
if (!flag) {
flag = true;
// 如果是立即执行,则在wait毫秒内开始时执行
typeof func === 'function' && func();
timer = setTimeout(() => {
flag = false;
}, wait);
}
} else {
if (!flag) {
flag = true;
// 如果是非立即执行,则在wait毫秒内的结束处执行
timer = setTimeout(() => {
flag = false;
typeof func === 'function' && func();
}, wait);
}
}
}
export default throttle;
@@ -0,0 +1,55 @@
// padStart 的 polyfill,因为某些机型或情况,还无法支持es7的padStart,比如电脑版的微信小程序
// 所以这里做一个兼容polyfill的兼容处理
if (!String.prototype.padStart) {
// 为了方便表示这里 fillString 用了ES6 的默认参数,不影响理解
String.prototype.padStart = function (this: string, maxLength: number, fillString: string = ' '): string {
if (Object.prototype.toString.call(fillString) !== '[object String]')
throw new TypeError('fillString must be String');
let str = this;
if (str.length >= maxLength) return String(str);
let fillLength = maxLength - str.length,
times = Math.ceil(fillLength / fillString.length);
while ((times >>= 1)) {
fillString += fillString;
if (times === 1) {
fillString += fillString;
}
}
return fillString.slice(0, fillLength) + str;
};
}
/**
* 时间格式化
* @param dateTime 时间戳、Date对象或null,默认当前时间
* @param fmt 格式化字符串,默认 'yyyy-mm-dd'
* @returns 格式化后的时间字符串
*/
function timeFormat(dateTime: number | string | Date | null = null, fmt: string = 'yyyy-mm-dd'): string {
// 如果为null,则格式化当前时间
if (!dateTime) dateTime = Number(new Date());
// 如果dateTime长度为10或者13,则为秒和毫秒的时间戳,如果超过13位,则为其他的时间格式
if (typeof dateTime === 'number' || typeof dateTime === 'string') {
if (dateTime.toString().length == 10) dateTime = Number(dateTime) * 1000;
}
const date = new Date(dateTime);
let ret: RegExpExecArray | null;
const opt: Record<string, string> = {
'y+': date.getFullYear().toString(), // 年
'm+': (date.getMonth() + 1).toString(), // 月
'd+': date.getDate().toString(), // 日
'h+': date.getHours().toString(), // 时
'M+': date.getMinutes().toString(), // 分
's+': date.getSeconds().toString() // 秒
// 有其他格式化字符需求可以继续添加,必须转化成字符串
};
for (const k in opt) {
ret = new RegExp('(' + k + ')').exec(fmt);
if (ret) {
fmt = fmt.replace(ret[1], ret[1].length == 1 ? opt[k] : opt[k].padStart(ret[1].length, '0'));
}
}
return fmt;
}
export default timeFormat;
@@ -0,0 +1,48 @@
import timeFormat from './timeFormat';
/**
* 时间戳转为多久之前
* @param dateTime 时间戳、Date对象或null,默认当前时间
* @param format 时间格式字符串或false,超出范围时返回指定格式,否则返回多久以前
* @returns 格式化后的时间字符串
*/
function timeFrom(dateTime: number | string | Date | null = null, format: string | false = 'yyyy-mm-dd'): string {
// 如果为null,则格式化当前时间
if (!dateTime) dateTime = Number(new Date());
// 如果dateTime长度为10或者13,则为秒和毫秒的时间戳,如果超过13位,则为其他的时间格式
if (typeof dateTime === 'number' || typeof dateTime === 'string') {
if (dateTime.toString().length == 10) dateTime = Number(dateTime) * 1000;
}
const timestamp = +new Date(Number(dateTime));
const timer = (Number(new Date()) - timestamp) / 1000;
// 如果小于5分钟,则返回"刚刚",其他以此类推
let tips = '';
switch (true) {
case timer < 300:
tips = '刚刚';
break;
case timer >= 300 && timer < 3600:
tips = parseInt(String(timer / 60)) + '分钟前';
break;
case timer >= 3600 && timer < 86400:
tips = parseInt(String(timer / 3600)) + '小时前';
break;
case timer >= 86400 && timer < 2592000:
tips = parseInt(String(timer / 86400)) + '天前';
break;
default:
// 如果format为false,则无论什么时间戳,都显示xx之前
if (format === false) {
if (timer >= 2592000 && timer < 365 * 86400) {
tips = parseInt(String(timer / (86400 * 30))) + '个月前';
} else {
tips = parseInt(String(timer / (86400 * 365))) + '年前';
}
} else {
tips = timeFormat(timestamp, format as string);
}
}
return tips;
}
export default timeFrom;
@@ -0,0 +1,14 @@
/**
* 显示无图标的 Toast 提示
* @param title 提示文本
* @param option 显示时长(毫秒)默认1500 /显示图标,默认为none,
*/
function toast(title: string, option: number | string | Record<string, any> = 1500): void {
uni.showToast({
title: title,
icon: typeof option === 'string' ? option : typeof option === 'object' ? option.icon || 'none' : 'none',
duration: typeof option === 'number' ? option : typeof option === 'object' ? option.duration || '1500' : 1500
});
}
export default toast;
@@ -0,0 +1,21 @@
/**
* 去除字符串空格
* @param str 输入字符串
* @param pos 去除位置,'both' | 'left' | 'right' | 'all',默认'both'
* @returns 去除空格后的字符串
*/
function trim(str: string, pos: 'both' | 'left' | 'right' | 'all' = 'both'): string {
if (pos === 'both') {
return str.replace(/^\s+|\s+$/g, '');
} else if (pos === 'left') {
return str.replace(/^\s*/, '');
} else if (pos === 'right') {
return str.replace(/(\s*$)/g, '');
} else if (pos === 'all') {
return str.replace(/\s+/g, '');
} else {
return str;
}
}
export default trim;
@@ -0,0 +1,38 @@
import type { ThemeType } from '../../types/global';
/**
* 根据主题type值,获取对应的图标
* @param type 主题名称, primary|info|error|warning|success,默认success
* @param fill 是否使用fill填充实体的图标,默认false
* @returns 图标名称字符串
*/
function type2icon(type: ThemeType = 'success', fill: boolean = false): string {
// 如果非预置值,默认为success
if (!['primary', 'info', 'error', 'warning', 'success'].includes(type)) type = 'success';
let iconName = '';
// 目前(2019-12-12),info和primary使用同一个图标
switch (type) {
case 'primary':
iconName = 'info-circle';
break;
case 'info':
iconName = 'info-circle';
break;
case 'error':
iconName = 'close-circle';
break;
case 'warning':
iconName = 'error-circle';
break;
case 'success':
iconName = 'checkmark-circle';
break;
default:
iconName = 'checkmark-circle';
}
// 是否是实体类型,加上-fill,在icon组件库中,实体的类名是后面加-fill的
if (fill) iconName += '-fill';
return iconName;
}
export default type2icon;