feat: 完成分类页开发

This commit is contained in:
R524809
2026-04-22 11:18:56 +08:00
parent 28151018ee
commit 3bb8476e93
42 changed files with 1070 additions and 56 deletions
+149
View File
@@ -0,0 +1,149 @@
export type CategoryItem = {
id: string;
title: string;
subtitle: string;
icon: string;
img?: string;
ageBand: string;
difficulty: string;
path: string;
available: boolean;
likes: number;
downloads: number;
date: string;
};
export type CategoryGroup = {
id: string;
name: string;
icon: string;
items: CategoryItem[];
};
export type CategoryDataset = {
searchPlaceholder: string;
categories: CategoryGroup[];
};
const DIFFICULTY_MAP: Record<string, string> = {
beginner: '入门',
basic: '基础',
intermediate: '进阶',
advanced: '挑战',
};
function d(key: string): string {
return DIFFICULTY_MAP[key] ?? key;
}
function age(min: number, max: number): string {
return `${min}-${max}`;
}
const TODAY = '2026-4-22';
/** 基于 id 生成稳定的伪随机统计数 */
function statsFromId(id: string): { likes: number; downloads: number } {
let h = 0;
for (let i = 0; i < id.length; i++) h = (h * 31 + id.charCodeAt(i)) | 0;
const likes = 100 + Math.abs(h % 900);
const downloads = 20 + Math.abs((h >>> 4) % 200);
return { likes, downloads };
}
type ItemInput = Omit<CategoryItem, 'likes' | 'downloads' | 'date'>;
function item(input: ItemInput): CategoryItem {
const { likes, downloads } = statsFromId(input.id);
return { ...input, likes, downloads, date: TODAY };
}
// prettier-ignore
export const CATEGORY_DATA: CategoryDataset = {
searchPlaceholder: '搜索练习纸...',
categories: [
{
id: 'math', name: '数感启蒙', icon: '📐',
items: [
item({ id: 'number-find', title: '找数字,涂一涂', subtitle: '在数字方阵中找出目标数字并涂色', icon: '🔍', img: '/assets/entrancePicture/math/number-find.png', ageBand: age(3, 5), difficulty: d('beginner'), path: '/mathPages/mathDraw/mathDraw?id=number-find', available: true }),
item({ id: 'number-write', title: '看数字,写一写', subtitle: '按笔画顺序练习书写数字', icon: '✏️', img: '/assets/entrancePicture/math/number-write.png', ageBand: age(3, 5), difficulty: d('beginner'), path: '/mathPages/mathDraw/mathDraw?id=number-write', available: true }),
item({ id: 'number-coloring', title: '按数字,涂颜色', subtitle: '按指定数字给对应圆圈涂色', icon: '🎨', img: '/assets/entrancePicture/math/number-coloring.png', ageBand: age(3, 5), difficulty: d('beginner'), path: '/mathPages/mathDraw/mathDraw?id=number-coloring', available: true }),
item({ id: 'counting-matching', title: '数一数,连一连', subtitle: '连线配对数字和对应数量图形', icon: '🔗', img: '/assets/entrancePicture/math/count-match.png', ageBand: age(3, 5), difficulty: d('beginner'), path: '/mathPages/mathDraw/mathDraw?id=counting-matching', available: true }),
item({ id: 'number-object-match', title: '数物连线', subtitle: '连线相同数量的物品和数字', icon: '🔗', img: '/assets/entrancePicture/math/number-object-match.png', ageBand: age(3, 5), difficulty: d('beginner'), path: '/mathPages/mathDraw/mathDraw?id=number-object-match', available: true }),
item({ id: 'number-object-fill', title: '数物填写', subtitle: '数物品数量,填写对应数字', icon: '✏️', img: '/assets/entrancePicture/math/number-object-fill.png', ageBand: age(3, 5), difficulty: d('beginner'), path: '/mathPages/mathDraw/mathDraw?id=number-object-fill', available: true }),
item({ id: 'counting-select', title: '数一数,选一选', subtitle: '数出物品数量,圈出正确答案', icon: '✓', img: '/assets/entrancePicture/math/counting-select.png', ageBand: age(3, 5), difficulty: d('beginner'), path: '/mathPages/mathDraw/mathDraw?id=counting-select', available: true }),
item({ id: 'counting-fill', title: '数一数,填一填', subtitle: '数出物品数量,填写数字', icon: '✏️', img: '/assets/entrancePicture/math/counting-fill.png', ageBand: age(3, 5), difficulty: d('beginner'), path: '/mathPages/mathDraw/mathDraw?id=counting-fill', available: true }),
item({ id: 'compare', title: '数一数,比大小', subtitle: '比较数量,填入 ><=', icon: '⚖️', img: '/assets/entrancePicture/math/compare.png', ageBand: age(4, 6), difficulty: d('basic'), path: '/mathPages/mathDraw/mathDraw?id=compare', available: true }),
item({ id: 'number-sort', title: '数字排序', subtitle: '写出正确的数字顺序', icon: '🔢', img: '/assets/entrancePicture/math/number-sort.png', ageBand: age(4, 6), difficulty: d('basic'), path: '/mathPages/mathDraw/mathDraw?id=number-sort', available: true }),
item({ id: 'missing-number', title: '填上缺少的数字', subtitle: '在数列中找出并填写缺失数字', icon: '❓', img: '/assets/entrancePicture/math/missing-number.png', ageBand: age(4, 6), difficulty: d('basic'), path: '/mathPages/mathDraw/mathDraw?id=missing-number', available: true }),
item({ id: 'number-decompose', title: '10以内数的分与合', subtitle: '把数字分一分,合一合', icon: '🔢', img: '/assets/entrancePicture/math/number-decompose.png', ageBand: age(4, 6), difficulty: d('beginner'), path: '/mathPages/mathDraw/mathDraw?id=number-decompose', available: true }),
item({ id: 'number-decompose-20', title: '20以内数的分与合', subtitle: '把数字分一分,合一合', icon: '🔢', img: '/assets/entrancePicture/math/number-decompose-20.png', ageBand: age(5, 7), difficulty: d('basic'), path: '/mathPages/mathDraw/mathDraw?id=number-decompose-20', available: true }),
item({ id: 'one-digit-addition', title: '一位数加法', subtitle: '通过圆点学习一位数加法运算', icon: '', img: '/assets/entrancePicture/math/one-digit-addition.png', ageBand: age(4, 6), difficulty: d('basic'), path: '/mathPages/mathDraw/mathDraw?id=one-digit-addition', available: true }),
item({ id: 'addition-5', title: '5以内加法', subtitle: '图形化展示 5 以内加法', icon: '', img: '/assets/entrancePicture/math/addition-5.png', ageBand: age(3, 5), difficulty: d('beginner'), path: '/mathPages/mathDraw/mathDraw?id=addition-5', available: true }),
item({ id: 'addition-10', title: '10以内加法', subtitle: '图形化展示 10 以内加法', icon: '', img: '/assets/entrancePicture/math/addition-10.png', ageBand: age(4, 6), difficulty: d('basic'), path: '/mathPages/mathDraw/mathDraw?id=addition-10', available: true }),
item({ id: 'subtraction-10', title: '10以内减法', subtitle: '图形化展示 10 以内减法', icon: '', img: '/assets/entrancePicture/math/subtraction-10.png', ageBand: age(4, 6), difficulty: d('basic'), path: '/mathPages/mathDraw/mathDraw?id=subtraction-10', available: true }),
item({ id: 'addition-subtraction-10', title: '10以内加减法', subtitle: '加减法混合运算', icon: '±', img: '/assets/entrancePicture/math/addition-subtraction-10.png', ageBand: age(5, 7), difficulty: d('basic'), path: '/mathPages/mathDraw/mathDraw?id=addition-subtraction-10', available: true }),
item({ id: 'make-ten', title: '凑十法练习', subtitle: '20 以内进位加法', icon: '', img: '/assets/entrancePicture/math/make-ten.png', ageBand: age(5, 7), difficulty: d('intermediate'), path: '/mathPages/mathDraw/mathDraw?id=make-ten', available: true }),
item({ id: 'break-ten', title: '破十法练习', subtitle: '20 以内退位减法', icon: '', img: '/assets/entrancePicture/math/break-ten.png', ageBand: age(5, 7), difficulty: d('intermediate'), path: '/mathPages/mathDraw/mathDraw?id=break-ten', available: true }),
item({ id: 'flat-ten', title: '平十法练习', subtitle: '20 以内退位减法', icon: '', img: '/assets/entrancePicture/math/flat-ten.png', ageBand: age(5, 7), difficulty: d('intermediate'), path: '/mathPages/mathDraw/mathDraw?id=flat-ten', available: true }),
item({ id: 'borrow-ten', title: '借十法练习', subtitle: '20 以上退位减法', icon: '', img: '/assets/entrancePicture/math/borrow-ten.png', ageBand: age(6, 8), difficulty: d('advanced'), path: '/mathPages/mathDraw/mathDraw?id=borrow-ten', available: true }),
item({ id: 'practice-addition', title: '加法运算', subtitle: '10/20/50/100 以内加法', icon: '', img: '/assets/entrancePicture/math/practice-addition.png', ageBand: age(5, 8), difficulty: d('intermediate'), path: '/mathPages/mathDraw/mathDraw?id=practice-addition', available: true }),
item({ id: 'practice-subtraction', title: '减法运算', subtitle: '10/20/50/100 以内减法', icon: '', img: '/assets/entrancePicture/math/practice-subtraction.png', ageBand: age(5, 8), difficulty: d('intermediate'), path: '/mathPages/mathDraw/mathDraw?id=practice-subtraction', available: true }),
item({ id: 'practice-mixed', title: '混合运算', subtitle: '10/20/50/100 以内加减法混合', icon: '±', img: '/assets/entrancePicture/math/practice-mixed.png', ageBand: age(5, 8), difficulty: d('intermediate'), path: '/mathPages/mathDraw/mathDraw?id=practice-mixed', available: true }),
item({ id: 'multiplication-table', title: '九九乘法表', subtitle: '学习九九乘法口诀', icon: '✖️', img: '/assets/entrancePicture/math/multiplication-table.png', ageBand: age(6, 8), difficulty: d('intermediate'), path: '/mathPages/mathDraw/mathDraw?id=multiplication-table', available: true }),
],
},
{
id: 'pinyin', name: '汉语拼音', icon: '🔤',
items: [
item({ id: 'pinyin-initials', title: '声母描红', subtitle: '23个声母认读与书写练习', icon: '🔤', ageBand: age(5, 7), difficulty: d('basic'), path: '', available: false }),
item({ id: 'pinyin-finals', title: '韵母描红', subtitle: '24个韵母认读与书写练习', icon: '🔤', ageBand: age(5, 7), difficulty: d('basic'), path: '', available: false }),
item({ id: 'pinyin-overall', title: '整体认读音节', subtitle: '16个整体认读音节练习', icon: '🔤', ageBand: age(5, 7), difficulty: d('intermediate'), path: '', available: false }),
],
},
{
id: 'puzzle', name: '益智游戏', icon: '🧩',
items: [
item({ id: 'color-shape-match', title: '根据颜色画图形', subtitle: '根据颜色画出对应图形', icon: '🎯', img: '/assets/entrancePicture/focus/color-shape-match.png', ageBand: age(4, 6), difficulty: d('basic'), path: '/focusPages/focusDraw/focusDraw?id=color-shape-match', available: true }),
item({ id: 'shape-symbol', title: '图形符号配对', subtitle: '根据图形画对应符号', icon: '🔗', img: '/assets/entrancePicture/focus/shape-symbol.png', ageBand: age(4, 6), difficulty: d('basic'), path: '/focusPages/focusDraw/focusDraw?id=shape-symbol', available: true }),
item({ id: 'shape-recognition', title: '识别形状', subtitle: '识别形状,涂一涂', icon: '🔍', img: '/assets/entrancePicture/focus/shape-recognition.png', ageBand: age(3, 5), difficulty: d('beginner'), path: '/focusPages/focusDraw/focusDraw?id=shape-recognition', available: true }),
item({ id: 'position-coloring', title: '方位涂涂乐', subtitle: '观察位置,在方格中涂色', icon: '📍', img: '/assets/entrancePicture/focus/position-coloring.png', ageBand: age(4, 6), difficulty: d('basic'), path: '/focusPages/focusDraw/focusDraw?id=position-coloring', available: true }),
item({ id: 'color-pattern', title: '颜色找规律', subtitle: '观察颜色规律,在空白图形中涂色', icon: '🎨', img: '/assets/entrancePicture/focus/color-pattern.png', ageBand: age(4, 6), difficulty: d('basic'), path: '/focusPages/focusDraw/focusDraw?id=color-pattern', available: true }),
item({ id: 'match-connect', title: '连连看', subtitle: '根据物品连一连', icon: '🔗', img: '/assets/entrancePicture/focus/match-connect.png', ageBand: age(3, 6), difficulty: d('beginner'), path: '/focusPages/focusDraw/focusDraw?id=match-connect', available: true }),
item({ id: 'line-recognition', title: '线条识别', subtitle: '认识不同线条,画出颜色对应的线条', icon: '📏', img: '/assets/entrancePicture/focus/line-recognition.png', ageBand: age(3, 5), difficulty: d('beginner'), path: '/focusPages/focusDraw/focusDraw?id=line-recognition', available: true }),
item({ id: 'grid-reasoning', title: '方格推理', subtitle: '推理出合并方格并连线', icon: '🧩', img: '/assets/entrancePicture/focus/grid-reasoning.png', ageBand: age(5, 7), difficulty: d('intermediate'), path: '/focusPages/focusDraw/focusDraw?id=grid-reasoning', available: true }),
item({ id: 'code-connect', title: '译码连线', subtitle: '按数字顺序将数字对应颜色连线', icon: '🔢', img: '/assets/entrancePicture/focus/code-connect.png', ageBand: age(5, 7), difficulty: d('intermediate'), path: '/focusPages/focusDraw/focusDraw?id=code-connect', available: true }),
item({ id: 'dot-connect', title: '数字点连线', subtitle: '按数字顺序连点成图', icon: '🔗', img: '/assets/entrancePicture/focus/dot-connect.png', ageBand: age(3, 6), difficulty: d('beginner'), path: '/focusPages/focusDraw/focusDraw?id=dot-connect', available: true }),
item({ id: 'grid-drawing-3x3', title: '格子仿画 3×3', subtitle: '简单有趣,培养专注力', icon: '🎨', img: '/assets/entrancePicture/focus/grid-drawing-3x3.png', ageBand: age(3, 5), difficulty: d('beginner'), path: '/focusPages/focusDraw/focusDraw?id=grid-drawing-3x3', available: true }),
item({ id: 'grid-drawing-5x5', title: '格子仿画 5×5', subtitle: '创意挑战,提升观察力', icon: '🎨', img: '/assets/entrancePicture/focus/grid-drawing-5x5.png', ageBand: age(4, 6), difficulty: d('basic'), path: '/focusPages/focusDraw/focusDraw?id=grid-drawing-5x5', available: true }),
item({ id: 'grid-drawing-7x7', title: '格子仿画 7×7', subtitle: '大师挑战,锻炼耐心', icon: '🎨', img: '/assets/entrancePicture/focus/grid-drawing-7x7.png', ageBand: age(5, 8), difficulty: d('intermediate'), path: '/focusPages/focusDraw/focusDraw?id=grid-drawing-7x7', available: true }),
],
},
{
id: 'chinese', name: '趣味识字', icon: '✏️',
items: [
item({ id: 'word-recognition', title: '识字卡', subtitle: '输入或选字生成涂色识字卡', icon: '📖', ageBand: age(3, 6), difficulty: d('beginner'), path: '/pages/index/index', available: true }),
item({ id: 'copybook', title: '练字帖', subtitle: '选字生成田字格笔顺练字帖', icon: '✏️', ageBand: age(4, 7), difficulty: d('basic'), path: '/pages/copyBook/copyBook', available: true }),
],
},
{
id: 'english', name: '英语启蒙', icon: '🔤',
items: [
item({ id: 'letter-tracing-single', title: '看图描红', subtitle: '单字母配图、例句与六行四线三格描红', icon: '🔠', img: '/assets/entrancePicture/english/letter-tracing-single.jpg', ageBand: age(4, 7), difficulty: d('beginner'), path: '/englishPages/letterTracing/letterTracing?id=letter-tracing-single', available: true }),
item({ id: 'letter-tracing-upper-lower', title: '字母总览', subtitle: 'Uppercase / Lowercase 分区总览', icon: '🔡', img: '/assets/entrancePicture/english/letter-tracing-upper-lower.jpg', ageBand: age(4, 7), difficulty: d('beginner'), path: '/englishPages/letterTracing/letterTracing?id=letter-tracing-upper-lower', available: true }),
item({ id: 'letter-tracing-case-pairing', title: '大小写练习', subtitle: '半组字母左大写右小写,逐行对照描红', icon: '✏️', img: '/assets/entrancePicture/english/letter-tracing-case-pairing.jpg', ageBand: age(4, 7), difficulty: d('beginner'), path: '/englishPages/letterTracing/letterTracing?id=letter-tracing-case-pairing', available: true }),
item({ id: 'letter-tracing-two-column', title: '两列练习', subtitle: '左 A–M、右 NZ,大小写配对描红', icon: '🔤', img: '/assets/entrancePicture/english/letter-tracing-two-column.jpg', ageBand: age(4, 7), difficulty: d('beginner'), path: '/englishPages/letterTracing/letterTracing?id=letter-tracing-two-column', available: true }),
item({ id: 'letter-tracing-half', title: '单字母逐行', subtitle: '13 字母半表逐字练习', icon: '📝', img: '/assets/entrancePicture/english/letter-tracing-half.jpg', ageBand: age(4, 7), difficulty: d('basic'), path: '/englishPages/letterTracing/letterTracing?id=letter-tracing-half', available: true }),
item({ id: 'letter-tracing-three', title: '三字母精练', subtitle: '每页 3 个字母,大写 + 小写深度书写', icon: '🔤', img: '/assets/entrancePicture/english/letter-tracing-three.jpg', ageBand: age(4, 7), difficulty: d('basic'), path: '/englishPages/letterTracing/letterTracing?id=letter-tracing-three', available: true }),
],
},
{
id: 'craft', name: '创意手工', icon: '🌈',
items: [
item({ id: 'craft-coloring', title: '涂色卡', subtitle: '动物、交通、节日主题涂色', icon: '🎨', ageBand: age(3, 6), difficulty: d('beginner'), path: '', available: false }),
item({ id: 'craft-origami', title: '折纸模板', subtitle: '打印后即可折叠的趣味模板', icon: '🪭', ageBand: age(4, 7), difficulty: d('basic'), path: '', available: false }),
item({ id: 'craft-stickers', title: '贴纸打印', subtitle: '奖励贴纸与装饰贴纸', icon: '⭐', ageBand: age(3, 8), difficulty: d('beginner'), path: '', available: false }),
],
},
],
};
+13
View File
@@ -0,0 +1,13 @@
{
"navigationStyle": "custom",
"navigationBarTitleText": "分类",
"navigationBarTextStyle": "black",
"navigationBarBackgroundColor": "#FEF6E7",
"backgroundColor": "#FEF6E7",
"enablePullDownRefresh": false,
"usingComponents": {
"nav-bar": "../../components3.0/nav-bar/nav-bar",
"van-icon": "@vant/weapp/icon/index",
"toy-icon": "/toy/icon/icon"
}
}
+263
View File
@@ -0,0 +1,263 @@
@import '../../style/theme.less';
@import '../../style/tags.less';
.cat-page {
height: 100vh;
background: @bg-page;
display: flex;
flex-direction: column;
overflow: hidden;
color: @text-secondary;
}
.cat-body {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
.cat-search-wrap {
padding: @section-gap-xs @page-padding-x 0;
flex-shrink: 0;
}
.cat-search {
display: flex;
flex-direction: row;
align-items: center;
gap: 16rpx;
background: #e5dcc9;
border-radius: 999rpx;
padding: 20rpx 32rpx;
box-sizing: border-box;
}
.cat-search__icon {
flex-shrink: 0;
line-height: 1;
}
.cat-search__input {
flex: 1;
font-size: 28rpx;
color: @text-title;
min-height: 40rpx;
line-height: 40rpx;
}
.cat-search__placeholder {
font-size: 28rpx;
color: rgba(50, 46, 37, 0.45);
}
.cat-main {
flex: 1;
display: flex;
flex-direction: row;
overflow: hidden;
margin-top: @section-gap-xs;
}
.cat-sidebar {
width: 180rpx;
flex-shrink: 0;
background: @bg-header;
border-top-right-radius: @radius-lg;
padding: 16rpx 0;
box-sizing: border-box;
}
.cat-sidebar__item {
display: flex;
flex-direction: column;
align-items: center;
gap: 6rpx;
padding: 24rpx 12rpx;
margin: 4rpx 12rpx;
border-radius: 999rpx;
transition: all 0.25s ease;
}
.cat-sidebar__item--active {
background: @brand;
transform: scale(1.05);
box-shadow: 0 4rpx 16rpx rgba(255, 215, 9, 0.35);
}
.cat-sidebar__icon {
font-size: 36rpx;
line-height: 1;
}
.cat-sidebar__name {
font-size: 24rpx;
font-weight: 500;
color: @text-secondary;
white-space: nowrap;
text-align: center;
}
.cat-sidebar__item--active .cat-sidebar__name {
color: @text-selected-btn;
font-weight: 700;
}
.cat-content {
flex: 1;
background: @bg-page;
padding: 0;
box-sizing: border-box;
}
.cat-content__inner {
padding: 8rpx 16rpx;
display: flex;
flex-direction: column;
gap: 16rpx;
}
.cat-content__footer {
height: 48rpx;
}
.cat-card {
background: @bg-white;
border-radius: 24rpx;
padding: 16rpx;
display: flex;
flex-direction: row;
gap: 20rpx;
box-shadow: 0 4rpx 20rpx rgba(50, 46, 37, 0.06);
transition: box-shadow 0.2s ease;
overflow: hidden;
}
.cat-card__thumb {
width: 160rpx;
height: 180rpx;
flex-shrink: 0;
border-radius: 10rpx;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
border: 1rpx solid #bbb;
}
.cat-card__thumb-img {
width: 100%;
height: 100%;
display: block;
}
.cat-card__thumb-icon {
font-size: 56rpx;
line-height: 1;
}
.cat-card__body {
flex: 1;
width: 0;
display: flex;
flex-direction: column;
justify-content: space-between;
overflow: hidden;
gap: 8rpx;
padding: 4rpx 0;
}
.cat-card__header {
display: flex;
flex-direction: row;
align-items: center;
gap: 12rpx;
}
.cat-card__title {
font-size: 30rpx;
font-weight: 700;
color: @text-title;
line-height: 1.35;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
flex: 1;
min-width: 0;
}
.cat-card__coming {
flex-shrink: 0;
padding: 4rpx 14rpx;
border-radius: 999rpx;
background: rgba(50, 46, 37, 0.08);
font-size: 20rpx;
color: @text-gray;
font-weight: 500;
}
.cat-card__subtitle {
font-size: 24rpx;
color: @text-gray;
line-height: 1.4;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.cat-card__tags {
display: flex;
flex-direction: row;
gap: 12rpx;
margin-top: 4rpx;
}
.cat-card__footer {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
margin-top: 4rpx;
}
.cat-card__stats {
display: flex;
flex-direction: row;
align-items: center;
gap: 16rpx;
}
.cat-card__stat {
display: flex;
flex-direction: row;
align-items: center;
gap: 4rpx;
font-size: 20rpx;
color: #7c766a;
line-height: 20rpx;
}
.cat-card__date {
font-size: 20rpx;
color: #7c766a;
line-height: 20rpx;
}
.cat-empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 120rpx 0;
gap: 24rpx;
}
.cat-empty__icon {
font-size: 80rpx;
line-height: 1;
}
.cat-empty__text {
font-size: 28rpx;
color: @text-gray;
}
+151
View File
@@ -0,0 +1,151 @@
import { CATEGORY_DATA, type CategoryItem } from './category.data';
type CategoryTab = {
id: string;
name: string;
icon: string;
};
type DisplayItem = CategoryItem & {
thumbBg: string;
};
const CATEGORY_BG: Record<string, string> = {
math: '#fff3c4',
pinyin: '#d8f0d0',
puzzle: '#f5e6dc',
chinese: '#d8f0d0',
english: '#e8f4ff',
craft: '#fce4ec',
};
const ALL_TAB: CategoryTab = { id: 'all', name: '全部', icon: '📋' };
const SIDEBAR_TABS: CategoryTab[] = [
ALL_TAB,
...CATEGORY_DATA.categories.map((c) => ({
id: c.id,
name: c.name,
icon: c.icon,
})),
];
function buildAllItems(): DisplayItem[] {
const items: DisplayItem[] = [];
for (const group of CATEGORY_DATA.categories) {
for (const item of group.items) {
items.push({
...item,
thumbBg: CATEGORY_BG[group.id] ?? '#f0e7d6',
});
}
}
return items;
}
function getItemsByCategory(categoryId: string): DisplayItem[] {
if (categoryId === 'all') return buildAllItems();
const group = CATEGORY_DATA.categories.find((c) => c.id === categoryId);
if (!group) return [];
return group.items.map((item) => ({
...item,
thumbBg: CATEGORY_BG[group.id] ?? '#f0e7d6',
}));
}
function filterByKeyword(items: DisplayItem[], keyword: string): DisplayItem[] {
if (!keyword) return items;
const kw = keyword.toLowerCase();
return items.filter(
(item) =>
item.title.toLowerCase().includes(kw) ||
item.subtitle.toLowerCase().includes(kw),
);
}
const TAB_BAR_PATHS = new Set([
'/pages/home/home',
'/pages/category/category',
'/pages/age/age',
'/pages/favorites/favorites',
'/pages/profile/profile',
]);
Page({
data: {
searchPlaceholder: CATEGORY_DATA.searchPlaceholder,
searchKeyword: '',
sidebarTabs: SIDEBAR_TABS,
activeCategoryId: 'all',
displayItems: buildAllItems() as DisplayItem[],
scrollIntoViewId: '',
},
onTapCategory(e: WechatMiniprogram.TouchEvent) {
const id = e.currentTarget.dataset.id as string;
if (!id || id === this.data.activeCategoryId) return;
const items = getItemsByCategory(id);
const filtered = filterByKeyword(items, this.data.searchKeyword);
this.setData({
activeCategoryId: id,
displayItems: filtered,
scrollIntoViewId: '',
});
},
onSearchInput(e: WechatMiniprogram.Input) {
const keyword = (e.detail.value ?? '').trim();
const items = getItemsByCategory(this.data.activeCategoryId);
const filtered = filterByKeyword(items, keyword);
this.setData({
searchKeyword: keyword,
displayItems: filtered,
});
},
onClearSearch() {
const items = getItemsByCategory(this.data.activeCategoryId);
this.setData({
searchKeyword: '',
displayItems: items,
});
},
onTapItem(e: WechatMiniprogram.TouchEvent) {
const path = e.currentTarget.dataset.path as string;
const title = e.currentTarget.dataset.title as string;
const available = e.currentTarget.dataset.available;
if (!available || !path) {
wx.showToast({
title: title ? `${title} 即将上线` : '即将上线',
icon: 'none',
});
return;
}
if (TAB_BAR_PATHS.has(path)) {
wx.switchTab({ url: path });
return;
}
wx.navigateTo({ url: path });
},
onShareAppMessage() {
return {
title: '涂鸦丫 - 全部练习分类',
path: '/pages/category/category',
};
},
onShareTimeline() {
return {
title: '涂鸦丫 - 全部练习分类',
query: '',
};
},
});
+125
View File
@@ -0,0 +1,125 @@
<view class="cat-page">
<nav-bar title="分类" />
<view class="cat-body">
<view class="cat-search-wrap">
<view class="cat-search">
<van-icon
name="search"
size="36rpx"
color="rgba(50, 46, 37, 0.45)"
class="cat-search__icon" />
<input
class="cat-search__input"
placeholder="{{searchPlaceholder}}"
placeholder-class="cat-search__placeholder"
value="{{searchKeyword}}"
bindinput="onSearchInput"
confirm-type="search" />
<van-icon
wx:if="{{searchKeyword}}"
name="clear"
size="32rpx"
color="rgba(50, 46, 37, 0.35)"
bindtap="onClearSearch" />
</view>
</view>
<view class="cat-main">
<scroll-view
class="cat-sidebar"
scroll-y
enhanced
show-scrollbar="{{false}}">
<view
wx:for="{{sidebarTabs}}"
wx:key="id"
class="cat-sidebar__item {{activeCategoryId === item.id ? 'cat-sidebar__item--active' : ''}}"
data-id="{{item.id}}"
bindtap="onTapCategory">
<!-- <text class="cat-sidebar__icon">{{item.icon}}</text> -->
<text class="cat-sidebar__name">{{item.name}}</text>
</view>
</scroll-view>
<scroll-view
class="cat-content"
scroll-y
enhanced
show-scrollbar="{{false}}"
scroll-into-view="{{scrollIntoViewId}}">
<view class="cat-content__inner">
<view
wx:for="{{displayItems}}"
wx:key="id"
class="cat-card"
data-path="{{item.path}}"
data-title="{{item.title}}"
data-available="{{item.available}}"
bindtap="onTapItem">
<view
class="cat-card__thumb"
style="background-color: {{item.thumbBg}}">
<image
wx:if="{{item.img}}"
class="cat-card__thumb-img"
src="{{item.img}}"
mode="aspectFill" />
<text wx:else class="cat-card__thumb-icon"
>{{item.icon}}</text
>
</view>
<view class="cat-card__body">
<view class="cat-card__header">
<text class="cat-card__title"
>{{item.title}}</text
>
</view>
<text class="cat-card__subtitle"
>{{item.subtitle}}</text
>
<view class="cat-card__tags">
<text class="tag tag--age"
>{{item.ageBand}}</text
>
<text class="tag tag--diff"
>{{item.difficulty}}</text
>
</view>
<view class="cat-card__footer">
<view class="cat-card__stats">
<view class="cat-card__stat">
<toy-icon
name="heart-o"
size="20rpx"
color="#7c766a" />
<text>{{item.likes}}</text>
</view>
<view class="cat-card__stat">
<toy-icon
name="download"
size="20rpx"
color="#7c766a" />
<text>{{item.downloads}}</text>
</view>
</view>
<text class="cat-card__date"
>{{item.date}}</text
>
</view>
</view>
</view>
<view
wx:if="{{displayItems.length === 0}}"
class="cat-empty">
<text class="cat-empty__icon">🔍</text>
<text class="cat-empty__text">没有找到匹配的内容</text>
</view>
<view class="cat-content__footer"></view>
</view>
</scroll-view>
</view>
</view>
</view>