Prune stale docs and update .hermes content
Delete a large set of outdated documentation (many files under docs/ and .hermes/plans/, including audits, design, prd, technical, planning, assets, and todos). Update and consolidate .hermes content: refresh shared-memory pages (decision-log, development-workflow, document-map, pitfalls, project-overview, team-conventions) and several skills/references under .hermes/skills. Also modify AGENTS.md, README.md, UI_CODING_STANDARD.md, docs/README.md and .encoding-check-ignore. Purpose: clean up stale planning/audit material and keep current hermes documentation and related top-level docs in sync.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
ArrowRight,
|
||||
AlertCircle,
|
||||
ArrowRight,
|
||||
BookOpen,
|
||||
Camera,
|
||||
CheckCircle2,
|
||||
@@ -16,8 +16,8 @@ import {
|
||||
Heart,
|
||||
LogIn,
|
||||
MessageCircle,
|
||||
Pencil,
|
||||
Palette,
|
||||
Pencil,
|
||||
Plus,
|
||||
Search,
|
||||
Settings,
|
||||
@@ -47,22 +47,22 @@ import communityQqQrImage from '../../../media/social-media-group/qq.png';
|
||||
import communityWechatQrImage from '../../../media/social-media-group/wechat.png';
|
||||
import type { PublicUserSummary } from '../../../packages/shared/src/contracts/auth';
|
||||
import type {
|
||||
ConfirmWechatProfileRechargeOrderResponse,
|
||||
CustomWorldLibraryEntry,
|
||||
PlatformBrowseHistoryEntry,
|
||||
ConfirmWechatProfileRechargeOrderResponse,
|
||||
ProfileDashboardCardKey,
|
||||
ProfileDashboardSummary,
|
||||
ProfilePlayedWorkSummary,
|
||||
ProfilePlayStatsResponse,
|
||||
ProfileReferralInviteCenterResponse,
|
||||
ProfileRechargeCenterResponse,
|
||||
ProfileRechargeProduct,
|
||||
WechatMiniProgramPayParams,
|
||||
ProfileReferralInviteCenterResponse,
|
||||
ProfileSaveArchiveSummary,
|
||||
ProfileTaskCenterResponse,
|
||||
ProfileTaskItem,
|
||||
ProfileWalletLedgerResponse,
|
||||
RedeemProfileRewardCodeResponse,
|
||||
WechatMiniProgramPayParams,
|
||||
} from '../../../packages/shared/src/contracts/runtime';
|
||||
import type { HydratedSavedGameSnapshot } from '../../persistence/runtimeSnapshotTypes';
|
||||
import { buildPublicWorkDetailUrl } from '../../routing/appPageRoutes';
|
||||
@@ -77,8 +77,8 @@ import {
|
||||
claimRpgProfileTaskReward,
|
||||
confirmWechatRpgProfileRechargeOrder,
|
||||
createRpgProfileRechargeOrder,
|
||||
getRpgProfileReferralInviteCenter,
|
||||
getRpgProfileRechargeCenter,
|
||||
getRpgProfileReferralInviteCenter,
|
||||
getRpgProfileTasks,
|
||||
getRpgProfileWalletLedger,
|
||||
redeemRpgProfileReferralInviteCode,
|
||||
@@ -242,6 +242,15 @@ type DiscoverChannel =
|
||||
| 'ranking'
|
||||
| 'edutainment';
|
||||
type PlatformRankingTab = 'hot' | 'remix' | 'new' | 'like';
|
||||
type PlatformCategoryKindFilter =
|
||||
| 'all'
|
||||
| 'puzzle'
|
||||
| 'match3d'
|
||||
| 'square-hole'
|
||||
| 'visual-novel'
|
||||
| 'big-fish'
|
||||
| 'custom-world';
|
||||
type PlatformCategorySortMode = 'composite' | 'latest' | 'play' | 'like';
|
||||
|
||||
const COMMUNITY_QR_CODES = [
|
||||
{
|
||||
@@ -269,6 +278,27 @@ const EDUTAINMENT_DISCOVER_CHANNEL = {
|
||||
id: 'edutainment',
|
||||
label: EDUTAINMENT_WORK_TAG,
|
||||
} as const;
|
||||
const PLATFORM_CATEGORY_KIND_FILTERS: Array<{
|
||||
id: PlatformCategoryKindFilter;
|
||||
label: string;
|
||||
}> = [
|
||||
{ id: 'all', label: '全部' },
|
||||
{ id: 'puzzle', label: '拼图' },
|
||||
{ id: 'match3d', label: '抓鹅' },
|
||||
{ id: 'square-hole', label: '方洞' },
|
||||
{ id: 'visual-novel', label: '视觉' },
|
||||
{ id: 'big-fish', label: '大鱼' },
|
||||
{ id: 'custom-world', label: 'RPG' },
|
||||
];
|
||||
const PLATFORM_CATEGORY_SORT_OPTIONS: Array<{
|
||||
id: PlatformCategorySortMode;
|
||||
label: string;
|
||||
}> = [
|
||||
{ id: 'composite', label: '综合' },
|
||||
{ id: 'latest', label: '最新' },
|
||||
{ id: 'play', label: '游玩' },
|
||||
{ id: 'like', label: '点赞' },
|
||||
];
|
||||
const BABY_LOVE_DRAWING_DEFAULT_CARD = {
|
||||
title: '宝贝爱画',
|
||||
subtitle: '空白画板',
|
||||
@@ -1391,6 +1421,123 @@ function PlatformCategoryGameItem({
|
||||
);
|
||||
}
|
||||
|
||||
function PlatformCategoryFilterDialog({
|
||||
kindFilter,
|
||||
sortMode,
|
||||
resultCount,
|
||||
onKindFilterChange,
|
||||
onSortModeChange,
|
||||
onReset,
|
||||
onClose,
|
||||
}: {
|
||||
kindFilter: PlatformCategoryKindFilter;
|
||||
sortMode: PlatformCategorySortMode;
|
||||
resultCount: number;
|
||||
onKindFilterChange: (filter: PlatformCategoryKindFilter) => void;
|
||||
onSortModeChange: (mode: PlatformCategorySortMode) => void;
|
||||
onReset: () => void;
|
||||
onClose: () => void;
|
||||
}) {
|
||||
return (
|
||||
<div className="platform-modal-backdrop fixed inset-0 z-[90] flex items-end justify-center px-3 py-4 sm:items-center">
|
||||
<button
|
||||
type="button"
|
||||
aria-label="关闭分类筛选"
|
||||
className="absolute inset-0"
|
||||
onClick={onClose}
|
||||
/>
|
||||
<div
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="分类筛选"
|
||||
className="platform-modal-shell platform-category-filter-dialog relative w-full max-w-md overflow-hidden rounded-[1.35rem]"
|
||||
>
|
||||
<div className="flex min-w-0 items-center justify-between gap-3 px-4 py-4">
|
||||
<div className="min-w-0">
|
||||
<div className="text-base font-black text-[var(--platform-text-strong)]">
|
||||
分类筛选
|
||||
</div>
|
||||
<div className="mt-0.5 text-xs font-semibold text-[var(--platform-text-soft)]">
|
||||
{resultCount} 个作品
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="platform-modal-close flex h-9 w-9 items-center justify-center rounded-full"
|
||||
aria-label="关闭"
|
||||
>
|
||||
<XCircle className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 px-4 pb-4">
|
||||
<div className="grid gap-2">
|
||||
<div className="text-xs font-black text-[var(--platform-text-soft)]">
|
||||
玩法
|
||||
</div>
|
||||
<div className="platform-category-filter-dialog__options">
|
||||
{PLATFORM_CATEGORY_KIND_FILTERS.map((option) => {
|
||||
const active = option.id === kindFilter;
|
||||
|
||||
return (
|
||||
<button
|
||||
key={option.id}
|
||||
type="button"
|
||||
onClick={() => onKindFilterChange(option.id)}
|
||||
className={`platform-category-filter-dialog__option ${active ? 'platform-category-filter-dialog__option--active' : ''}`}
|
||||
>
|
||||
{option.label}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-2">
|
||||
<div className="text-xs font-black text-[var(--platform-text-soft)]">
|
||||
排序
|
||||
</div>
|
||||
<div className="platform-category-filter-dialog__options">
|
||||
{PLATFORM_CATEGORY_SORT_OPTIONS.map((option) => {
|
||||
const active = option.id === sortMode;
|
||||
|
||||
return (
|
||||
<button
|
||||
key={option.id}
|
||||
type="button"
|
||||
onClick={() => onSortModeChange(option.id)}
|
||||
className={`platform-category-filter-dialog__option ${active ? 'platform-category-filter-dialog__option--active' : ''}`}
|
||||
>
|
||||
{option.label}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="platform-category-filter-dialog__actions">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onReset}
|
||||
className="platform-category-filter-dialog__action platform-category-filter-dialog__action--secondary"
|
||||
>
|
||||
重置
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="platform-category-filter-dialog__action platform-category-filter-dialog__action--primary"
|
||||
>
|
||||
完成
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function buildPublicCategoryGroups(
|
||||
featuredEntries: PlatformPublicGalleryCard[],
|
||||
latestEntries: PlatformPublicGalleryCard[],
|
||||
@@ -1830,6 +1977,64 @@ function getPlatformCategoryCompositeScore(entry: PlatformPublicGalleryCard) {
|
||||
);
|
||||
}
|
||||
|
||||
function getPlatformCategoryKindFilter(entry: PlatformPublicGalleryCard) {
|
||||
if (isPuzzleGalleryEntry(entry)) {
|
||||
return 'puzzle';
|
||||
}
|
||||
|
||||
if (isMatch3DGalleryEntry(entry)) {
|
||||
return 'match3d';
|
||||
}
|
||||
|
||||
if (isSquareHoleGalleryEntry(entry)) {
|
||||
return 'square-hole';
|
||||
}
|
||||
|
||||
if (isVisualNovelGalleryEntry(entry)) {
|
||||
return 'visual-novel';
|
||||
}
|
||||
|
||||
if (isBigFishGalleryEntry(entry)) {
|
||||
return 'big-fish';
|
||||
}
|
||||
|
||||
return 'custom-world';
|
||||
}
|
||||
|
||||
function matchesPlatformCategoryKindFilter(
|
||||
entry: PlatformPublicGalleryCard,
|
||||
kindFilter: PlatformCategoryKindFilter,
|
||||
) {
|
||||
return (
|
||||
kindFilter === 'all' || getPlatformCategoryKindFilter(entry) === kindFilter
|
||||
);
|
||||
}
|
||||
|
||||
function sortPlatformCategoryEntries(
|
||||
entries: PlatformPublicGalleryCard[],
|
||||
sortMode: PlatformCategorySortMode,
|
||||
) {
|
||||
return [...entries].sort((left, right) => {
|
||||
if (sortMode === 'latest') {
|
||||
return getPlatformWorldTimestamp(right) - getPlatformWorldTimestamp(left);
|
||||
}
|
||||
|
||||
const metricDiff =
|
||||
sortMode === 'play'
|
||||
? getPlatformWorldPlayCount(right) - getPlatformWorldPlayCount(left)
|
||||
: sortMode === 'like'
|
||||
? getPlatformWorldLikeCount(right) - getPlatformWorldLikeCount(left)
|
||||
: getPlatformCategoryCompositeScore(right) -
|
||||
getPlatformCategoryCompositeScore(left);
|
||||
|
||||
if (metricDiff !== 0) {
|
||||
return metricDiff;
|
||||
}
|
||||
|
||||
return getPlatformWorldTimestamp(right) - getPlatformWorldTimestamp(left);
|
||||
});
|
||||
}
|
||||
|
||||
function getPlatformCategoryPrimaryMetric(entry: PlatformPublicGalleryCard) {
|
||||
const likeCount = getPlatformWorldLikeCount(entry);
|
||||
if (likeCount > 0) {
|
||||
@@ -3463,6 +3668,12 @@ export function RpgEntryHomeView({
|
||||
const [selectedCategoryTag, setSelectedCategoryTag] = useState<string | null>(
|
||||
null,
|
||||
);
|
||||
const [categoryKindFilter, setCategoryKindFilter] =
|
||||
useState<PlatformCategoryKindFilter>('all');
|
||||
const [categorySortMode, setCategorySortMode] =
|
||||
useState<PlatformCategorySortMode>('composite');
|
||||
const [isCategoryFilterPanelOpen, setIsCategoryFilterPanelOpen] =
|
||||
useState(false);
|
||||
const [discoverChannel, setDiscoverChannel] =
|
||||
useState<DiscoverChannel>('recommend');
|
||||
const mobileDiscoverFeedRef = useRef<HTMLElement | null>(null);
|
||||
@@ -3586,17 +3797,24 @@ export function RpgEntryHomeView({
|
||||
return [];
|
||||
}
|
||||
|
||||
return [...activeCategoryGroup.entries].sort((left, right) => {
|
||||
const scoreDiff =
|
||||
getPlatformCategoryCompositeScore(right) -
|
||||
getPlatformCategoryCompositeScore(left);
|
||||
if (scoreDiff !== 0) {
|
||||
return scoreDiff;
|
||||
}
|
||||
|
||||
return getPlatformWorldTimestamp(right) - getPlatformWorldTimestamp(left);
|
||||
});
|
||||
}, [activeCategoryGroup]);
|
||||
return sortPlatformCategoryEntries(
|
||||
activeCategoryGroup.entries.filter((entry) =>
|
||||
matchesPlatformCategoryKindFilter(entry, categoryKindFilter),
|
||||
),
|
||||
categorySortMode,
|
||||
);
|
||||
}, [activeCategoryGroup, categoryKindFilter, categorySortMode]);
|
||||
const activeCategoryRawCount = activeCategoryGroup?.entries.length ?? 0;
|
||||
const activeCategoryFilterLabel =
|
||||
PLATFORM_CATEGORY_KIND_FILTERS.find(
|
||||
(option) => option.id === categoryKindFilter,
|
||||
)?.label ?? '全部';
|
||||
const activeCategorySortLabel =
|
||||
PLATFORM_CATEGORY_SORT_OPTIONS.find(
|
||||
(option) => option.id === categorySortMode,
|
||||
)?.label ?? '综合';
|
||||
const activeCategoryFilterCount = activeCategoryEntries.length;
|
||||
const categoryFilterApplied = categoryKindFilter !== 'all';
|
||||
const visibleTabs = useMemo<PlatformHomeTab[]>(
|
||||
() =>
|
||||
isAuthenticated
|
||||
@@ -3928,7 +4146,7 @@ export function RpgEntryHomeView({
|
||||
setIsWalletLedgerOpen(true);
|
||||
loadWalletLedger();
|
||||
};
|
||||
const loadRechargeCenter = () => {
|
||||
const loadRechargeCenter = useCallback(() => {
|
||||
setRechargeError(null);
|
||||
setIsLoadingRechargeCenter(true);
|
||||
void getRpgProfileRechargeCenter()
|
||||
@@ -3940,7 +4158,7 @@ export function RpgEntryHomeView({
|
||||
);
|
||||
})
|
||||
.finally(() => setIsLoadingRechargeCenter(false));
|
||||
};
|
||||
}, []);
|
||||
const refreshRechargeState = useCallback(
|
||||
() => {
|
||||
loadRechargeCenter();
|
||||
@@ -4301,6 +4519,18 @@ export function RpgEntryHomeView({
|
||||
const submitMobileSearch = () => {
|
||||
submitWorkSearch(mobileSearchKeyword);
|
||||
};
|
||||
const cycleCategorySortMode = () => {
|
||||
const currentIndex = PLATFORM_CATEGORY_SORT_OPTIONS.findIndex(
|
||||
(option) => option.id === categorySortMode,
|
||||
);
|
||||
const nextIndex =
|
||||
currentIndex >= 0
|
||||
? (currentIndex + 1) % PLATFORM_CATEGORY_SORT_OPTIONS.length
|
||||
: 0;
|
||||
setCategorySortMode(
|
||||
PLATFORM_CATEGORY_SORT_OPTIONS[nextIndex]?.id ?? 'composite',
|
||||
);
|
||||
};
|
||||
const desktopHeroEntry =
|
||||
featuredShelf[0] ?? generalLatestEntries[0] ?? myEntries[0] ?? null;
|
||||
const desktopHeroCover = desktopHeroEntry
|
||||
@@ -4904,12 +5134,18 @@ export function RpgEntryHomeView({
|
||||
<div className="platform-category-filter-row">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsCategoryFilterPanelOpen(true)}
|
||||
aria-haspopup="dialog"
|
||||
className="platform-category-filter-button"
|
||||
>
|
||||
<SlidersHorizontal className="h-4 w-4" />
|
||||
<span>筛选</span>
|
||||
<span>
|
||||
{categoryFilterApplied
|
||||
? activeCategoryFilterLabel
|
||||
: '筛选'}
|
||||
</span>
|
||||
<span className="platform-category-filter-button__count">
|
||||
{activeCategoryGroup.entries.length}
|
||||
{activeCategoryFilterCount}
|
||||
</span>
|
||||
</button>
|
||||
<span className="platform-category-filter-divider" />
|
||||
@@ -4933,22 +5169,27 @@ export function RpgEntryHomeView({
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={cycleCategorySortMode}
|
||||
className="platform-category-sort-button"
|
||||
>
|
||||
<span>按综合排序</span>
|
||||
<span>按{activeCategorySortLabel}排序</span>
|
||||
<ChevronDown className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
|
||||
<div className="platform-category-game-list">
|
||||
{activeCategoryEntries.map((entry) => (
|
||||
<PlatformCategoryGameItem
|
||||
key={`${buildPublicGalleryCardKey(entry)}:mobile-category:${activeCategoryGroup.tag}`}
|
||||
entry={entry}
|
||||
categoryTag={activeCategoryGroup.tag}
|
||||
onClick={() => onOpenGalleryDetail(entry)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{activeCategoryEntries.length > 0 ? (
|
||||
<div className="platform-category-game-list">
|
||||
{activeCategoryEntries.map((entry) => (
|
||||
<PlatformCategoryGameItem
|
||||
key={`${buildPublicGalleryCardKey(entry)}:mobile-category:${activeCategoryGroup.tag}:${categoryKindFilter}:${categorySortMode}`}
|
||||
entry={entry}
|
||||
categoryTag={activeCategoryGroup.tag}
|
||||
onClick={() => onOpenGalleryDetail(entry)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<EmptyShelf text="当前筛选下没有作品。" />
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<EmptyShelf text="公开广场暂时还没有可分类的作品。" />
|
||||
@@ -5069,35 +5310,63 @@ export function RpgEntryHomeView({
|
||||
<SectionHeader title="作品分类" detail="GAME CATEGORY" />
|
||||
{isLoadingPlatform ? (
|
||||
<EmptyShelf text="正在读取作品分类..." />
|
||||
) : activeCategoryGroup && desktopCategoryGrid.length > 0 ? (
|
||||
) : activeCategoryGroup && activeCategoryRawCount > 0 ? (
|
||||
<>
|
||||
<div className="mb-4 flex min-w-0 items-center gap-2 overflow-x-auto pb-1 scrollbar-hide">
|
||||
{categoryGroups.map((group) => {
|
||||
const active = group.tag === activeCategoryGroup.tag;
|
||||
<div className="mb-4 flex min-w-0 items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsCategoryFilterPanelOpen(true)}
|
||||
aria-haspopup="dialog"
|
||||
className="platform-category-filter-button"
|
||||
>
|
||||
<SlidersHorizontal className="h-4 w-4" />
|
||||
<span>
|
||||
{categoryFilterApplied ? activeCategoryFilterLabel : '筛选'}
|
||||
</span>
|
||||
<span className="platform-category-filter-button__count">
|
||||
{activeCategoryFilterCount}
|
||||
</span>
|
||||
</button>
|
||||
<div className="flex min-w-0 flex-1 items-center gap-2 overflow-x-auto pb-1 scrollbar-hide">
|
||||
{categoryGroups.map((group) => {
|
||||
const active = group.tag === activeCategoryGroup.tag;
|
||||
|
||||
return (
|
||||
<button
|
||||
key={`${group.tag}:desktop-discover-category`}
|
||||
type="button"
|
||||
onClick={() => setSelectedCategoryTag(group.tag)}
|
||||
className={`platform-category-chip shrink-0 ${active ? 'platform-category-chip--active' : ''}`}
|
||||
>
|
||||
{group.tag}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="grid gap-4 xl:grid-cols-3">
|
||||
{desktopCategoryGrid.map((entry) => (
|
||||
<WorldCard
|
||||
key={`${buildPublicGalleryCardKey(entry)}:desktop-discover-category:${activeCategoryGroup.tag}`}
|
||||
entry={entry}
|
||||
onClick={() => openRecommendGalleryDetail(entry)}
|
||||
className="w-full min-w-0"
|
||||
authorAvatarUrl={getPublicEntryAuthorAvatarUrl(entry)}
|
||||
/>
|
||||
))}
|
||||
return (
|
||||
<button
|
||||
key={`${group.tag}:desktop-discover-category`}
|
||||
type="button"
|
||||
onClick={() => setSelectedCategoryTag(group.tag)}
|
||||
className={`platform-category-chip shrink-0 ${active ? 'platform-category-chip--active' : ''}`}
|
||||
>
|
||||
{group.tag}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={cycleCategorySortMode}
|
||||
className="platform-category-sort-button shrink-0"
|
||||
>
|
||||
<span>{activeCategorySortLabel}</span>
|
||||
<ChevronDown className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
{desktopCategoryGrid.length > 0 ? (
|
||||
<div className="grid gap-4 xl:grid-cols-3">
|
||||
{desktopCategoryGrid.map((entry) => (
|
||||
<WorldCard
|
||||
key={`${buildPublicGalleryCardKey(entry)}:desktop-discover-category:${activeCategoryGroup.tag}:${categoryKindFilter}:${categorySortMode}`}
|
||||
entry={entry}
|
||||
onClick={() => openRecommendGalleryDetail(entry)}
|
||||
className="w-full min-w-0"
|
||||
authorAvatarUrl={getPublicEntryAuthorAvatarUrl(entry)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<EmptyShelf text="当前筛选下没有作品。" />
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<EmptyShelf text="暂时还没有可分类的作品。" />
|
||||
@@ -5747,35 +6016,65 @@ export function RpgEntryHomeView({
|
||||
<SectionHeader title="作品分类" detail="GAME CATEGORY" />
|
||||
{isLoadingPlatform ? (
|
||||
<EmptyShelf text="正在读取作品分类..." />
|
||||
) : activeCategoryGroup && desktopCategoryGrid.length > 0 ? (
|
||||
) : activeCategoryGroup && activeCategoryRawCount > 0 ? (
|
||||
<>
|
||||
<div className="mb-4 flex min-w-0 items-center gap-2 overflow-x-auto pb-1 scrollbar-hide">
|
||||
{categoryGroups.map((group) => {
|
||||
const active = group.tag === activeCategoryGroup.tag;
|
||||
<div className="mb-4 flex min-w-0 items-center gap-2">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsCategoryFilterPanelOpen(true)}
|
||||
aria-haspopup="dialog"
|
||||
className="platform-category-filter-button"
|
||||
>
|
||||
<SlidersHorizontal className="h-4 w-4" />
|
||||
<span>
|
||||
{categoryFilterApplied
|
||||
? activeCategoryFilterLabel
|
||||
: '筛选'}
|
||||
</span>
|
||||
<span className="platform-category-filter-button__count">
|
||||
{activeCategoryFilterCount}
|
||||
</span>
|
||||
</button>
|
||||
<div className="flex min-w-0 flex-1 items-center gap-2 overflow-x-auto pb-1 scrollbar-hide">
|
||||
{categoryGroups.map((group) => {
|
||||
const active = group.tag === activeCategoryGroup.tag;
|
||||
|
||||
return (
|
||||
<button
|
||||
key={`${group.tag}:desktop-category`}
|
||||
type="button"
|
||||
onClick={() => setSelectedCategoryTag(group.tag)}
|
||||
className={`platform-category-chip shrink-0 ${active ? 'platform-category-chip--active' : ''}`}
|
||||
>
|
||||
{group.tag}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="grid gap-4 xl:grid-cols-3">
|
||||
{desktopCategoryGrid.map((entry) => (
|
||||
<WorldCard
|
||||
key={`${buildPublicGalleryCardKey(entry)}:desktop-category:${activeCategoryGroup.tag}`}
|
||||
entry={entry}
|
||||
onClick={() => openRecommendGalleryDetail(entry)}
|
||||
className="w-full min-w-0"
|
||||
authorAvatarUrl={getPublicEntryAuthorAvatarUrl(entry)}
|
||||
/>
|
||||
))}
|
||||
return (
|
||||
<button
|
||||
key={`${group.tag}:desktop-category`}
|
||||
type="button"
|
||||
onClick={() => setSelectedCategoryTag(group.tag)}
|
||||
className={`platform-category-chip shrink-0 ${active ? 'platform-category-chip--active' : ''}`}
|
||||
>
|
||||
{group.tag}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={cycleCategorySortMode}
|
||||
className="platform-category-sort-button shrink-0"
|
||||
>
|
||||
<span>{activeCategorySortLabel}</span>
|
||||
<ChevronDown className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
{desktopCategoryGrid.length > 0 ? (
|
||||
<div className="grid gap-4 xl:grid-cols-3">
|
||||
{desktopCategoryGrid.map((entry) => (
|
||||
<WorldCard
|
||||
key={`${buildPublicGalleryCardKey(entry)}:desktop-category:${activeCategoryGroup.tag}:${categoryKindFilter}:${categorySortMode}`}
|
||||
entry={entry}
|
||||
onClick={() => openRecommendGalleryDetail(entry)}
|
||||
className="w-full min-w-0"
|
||||
authorAvatarUrl={getPublicEntryAuthorAvatarUrl(entry)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<EmptyShelf text="当前筛选下没有作品。" />
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<EmptyShelf text="暂时还没有可分类的作品。" />
|
||||
@@ -5885,6 +6184,20 @@ export function RpgEntryHomeView({
|
||||
onClose={() => setRechargePaymentResult(null)}
|
||||
/>
|
||||
) : null;
|
||||
const categoryFilterDialog: ReactNode = isCategoryFilterPanelOpen ? (
|
||||
<PlatformCategoryFilterDialog
|
||||
kindFilter={categoryKindFilter}
|
||||
sortMode={categorySortMode}
|
||||
resultCount={activeCategoryFilterCount}
|
||||
onKindFilterChange={setCategoryKindFilter}
|
||||
onSortModeChange={setCategorySortMode}
|
||||
onReset={() => {
|
||||
setCategoryKindFilter('all');
|
||||
setCategorySortMode('composite');
|
||||
}}
|
||||
onClose={() => setIsCategoryFilterPanelOpen(false)}
|
||||
/>
|
||||
) : null;
|
||||
|
||||
if (!isDesktopLayout) {
|
||||
const isMobileRecommendTab = activeTab === 'home';
|
||||
@@ -5969,6 +6282,7 @@ export function RpgEntryHomeView({
|
||||
{rewardCodeModal}
|
||||
{rechargeModal}
|
||||
{rechargePaymentResultModal}
|
||||
{categoryFilterDialog}
|
||||
{isTaskCenterOpen ? (
|
||||
<ProfileTaskCenterModal
|
||||
center={taskCenter}
|
||||
@@ -6101,6 +6415,7 @@ export function RpgEntryHomeView({
|
||||
{rewardCodeModal}
|
||||
{rechargeModal}
|
||||
{rechargePaymentResultModal}
|
||||
{categoryFilterDialog}
|
||||
{isTaskCenterOpen ? (
|
||||
<ProfileTaskCenterModal
|
||||
center={taskCenter}
|
||||
|
||||
Reference in New Issue
Block a user