feat: 首页、分类页加载数据逻辑重构
This commit is contained in:
@@ -77,6 +77,7 @@ function item(input: ItemInput): CategoryItem {
|
||||
};
|
||||
}
|
||||
|
||||
/** 目前只作为兜底数据 */
|
||||
const CATEGORY_ITEMS_BY_ID: Record<string, CategoryItem[]> = {
|
||||
math: [
|
||||
item({
|
||||
|
||||
@@ -142,8 +142,8 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
border: 1rpx solid #bbb;
|
||||
background: #f0f0f0;
|
||||
border: 1rpx solid rgba(50, 46, 37, 0.08);
|
||||
background: #f5edd8;
|
||||
}
|
||||
|
||||
.cat-card__thumb-img {
|
||||
@@ -261,4 +261,46 @@
|
||||
.cat-empty__text {
|
||||
font-size: 28rpx;
|
||||
color: @text-gray;
|
||||
}
|
||||
|
||||
// ── Skeleton ──
|
||||
@keyframes skeleton-pulse {
|
||||
0% {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.cat-card--skeleton {
|
||||
pointer-events: none;
|
||||
|
||||
.cat-card__thumb {
|
||||
background: #e8e2d5;
|
||||
animation: skeleton-pulse 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.cat-card__title,
|
||||
.cat-card__subtitle {
|
||||
background: #e8e2d5;
|
||||
border-radius: 8rpx;
|
||||
color: transparent;
|
||||
animation: skeleton-pulse 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.cat-card__title {
|
||||
width: 60%;
|
||||
height: 32rpx;
|
||||
}
|
||||
|
||||
.cat-card__subtitle {
|
||||
width: 80%;
|
||||
height: 24rpx;
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,16 @@ type CategoryTab = {
|
||||
icon: string;
|
||||
};
|
||||
|
||||
type CategoryDataset = {
|
||||
searchPlaceholder: string;
|
||||
categories: Array<{
|
||||
id: string;
|
||||
name: string;
|
||||
icon: string;
|
||||
items: CategoryItem[];
|
||||
}>;
|
||||
};
|
||||
|
||||
const SIDEBAR_TABS: CategoryTab[] = CATEGORY_LIST_WITH_ALL.map(
|
||||
({ id, name, icon }) => ({
|
||||
id,
|
||||
@@ -15,25 +25,34 @@ const SIDEBAR_TABS: CategoryTab[] = CATEGORY_LIST_WITH_ALL.map(
|
||||
}),
|
||||
);
|
||||
|
||||
const TAB_BAR_PATHS = new Set([
|
||||
'/pages/home/home',
|
||||
'/pages/category/category',
|
||||
'/pages/age/age',
|
||||
'/pages/favorites/favorites',
|
||||
'/pages/profile/profile',
|
||||
]);
|
||||
|
||||
// Page-level mutable state (shared across methods, set once on load)
|
||||
let _categoryData: CategoryDataset | null = null;
|
||||
|
||||
function buildAllItems(): CategoryItem[] {
|
||||
if (!_categoryData) return [];
|
||||
const items: CategoryItem[] = [];
|
||||
for (const group of CATEGORY_DATA.categories) {
|
||||
for (const group of _categoryData.categories) {
|
||||
for (const item of group.items) {
|
||||
items.push({
|
||||
...item,
|
||||
});
|
||||
items.push({ ...item });
|
||||
}
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
function getItemsByCategory(categoryId: string): CategoryItem[] {
|
||||
if (!_categoryData) return [];
|
||||
if (categoryId === 'all') return buildAllItems();
|
||||
const group = CATEGORY_DATA.categories.find((c) => c.id === categoryId);
|
||||
const group = _categoryData.categories.find((c) => c.id === categoryId);
|
||||
if (!group) return [];
|
||||
return group.items.map((item) => ({
|
||||
...item,
|
||||
}));
|
||||
return group.items.map((item) => ({ ...item }));
|
||||
}
|
||||
|
||||
function filterByKeyword(
|
||||
@@ -49,33 +68,64 @@ function filterByKeyword(
|
||||
);
|
||||
}
|
||||
|
||||
const TAB_BAR_PATHS = new Set([
|
||||
'/pages/home/home',
|
||||
'/pages/category/category',
|
||||
'/pages/age/age',
|
||||
'/pages/favorites/favorites',
|
||||
'/pages/profile/profile',
|
||||
]);
|
||||
// Skeleton placeholder items for loading state
|
||||
const SKELETON_ITEMS = Array.from({ length: 4 }, (_, i) => ({
|
||||
id: `skeleton-${i}`,
|
||||
title: '\u00A0',
|
||||
subtitle: '\u00A0',
|
||||
skeleton: true,
|
||||
}));
|
||||
|
||||
Page({
|
||||
data: {
|
||||
searchPlaceholder: CATEGORY_DATA.searchPlaceholder,
|
||||
searchPlaceholder: '搜索练习纸...',
|
||||
searchKeyword: '',
|
||||
sidebarTabs: SIDEBAR_TABS,
|
||||
activeCategoryId: 'all',
|
||||
displayItems: buildAllItems() as CategoryItem[],
|
||||
displayItems: SKELETON_ITEMS as any[],
|
||||
scrollIntoViewId: '',
|
||||
loading: true,
|
||||
},
|
||||
|
||||
onLoad(options: Record<string, string>) {
|
||||
const id = options.id;
|
||||
if (id && id !== 'all') {
|
||||
const items = getItemsByCategory(id);
|
||||
this.setData({
|
||||
activeCategoryId: id,
|
||||
displayItems: items,
|
||||
});
|
||||
void this.initData(options);
|
||||
},
|
||||
|
||||
async initData(options: Record<string, string>) {
|
||||
wx.showLoading({ title: '加载中', mask: false });
|
||||
|
||||
const app = getApp<IAppOption>();
|
||||
const config = await app.getPageConfig();
|
||||
|
||||
// Use cloud config if available, otherwise fallback to local static data
|
||||
if (config?.category?.categories?.length) {
|
||||
_categoryData = {
|
||||
searchPlaceholder:
|
||||
config.category.searchPlaceholder || '搜索练习纸...',
|
||||
categories: config.category.categories.map((cat) => ({
|
||||
id: cat.id,
|
||||
name: cat.name,
|
||||
icon: cat.icon,
|
||||
items: (cat.items || []).map(
|
||||
(raw) => raw as unknown as CategoryItem,
|
||||
),
|
||||
})),
|
||||
};
|
||||
} else {
|
||||
_categoryData = CATEGORY_DATA;
|
||||
}
|
||||
|
||||
const activeId = options.id && options.id !== 'all' ? options.id : 'all';
|
||||
const items = getItemsByCategory(activeId);
|
||||
|
||||
this.setData({
|
||||
loading: false,
|
||||
searchPlaceholder: _categoryData.searchPlaceholder,
|
||||
activeCategoryId: activeId,
|
||||
displayItems: items,
|
||||
});
|
||||
|
||||
wx.hideLoading();
|
||||
},
|
||||
|
||||
onTapCategory(e: WechatMiniprogram.TouchEvent) {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<nav-bar title="分类" />
|
||||
|
||||
<view class="cat-body">
|
||||
<view class="cat-search-wrap">
|
||||
<!-- <view class="cat-search-wrap">
|
||||
<view class="cat-search">
|
||||
<van-icon
|
||||
name="search"
|
||||
@@ -23,7 +23,7 @@
|
||||
color="rgba(50, 46, 37, 0.35)"
|
||||
bindtap="onClearSearch" />
|
||||
</view>
|
||||
</view>
|
||||
</view> -->
|
||||
|
||||
<view class="cat-main">
|
||||
<scroll-view
|
||||
@@ -52,18 +52,20 @@
|
||||
<view
|
||||
wx:for="{{displayItems}}"
|
||||
wx:key="id"
|
||||
class="cat-card"
|
||||
class="cat-card {{item.skeleton ? 'cat-card--skeleton' : ''}}"
|
||||
data-path="{{item.path}}"
|
||||
data-title="{{item.title}}"
|
||||
data-available="{{item.available}}"
|
||||
bindtap="onTapItem">
|
||||
bindtap="{{item.skeleton ? '' : 'onTapItem'}}">
|
||||
<view class="cat-card__thumb">
|
||||
<image
|
||||
wx:if="{{item.previewImg}}"
|
||||
class="cat-card__thumb-img"
|
||||
src="{{item.previewImg}}"
|
||||
mode="aspectFill" />
|
||||
<text wx:else class="cat-card__thumb-icon"
|
||||
<text
|
||||
wx:elif="{{!item.skeleton}}"
|
||||
class="cat-card__thumb-icon"
|
||||
>{{item.icon}}</text
|
||||
>
|
||||
</view>
|
||||
@@ -76,7 +78,9 @@
|
||||
<text class="cat-card__subtitle"
|
||||
>{{item.subtitle}}</text
|
||||
>
|
||||
<view class="cat-card__tags">
|
||||
<view
|
||||
wx:if="{{!item.skeleton}}"
|
||||
class="cat-card__tags">
|
||||
<text class="tag tag--age"
|
||||
>{{item.ageBand}}</text
|
||||
>
|
||||
@@ -84,7 +88,9 @@
|
||||
>{{item.difficultyLabel}}</text
|
||||
>
|
||||
</view>
|
||||
<view class="cat-card__footer">
|
||||
<view
|
||||
wx:if="{{!item.skeleton}}"
|
||||
class="cat-card__footer">
|
||||
<view class="cat-card__stats">
|
||||
<view class="cat-card__stat">
|
||||
<toy-icon
|
||||
@@ -109,7 +115,7 @@
|
||||
</view>
|
||||
|
||||
<view
|
||||
wx:if="{{displayItems.length === 0}}"
|
||||
wx:if="{{displayItems.length === 0 && !loading}}"
|
||||
class="cat-empty">
|
||||
<text class="cat-empty__icon">🔍</text>
|
||||
<text class="cat-empty__text">没有找到匹配的内容</text>
|
||||
|
||||
Reference in New Issue
Block a user