diff --git a/assets/Scripts/chat18x/data/GirlData.ts b/assets/Scripts/chat18x/data/GirlData.ts index 93a070a5..e90fa454 100644 --- a/assets/Scripts/chat18x/data/GirlData.ts +++ b/assets/Scripts/chat18x/data/GirlData.ts @@ -4,145 +4,325 @@ import { BaseData } from "./BaseData"; import proto from 'db://assets/Scripts/proto/proto.pb.js'; -export interface GirlBriefVO { - id: number; - name: string; - age: number; - tagKey: string; - priceType: number; - price: number; // 用 number 存(i64 转) - avatar: string; - star: number; +// 分类的数据结构 +interface CategoryBucket { + // 技师的简要数据 + briefs: Map; + // 技师的详细数据 + details: Map; + // 技师的id + girlIds: number[]; } -export interface GirlPhotoVO { - pic: string; - isRelease: boolean; - price: number; -} - -export interface GirlDetailVO { - id: number; // = brief.id - desc: string; - isRelease: boolean; - chatCount: number; - photos: GirlPhotoVO[]; -} +// 每日推荐专用分类 +const DAILY_BUCKET = "__daily__"; export class GirlData extends BaseData { - // 实体归一化 - private _briefs = new Map(); - private _details = new Map(); - - // 列表索引 - private _dailyRecommend: number[] = []; - private _categoryPages = new Map>(); // category -> (page -> [girlId]) - - get briefById() { return this._briefs; } - get detailById() { return this._details; } - get dailyRecommendIds() { return this._dailyRecommend; } - + // 大分类 + private _categories = new Map(); + // 每日推荐(存放在 DAILY_BUCKET 中,同时保留 id 索引便于 UI 使用) + private _dailyRecommendIds: number[] = []; public reset(): void { - this._briefs.clear(); - this._details.clear(); - this._dailyRecommend = []; - this._categoryPages.clear(); + this._categories.clear(); } public clear(): void { - this._briefs.clear(); - this._details.clear(); - this._dailyRecommend = []; - this._categoryPages.clear(); + this._categories.clear(); } public destroy(): void { super.destroy(); - this._briefs = null; - this._details = null; - this._dailyRecommend = null; - this._categoryPages = null; + this._categories = null; + } + + /** 保障 _categories 存在 */ + private ensureCategories(): Map { + if (!this._categories) { + this._categories = new Map(); + } + return this._categories; + } + + /** 获取或创建分类桶 */ + private ensureBucket(categoryId: string): CategoryBucket { + const cats = this.ensureCategories(); + let bucket = cats.get(categoryId); + if (!bucket) { + bucket = { + briefs: new Map(), + details: new Map(), + girlIds: [], + }; + cats.set(categoryId, bucket); + } + return bucket; + } + + /** + * 合并一批简要信息到指定分类;并维护 girlIds 顺序(去重追加) + */ + public mergeBriefs(categoryId: string, respOrList: proto.cs.IGirlBrief[]): void { + if (!respOrList) return; + let allData = this.parseAllGirlBrief(respOrList); + if (!allData) return; + const bucket = this.ensureBucket(categoryId); + for (const g of allData) { + const id = g.id; + bucket.briefs.set(id, g); + // TODO,去重 + bucket.girlIds.push(id); + } + console.log("保存技师的简要数据: ", bucket); + } + + /** 取分类下的全部 girlIds(若分类不存在返回空数组) */ + public getGirlIds(categoryId: string): number[] { + const b = this.ensureCategories().get(categoryId); + return b ? b.girlIds : []; + } + + /** --------------------------------------------------------- 技师的简要数据 --------------------------------------------------------- */ + + // 解析技师简要数据,一个 + private parseOneGirlBrief(data: proto.cs.IGirlBrief): proto.cs.IGirlBrief | null { + if (!data) return null; + const oneData: proto.cs.IGirlBrief = { + id: data.id, // id + name: data.name, // 名字 + age: data.age, // 年龄 + tagKey: data.tagKey, // 标签 + priceType: data.priceType, // 付费类型 + price: data.price, // 价格 + avatar: data.avatar, // 头像 + star: data.star, // 星级 + }; + return oneData; + } + + // 解析技师简要数据,多个 + private parseAllGirlBrief(data: proto.cs.IGirlBrief[]): proto.cs.IGirlBrief[] | null { + if (!data) return null; + let allData = []; + for (const value of data) { + const oneData: proto.cs.IGirlBrief = this.parseOneGirlBrief(value); + if (oneData) { + allData.push(oneData); + } + } + return allData; + } + + /** 保存技师的简要数据 */ + public setGirlBriefs(categoryId: string, data: proto.cs.ICSGetGirlListRes): void { + this.mergeBriefs(categoryId, data.girls); + } + + /** 取某分类 + girlId 的简要信息 */ + private getGrilBrief(categoryId: string, girlId: number): proto.cs.IGirlBrief | null { + const b = this.ensureCategories().get(categoryId); + if (!b) return null; + return b.briefs.get(girlId) ?? null; + } + + /** 获取名字 */ + public getGrilName(categoryId: string, girlId: number): string { + let oneData = this.getGrilBrief(categoryId, girlId); + if (!oneData) return ""; + return oneData.name; + } + + /** 获取年龄 */ + public getGrilAge(categoryId: string, girlId: number): number { + let oneData = this.getGrilBrief(categoryId, girlId); + if (!oneData) return 0; + return oneData.age; + } + + /** 获取标签 */ + public getGrilTagKey(categoryId: string, girlId: number): string { + let oneData = this.getGrilBrief(categoryId, girlId); + if (!oneData) return ""; + return oneData.tagKey; } - /** 合并 Brief 列表到缓存(去重+覆盖最新字段) */ - mergeBriefs(list: pb.cs.IGirlBrief[] | pb.cs.GirlBrief[]): void { - for (const g of list) { - const id = g.id!; - this._briefs.set(id, { - id, - name: g.name || "", - age: g.age || 0, - tagKey: g.tagKey || "", - priceType: g.priceType || 0, - price: i64(g.price), - avatar: g.avatar || "", - star: g.star || 0, - }); + /** 获取付费类型 */ + public getGrilPriceType(categoryId: string, girlId: number): number { + let oneData = this.getGrilBrief(categoryId, girlId); + if (!oneData) return 0; + return oneData.priceType; + } + + /** 获取价格 */ + public getGrilPrice(categoryId: string, girlId: number): number { + let oneData = this.getGrilBrief(categoryId, girlId); + if (!oneData) return 0; + return oneData.price; + } + + /** 获取头像 */ + public getGrilAvatar(categoryId: string, girlId: number): string { + let oneData = this.getGrilBrief(categoryId, girlId); + if (!oneData) return ""; + return oneData.avatar; + } + + /** 获取星级 */ + public getGrilStar(categoryId: string, girlId: number): number { + let oneData = this.getGrilBrief(categoryId, girlId); + if (!oneData) return 0; + return oneData.star; + } + + /** --------------------------------------------------------- 技师的详细数据 --------------------------------------------------------- */ + + // 解析技师图片数据,一个 + private parseOneGirlPhoto(data: proto.cs.IGirlPhoto): proto.cs.IGirlPhoto | null { + if (!data) return null; + const oneData: proto.cs.IGirlPhoto = { + pic: data.pic, // 图片路径 + isRelease: data.isRelease, // 是否解锁 + price: data.price, // 价格 + }; + return oneData; + } + + // 解析技师图片数据,多个 + private parseAllGirlPhoto(data: proto.cs.IGirlPhoto[]): proto.cs.IGirlPhoto[] | null { + if (!data) return null; + let allData = []; + for (const value of data) { + const oneData: proto.cs.IGirlPhoto = this.parseOneGirlPhoto(value); + if (oneData) { + allData.push(oneData); + } } + return allData; } - /** 每日推荐:只维护 id 索引,实体来自 _briefs */ - applyDailyRecommend(res: pb.cs.ICSDailyRecommendRes): void { - const girls = res.girls ?? []; - this.mergeBriefs(girls); - this._dailyRecommend = girls.map(g => g.id!); + // 解析技师详细数据,一个 + private parseOneGirlDetail(data: proto.cs.IGirlDetail): proto.cs.IGirlDetail | null { + if (!data) return null; + const photoData = this.parseAllGirlPhoto(data.photos); + const briefData = this.parseOneGirlBrief(data.brief); + const oneData: proto.cs.IGirlDetail = { + brief: briefData, // 技师简要数据 + desc: data.desc, // 技师描述 + photos: photoData, // 技师图片列表 + isRelease: data.isRelease, // 技师是否解锁 + chatCount: data.chatCount, // 剩余聊天次数 + }; + return oneData; } - /** 分类分页列表:category + page -> id[] */ - applyGirlList(category: number, page: number, res: pb.cs.ICSGetGirlListRes): void { - const girls = res.girls ?? []; - this.mergeBriefs(girls); - - if (!this._categoryPages.has(category)) { - this._categoryPages.set(category, new Map()); + // 解析技师详细数据,多个 + private parseAllGirlDetail(data: proto.cs.IGirlDetail[]): proto.cs.IGirlDetail[] | null { + if (!data) return null; + let allData = []; + for (const value of data) { + const oneData: proto.cs.IGirlDetail = this.parseOneGirlDetail(value); + if (oneData) { + allData.push(oneData); + } } - const pages = this._categoryPages.get(category)!; - pages.set(page, girls.map(g => g.id!)); + return allData; } - /** 详情:合并 brief + detail */ - applyGirlDetail(res: pb.cs.ICSGetGirlDetailRes): void { - const d = res.detail!; - const b = d.brief!; - this.mergeBriefs([b]); - - const vo: GirlDetailVO = { - id: b.id!, - desc: d.desc || "", - isRelease: !!d.isRelease, - chatCount: d.chatCount || 0, - photos: (d.photos ?? []).map(p => ({ - pic: p.pic || "", - isRelease: !!p.isRelease, - price: p.price || 0, - })), - }; - this._details.set(vo.id, vo); - } - - /** 页面读取:根据分类+页获取 id 列表 */ - getPageIds(category: number, page: number): number[] { - const pages = this._categoryPages.get(category); - return pages?.get(page) ?? []; - } - - /** 本地变更:聊天次数变更(成功买次数后) */ - incChatCount(girlId: number, delta: number) { - const d = this._details.get(girlId); - if (d) d.chatCount = Math.max(0, d.chatCount + delta); - } - - /** 本地变更:解锁技师/照片(例如购买后或服务器回调) */ - setGirlReleased(girlId: number, v: boolean) { - const d = this._details.get(girlId); - if (d) d.isRelease = v; - } - setPhotoReleased(girlId: number, pic: string, v: boolean) { - const d = this._details.get(girlId); + /** 保存技师的详细数据 */ + public setGirlDetails(categoryId: string, data: proto.cs.ICSGetGirlDetailRes): void { + const d = data?.detail; if (!d) return; - const p = d.photos.find(x => x.pic === pic); - if (p) p.isRelease = v; + // 合并简要信息 + if (d.brief) this.mergeBriefs(categoryId, [d.brief]); + + const oneDetail = this.parseOneGirlDetail(d); + const bucket = this.ensureBucket(categoryId); + // id + const id = d.brief?.id ?? 0; + // 写入详情 + bucket.details.set(id, oneDetail); + console.log("保存技师的详细数据: ", bucket); + } + + /** 取某分类 + girlId 的详细信息 */ + public getGrilDetail(categoryId: string, girlId: number): proto.cs.IGirlDetail | null { + const b = this.ensureCategories().get(categoryId); + if (!b) return null; + return b.details.get(girlId) ?? null; + } + + /** 获取技师描述 */ + public getGrilDesc(categoryId: string, girlId: number): string { + let oneData = this.getGrilDetail(categoryId, girlId); + if (!oneData) return ""; + return oneData.desc; + } + + /** 获取技师是否解锁 */ + public getGrilIsRelease(categoryId: string, girlId: number): boolean { + let oneData = this.getGrilDetail(categoryId, girlId); + if (!oneData) return false; + return oneData.isRelease; + } + + /** 获取技师剩余聊天次数 */ + public getGrilChatCount(categoryId: string, girlId: number): number { + let oneData = this.getGrilDetail(categoryId, girlId); + if (!oneData) return 0; + return oneData.chatCount; + } + + /** 获取技师图片数据,通过 index */ + public getGrilPhotoData(categoryId: string, girlId: number, index: number): proto.cs.IGirlPhoto | null { + let oneData = this.getGrilDetail(categoryId, girlId); + if (!oneData || !oneData.photos) return null; + // 下标越界保护 + if (index < 0 || index >= oneData.photos.length) return null; + + return oneData.photos[index] ?? null; + } + + /** 获取技师图片的路径 */ + public getGrilPhotoPic(categoryId: string, girlId: number, index: number): string { + let oneData = this.getGrilPhotoData(categoryId, girlId, index); + if (!oneData) return ""; + return oneData.pic; + } + + /** 获取技师图片是否解锁 */ + public getGrilPhotoIsRelease(categoryId: string, girlId: number, index: number): boolean { + let oneData = this.getGrilPhotoData(categoryId, girlId, index); + if (!oneData) return false; + return oneData.isRelease; + } + + /** 获取技师图片的价格 */ + public getGrilPhotoPrice(categoryId: string, girlId: number, index: number): number { + let oneData = this.getGrilPhotoData(categoryId, girlId, index); + if (!oneData) return 0; + return oneData.price; + } + + /** --------------------------------------------------------- 每日推荐 --------------------------------------------------------- */ + + /** 保存每日推荐:放入特殊分类 DAILY_BUCKET,并维护 id 索引 */ + public setDailyRecommend(res: proto.cs.ICSDailyRecommendRes): void { + this.mergeBriefs(DAILY_BUCKET, res.girls); + this._dailyRecommendIds = res.girls.map(g => g.id ?? 0); } + /** 获取每日推荐的简要信息 */ + private getRecommendGrilBrief(categoryId: string, index: number): proto.cs.IGirlBrief | null { + const b = this.ensureCategories().get(categoryId); + if (!b) return null; + if (index < 0 || index >= b.girlIds.length) return null; + return b.briefs.get(b.girlIds[index]) ?? null; + } + + /** 获取名字 */ + public getRecommendGrilName(index: number): string { + let oneData = this.getRecommendGrilBrief(DAILY_BUCKET, index); + if (!oneData) return ""; + return oneData.name; + } } diff --git a/assets/Scripts/chat18x/network/services/ChatService.ts b/assets/Scripts/chat18x/network/services/ChatService.ts new file mode 100644 index 00000000..5fa0c35a --- /dev/null +++ b/assets/Scripts/chat18x/network/services/ChatService.ts @@ -0,0 +1,24 @@ +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 ChatService { + private static _I: ChatService | null = null; + public static get I(): ChatService { + if (!ChatService._I) ChatService._I = new ChatService(); + return ChatService._I; + } + private constructor(private api = ApiClient.I) {} + + // 购买聊天次数 + public async reqBuyChat(req: proto.cs.ICSBuyChatReq): Promise> { + let epData: Endpoint = { + path: "api/logic/buyChat", + method: "POST", + codec: "json", + needsAuth: true + }; + return this.api.call(epData, req); + } +} diff --git a/assets/Scripts/chat18x/network/services/ChatService.ts.meta b/assets/Scripts/chat18x/network/services/ChatService.ts.meta new file mode 100644 index 00000000..211964fd --- /dev/null +++ b/assets/Scripts/chat18x/network/services/ChatService.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "16cb7bbc-91fd-4987-ac99-79f4cf1e732b", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/assets/Scripts/chat18x/network/services/GirlService.ts b/assets/Scripts/chat18x/network/services/GirlService.ts new file mode 100644 index 00000000..39bad043 --- /dev/null +++ b/assets/Scripts/chat18x/network/services/GirlService.ts @@ -0,0 +1,46 @@ +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 GirlService { + private static _I: GirlService | null = null; + public static get I(): GirlService { + if (!GirlService._I) GirlService._I = new GirlService(); + return GirlService._I; + } + private constructor(private api = ApiClient.I) {} + + // 每日推荐 + public async reqDailyRecommend(req: proto.cs.ICSDailyRecommendReq): Promise> { + let epData: Endpoint = { + path: "api/logic/dailyRecommend", + method: "POST", + codec: "json", + needsAuth: true + }; + return this.api.call(epData, req); + } + + // 获取技师列表 + public async reqGetGirlList(req: proto.cs.ICSGetGirlListReq): Promise> { + let epData: Endpoint = { + path: "api/logic/getGirls", + method: "POST", + codec: "json", + needsAuth: true + }; + return this.api.call(epData, req); + } + + // 获取技师详细信息 + public async reqGetGirlDetail(req: proto.cs.ICSGetGirlDetailReq): Promise> { + let epData: Endpoint = { + path: "api/logic/girlDetail", + method: "POST", + codec: "json", + needsAuth: true + }; + return this.api.call(epData, req); + } +} diff --git a/assets/Scripts/chat18x/network/services/GirlService.ts.meta b/assets/Scripts/chat18x/network/services/GirlService.ts.meta new file mode 100644 index 00000000..1fdf392c --- /dev/null +++ b/assets/Scripts/chat18x/network/services/GirlService.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "c115537a-22f6-43a3-be0a-dc5c3a8ca1b7", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/assets/Scripts/chat18x/network/services/ShopService.ts b/assets/Scripts/chat18x/network/services/ShopService.ts new file mode 100644 index 00000000..ad294864 --- /dev/null +++ b/assets/Scripts/chat18x/network/services/ShopService.ts @@ -0,0 +1,24 @@ +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 ShopService { + private static _I: ShopService | null = null; + public static get I(): ShopService { + if (!ShopService._I) ShopService._I = new ShopService(); + return ShopService._I; + } + private constructor(private api = ApiClient.I) {} + + // 获取商品列表 + public async reqShopList(req: proto.cs.ICSGetShopReq): Promise> { + let epData: Endpoint = { + path: "api/logic/shop", + method: "POST", + codec: "json", + needsAuth: true + }; + return this.api.call(epData, req); + } +} diff --git a/assets/Scripts/chat18x/network/services/ShopService.ts.meta b/assets/Scripts/chat18x/network/services/ShopService.ts.meta new file mode 100644 index 00000000..9c5a59b8 --- /dev/null +++ b/assets/Scripts/chat18x/network/services/ShopService.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "ba67b42d-8326-4aa9-8ef3-fb99821ac345", + "files": [], + "subMetas": {}, + "userData": {} +} diff --git a/assets/Scripts/chat18x/network/services/ThemeService.ts b/assets/Scripts/chat18x/network/services/ThemeService.ts new file mode 100644 index 00000000..7cddacdf --- /dev/null +++ b/assets/Scripts/chat18x/network/services/ThemeService.ts @@ -0,0 +1,24 @@ +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 ThemeService { + private static _I: ThemeService | null = null; + public static get I(): ThemeService { + if (!ThemeService._I) ThemeService._I = new ThemeService(); + return ThemeService._I; + } + private constructor(private api = ApiClient.I) {} + + // 获取大厅分类列表 + public async reqHallTheme(req: proto.cs.ICSHallThemeReq): Promise> { + let epData: Endpoint = { + path: "api/logic/hallTheme", + method: "POST", + codec: "json", + needsAuth: true + }; + return this.api.call(epData, req); + } +} diff --git a/assets/Scripts/chat18x/network/services/ThemeService.ts.meta b/assets/Scripts/chat18x/network/services/ThemeService.ts.meta new file mode 100644 index 00000000..818e4355 --- /dev/null +++ b/assets/Scripts/chat18x/network/services/ThemeService.ts.meta @@ -0,0 +1,9 @@ +{ + "ver": "4.0.24", + "importer": "typescript", + "imported": true, + "uuid": "dc24010f-aa24-4e1a-8ef3-bc543a72cdf8", + "files": [], + "subMetas": {}, + "userData": {} +}