import request from '@/config/axios' import { isMock, mockPage, mockResult } from './mock' /** * 绘境AI —— ad 模块 admin 接口(广告位管理 + 广告收入台账对账) * 契约:contracts/api-schemas/ad.yaml(字段严格对齐契约#7 ad-slot.schema.json,不重造广告位结构) * - POST /admin-api/ad/slot/create 新建广告位(slotId 业务唯一) * - PUT /admin-api/ad/slot/update 更新(按主键 id,slotId 不可改) * - DELETE /admin-api/ad/slot/delete?id= 逻辑删除 * - GET /admin-api/ad/slot/get?id= 详情 * - GET /admin-api/ad/slot/page 分页(按 type/provider/enabled 筛选) * - GET /admin-api/ad/revenue/page 广告收入台账分页(归因/计费对账,只读) * 端与鉴权:admin-api 走 huijing 管理员 Token + RBAC;CommonResult code=0 成功(request 已解包 data)。 * 资金口径:金额一律「分」(BIGINT),禁浮点;ecpmFloor/ecpm/revenueAmount 均为分。 */ /** 合规植入规则(对齐 ad.yaml AdComplianceVO / 契约#7 compliance) */ export interface AdComplianceVO { blockMinor?: boolean // 识别为未成年则不展示/不计费 maxPerSession?: number // 单会话最大展示次数 } /** 广告位新建/更新入参(对齐 ad.yaml AdSlotSaveReqVO) */ export interface AdSlotSaveReqVO { id?: number // 更新必填、新建不传 slotId: string // 业务唯一键 uk_slot(新建唯一、更新不可改) type: string // rewarded | interstitial | banner provider: string // csj | gdt | mock(MVP 默认 mock) placement?: string // game_end | pause | feed providerSlotId?: string // 联盟侧真实广告位 ID(provider!=mock 时必填) ecpmFloor?: number // eCPM 底价(分) enabled: boolean compliance?: AdComplianceVO } /** 广告位详情(对齐 ad.yaml AdSlotRespVO) */ export interface AdSlotRespVO { id: number slotId: string type: string provider: string placement?: string providerSlotId?: string ecpmFloor?: number enabled: boolean compliance?: AdComplianceVO createTime?: string updateTime?: string } /** 广告位分页查询参数(对齐 ad.yaml /admin-api/ad/slot/page) */ export interface AdSlotPageReqVO extends PageParam { type?: string provider?: string enabled?: boolean } /** 广告收入台账记录(对齐 ad.yaml AdRevenueRespVO) */ export interface AdRevenueRespVO { id: number // = trade 侧 source_ref 对账锚点 slotId: string gameId: number creatorUserId: number // 收入归因到的创作者用户 ID eventType: string // impression | reward provider: string ecpm?: number // 分 revenueAmount: number // 本条广告收入(分) traceId: string // 计费幂等键 / 对账 traceId settleStatus: number // 0未结算 1已结算 statDate?: string // 归集日 yyyy-MM-dd createTime?: string } /** 广告收入台账查询参数(对齐 ad.yaml /admin-api/ad/revenue/page) */ export interface AdRevenuePageReqVO extends PageParam { slotId?: string gameId?: number creatorUserId?: number eventType?: string // impression | reward settleStatus?: number // 0未结算 1已结算 statDate?: string } // ---- mock 数据 ---- const mockSlotList: AdSlotRespVO[] = [ { id: 1, slotId: 'slot_game_end_rewarded', type: 'rewarded', provider: 'mock', placement: 'game_end', providerSlotId: '', ecpmFloor: 3500, enabled: true, compliance: { blockMinor: true, maxPerSession: 3 }, createTime: '2026-06-05 10:00:00', updateTime: '2026-06-05 10:00:00' }, { id: 2, slotId: 'slot_pause_interstitial', type: 'interstitial', provider: 'mock', placement: 'pause', providerSlotId: '', ecpmFloor: 2000, enabled: true, compliance: { blockMinor: true, maxPerSession: 5 }, createTime: '2026-06-05 10:05:00', updateTime: '2026-06-05 10:05:00' }, { id: 3, slotId: 'slot_feed_banner', type: 'banner', provider: 'mock', placement: 'feed', providerSlotId: '', ecpmFloor: 800, enabled: false, compliance: { blockMinor: true, maxPerSession: 10 }, createTime: '2026-06-05 10:10:00', updateTime: '2026-06-06 09:00:00' } ] const mockRevenueList: AdRevenueRespVO[] = [ { id: 90001, slotId: 'slot_game_end_rewarded', gameId: 2001, creatorUserId: 30001, eventType: 'reward', provider: 'mock', ecpm: 3500, revenueAmount: 3, traceId: 'trace-aaa-0001', settleStatus: 0, statDate: '2026-06-07', createTime: '2026-06-07 12:30:00' }, { id: 90002, slotId: 'slot_pause_interstitial', gameId: 2002, creatorUserId: 30002, eventType: 'impression', provider: 'mock', ecpm: 2000, revenueAmount: 2, traceId: 'trace-bbb-0002', settleStatus: 1, statDate: '2026-06-06', createTime: '2026-06-06 18:10:00' } ] /** 广告位分页 */ export const getAdSlotPage = async (params: AdSlotPageReqVO) => { if (isMock()) { let filtered = mockSlotList if (params.type) filtered = filtered.filter((it) => it.type === params.type) if (params.provider) filtered = filtered.filter((it) => it.provider === params.provider) if (params.enabled != null) filtered = filtered.filter((it) => it.enabled === params.enabled) return mockPage(filtered, params.pageNo, params.pageSize) } return await request.get({ url: '/ad/slot/page', params }) } /** 广告位详情 */ export const getAdSlot = async (id: number) => { if (isMock()) { return mockResult(mockSlotList.find((it) => it.id === id) || mockSlotList[0]) } return await request.get({ url: '/ad/slot/get?id=' + id }) } /** 新建广告位(返回主键 ID) */ export const createAdSlot = async (data: AdSlotSaveReqVO) => { if (isMock()) { return mockResult(Date.now()) } return await request.post({ url: '/ad/slot/create', data }) } /** 更新广告位(slotId 不可改) */ export const updateAdSlot = async (data: AdSlotSaveReqVO) => { if (isMock()) { return mockResult(true) } return await request.put({ url: '/ad/slot/update', data }) } /** 删除广告位(逻辑删除) */ export const deleteAdSlot = async (id: number) => { if (isMock()) { return mockResult(true) } return await request.delete({ url: '/ad/slot/delete?id=' + id }) } /** 广告收入台账分页(只读对账) */ export const getAdRevenuePage = async (params: AdRevenuePageReqVO) => { if (isMock()) { let filtered = mockRevenueList if (params.slotId) filtered = filtered.filter((it) => it.slotId === params.slotId) if (params.gameId != null) filtered = filtered.filter((it) => it.gameId === params.gameId) if (params.creatorUserId != null) filtered = filtered.filter((it) => it.creatorUserId === params.creatorUserId) if (params.eventType) filtered = filtered.filter((it) => it.eventType === params.eventType) if (params.settleStatus != null) filtered = filtered.filter((it) => it.settleStatus === params.settleStatus) return mockPage(filtered, params.pageNo, params.pageSize) } return await request.get({ url: '/ad/revenue/page', params }) }