Files
Monopoly/assets/res-native/tools/Tools.ts
2026-03-30 09:39:59 +08:00

633 lines
20 KiB
TypeScript
Raw 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.
import { _decorator, SpriteFrame, Node, Sprite, instantiate, Label, Prefab, Color, Size, UITransform, RichText, Component, Texture2D, ImageAsset } from 'cc';
import { httpRequest } from '../network/HttpRequest';
import { app } from '../../app/app';
const { ccclass } = _decorator;
//@ts-ignore
import CryptoJS from "crypto-js";
import { LabelConfig } from '../setting/LabLanguage';
import { LANG_SET } from '../setting/LangSet';
type ConstructorOf<T> = new (...args: any[]) => T;
// 定义数据接口
interface DataObject {
[key: string]: string | number | boolean;
}
/** 事件名称 */
export enum EventType {
/** 刷新玩家数据 */
RefreshUserInfo = "RefreshUserInfo",
/** 刷新玩家数据成功 */
RefreshUserInfoSuccess = "RefreshUserInfoSuccess",
}
// 定义阈值和对应的num值
const sizeThresholds = [
{ size: 20000000, num: 100 },
{ size: 19000000, num: 95 },
{ size: 18000000, num: 90 },
{ size: 17000000, num: 85 },
{ size: 16000000, num: 80 },
{ size: 15000000, num: 75 },
{ size: 14000000, num: 70 },
{ size: 13000000, num: 65 },
{ size: 12000000, num: 60 },
{ size: 11000000, num: 55 },
{ size: 10000000, num: 50 },
{ size: 9000000, num: 45 },
{ size: 8000000, num: 40 },
{ size: 7000000, num: 35 },
{ size: 6000000, num: 30 },
{ size: 5000000, num: 25 },
{ size: 4000000, num: 20 },
{ size: 3000000, num: 15 },
{ size: 2000000, num: 10 },
{ size: 1000000, num: 5 },
{ size: 500000, num: 2.5 },
{ size: 250000, num: 1.5 }
];
/** 用户代理等级和图片对照表 */
const AGENT_LV_TO_PIC = {
["等级青铜"] : "a",
["等级白银"] : "b",
["等级黄金"] : "c",
["等级VIP"] : "d",
["等级超级VIP"] : "e"
}
/** 通用工具类 */
@ccclass('Tools')
export class Tools
{
//#region Sprite相关操作
/** 设置节点的SpriteFrame 默认从resource/ui下面找 */
static SetSpriteFrame(node:Node, uiname:string, uipath:string, fuc:Function = null) {
app.manager.loader.load({
bundle: "", // 不传入bundle默认为resources
path: 'xxx/xxxx',
type: SpriteFrame,
onComplete(asset){
if (node){
let c = node.getComponent(Sprite)
if (c) c.spriteFrame = asset
if (fuc) fuc()
}
}
})
}
/** 加载远程图片 */
static remoteLoadSprite(url: string, node: Sprite, fuc?: Function) {
app.manager.loader.loadRemote( {
url: url,
ext: null,
onComplete: (result: ImageAsset | null) => {
if (result) {
if(node) {
let sp = new SpriteFrame()
let tex = new Texture2D();
tex.image = result;
sp.texture = tex
node.spriteFrame = sp
if (fuc) fuc()
}
}
}
})
}
/** 设置子节点uisprite */
static SetChildSprite(node:Node, path:string, uiname:string, uipath:string, fuc:Function = null) {
let n = node.getChildByPath(path)
if (n) this.SetSpriteFrame(n, uiname, uipath, fuc)
}
/**
* 设置艺术字
* itemPath:字体预制件位置, numPath:字体图片位置, pre:图片名前缀
*/
// static SetArtNums(str:any, item:Node, itemPath:string = "ui/numbers/num_Item", numPath:string = "ui/numbers", pre:string = "numbers_0") {
// str = String(str)
// item.destroyAllChildren()
// let m_resLoader = new ResLoader();
// m_resLoader.loadPrefabNode(itemPath, (prefab:Node) => {
// console.log(itemPath, prefab)
// // 根据字符串长度,显示对应数量的数字图片
// for (let i = 0; i < str.length; i++) {
// let n = Tools.AddChild(item, prefab, "num_" + i)
// n.active = true
// this.SetSpriteFrame(n, pre + (str[i] == "." ? "dot" : str[i]), numPath)
// }
// })
// }
//#endregion
//#region http相关
/** 发送http请求 */
static httpReq(str:string, param:any, callback:Function, failCallBack:Function = null) {
httpRequest.post("/api/" + str, param, callback, failCallBack)
}
//#endregion
//#region Node 相关
/** 添加子节点 */
static AddChild(node:Node, prefab:Node|Prefab, name:string = null):Node {
var n = instantiate(prefab as Node)
node.addChild(n)
if (name) n.name = name
return n
}
/** 设置子节点显示 */
static ActChild(node:Node, path:string, act:boolean)
{
let n = node.getChildByPath(path)
if (n) n.active = act
}
/** 获取子节点组件 */
static GetChildComp<T extends Component>(node: Node, path: string, compClass: ConstructorOf<T>): T {
return node.getChildByPath(path)?.getComponent(compClass);
}
//#endregion
//#region Text 相关
/** 设置子节点文字 */
static SetText(node:Node, str:string) {
str = String(str)
node.getComponent(Label).string = str
}
/** 设置子节点文字 */
static SetChildText(node:Node, path:string, str:string) {
str = String(str)
let n = node.getChildByPath(path)
n.getComponent(Label).string = str
}
/** 设置子节点文字 */
static SetChildRichText(node:Node, path:string, str:string) {
str = String(str)
let n = node.getChildByPath(path)
n.getComponent(RichText).string = str
}
/** 设置节点文字颜色 */
static SetLabColor(node:Node, str:string) {
node.getComponent(Label).color = Color.fromHEX(new Color(), str)
}
/** 设置子节点字体颜色 */
static SetChildLabColor(node:Node, path:string, str:string) {
let n = node.getChildByPath(path)
n.getComponent(Label).color = Color.fromHEX(new Color(), str)
}
/** 设置节点大小 */
static SetSize(node:Node, size:Size) {
node.getComponent(UITransform).setContentSize(size);
}
/** 设置节点触摸事件 */
static SetTouchEndEvt(node:Node, func:Function) {
node.off(Node.EventType.TOUCH_END)
node.on(Node.EventType.TOUCH_END, func)
}
/** 设置子节点触摸事件 */
static SetChildTouchEndEvt(node:Node, path:string, func:Function) {
let n = node.getChildByPath(path)
if (n) this.SetTouchEndEvt(n, func)
}
//#endregion
//#region 字符串操作
/**
** 替换占位符{0},{1}....
** 例子: const message = Tools.stringFormat("My name is {0} and I am {1} years old.", name, age);
*/
static StringFormat(format: string, ...args: any[]): string {
return format.replace(/{(\d+)}/g, (match, index) => {
return typeof args[index] !== "undefined" ? args[index] : match;
});
}
static StringLFormat(format: string, ...args: any[]): string {
format = this.GetLocalized(format)
return this.StringFormat(format, ...args)
}
//#endregion
//#region 其他
/** 本地化 */
static GetLocalized(str : string) {
//return str
return LabelConfig[str] ? LabelConfig[str][LANG_SET.langIndex] : str
}
/** 压缩上传图片成base64 */
static CompressImageToBase64(param:any, callback:Function = null){
var fileList = param.files[0];
var reader = new FileReader();
reader.readAsDataURL(fileList);
reader.onload = (event) => {
let image = new Image() //新建一个img标签还没嵌入DOM节点)
var dataImg = event.target.result;
var num = 1;
//@ts-ignore
image.src = event.target.result
image.onload = () => {
//由于不能将太多Base64字符给服务端发过于咱们压缩一下
//如果想支持更大图片,请继续加判断,增加除数
// 检查文件大小是否超过20M
if (fileList.size > 20000000) {
console.log("文件大小不能大于20M");
param.value = '';
return;
}
// 根据文件大小设置num值
for (const threshold of sizeThresholds) {
if (fileList.size > threshold.size) {
num = threshold.num;
break;
}
}
let canvas = document.createElement('canvas');
let context = canvas.getContext('2d');
let imageWidth = image.width / num; //压缩后图片的大小
let imageHeight = image.height / num;
const minSize = 640; // 设置最小尺寸
if (imageWidth < minSize || imageHeight < minSize) {
const scaleFactor = Math.max(minSize / imageWidth, minSize / imageHeight);
imageWidth *= scaleFactor;
imageHeight *= scaleFactor;
}
canvas.width = imageWidth;
canvas.height = imageHeight;
context.drawImage(image, 0, 0, imageWidth, imageHeight);
dataImg = canvas.toDataURL('image/png');
//此时的dataImg就是你要上传给服务器的字符
param.value = '';
if (callback) callback(dataImg)
return dataImg;
}
};
}
/** 生成二维码 */
// static SetQRCode(text: string, item : Node){
// const qr = QRCode(0, 'M');
// qr.addData(text);
// qr.make();
// const dataUrl = qr.createDataURL(4, 4);
// const img = new Image();
// img.src = dataUrl;
// assetManager.loadRemote(dataUrl, {ext : '.png'}, (err, imgAsset: ImageAsset) => {
// if (err) {
// console.error(err.message || err);
// return;
// }
// const sp = new SpriteFrame()
// const tx = new Texture2D()
// tx.image = imgAsset
// sp.texture = tx
// item.getComponent(Sprite).spriteFrame = sp
// })
// }
/** 通过base64字符串设置图片 */
static SetBase64Pic(src: string, node: Node): void {
let image = new Image()
image.src = src // base 64是string看后端返回是二进制是否带头data:image/png;base64, 不带要手动添加
image.onload = () => {
let texture = new Texture2D()
texture.image = new ImageAsset(image)
let _frame = new SpriteFrame()
_frame.texture = texture
// 获取节点的容器
let c = node.getComponent(Sprite)
if (c) c.spriteFrame = _frame
}
}
/** 复制到剪切板 */
static CopyToClipboard(str:string) {
var input = str + '';
const el = document.createElement('textarea');
el.value = input;
el.setAttribute('readonly', '');
el.style.contain = 'strict';
el.style.position = 'absolute';
el.style.left = '-9999px';
el.style.fontSize = '12pt'; // Prevent zooming on iOS
const selection = getSelection();
var originalRange = null;
if (selection.rangeCount > 0) {
originalRange = selection.getRangeAt(0);
}
document.body.appendChild(el);
el.select();
el.selectionStart = 0;
el.selectionEnd = input.length;
var success = false;
try {
success = document.execCommand('copy');
} catch (err) {}
document.body.removeChild(el);
if (originalRange) {
selection.removeAllRanges();
selection.addRange(originalRange);
}
app.manager.ui.showToast("复制成功")
return success;
}
/** 获取代理等级本地化 */
static GetAgentLvLocalized(agentLv:string, lv :number) {
return this.StringFormat(this.GetLocalized(agentLv), lv)
}
/** 设置代理等级图标 */
// static SetAgentLvIcon(item:Node, agentLv:string, lv :number) {
// if (!AGENT_LV_TO_PIC[agentLv]) return
// var icon = "icon" + AGENT_LV_TO_PIC[agentLv] + this.Return2LengthNumber(lv)
// this.SetSpriteFrame(item, icon, "Lobby/ui/allagent/icon")
// }
/** 设置子节点代理等级图标 */
// static SetChildAgentLvIcon(item:Node, path :string, agentLv:string, lv :number) {
// let n = item.getChildByPath(path)
// this.SetAgentLvIcon(n, agentLv, lv)
// }
/** 返回两位数 */
static Return2LengthNumber(n : number) {
return n < 10 ? "0" + n : n.toString()
}
//#endregion
//#region 数学算法
static MathClampZeroToOne(value: number): number {
return Math.max(0, Math.min(1, value));
}
static MathClamp(value: number, min: number, max: number): number {
return Math.max(min, Math.min(max, value));
}
static getRandomInt(min: number, max: number): number {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
//#endregion
static OpenWeb(url:string) {
const link = document.createElement('a');
link.href = url;
link.target = '_blank';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
/**
* 获取指定长度随机字符串
*/
public static getRandomStr (len:number) {
let chars =
'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz'
let maxPos = chars.length
let pwd = ''
for (let i = 0; i < len; i++) {
pwd += chars.charAt(Math.floor(Math.random() * maxPos))
}
return pwd
}
/**
* 使用 crypto-js 生成签名
* @param data - 要签名的数据
* @param key - 签名密钥
* @param encrypt - 加密算法 (sha1, sha256)
* @returns Promise<string> 签名结果
*/
public static generateSignNative(
data: Record<string, string>,
key: string,
encrypt: string = 'sha256'
): Promise<string> {
// 1. 对数据进行排序
const sortedData = Tools.sortData(data);
// 2. 构建查询字符串(自定义实现)
const queryString = Object.keys(sortedData)
.map(k => encodeURIComponent(k) + "=" + encodeURIComponent(sortedData[k]))
.join("&");
// 3. URL解码并拼接密钥
const decodedString = decodeURIComponent(queryString);
const str = decodedString + key;
// 4. 根据算法生成签名
let hash;
switch (encrypt.toLowerCase()) {
case 'sha1':
hash = CryptoJS.SHA1(str);
break;
case 'sha256':
default:
hash = CryptoJS.SHA256(str);
break;
}
// 5. 返回十六进制字符串
return Promise.resolve(hash.toString(CryptoJS.enc.Hex));
}
// 示例排序函数(你已有的话可以直接用)
public static sortData(data: Record<string, string>): Record<string, string> {
return Object.keys(data)
.sort()
.reduce((acc, key) => {
acc[key] = data[key];
return acc;
}, {} as Record<string, string>);
}
//#region 加密解密
/** 解析JWT令牌 */
public static parseJWT(token: string) {
try {
const parts = token.split('.');
if (parts.length !== 3) {
throw new Error('Invalid JWT token');
}
// 解码 payload 部分(第二部分)
const payload = JSON.parse(atob(parts[1]));
return payload;
} catch (error) {
console.error('Failed to parse JWT token:', error);
return null;
}
}
/**
* 解密 DES-ECB 模式加密的数据
* @param encryptedData base64编码的加密数据
* @param secretKey 8位密钥
* @returns 解密后的字符串
*/
static desEcbDecrypt(encryptedData: string, secretKey: string): string {
try {
// 确保密钥为8位 (PHP的openssl_encrypt会自动截取前8位)
const key = secretKey.length > 8 ? secretKey.substring(0, 8) : this.padKey(secretKey, 8);
// 创建CryptoJS格式的密钥
const keyWordArray = CryptoJS.enc.Utf8.parse(key);
// 解密
const decrypted = CryptoJS.DES.decrypt(
encryptedData,
keyWordArray,
{
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
}
);
return decrypted.toString(CryptoJS.enc.Utf8);
} catch (error) {
console.error('DES-ECB decryption failed:', error);
return '';
}
}
/**
* 加密 DES-ECB 模式数据
* @param data 待加密的数据
* @param secretKey 8位密钥
* @returns base64编码的加密数据
*/
static desEcbEncrypt(data: string, secretKey: string): string {
try {
// 确保密钥为8位
const key = secretKey.length > 8 ? secretKey.substring(0, 8) : this.padKey(secretKey, 8);
// 创建CryptoJS格式的密钥
const keyWordArray = CryptoJS.enc.Utf8.parse(key);
// 加密
const encrypted = CryptoJS.DES.encrypt(
data,
keyWordArray,
{
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
}
);
return encrypted.toString();
} catch (error) {
console.error('DES-ECB encryption failed:', error);
return '';
}
}
/**
* 补齐或截断密钥到指定长度
* @param key 原始密钥
* @param length 目标长度
*/
private static padKey(key: string, length: number): string {
if (key.length > length) {
return key.substring(0, length);
} else if (key.length < length) {
while (key.length < length) {
key += '\0'; // 用null字符补齐
}
return key;
}
return key;
}
/**
* 3DES-ECB 解密 (Triple DES)
* @param encryptedData base64编码的加密数据
* @param secretKey 24位密钥 (3DES需要24字节密钥)
* @returns 解密后的字符串
*/
static tripleDesEcbDecrypt(encryptedData: string, secretKey: string): string {
try {
// 确保密钥为24位
const key = this.padKey(secretKey, 24);
// 创建CryptoJS格式的密钥
const keyWordArray = CryptoJS.enc.Utf8.parse(key);
// 解密
const decrypted = CryptoJS.TripleDES.decrypt(
encryptedData,
keyWordArray,
{
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
}
);
return decrypted.toString(CryptoJS.enc.Utf8);
} catch (error) {
console.error('3DES-ECB decryption failed:', error);
return '';
}
}
/**
* 3DES-ECB 加密 (Triple DES)
* @param data 待加密的数据
* @param secretKey 24位密钥
* @returns base64编码的加密数据
*/
static tripleDesEcbEncrypt(data: string, secretKey: string): string {
try {
// 确保密钥为24位
const key = this.padKey(secretKey, 24);
// 创建CryptoJS格式的密钥
const keyWordArray = CryptoJS.enc.Utf8.parse(key);
// 加密
const encrypted = CryptoJS.TripleDES.encrypt(
data,
keyWordArray,
{
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
}
);
return encrypted.toString();
} catch (error) {
console.error('3DES-ECB encryption failed:', error);
return '';
}
}
//#endregion
}