Files
Genarrative/src/components/game-distribution/gameGalleryViewState.ts
T
kdletters 328ac31844 恢复游戏分发完整实现(特性分支)
- 主站:游戏广场、详情、在线游玩、网页发布与作者中心,以及共享契约与客户端服务
- 后端:module-game-distribution 领域层、SpacetimeDB 表/迁移/绑定、spacetime-client facade、api-server 路由与发行网关
- 后台:游戏审核页(待审列表、通过/拒绝、安全下架)
- AGC:发布面板、本地导出包读取命令与发布服务,含默认跳过的真实链路测试
- 运维:发行来源 nginx 模板与门禁、game-distribution:publish 灰度发布开关、OSS PutObject 受控重试
- 文档:主规范、里程碑与实施计划、决策日志与踩坑记录
2026-09-20 20:49:42 +08:00

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;
}