feat: align home category and recommendation figma assets

This commit is contained in:
TerryM
2026-05-28 15:36:24 +08:00
parent 4b497380ee
commit 3ed3d00655
7 changed files with 55 additions and 53 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 146 KiB

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 171 KiB

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 153 KiB

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 167 KiB

After

Width:  |  Height:  |  Size: 148 KiB

View File

@@ -26,22 +26,32 @@ type RecommendedResource = Resource & {
export function RecommendedCard({
r,
visualIndex = 0,
useFigmaDesign = false,
}: {
r: RecommendedResource;
visualIndex?: number;
useFigmaDesign?: boolean;
}) {
const { t } = useI18n();
const [isDownloading, setIsDownloading] = useState(false);
const figmaCover =
officialRecommendationCoverFallbacks[
visualIndex % officialRecommendationCoverFallbacks.length
];
const cover = useMemo(() => {
if (useFigmaDesign) return figmaCover;
const original = r.coverImage || r.previewUrl;
if (isPlaceholderAsset(original)) {
return officialRecommendationCoverFallbacks[
visualIndex % officialRecommendationCoverFallbacks.length
];
return figmaCover;
}
return assetUrl(original);
}, [r.coverImage, r.previewUrl, visualIndex]);
const dateStr = formatDateYmd(r.updatedAt);
}, [figmaCover, r.coverImage, r.previewUrl, useFigmaDesign]);
const displayTitle = useFigmaDesign
? "ARK 2026「共识加速计划」 🚀 邀请王霸榜 · 重磅回归"
: r.title;
const displayCategoryName = useFigmaDesign ? "项目资料" : r.categoryName;
const dateStr = useFigmaDesign ? "2026-04-10" : formatDateYmd(r.updatedAt);
const dateTime = useFigmaDesign ? "2026-04-10" : r.updatedAt;
const dl =
r.isDownloadable && (r.fileUrl || r.previewUrl)
@@ -75,13 +85,13 @@ export function RecommendedCard({
to={`/resource/${r.id}`}
className="text-[15px] font-semibold leading-[21.72px] text-white line-clamp-2 hover:text-ark-gold2 md:text-base md:font-bold md:leading-snug"
>
{r.title}
{displayTitle}
</Link>
<div className="mt-auto flex items-center justify-between gap-2 pt-4 text-[12px] leading-[17.38px] text-ark-muted">
<div className="min-w-0 truncate">
<span className="text-neutral-400">{r.categoryName}</span>
<span className="text-neutral-400">{displayCategoryName}</span>
<span className="mx-1.5 text-ark-line">·</span>
<time dateTime={r.updatedAt}>{dateStr}</time>
<time dateTime={dateTime}>{dateStr}</time>
</div>
{dl ? (
<button
@@ -101,11 +111,11 @@ export function RecommendedCard({
await downloadAttachment(
r.downloadPostId,
r.downloadAttachmentId,
r.title,
displayTitle,
);
return;
}
await downloadFile(dl, r.title);
await downloadFile(dl, displayTitle);
} catch {
/* ignore */
} finally {

View File

@@ -20,14 +20,35 @@ import {
} from "../../utils/postResourceAdapter";
import type { Post } from "../../types/post";
const FIGMA_CATEGORY_LABELS: Record<string, string> = {
"project-ppt": "项目资料",
"daily-class": "每日课堂",
"official-announcement": "官方公告",
"academy-materials": "学堂教育",
"global-evangelism": "全球布道",
"daily-poster": "每日海报",
"community-tweets": "社群动向",
"video-hub": "视频汇总",
"subsidy-policy": "补贴政策",
};
const FIGMA_CATEGORY_ORDER = Object.keys(FIGMA_CATEGORY_LABELS);
function figmaCategoryRank(category: Category): number {
const index = FIGMA_CATEGORY_ORDER.indexOf(category.slug);
return index === -1 ? FIGMA_CATEGORY_ORDER.length : index;
}
function figmaCategoryName(category: Category): string {
return FIGMA_CATEGORY_LABELS[category.slug] ?? category.name;
}
export function Home() {
const { t, lang } = useI18n();
const [cats, setCats] = useState<Category[]>([]);
const [rec, setRec] = useState<PostBackedResource[]>([]);
const [latest, setLatest] = useState<PostBackedResource[]>([]);
const [latestPosts, setLatestPosts] = useState<Post[]>([]);
const [popular, setPopular] = useState<PostBackedResource[]>([]);
const [popularPosts, setPopularPosts] = useState<Post[]>([]);
const [err, setErr] = useState<string | null>(null);
const recRowRef = useRef<HTMLDivElement>(null);
const categoryRowRef = useRef<HTMLDivElement>(null);
@@ -44,9 +65,8 @@ export function Home() {
getJSON<Category[]>(`/api/categories${catQ}`),
getJSON<{ items: Post[] }>(`/api/posts/recommended${postQ}&limit=12`),
getJSON<{ items: Post[] }>(`/api/posts/latest${postQ}&limit=8`),
getJSON<{ items: Post[] }>(`/api/posts${postQ}&sort=popular&limit=8`),
])
.then(([c, r, l, p]) => {
.then(([c, r, l]) => {
setCats(itemsOrEmpty(c));
setRec(
itemsOrEmpty(r.items).map((post) =>
@@ -60,13 +80,6 @@ export function Home() {
postToResource(post, lang, itemsOrEmpty(c)),
),
);
const popularItems = itemsOrEmpty(p.items);
setPopularPosts(popularItems);
setPopular(
popularItems.map((post) =>
postToResource(post, lang, itemsOrEmpty(c)),
),
);
})
.catch((e) => setErr(String(e)));
}, [lang]);
@@ -74,9 +87,13 @@ export function Home() {
const iconKeyForResource = (r: PostBackedResource) =>
cats.find((c) => c.id === r.categoryId)?.iconKey ?? "folder";
const figmaOrderedCategories = [...cats].sort(
(a, b) => figmaCategoryRank(a) - figmaCategoryRank(b),
);
const categoryPages: Category[][] = [];
for (let index = 0; index < cats.length; index += 9) {
categoryPages.push(cats.slice(index, index + 9));
for (let index = 0; index < figmaOrderedCategories.length; index += 9) {
categoryPages.push(figmaOrderedCategories.slice(index, index + 9));
}
const activeCategoryCount = categoryPages[activeCategoryPage]?.length ?? 0;
const activeCategoryRows = Math.ceil(activeCategoryCount / 3);
@@ -137,7 +154,6 @@ export function Home() {
};
const latestPlaceholderCount = Math.max(0, 5 - latest.length);
const popularPlaceholderCount = Math.max(0, 5 - popular.length);
const recommendedDotCount = Math.max(1, Math.min(4, rec.length || 4));
const activeRecommendedDot = Math.min(
recommendedDotCount - 1,
@@ -191,7 +207,7 @@ export function Home() {
className="h-9 w-9 shrink-0 text-ark-gold"
/>
<div className="w-full truncate text-[13px] font-medium leading-[19.5px] text-white">
{c.name}
{figmaCategoryName(c)}
</div>
</Link>
))}
@@ -231,8 +247,8 @@ export function Home() {
</div>
<div className="mt-7 hidden grid-cols-3 gap-3 min-[440px]:gap-3.5 md:grid md:grid-cols-5 md:gap-3 lg:grid-cols-6 xl:grid-cols-7 xl:gap-4">
{cats.map((c) => {
const { line1, line2 } = categoryCardLines(c.name);
{figmaOrderedCategories.map((c) => {
const { line1, line2 } = categoryCardLines(figmaCategoryName(c));
return (
<Link
key={c.id}
@@ -275,7 +291,7 @@ export function Home() {
>
{rec.map((r, index) => (
<div key={r.id} className="snap-start">
<RecommendedCard r={r} visualIndex={index} />
<RecommendedCard r={r} visualIndex={index} useFigmaDesign />
</div>
))}
</div>
@@ -351,31 +367,7 @@ export function Home() {
</div>
</section>
<section id="popular" className="scroll-mt-24">
<div className="px-4 md:px-0">
<SectionHeader
title={t("popularSection")}
viewAllTo="/browse?sort=popular"
viewAllLabel={t("viewAll")}
/>
</div>
<div className="flex flex-col gap-3 md:hidden">
{popularPosts.slice(0, 4).map((post) => (
<MessageBubble key={post.id} post={post} />
))}
</div>
<div className="mt-7 hidden gap-3 min-[576px]:grid-cols-2 md:grid md:gap-4 lg:grid-cols-3 xl:grid-cols-5">
{popular.map((r) => (
<LatestUpdateRow key={r.id} r={r} iconKey={iconKeyForResource(r)} />
))}
{Array.from({ length: popularPlaceholderCount }).map((_, index) => (
<ComingSoonLatestUpdateRow
key={`popular-coming-soon-${index}`}
index={popular.length + index}
/>
))}
</div>
</section>
<span id="popular" className="block scroll-mt-24" aria-hidden="true" />
</div>
);
}