lili e71f2efc1a fix(studio): 清除 stale「绘境/Huijing」品牌串 → 造梦AI(品牌统一,rename 收尾)
E6 走查逮到 6 处用户可见旧品牌串(profile 昵称默认/locale brand/mock 文案),违项目品牌口径(品牌=造梦AI,绘境/huijing 已彻底淘汰除 git 历史):
- locales zh/en brand: 绘境AI/Huijing AI → 造梦AI(en 取与 index.html <title> 一致的 CJK 品牌,仓内无既定罗马化)
- mock project/community/feed 文案 + store/user 默认昵称「绘境创作者」→「造梦创作者」
- 仅字符串字面值,零逻辑改;grep 实证 src 内 绘境/Huijing 归零

验证:`npm run build` 绿。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 16:27:52 -07:00

198 lines
7.2 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.

/**
* feed 模块 mock handler严格贴 contracts/api-schemas/feed.yaml 响应结构)。
* 覆盖端点:
* GET /app-api/feed/stream → FeedStreamRespVO
* GET /app-api/feed/zones → ZoneRespVO[]
* GET /app-api/feed/zone/stream → FeedStreamRespVO
* POST /app-api/feed/interact → InteractResultRespVO
* GET /app-api/feed/share/:gameId → ShareMetaRespVO
*
* 假数据≥10 张卡(够竖屏刷)+ cursor 翻页offset 编码进 cursor
*/
import type { MockRoute, MockRequest } from './shared'
import { MockError, seededInt } from './shared'
import type {
FeedCard,
FeedStream,
Zone,
InteractResult,
ShareMeta,
FeedInteractReq,
} from '../api/types'
/** demo 版本 ID与 runtime mock 对齐:所有 mock 游戏都路由到同一 demo 包entry='demo' */
const DEMO_VERSION_ID = 9001
/** 生成内联 SVG 封面data URI离线可用避免外链依赖 */
function cover(seed: number, title: string): string {
// 用 seed 选一组深色科技风渐变色
const palettes = [
['#101522', '#1b2440'],
['#0d1b1e', '#13343a'],
['#1a0f24', '#2a1740'],
['#0f1a14', '#163a2a'],
['#241016', '#3a1722'],
]
const [c1, c2] = palettes[seed % palettes.length]
const accent = ['#31e6ff', '#7cf2a4', '#ff669d', '#ffc857', '#9d7cff'][seed % 5]
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="375" height="640" viewBox="0 0 375 640">
<defs><linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
<stop offset="0" stop-color="${c1}"/><stop offset="1" stop-color="${c2}"/></linearGradient></defs>
<rect width="375" height="640" fill="url(#g)"/>
<circle cx="300" cy="120" r="90" fill="${accent}" opacity="0.12"/>
<text x="28" y="540" fill="#eef6ff" font-size="30" font-family="sans-serif" font-weight="700">${title}</text>
<text x="28" y="575" fill="${accent}" font-size="15" font-family="sans-serif">造梦 demo 游戏</text></svg>`
return `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`
}
/** 卡片标题词库(拼出有辨识度的 demo 标题) */
const TITLES = [
'霓虹躲避',
'星际跑酷',
'点击工厂',
'方块消除',
'弹幕射手',
'迷宫探险',
'节奏大师',
'像素农场',
'重力翻转',
'深渊潜水',
'光速摩托',
'糖果连连',
]
const AUTHORS = ['阿青', '小绿', '粉黛', '琥珀', '紫电', '深蓝']
/**
* 生成第 idx 张卡idx 从 1 开始,保证字段稳定可复现)。
* gameId = idxversionId 统一 DEMO_VERSION_ID试玩链路真跑同一 demo 包)。
*/
function makeCard(idx: number, zoneId: number): FeedCard {
const title = TITLES[(idx - 1) % TITLES.length]
return {
gameId: idx,
versionId: DEMO_VERSION_ID,
title,
coverUrl: cover(idx, title),
authorName: AUTHORS[(idx - 1) % AUTHORS.length],
// packageUrl 指向 runtime 取包端点(宿主真实加载时会再调 runtime.getPackage
packageUrl: `/games/${idx}/versions/${DEMO_VERSION_ID}/manifest.json`,
zoneId,
qualityScore: seededInt(idx, 60, 98), // 运营质量分(0-100)
playCount: seededInt(idx * 7, 120, 99999),
likeCount: seededInt(idx * 13, 30, 8888),
liked: false,
favorited: false,
}
}
/** 互动计数本地态(按 gameId+action 累计,模拟服务端聚合回显) */
const interactState = new Map<string, { active: boolean; total: number }>()
/**
* 通用流分页:按 cursor编码 offset返回一页PAGE_SIZE 条。
* @param zoneId 专区 ID0=默认混合流)
* @param cursor 上页游标('' 或 undefined=首屏)
*/
function paginate(zoneId: number, cursor: string | undefined): FeedStream {
const PAGE = 10
const TOTAL = 36 // 总量(多页可刷,验证上拉加载)
const offset = cursor ? parseInt(cursor, 10) || 0 : 0
const end = Math.min(offset + PAGE, TOTAL)
const list: FeedCard[] = []
for (let i = offset + 1; i <= end; i++) {
// zone 流用 zoneId混合流给卡片分配 1/2 两个专区
const z = zoneId > 0 ? zoneId : (i % 2 === 0 ? 1 : 2)
list.push(makeCard(i, z))
}
const hasMore = end < TOTAL
return {
list,
nextCursor: hasMore ? String(end) : '',
hasMore,
}
}
/** GET /app-api/feed/stream */
function streamHandler(req: MockRequest): FeedStream {
return paginate(0, req.query.cursor)
}
/** GET /app-api/feed/zones */
function zonesHandler(): Zone[] {
// 双轨1 官方精选 / 2 UGC 普通(贴 ZoneRespVO
return [
{ id: 1, name: '官方精选', type: 1, sort: 1 },
{ id: 2, name: 'UGC 广场', type: 2, sort: 2 },
]
}
/** GET /app-api/feed/zone/stream?zoneId&cursor */
function zoneStreamHandler(req: MockRequest): FeedStream {
const zoneId = parseInt(req.query.zoneId, 10)
if (!zoneId) {
// 贴契约zoneId 必填缺失给一个业务错误码feed 段)
throw new MockError(1103001001, '缺少 zoneId')
}
return paginate(zoneId, req.query.cursor)
}
/** POST /app-api/feed/interact */
function interactHandler(req: MockRequest): InteractResult {
const body = (req.body ?? {}) as Partial<FeedInteractReq>
const gameId = Number(body.gameId)
const action = Number(body.action)
if (!gameId || ![1, 2, 3, 4].includes(action)) {
throw new MockError(1103001002, 'gameId/action 非法')
}
// 举报必须带 reason贴契约约束
if (action === 4 && !body.reason) {
throw new MockError(1103004001, '举报需填写理由')
}
const key = `${gameId}:${action}`
const prev = interactState.get(key) ?? {
active: false,
total: seededInt(gameId * action, 10, 5000),
}
// 点赞/收藏可切换;分享/举报恒 active=true 且累加
let active: boolean
let total = prev.total
if (action === 1 || action === 2) {
active = body.active ?? !prev.active
total += active ? 1 : -1
if (total < 0) total = 0
} else {
active = true
total += 1
}
interactState.set(key, { active, total })
return { gameId, action, active, totalCount: total }
}
/** GET /app-api/feed/share/:gameId?channel */
function shareHandler(req: MockRequest): ShareMeta {
const gameId = Number(req.params.gameId)
if (!gameId) throw new MockError(1103002001, '游戏不存在或未发布')
const channel = req.query.channel || 'direct'
const title = TITLES[(gameId - 1) % TITLES.length]
return {
gameId,
// GAP-3分享落地页能直接拿到「即玩版本」。mock 统一回 DEMO_VERSION_ID已发布态
// 与 feed 卡片/runtime 取包同源);真实后端仅在 isGamePublished 时回填,未发布缺省。
versionId: DEMO_VERSION_ID,
shareUrl: `https://wanxiang.ai/share/${gameId}?utm_source=${encodeURIComponent(channel)}`,
ogTitle: `来玩《${title}`,
ogImage: cover(gameId, title),
ogDescription: '造梦AI 一句话生成的小游戏,点开即玩',
channel,
}
}
/** 导出本模块路由表(供 mock/index 汇总) */
export const feedRoutes: MockRoute[] = [
{ method: 'GET', pattern: '/app-api/feed/stream', handler: streamHandler },
{ method: 'GET', pattern: '/app-api/feed/zones', handler: zonesHandler },
{ method: 'GET', pattern: '/app-api/feed/zone/stream', handler: zoneStreamHandler },
{ method: 'POST', pattern: '/app-api/feed/interact', handler: interactHandler },
{ method: 'GET', pattern: '/app-api/feed/share/:gameId', handler: shareHandler },
]