# Conflicts:
#	assets/Scripts/Sub/UI/PreloadUI.ts
This commit is contained in:
2025-08-15 09:42:44 +08:00
58 changed files with 39713 additions and 756 deletions
+4
View File
@@ -25,6 +25,7 @@ import Utils from "../../Main/Common/Utils";
import { InnerMsgCode } from "../../Main/Config/InnerMsgCode";
import { promptItem } from "../Item/promptItem";
import LanguageUtils, { LanguageType } from "../../Main/Common/LanguageUtils";
import { DataManager } from "../../chat18x/data/DataManager";
const { ccclass, property } = _decorator;
@ccclass("PreloadUI")
@@ -57,6 +58,9 @@ export class PreloadUI extends Component {
this.jinduFilled.fillRange = 0;
//this.btnLogin.node.active = false;
// 初始化数据层
DataManager.I.init()
this.preloadFiles();
LanguageUtils.setLanguage(LanguageType.CN);
+78
View File
@@ -0,0 +1,78 @@
/**
* 数据类基类
* 提供数据类的生命周期管理和基础功能
*/
export class BaseData {
/** 是否已初始化 */
protected _isInitialized: boolean = false;
/** 是否已销毁 */
protected _isDestroyed: boolean = false;
/** 数据ID */
protected _dataId: string = '';
/**
* 初始化数据
* @param dataId 数据ID
*/
public init(dataId?: string): void{
if (this._isInitialized) return;
this._isInitialized = true;
if (dataId) {
this._dataId = dataId;
}
}
/**
* 重置数据到初始状态
*/
public reset(): void{
}
/**
* 清空数据内容
*/
public clear(): void{
}
/**
* 销毁数据对象
*/
public destroy(): void{
if (this._isDestroyed) return;
this.clear();
this._isDestroyed = true;
}
/**
* 获取数据ID
*/
public getDataId(): string {
return this._dataId;
}
/**
* 设置数据ID
*/
public setDataId(dataId: string): void {
this._dataId = dataId;
}
/**
* 是否已初始化
*/
public isInitialized(): boolean {
return this._isInitialized;
}
/**
* 是否已销毁
*/
public isDestroyed(): boolean {
return this._isDestroyed;
}
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "16b07bae-92dc-42b0-bb68-493bb15d8099",
"files": [],
"subMetas": {},
"userData": {}
}
+119
View File
@@ -0,0 +1,119 @@
/**
* 数据管理器
* 统一管理所有数据类
*/
import { BaseData } from "./BaseData";
import { LoginData } from "./LoginData";
import { PlayerData } from "./PlayerData";
/**
* 集中定义数据ID,避免写错字符串
*/
export enum DataId {
Login = "login",
Player = "player",
}
/** 构造器类型 */
type DataCtor<T extends BaseData = BaseData> = new () => T;
export class DataManager {
private static _I: DataManager | null = null;
public static get I(): DataManager {
if (!DataManager._I) DataManager._I = new DataManager();
return DataManager._I;
}
// 数据实例缓存
private _dataMap: Map<string, BaseData> = new Map();
// 数据ID到构造器的映射
private _config: Map<string, DataCtor> = new Map();
private constructor() {}
/**
* 初始化:注册 数据ID → 构造器
*/
public init(): void {
console.log("[DataManager] 数据管理器初始化");
this.register(DataId.Login, LoginData);
this.register(DataId.Player, PlayerData);
}
/**
* 注册一个数据ID及其对应的构造器
*/
public register<T extends BaseData>(dataId: string, ctor: DataCtor<T>): void {
if (this._config.has(dataId)) {
console.warn(`[DataManager] 重复注册数据ID: ${dataId},将覆盖旧构造器`);
}
this._config.set(dataId, ctor);
}
/**
* 获取数据(已存在则直接返回;否则按配置自动实例化并缓存)
*/
public getDataById<T extends BaseData = BaseData>(dataId: string): T | null {
// 1) 先从缓存取
const cached = this._dataMap.get(dataId) as T | undefined;
if (cached) return cached;
// 2) 缓存没有→ 查找构造器
const Ctor = this._config.get(dataId) as DataCtor<T> | undefined;
if (!Ctor) {
console.warn(`[DataManager] 未找到 dataId 的构造器配置: ${dataId}`);
return null;
}
// 3) 动态创建 & init & 缓存
const instance = new Ctor();
// 若你的 BaseData.init 接受 dataId,可传入
instance.init?.(dataId);
this._dataMap.set(dataId, instance);
console.log(`[DataManager] 实例化并缓存数据: ${dataId}`);
return instance;
}
/**
* 移除数据
*/
public removeData(dataId: string): boolean {
const data = this._dataMap.get(dataId);
if (data) {
data.destroy?.();
this._dataMap.delete(dataId);
console.log(`[DataManager] 移除数据: ${dataId}`);
return true;
}
return false;
}
/**
* 重置所有数据
*/
public resetAllData(): void {
console.log("[DataManager] 重置所有数据");
this._dataMap.forEach((data) => data.reset?.());
}
/**
* 清空所有数据
*/
public clearAllData(): void {
console.log("[DataManager] 清空所有数据");
this._dataMap.forEach((data) => data.clear?.());
}
/**
* 销毁所有数据
*/
public destroyAllData(): void {
console.log("[DataManager] 销毁所有数据");
this._dataMap.forEach((data) => data.destroy?.());
this._dataMap.clear();
}
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "6ecd13c4-1240-4d6a-b783-5fca62a8f32e",
"files": [],
"subMetas": {},
"userData": {}
}
+48
View File
@@ -0,0 +1,48 @@
/**
* 登录数据
*/
import { BaseData } from "./BaseData";
export class LoginData extends BaseData {
// 登录状态:0未登录 1登录中 2已登录
private _loginState = 0;
// token
private _token = "";
/**是否已登录成功 */
public isLogin(): boolean {
return this._loginState === 2;
}
/** 设置登录状态 */
public setLoginState(state: number): void {
this._loginState = state;
}
/** 获取登录状态 */
public getLoginState(): number {
return this._loginState;
}
/** 设置登录token */
public setToken(token: string): void {
this._token = token;
}
/** 获取登录token */
public getToken(): string {
return this._token;
}
public reset(): void {
}
public clear(): void {
}
public destroy(): void {
super.destroy();
}
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "2095514d-fe00-441c-aab1-fc6059334eec",
"files": [],
"subMetas": {},
"userData": {}
}
+32
View File
@@ -0,0 +1,32 @@
/**
* 玩家数据
*/
import { BaseData } from "./BaseData";
export class PlayerData extends BaseData {
private _name: string = '';
private _score: number = 0;
public reset(): void {
this._name = '';
this._score = 0;
}
public clear(): void {
this._name = '';
this._score = 0;
}
public destroy(): void {
super.destroy();
// 这里可以做额外释放资源的操作
}
public setName(name: string): void {
this._name = name;
}
public getName(): string {
return this._name;
}
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "413f999c-d31c-4a06-97d6-116de37d3077",
"files": [],
"subMetas": {},
"userData": {}
}
+9
View File
@@ -0,0 +1,9 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "3bd0a57e-1aa4-4a82-a6a6-b8b7ea86929f",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,9 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "12bc03b2-c6a3-4f19-afd9-5b48aa490068",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,86 @@
import { HttpManager, HttpMethod } from "../transport/HttpManager";
import { JsonCodec } from "../codec/JsonCodec";
import { ProtoCodec } from "../codec/ProtoCodec";
import type { Endpoint } from "./endpoints";
import { ApiCode, type ApiResponse } from "./types";
import { DataManager, DataId } from "db://assets/Scripts/chat18x/data/DataManager";
import { LoginData } from "db://assets/Scripts/chat18x/data/LoginData";
export class ApiClient {
private static _I: ApiClient | null = null;
public static get I(): ApiClient {
if (!ApiClient._I) ApiClient._I = new ApiClient();
return ApiClient._I;
}
private constructor(private http = HttpManager.I) {}
// TODO
private get baseUrl() { return "http://43.139.27.67:8081" }
private get token() { return DataManager.I.getDataById<LoginData>(DataId.Login).getToken();}
public async call<Req, Res>(ep: Endpoint<Req, Res>, req: Req): Promise<ApiResponse<Res>> {
const url = `${this.baseUrl}/${ep.path.replace(/^\/+/, "")}`;
const headers: Record<string, string> = {};
let responseType: XMLHttpRequestResponseType | undefined;
let rawBody: XMLHttpRequestBodyInit | null | undefined;
let jsonData: any;
if (ep.needsAuth && this.token) {
headers["Authorization"] = `Bearer ${this.token}`;
}
// 编码
if (ep.codec === "json") {
jsonData = (ep.method === "GET") ? req : JsonCodec.encode(req);
if (ep.method !== "GET") headers["Content-Type"] = "application/json";
} else if (ep.codec === "proto") {
if (!ep.reqType || !ep.resType) {
return { code: ApiCode.UNKNOWN, msg: "proto types missing" };
}
rawBody = ProtoCodec.encode(ep.reqType, req);
headers["Content-Type"] = "application/x-protobuf";
headers["Accept"] = "application/x-protobuf";
responseType = "arraybuffer";
}
// 发送
const r = await this.http.request(url, jsonData, ep.method, { headers, responseType, rawBody });
// HTTP/网络错误 → 统一返回
if (!r.ok) {
if (r.status === 401) {
return { code: ApiCode.AUTH_EXPIRED, msg: "auth expired" };
}
if (r.error === "timeout") {
return { code: ApiCode.TIMEOUT, msg: "request timeout" };
}
if (r.error === "network") {
return { code: ApiCode.NETWORK_ERROR, msg: "network error" };
}
return { code: ApiCode.HTTP_ERROR, msg: `http error: ${r.status}` };
}
// 解码 + 统一化
try {
if (ep.codec === "json") {
const parsed = JsonCodec.decode<any>(r.body);
// 如果后端本来就返回 {code,msg,data},直接透传(并确保有默认 msg)
if (parsed && typeof parsed === "object" && typeof parsed.code === "number") {
return {
code: parsed.code,
msg: parsed.msg ?? (parsed.code === 1 ? "ok" : "error"),
data: parsed.data as Res
};
}
// 否则我们包一层
return { code: ApiCode.OK, msg: "ok", data: parsed as Res };
} else if (ep.codec === "proto") {
const data = ProtoCodec.decode<Res>(ep.resType!, r.body);
// proto 场景一般没有 {code,msg} 包装,我们统一包一层
return { code: ApiCode.OK, msg: "ok", data };
}
} catch (e) {
return { code: ApiCode.DECODE_ERROR, msg: "decode error" };
}
}
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "4e1d0cf6-8eff-4227-8fde-3233d639e798",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,14 @@
import type { Type } from "protobufjs";
import type { HttpMethod } from "../transport/HttpManager";
export type CodecKind = "json" | "proto";
export interface Endpoint<Req, Res> {
path: string;
method: HttpMethod;
codec: CodecKind;
reqType?: Type; // codec=proto 时必填
resType?: Type; // codec=proto 时必填
needsAuth?: boolean;
lock?: boolean;
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "02c8cc0b-86fa-486d-8b9e-8729aaa5ec19",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,15 @@
export interface ApiResponse<T = any> {
code: number; // 1 表示成功;其他为错误
msg: string;
data?: T;
}
export const ApiCode = {
OK: 1,
AUTH_EXPIRED: -401,
HTTP_ERROR: -400,
TIMEOUT: -408,
NETWORK_ERROR: -499,
DECODE_ERROR: -600,
UNKNOWN: -700,
} as const;
@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "5027239d-2468-4562-bd01-87ffae8e96a1",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,9 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "27ff0e59-c502-4120-8258-3981a4b1e51d",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,14 @@
export const JsonCodec = {
/** POST/PUT 的请求体直接用对象,让传输层序列化为 JSON */
encode<T = any>(obj: T): any {
return obj ?? {};
},
/** 根据传输层返回的类型做解码:string → JSON.parse,其他直接返回 */
decode<T = any>(resp: any): T {
if (typeof resp === "string") {
try { return JSON.parse(resp) as T; } catch { /* 可能是纯文本 */ }
}
return resp as T;
}
};
@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "4d188874-4871-46e7-bc80-a179ae1ed67a",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,14 @@
import type { Type } from "protobufjs";
export const ProtoCodec = {
encode(reqType: Type, obj: any): Uint8Array {
const err = reqType.verify(obj);
if (err) throw Error(err);
return reqType.encode(reqType.create(obj)).finish();
},
decode<T = any>(resType: Type, buf: ArrayBuffer | Uint8Array): T {
const u8 = buf instanceof Uint8Array ? buf : new Uint8Array(buf);
return resType.decode(u8) as unknown as T;
}
};
@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "7e271138-d20e-4236-8355-ca4bcec0a23e",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,9 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "b0d9acd9-4ee1-40ec-857b-f1fbc5def44e",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,25 @@
import { ApiClient } from "../client/ApiClient";
import type { Endpoint } from "../client/endpoints";
import type { ApiResponse } from "../client/types";
import proto from 'db://assets/Scripts/proto/proto.pb.js';
export class AuthService {
private static _I: AuthService | null = null;
public static get I(): AuthService {
if (!AuthService._I) AuthService._I = new AuthService();
return AuthService._I;
}
private constructor(private api = ApiClient.I) {}
private EP_LOGIN: Endpoint<proto.ProtoMsg.ILoginReq, proto.ProtoMsg.ILoginRsp> = {
path: "login",
method: "POST",
codec: "json",
needsAuth: false
};
// 登录
public async login(req: proto.ProtoMsg.ILoginReq): Promise<ApiResponse<proto.ProtoMsg.ILoginRsp>> {
return this.api.call(this.EP_LOGIN, req);
}
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "e7586869-13fe-4207-960b-eb1be7081812",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,9 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "426aac6d-7382-44fa-a017-3390f3412e96",
"files": [],
"subMetas": {},
"userData": {}
}
@@ -0,0 +1,102 @@
/**
* Http管理器
*
*/
import { sys } from "cc";
import SubManager from 'db://assets/Scripts/Sub/SubManager';
// 请求类型
export type HttpMethod = "GET" | "POST" | "PUT";
// 请求参数结构
export interface HttpSendOptions {
headers?: Record<string, string>;
responseType?: XMLHttpRequestResponseType;
rawBody?: Document | XMLHttpRequestBodyInit | null;
}
// 响应结构
export interface HttpRawResponse {
ok: boolean; // 是否 2xx
status: number; // HTTP 状态码,网络错误/超时为 0
body?: any; // xhr.response 或 xhr.responseText
error?: "timeout" | "network" | "http";
}
export class HttpManager {
private static _I: HttpManager | null = null;
public static get I(): HttpManager {
if (!HttpManager._I) HttpManager._I = new HttpManager();
return HttpManager._I;
}
private constructor() {}
/** 发起请求,供 ApiClient 使用 */
public async request(
url: string,
data: any,
method: HttpMethod = "GET",
opt: HttpSendOptions = {}
): Promise<HttpRawResponse> {
const xhr = new XMLHttpRequest();
// 超时
xhr.timeout = 10000;
// 响应类型
if (opt.responseType) xhr.responseType = opt.responseType;
if (method === "GET") {
url = url + this.buildQuery(data);
xhr.open(method, url, true);
if (sys.isNative) {
xhr.setRequestHeader("Accept-Encoding", "gzip,deflate");
xhr.setRequestHeader("content-type", "text/html;charset=utf-8");
}
} else {
xhr.open(method, url, true);
if (!opt.rawBody) {
xhr.setRequestHeader("Content-Type", "application/json");
}
}
if (opt.headers) {
Object.keys(opt.headers).forEach(k => xhr.setRequestHeader(k, opt.headers![k]));
}
return new Promise<HttpRawResponse>((resolve) => {
xhr.onreadystatechange = () => {
if (xhr.readyState === 4) {
const ok = xhr.status >= 200 && xhr.status < 300;
const body = xhr.response ?? xhr.responseText ?? null;
if (!ok) {
SubManager.ShowPrompt(`网络错误(${xhr.status}),请稍后再试`);
}
resolve({ ok, status: xhr.status, body, error: ok ? undefined : "http" });
}
};
xhr.onerror = () => {
SubManager.ShowPrompt("网络错误,请检查网络连接");
resolve({ ok: false, status: 0, error: "network" });
};
xhr.ontimeout = () => {
SubManager.ShowPrompt("请求超时,请检查网络连接");
resolve({ ok: false, status: 0, error: "timeout" });
};
if (method === "GET") {
xhr.send();
} else {
if (opt.rawBody !== undefined) xhr.send(opt.rawBody ?? null);
else xhr.send(JSON.stringify(data ?? {}));
}
});
}
/**
* 把对象转 ?a=1&b=2 的查询串
*/
private buildQuery(json: any) {
if (!json || typeof json !== "object") return "";
const pairs = Object.keys(json).map(
k => encodeURIComponent(k) + "=" + encodeURIComponent(json[k])
);
return pairs.length ? "?" + pairs.join("&") : "";
}
}
@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "3be6cfe1-3205-4d3e-b00a-bdba016b2493",
"files": [],
"subMetas": {},
"userData": {}
}
+9
View File
@@ -0,0 +1,9 @@
{
"ver": "1.2.0",
"importer": "directory",
"imported": true,
"uuid": "e439496c-96d0-4df3-a30f-a9505a94abe5",
"files": [],
"subMetas": {},
"userData": {}
}
+5409
View File
File diff suppressed because it is too large Load Diff
+9
View File
@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "typescript",
"imported": true,
"uuid": "ae0a4c66-1607-4fa8-858c-e077964fefd4",
"files": [],
"subMetas": {},
"userData": {}
}
File diff suppressed because it is too large Load Diff
+9
View File
@@ -0,0 +1,9 @@
{
"ver": "4.0.24",
"importer": "javascript",
"imported": true,
"uuid": "594e1ab1-6e6a-490f-9466-8818050d80e9",
"files": [],
"subMetas": {},
"userData": {}
}
+86
View File
@@ -193,6 +193,7 @@ export class GlobalConfig {
this.Temperature = _buf_.readFloat()
this.MaxTokens = _buf_.readInt()
this.Timeout = _buf_.readInt()
this.FreeChatTimes = _buf_.readInt()
}
/**
@@ -212,6 +213,10 @@ export class GlobalConfig {
* 超时时间(微秒)
*/
readonly Timeout: number
/**
* 单个角色免费聊天次数
*/
readonly FreeChatTimes: number
resolve(tables:Tables) {
@@ -219,6 +224,7 @@ export class GlobalConfig {
}
}
@@ -270,6 +276,44 @@ export class Language {
export class PurchaseConfig {
constructor(_buf_: ByteBuf) {
this.id = _buf_.readInt()
this.name = _buf_.readString()
this.desc = _buf_.readString()
this.count = _buf_.readInt()
}
/**
* 商品id
*/
readonly id: number
/**
* 名称
*/
readonly name: string
/**
* 数量
*/
readonly desc: string
/**
* 计费点ID
*/
readonly count: number
resolve(tables:Tables) {
}
}
export class Theme {
constructor(_buf_: ByteBuf) {
@@ -582,6 +626,10 @@ export class TbGlobalConfig {
* 超时时间(微秒)
*/
get Timeout(): number { return this._data.Timeout; }
/**
* 单个角色免费聊天次数
*/
get FreeChatTimes(): number { return this._data.FreeChatTimes; }
resolve(tables:Tables) {
this._data.resolve(tables)
@@ -592,6 +640,39 @@ export class TbGlobalConfig {
export class TbPurchaseConfig {
private _dataMap: Map<number, PurchaseConfig>
private _dataList: PurchaseConfig[]
constructor(_buf_: ByteBuf) {
this._dataMap = new Map<number, PurchaseConfig>()
this._dataList = []
for(let n = _buf_.readInt(); n > 0; n--) {
let _v: PurchaseConfig
_v = new PurchaseConfig(_buf_)
this._dataList.push(_v)
this._dataMap.set(_v.id, _v)
}
}
getDataMap(): Map<number, PurchaseConfig> { return this._dataMap; }
getDataList(): PurchaseConfig[] { return this._dataList; }
get(key: number): PurchaseConfig | undefined {
return this._dataMap.get(key);
}
resolve(tables:Tables) {
for(let data of this._dataList)
{
data.resolve(tables)
}
}
}
type ByteBufLoader = (file: string) => ByteBuf
export class Tables {
@@ -607,6 +688,8 @@ export class Tables {
get TbAiCharacters(): TbAiCharacters { return this._TbAiCharacters;}
private _TbGlobalConfig: TbGlobalConfig
get TbGlobalConfig(): TbGlobalConfig { return this._TbGlobalConfig;}
private _TbPurchaseConfig: TbPurchaseConfig
get TbPurchaseConfig(): TbPurchaseConfig { return this._TbPurchaseConfig;}
static getTableNames(): string[] {
let names: string[] = [];
@@ -616,6 +699,7 @@ export class Tables {
names.push('tbthemes');
names.push('tbaicharacters');
names.push('tbglobalconfig');
names.push('tbpurchaseconfig');
return names;
}
@@ -626,6 +710,7 @@ export class Tables {
this._TbThemes = new TbThemes(loader('tbthemes'))
this._TbAiCharacters = new TbAiCharacters(loader('tbaicharacters'))
this._TbGlobalConfig = new TbGlobalConfig(loader('tbglobalconfig'))
this._TbPurchaseConfig = new TbPurchaseConfig(loader('tbpurchaseconfig'))
this._TbLanguage.resolve(this)
this._TbGirls.resolve(this)
@@ -633,6 +718,7 @@ export class Tables {
this._TbThemes.resolve(this)
this._TbAiCharacters.resolve(this)
this._TbGlobalConfig.resolve(this)
this._TbPurchaseConfig.resolve(this)
}
}