328ac31844
- 主站:游戏广场、详情、在线游玩、网页发布与作者中心,以及共享契约与客户端服务 - 后端:module-game-distribution 领域层、SpacetimeDB 表/迁移/绑定、spacetime-client facade、api-server 路由与发行网关 - 后台:游戏审核页(待审列表、通过/拒绝、安全下架) - AGC:发布面板、本地导出包读取命令与发布服务,含默认跳过的真实链路测试 - 运维:发行来源 nginx 模板与门禁、game-distribution:publish 灰度发布开关、OSS PutObject 受控重试 - 文档:主规范、里程碑与实施计划、决策日志与踩坑记录
184 lines
5.2 KiB
TypeScript
184 lines
5.2 KiB
TypeScript
import type {
|
|
GameCategory,
|
|
GameDistributionGame,
|
|
} from '../../services/gameDistributionClient';
|
|
|
|
export const GAME_GALLERY_CATEGORIES: Array<GameCategory | '全部'> = [
|
|
'全部',
|
|
'动作',
|
|
'益智',
|
|
'冒险',
|
|
'休闲',
|
|
'模拟',
|
|
'策略',
|
|
'其他',
|
|
];
|
|
|
|
export type GameDeviceFilter = '全部' | '桌面' | '移动';
|
|
|
|
export const GAME_GALLERY_DEVICE_FILTERS: GameDeviceFilter[] = [
|
|
'全部',
|
|
'桌面',
|
|
'移动',
|
|
];
|
|
|
|
/** 目录筛选写入地址栏,分享和刷新都能回到同一份上下文。 */
|
|
const GALLERY_QUERY_KEYS = {
|
|
keyword: 'keyword',
|
|
category: 'category',
|
|
device: 'device',
|
|
} as const;
|
|
|
|
const GALLERY_STORAGE_KEYS = {
|
|
filters: 'game-gallery-filters',
|
|
scroll: 'game-gallery-scroll',
|
|
} as const;
|
|
|
|
/** 画廊挂在平台页签的滚动容器里,返回时按同一容器恢复位置。 */
|
|
export const GAME_GALLERY_SCROLL_CONTAINER_SELECTOR = '.platform-tab-panel';
|
|
|
|
export type GameGalleryFilters = {
|
|
keyword: string;
|
|
category: GameCategory | '全部';
|
|
device: GameDeviceFilter;
|
|
};
|
|
|
|
function readGalleryStorage() {
|
|
try {
|
|
return globalThis.sessionStorage ?? null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function normalizeCategory(value: string | null | undefined) {
|
|
const normalized = value?.trim() ?? '';
|
|
return (
|
|
GAME_GALLERY_CATEGORIES.find(
|
|
(item) => item !== '全部' && item === normalized,
|
|
) ?? '全部'
|
|
);
|
|
}
|
|
|
|
function normalizeDevice(value: string | null | undefined) {
|
|
const normalized = value?.trim() ?? '';
|
|
return (
|
|
GAME_GALLERY_DEVICE_FILTERS.find((item) => item === normalized) ?? '全部'
|
|
);
|
|
}
|
|
|
|
export function gameGalleryScrollKey(filters: GameGalleryFilters) {
|
|
return `${GALLERY_STORAGE_KEYS.scroll}:${filters.keyword.trim()}|${filters.category}|${filters.device}`;
|
|
}
|
|
|
|
/**
|
|
* 只有地址栏里明确出现的筛选项才覆盖返回上下文,避免把用户分享的
|
|
* `?category=动作` 当成“其他筛选项已被清空”。
|
|
*/
|
|
export function readGameGalleryQueryFilters(
|
|
search: string,
|
|
): Partial<GameGalleryFilters> {
|
|
const params = new URLSearchParams(search);
|
|
const filters: Partial<GameGalleryFilters> = {};
|
|
const keyword = params.get(GALLERY_QUERY_KEYS.keyword);
|
|
if (keyword !== null) filters.keyword = keyword;
|
|
const category = params.get(GALLERY_QUERY_KEYS.category);
|
|
if (category !== null) filters.category = normalizeCategory(category);
|
|
const device = params.get(GALLERY_QUERY_KEYS.device);
|
|
if (device !== null) filters.device = normalizeDevice(device);
|
|
return filters;
|
|
}
|
|
|
|
export function readStoredGameGalleryFilters(): Partial<GameGalleryFilters> {
|
|
const storage = readGalleryStorage();
|
|
if (!storage) return {};
|
|
try {
|
|
const raw = storage.getItem(GALLERY_STORAGE_KEYS.filters);
|
|
if (!raw) return {};
|
|
const parsed = JSON.parse(raw) as Partial<GameGalleryFilters> | null;
|
|
if (!parsed || typeof parsed !== 'object') return {};
|
|
const filters: Partial<GameGalleryFilters> = {};
|
|
if (typeof parsed.keyword === 'string') filters.keyword = parsed.keyword;
|
|
if (typeof parsed.category === 'string') {
|
|
filters.category = normalizeCategory(parsed.category);
|
|
}
|
|
if (typeof parsed.device === 'string') {
|
|
filters.device = normalizeDevice(parsed.device);
|
|
}
|
|
return filters;
|
|
} catch {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
export function writeStoredGameGalleryFilters(filters: GameGalleryFilters) {
|
|
const storage = readGalleryStorage();
|
|
if (!storage) return;
|
|
try {
|
|
storage.setItem(GALLERY_STORAGE_KEYS.filters, JSON.stringify(filters));
|
|
} catch {
|
|
// 存储被禁用时筛选仍在本页生效,只是刷新后不再恢复。
|
|
}
|
|
}
|
|
|
|
export function readStoredGameGalleryScrollTop(key: string) {
|
|
const storage = readGalleryStorage();
|
|
if (!storage) return 0;
|
|
try {
|
|
const value = Number(storage.getItem(key));
|
|
return Number.isFinite(value) && value > 0 ? value : 0;
|
|
} catch {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
export function writeStoredGameGalleryScrollTop(
|
|
key: string,
|
|
scrollTop: number,
|
|
) {
|
|
const storage = readGalleryStorage();
|
|
if (!storage) return;
|
|
try {
|
|
if (scrollTop > 0) storage.setItem(key, String(Math.round(scrollTop)));
|
|
else storage.removeItem(key);
|
|
} catch {
|
|
// 忽略存储失败,滚动位置不是业务状态。
|
|
}
|
|
}
|
|
|
|
export function syncGameGalleryQuery(filters: GameGalleryFilters) {
|
|
if (typeof window === 'undefined' || !window.history?.replaceState) return;
|
|
try {
|
|
const url = new URL(window.location.href);
|
|
const assign = (key: string, value: string) => {
|
|
if (value) url.searchParams.set(key, value);
|
|
else url.searchParams.delete(key);
|
|
};
|
|
assign(GALLERY_QUERY_KEYS.keyword, filters.keyword.trim());
|
|
assign(
|
|
GALLERY_QUERY_KEYS.category,
|
|
filters.category === '全部' ? '' : filters.category,
|
|
);
|
|
assign(
|
|
GALLERY_QUERY_KEYS.device,
|
|
filters.device === '全部' ? '' : filters.device,
|
|
);
|
|
window.history.replaceState(
|
|
window.history.state,
|
|
'',
|
|
`${url.pathname}${url.search}${url.hash}`,
|
|
);
|
|
} catch {
|
|
// 地址栏同步失败不影响筛选本身。
|
|
}
|
|
}
|
|
|
|
export function matchesGameDeviceFilter(
|
|
game: GameDistributionGame,
|
|
device: GameDeviceFilter,
|
|
) {
|
|
if (device === '桌面') return game.deviceSupport.desktop;
|
|
if (device === '移动') return game.deviceSupport.mobile;
|
|
return true;
|
|
}
|