first commit

This commit is contained in:
2026-03-30 09:39:59 +08:00
parent 6c52425fca
commit 5ac73d3c6d
4484 changed files with 1144395 additions and 0 deletions

View File

@@ -0,0 +1,632 @@
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
}