2025-04-24 17:03:28 +08:00

761 lines
22 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 數據工具類
* @author BrightLi
* @since 2020/5/3
*/
import Report from "../net/Report";
import SKLogger from "./SKLogger";
export default class SKDataUtil {
// 生成UUID
static uuid(): string {
if (typeof (window) !== "undefined" && typeof (window.crypto) !== "undefined" && typeof (window.crypto.getRandomValues) !== "undefined") {
let buf: Uint16Array = new Uint16Array(8);
window.crypto.getRandomValues(buf);
return (this.pad4(buf[0]) + this.pad4(buf[1]) + "-" + this.pad4(buf[2]) + "-" + this.pad4(buf[3]) + "-" + this.pad4(buf[4]) + "-" + this.pad4(buf[5]) + this.pad4(buf[6]) + this.pad4(buf[7]));
} else {
return this.random4() + this.random4() + "-" + this.random4() + "-" + this.random4() + "-" +
this.random4() + "-" + this.random4() + this.random4() + this.random4();
}
}
private static pad4(num: number): string {
let ret: string = num.toString(16);
while (ret.length < 4) {
ret = "0" + ret;
}
return ret;
}
private static random4(): string {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
// 隨機數
static random(min: number, max: number): number {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
// 從數組中隨機抽取一項
static randomList(value: any[]): any {
if (value == null || value.length < 1) {
return null;
}
let index = this.random(0, value.length - 1);
return value[index];
}
// 替換指定位置的字符
static replaceChar(value: string, index: number, char: string): string {
return value.substr(0, index) + char + value.substr(index + char.length);
}
// 從前查找相同字符出現的最後一個位置
static sameIndex(value: string, char: string): number {
var result: number = -1;
for (var i = 0; i < value.length; i++) {
if (value[i] == char) {
if (result == -1) {
result = i;
} else {
result++;
}
} else if (result != -1) {
break;
}
}
return result;
}
// 從後查找相同字符出現的最後一個位置
static sameLastIndex(value: string, char: string): number {
var result: number = -1;
for (var i = value.length - 1; i > 0; i--) {
if (value[i] == char) {
if (result == -1) {
result = i;
} else {
result--;
}
} else if (result != -1) {
break;
}
}
return result;
}
// 格式化數字不足位補0
static prefixInteger(num: number, length: number): string {
let result = (Array(length).join('0') + num).slice(-length);
return result;
}
// "YYYY-mm-dd HH:MM"
static formatDate(format: string, value: string | number): string {
let now = new Date(value);
let opt: any = {
"Y+": now.getFullYear().toString(),
"m+": (now.getMonth() + 1).toString(),
"d+": now.getDate().toString(),
"H+": now.getHours().toString(),
"M+": now.getMinutes().toString(),
"S+": now.getSeconds().toString(),
}
let result: RegExpExecArray;
for (let key in opt) {
result = new RegExp(`(${key})`).exec(format);
if (result) {
format = format.replace(result[1], (result[1].length == 1) ? (opt[key]) : (opt[key].padStart(result[1].length, "0")));
}
}
return format;
}
// 根據ID生成5位邀請碼
static encodeInvite(value: number) {
let key = 'E50CDG3HQA4B1NOPIJ2RSTUV67MWX89KLYZ';
let result = "";
while (value > 0) {
let mod = value % 35;
value = (value - mod) / 35;
result = key[mod] + result;
}
if (result.length < 5) {
result = "F" + result;
let len = result.length;
let min = 0;
let max = key.length - 1;
for (let i = len; i < 5; i++) {
let index = Math.floor(Math.random() * (max - min + 1)) + min;
result = key[index] + result;
}
}
return result;
}
// 解碼邀請碼
static decodeInvite(value: string): number {
let key = 'E50CDG3HQA4B1NOPIJ2RSTUV67MWX89KLYZ';
let index = value.indexOf("F");
if (index != -1) {
value = value.slice(index + 1, value.length);
}
let result = 0;
let p = 0;
for (let i = value.length - 1; i >= 0; i--) {
let char = value[i];
index = key.indexOf(char);
if (index != -1) {
result += index * Math.pow(35, p);
p++;
}
}
return result;
}
static numberOfList(list: number[], index: number, valid: number): number {
if (list == null || !SKDataUtil.isArray(list) || list.length < index) {
return valid;
}
return list[index];
}
// 從數組中獲得匹配
static numberByList(list: any[], key: string, value: number, targetKey: string): any {
for (let item of list) {
let temp = item[key];
if (!temp) {
continue;
}
if (temp == value) {
let result = item[targetKey];
return result;
}
}
return null;
}
// 是否存在KV
static hasKVByList(list: any[], key: string, value: any): boolean {
for (let item of list) {
if (item[key] == value) {
return true;
}
}
return false;
}
// 是否有互斥
static hasMutex(target: any[], mutex: any[], mutexKey: string, key: string): boolean {
for (let item of target) {
let temp = item[key];
if (!temp) {
continue;
}
// 如果是互斥數組
if (Array.isArray(temp)) {
for (let b of mutex) {
for (let a of temp) {
if (item[key] == b) {
return true;
}
}
}
} else {
for (let b of mutex) {
if (temp == b) {
return true;
}
}
}
}
return false;
}
// 獲得二維數組中的數值
static intOf2Array(list: number[][], one: number, two: number, valid: number): number {
if (!list) {
return valid;
}
if (one < 0 || one >= list.length) {
return valid;
}
let temp = list[one] as number[];
if (!temp) {
return valid;
}
if (two < 0 || two >= temp.length) {
return valid;
}
return temp[two];
}
// 根據概率返回真假
static probability(min: number, max: number): boolean {
let result = SKDataUtil.random(min, max);
if (result <= min) {
return true;
} else {
return false;
}
}
static textAssetByList(value: any[], index: number): cc.TextAsset {
if (value == null || index >= value.length) {
return null;
}
let result: cc.TextAsset = <cc.TextAsset>value[index];
return result;
}
static prefabByList(value: any[], index: number): cc.Prefab {
if (value == null || index >= value.length) {
return null;
}
let result: cc.Prefab = <cc.Prefab>value[index];
return result;
}
static atlasByList(value: any[], index: number): cc.SpriteAtlas {
if (value == null || index >= value.length) {
return null;
}
let result: cc.SpriteAtlas = <cc.SpriteAtlas>value[index];
return result;
}
static jsonAssetBy(value: any): cc.JsonAsset {
let result: cc.JsonAsset = <cc.JsonAsset>value;
return result;
}
static jsonAssetByList(value: any[], index: number): cc.JsonAsset {
if (value == null || index >= value.length) {
return null;
}
let result: cc.JsonAsset = <cc.JsonAsset>value[index];
return result;
}
static dictByDict(target: any, sheet: any, key: any): any {
if (!target) {
return null;
}
let dict = target[sheet];
if (!dict) {
return null;
}
let result = dict[key];
return result;
}
static findByDict(target: any, targetKey: any, value: any, resultKey: any, valid: any): any {
if (!target) {
return valid;
}
for (let key in target) {
let item = target[key];
if (item[targetKey] == value) {
return item[resultKey];
}
}
return valid;
}
static numberByString(target: string, splitter: string, index: number, valid: number = 0): number {
if (!target) {
return valid;
}
let temp = target.split(splitter);
if (temp.length < index) {
return valid;
}
let result = parseInt(temp[index]);
return result;
}
// JSON解析
static jsonBy(value: any): any {
if (value == null) {
return null;
}
if (!this.isString(value)) {
return value;
}
if (this.isEmptyString(value)) {
return null;
}
let result: any = null;
try {
result = JSON.parse(value);
} catch (error) {
SKLogger.warn(`JSON解析:${value},失敗:${error}`);
} finally {
return result;
}
}
// 轉換成JSON字符串
static toJson(value: any): string {
if (value == null) {
return "";
}
if (this.isString(value)) {
return value;
}
let result: string = "";
try {
result = JSON.stringify(value);
} catch (error) {
SKLogger.warn(`JSON轉換:${value},失敗:${error}`);
} finally {
return result;
}
}
// 是否為數字
static isNumber(value: any): boolean {
if (value == null) {
return false;
}
let type = typeof value;
if (type !== 'number') {
return false;
}
return true;
}
// 轉換成數字
static toNumber(value: any): number {
if (this.isNumber(value)) {
return value;
}
if (value == "undefined") {
return NaN;
}
if (this.isString(value)) {
let result = parseFloat(value);
return result;
}
return NaN;
}
// 版本號比較
static checkVersion(versionA: string, versionB: string): number {
let vA = versionA.split('.');
let vB = versionB.split('.');
for (let i = 0; i < vA.length; ++i) {
let a = parseInt(vA[i]);
let b = parseInt(vB[i] || '0');
if (a === b) {
continue;
} else {
return a - b;
}
}
if (vB.length > vA.length) {
return -1;
}
return 0;
}
// 是否為數組
static isArray(value: any): boolean {
if (value == null) {
return false;
}
let result = Array.isArray(value);
return result;
}
// 是否為對象
static isObject(value: any): boolean {
if (value == null) {
return false;
}
let type = typeof value;
if (type === "object") {
return true;
}
return false;
}
// 是否為字符串
static isString(value: any): boolean {
if (value == null) {
return false;
}
let result = typeof (value);
if (result == "string") {
return true;
}
return false;
}
// 是否為空字符串
static isEmptyString(value: any): boolean {
if (value == null) {
return true;
}
if (!this.isString(value)) {
return true;
}
let result = <string>value;
if (result.length < 1) {
return true;
}
return false;
}
// 正則測試
static testRegExp(value: string, regexp: RegExp, min: number, max: number, tip: string) {
if (value.length < min) {
return `長度不能少於${min}`;
}
if (value.length > max) {
return `最多只能${max}`;
}
let temp = regexp.test(value);
if (!temp) {
return tip;
}
return "";
}
// 檢查合法性
static checkSymbol(value: string, min: number, max: number, tip: string): string {
if (value.length < min) {
return `長度不能少於${min}`;
}
if (value.length > max) {
return `最多只能${max}`;
}
let regexp = new RegExp("[\r\n\t]");
let check = regexp.test(value);
if (check) {
return `不能有回車或製表符!`;
}
regexp = new RegExp("[`~!@#$^&*()=|{}':;',\\[\\].<>/?~@#¥……&*()——|{}【】‘;:”“'。,、?]");
check = regexp.test(value);
if (check) {
return tip;
}
return "";
}
// 正則測試
static testNotRegExp(value: string, regexp: RegExp, min: number, max: number, tip: string) {
if (value.length < min) {
return `長度不能少於${min}`;
}
if (value.length > max) {
return `最多只能${max}`;
}
let temp = regexp.test(value);
if (temp) {
return tip;
}
return "";
}
// 獲得鍵值
static valueForKey(target: any, key: any): any {
if (target == null) {
cc.warn(`$警告:獲得鍵值,對像不存在`);
return null;
}
if (key == null) {
cc.warn(`$警告:獲得鍵值,對象${target}KEY值不存在`);
return null;
}
if (this.isArray(target)) {
let result = this.getItemByList(target, key);
return result;
}
if (!this.isObject(target)) {
cc.warn(`$警告:獲得鍵值,${target}[${key}]不是一個對象`);
return null;
}
let temp: Object = <Object>target;
if (!temp.hasOwnProperty(key)) {
cc.warn(`$警告:獲得鍵值,${target.toString()}[${key}]不存在`);
return null;
}
return temp[key];
}
// 檢查是否合法
static checkValid(value: string): string {
if (value == null || value.length < 1) {
return "不能為空!";
}
let temp = value.trim();
if (value.length != temp.length) {
return "兩側不能有空格";
}
let list = [{ char: "\"", text: "雙引號" },
{ char: "\'", text: "單引號" }];
for (let item of list) {
if (value.indexOf(item.char) != -1) {
return `不能包含${item.text}`;
}
}
return "";
}
static trim(value: string): string {
if (value == null || value.length < 1) {
return "";
}
value = value.trim();
return value;
}
// 獲得最大值經驗值
static maxExp(config: any, exp: number): any {
let result = exp;
for (let item of config) {
if (exp < item.exp) {
result = item.exp;
break;
}
}
return result;
}
// 獲得當前階段的經驗值與最大值
static currentExp(config: any, exp: number): any {
let result: any = { value: 0, max: 0 };
for (let item of config) {
let level = item.level;
let max_exp = item.exp;
if (exp < max_exp) {
if (level > 1) {
let prev = config[`${level - 1}`];
result.value = exp - prev.exp;
result.max = max_exp - prev.exp;
} else {
result.value = exp;
result.max = max_exp;
}
break;
}
}
return result;
}
// 是否有屬性
static hasProperty(target: any, key: string | number | symbol): boolean {
if (target == null) {
return false;
}
if (!this.isObject(target)) {
return false;
}
let result = <Object>target;
if (result.hasOwnProperty(key)) {
return true;
}
return false;
}
// 獲得字典長度
static getLength(value: any): number {
if (value == null) {
return 0;
}
if (Array.isArray(value)) {
return value.length;
}
let length = 0;
for (let _key in value) {
length++;
}
return length;
}
// 對鍵求和
static getKeyTotal(value: any): number {
let total: number = 0;
for (let key in value) {
let count = parseInt(key);
total += count;
}
return total;
}
/**
* 深度拷貝
* @param value
* @returns
*/
static clone(value: any): any {
if (value == null) {
return null;
}
let result: any = Array.isArray(value) ? [] : {};
try {
result = JSON.parse(JSON.stringify(value));
} catch (error) {
let info = `警告:深拷貝對象:${value},失敗:${error}`
Report.report(info);
} finally {
return result;
}
}
// 克隆類實例
static cloneClass(value: any): any {
if (value == null) {
return null;
}
let result = Object.create(
Object.getPrototypeOf(value),
Object.getOwnPropertyDescriptors(value)
);
return result;
}
// 是否在範圍內
static atRange(target: number, range: number[]): boolean {
for (let item of range) {
if (target == item) {
return true;
}
}
return false;
}
static clamp(value: number, min: number, max: number): number {
if (value < min) {
return min;
}
if (value > max) {
return max;
}
return value;
}
// 將大數字單位轉化成 千、萬、千萬、億
static transform(value: number): string {
if (value == null) {
return "";
}
value = SKDataUtil.toNumber(value);
if (isNaN(value)) {
return "";
}
let newValue = ['', '', ''];
let fr = 1000;
const ad = 1;
let num = 3;
const fm = 1;
while (value / fr >= 1) {
fr *= 10;
num += 1;
}
if (num <= 4) { // 千
newValue[1] = '千';
newValue[0] = `${this.toDecimal1(value / 1000)}`;
} else if (num <= 8) { // 萬
const text1 = `${(num - 4) / 3 > 1 ? '千萬' : '萬'}`;
// tslint:disable-next-line:no-shadowed-variable
const fm = '萬' === text1 ? 10000 : 10000000;
newValue[1] = text1;
newValue[0] = `${this.toDecimal1(value / fm)}`;
} else if (num <= 16) {// 億
let text1 = (num - 8) / 3 > 1 ? '千億' : '億';
text1 = (num - 8) / 4 > 1 ? '萬億' : text1;
text1 = (num - 8) / 7 > 1 ? '千萬億' : text1;
// tslint:disable-next-line:no-shadowed-variable
let fm = 1;
if ('億' === text1) {
fm = 100000000;
} else if ('千億' === text1) {
fm = 100000000000;
} else if ('萬億' === text1) {
fm = 1000000000000;
} else if ('千萬億' === text1) {
fm = 1000000000000000;
}
newValue[1] = text1;
newValue[0] = `${this.toDecimal1(value / fm)}`;
}
if (value < 1000) {
newValue[1] = '';
newValue[0] = `${value}`;
}
let result = newValue.join('');
return result;
}
// 獲得數組元素
static getItemByList(list: any, index: number): any {
if (!this.isArray(list)) {
return null;
}
if (index < 0 || index >= list.length) {
return null;
}
let result = list[index];
return result;
}
// 獲得對像元素
static getItemByMap(obj: any, index: number): any {
if (index < 0) {
return null;
}
if (!this.isObject(obj)) {
return null;
}
let i = 0;
for (let key in obj) {
if (i == index) {
return obj[key];
}
i++;
}
}
// 保留1位小數
static toDecimal1(value: number): number {
let temp = this.toNumber(value);
if (isNaN(temp)) {
return 0;
}
temp = Math.floor(value * 10) / 10;
return temp;
}
// 保留2位小數
static toDecimal2(value: number): number {
let temp = this.toNumber(value);
if (isNaN(temp)) {
return 0;
}
temp = Math.floor(value * 100) / 100;
return temp;
}
// 是否為數字字符串
static isNumberString(value: string): boolean {
var regPos = /^\d+$/; //非負浮點數
if (regPos.test(value)) {
return true;
} else {
return false;
}
}
// 獲得距離
static distance(source: any, target: any): number {
if (source == null || target == null) {
return 0;
}
let sx = (source.x != null ? source.x : 0);
let sy = (source.y != null ? source.y : 0);
let tx = (target.x != null ? target.x : 0);
let ty = (target.y != null ? target.y : 0);
let xd = Math.abs(sx - tx);
let yd = Math.abs(sy - ty);
let result = Math.pow(xd * xd + yd * yd, 0.5);
return result;
}
}