feat:生产初版页面

This commit is contained in:
R524809
2026-03-26 17:33:04 +08:00
parent 3aaf340781
commit dc5e057cb3
63 changed files with 2940 additions and 543 deletions
+8
View File
@@ -0,0 +1,8 @@
{
"navigationBarTitleText": "分龄推荐",
"navigationBarTextStyle": "black",
"navigationBarBackgroundColor": "#ffffff",
"backgroundColor": "#F5F5F5",
"enablePullDownRefresh": false,
"usingComponents": {}
}
+108
View File
@@ -0,0 +1,108 @@
.age-page {
min-height: 100vh;
background: #f5f5f5;
padding-bottom: 48rpx;
box-sizing: border-box;
}
.age-hero {
background: #fff;
padding: 40rpx 32rpx 36rpx;
margin-bottom: 24rpx;
}
.age-hero__title {
display: block;
font-size: 44rpx;
font-weight: 700;
color: rgba(0, 0, 0, 0.88);
margin-bottom: 12rpx;
}
.age-hero__subtitle {
display: block;
font-size: 26rpx;
line-height: 1.5;
color: #999;
}
.age-grid {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 24rpx;
padding: 0 24rpx;
box-sizing: border-box;
}
.age-card {
width: calc((100% - 24rpx) / 2);
background: #fff;
border-radius: 32rpx;
overflow: hidden;
box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.06);
box-sizing: border-box;
}
.age-card__art {
height: 160rpx;
display: flex;
align-items: center;
justify-content: center;
}
.age-card__emoji {
font-size: 72rpx;
line-height: 1;
}
.age-card__body {
padding: 24rpx 24rpx 28rpx;
}
.age-card__label {
display: block;
font-size: 32rpx;
font-weight: 700;
color: rgba(0, 0, 0, 0.88);
margin-bottom: 8rpx;
}
.age-card__desc {
display: block;
font-size: 24rpx;
color: #999;
margin-bottom: 12rpx;
}
.age-card__count {
display: block;
font-size: 22rpx;
color: #ccc;
}
.age-tip {
margin: 40rpx 24rpx 0;
background: #fff;
border-radius: 24rpx;
padding: 28rpx 24rpx;
display: flex;
flex-direction: row;
align-items: flex-start;
gap: 20rpx;
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.04);
box-sizing: border-box;
}
.age-tip__duck {
font-size: 64rpx;
line-height: 1;
flex-shrink: 0;
}
.age-tip__text {
flex: 1;
font-size: 26rpx;
line-height: 1.55;
color: rgba(0, 0, 0, 0.65);
}
+44
View File
@@ -0,0 +1,44 @@
import { ALL_WORKSHEETS, getWorksheetsByAge } from '../../core/data/worksheets';
interface AgeGroup {
label: string;
desc: string;
emoji: string;
minAge: number;
maxAge: number;
count: number;
gradient: string;
}
Page({
data: {
ageGroups: [] as AgeGroup[],
},
onLoad() {
const groups = [
{ label: '3-4 岁', desc: '启蒙认知', emoji: '👶', minAge: 3, maxAge: 4, gradient: 'linear-gradient(135deg, #fff9c4, #fff176)' },
{ label: '4-5 岁', desc: '基础练习', emoji: '🧒', minAge: 4, maxAge: 5, gradient: 'linear-gradient(135deg, #c8e6c9, #81c784)' },
{ label: '5-6 岁', desc: '能力提升', emoji: '👦', minAge: 5, maxAge: 6, gradient: 'linear-gradient(135deg, #bbdefb, #64b5f6)' },
{ label: '6-7 岁', desc: '幼小衔接', emoji: '👧', minAge: 6, maxAge: 7, gradient: 'linear-gradient(135deg, #e1bee7, #ba68c8)' },
{ label: '7-8 岁', desc: '巩固挑战', emoji: '🎒', minAge: 7, maxAge: 8, gradient: 'linear-gradient(135deg, #ffccbc, #ff8a65)' },
];
const ageGroups = groups.map((g) => ({
...g,
count: getWorksheetsByAge(ALL_WORKSHEETS, g.minAge).length,
}));
this.setData({ ageGroups });
},
onAgeCardTap(e: WechatMiniprogram.TouchEvent) {
const minAge = Number(e.currentTarget.dataset.minAge);
const maxAge = Number(e.currentTarget.dataset.maxAge);
if (Number.isNaN(minAge) || Number.isNaN(maxAge)) return;
const title = `${minAge}-${maxAge}岁推荐`;
wx.navigateTo({
url: `/pages/category/category?minAge=${minAge}&maxAge=${maxAge}&title=${encodeURIComponent(title)}`,
});
},
});
+31
View File
@@ -0,0 +1,31 @@
<view class="age-page">
<view class="age-hero">
<text class="age-hero__title">分龄推荐</text>
<text class="age-hero__subtitle">按年龄段挑选适合的练习单,循序渐进更省心</text>
</view>
<view class="age-grid">
<view
wx:for="{{ageGroups}}"
wx:key="label"
class="age-card"
bindtap="onAgeCardTap"
data-min-age="{{item.minAge}}"
data-max-age="{{item.maxAge}}"
>
<view class="age-card__art" style="background: {{item.gradient}}">
<text class="age-card__emoji">{{item.emoji}}</text>
</view>
<view class="age-card__body">
<text class="age-card__label">{{item.label}}</text>
<text class="age-card__desc">{{item.desc}}</text>
<text class="age-card__count">{{item.count}} 个练习</text>
</view>
</view>
</view>
<view class="age-tip">
<text class="age-tip__duck">🦆</text>
<text class="age-tip__text">建议家长根据孩子当前能力微调难度,不必严格按年龄划分哦~</text>
</view>
</view>
+10
View File
@@ -0,0 +1,10 @@
{
"navigationBarTitleText": "分类",
"navigationBarTextStyle": "black",
"navigationBarBackgroundColor": "#ffffff",
"backgroundColor": "#F6F6F6",
"usingComponents": {
"worksheet-card": "../../components/shared/worksheet-card/worksheet-card",
"detail-card": "../../components/shared/detail-card/detail-card"
}
}
+81
View File
@@ -0,0 +1,81 @@
.category-page {
min-height: 100vh;
background: #f6f6f6;
padding-bottom: 48rpx;
}
.category-page__header {
padding: 24rpx 32rpx 16rpx;
background: #fff;
}
.category-page__header-title {
font-size: 34rpx;
font-weight: 600;
color: #333;
}
.category-page__filters {
background: #fff;
padding: 24rpx;
margin-bottom: 16rpx;
}
.category-page__filter-row {
display: flex;
flex-direction: row;
align-items: flex-start;
margin-bottom: 20rpx;
}
.category-page__filter-row:last-child {
margin-bottom: 0;
}
.category-page__filter-label {
flex-shrink: 0;
width: 72rpx;
font-size: 26rpx;
color: #999;
line-height: 56rpx;
}
.category-page__pills {
flex: 1;
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 16rpx;
}
.category-page__pill {
font-size: 24rpx;
padding: 8rpx 20rpx;
border-radius: 24rpx;
background: #f5f5f5;
color: #333;
}
.category-page__pill--active {
background: #f7ee47;
color: #333;
}
.category-page__list {
display: flex;
flex-direction: column;
}
.category-page__card-wrap {
margin: 0 24rpx 24rpx;
}
.category-page__empty {
padding: 120rpx 48rpx;
text-align: center;
}
.category-page__empty-text {
font-size: 28rpx;
color: #999;
}
+160
View File
@@ -0,0 +1,160 @@
import type { WorksheetType } from '../../core/models/worksheet';
import {
getWorksheetPath,
getWorksheetsByAge,
getWorksheetsByCategory,
} from '../../core/data/worksheets';
type AgeFilterKey = 'all' | '3-4' | '4-5' | '5-6' | '6-7' | '7-8';
type DiffFilterKey = 'all' | 'beginner' | 'basic' | 'intermediate';
const AGE_BAND: Record<Exclude<AgeFilterKey, 'all'>, [number, number]> = {
'3-4': [3, 4],
'4-5': [4, 5],
'5-6': [5, 6],
'6-7': [6, 7],
'7-8': [7, 8],
};
const DIFF_LABEL: Record<string, string> = {
beginner: '入门',
basic: '基础',
intermediate: '进阶',
advanced: '进阶+',
};
type DisplayItem = WorksheetType & {
ageRangeLabel: string;
difficultyLabel: string;
};
function filterByAgeBand(
list: WorksheetType[],
band: AgeFilterKey,
): WorksheetType[] {
if (band === 'all') return list;
const [lo, hi] = AGE_BAND[band];
const map = new Map<string, WorksheetType>();
for (let age = lo; age <= hi; age += 1) {
getWorksheetsByAge(list, age).forEach((w) => map.set(w.id, w));
}
return Array.from(map.values());
}
function filterByDifficulty(
list: WorksheetType[],
diff: DiffFilterKey,
): WorksheetType[] {
if (diff === 'all') return list;
return list.filter((w) => w.difficulty === diff);
}
function toDisplayList(list: WorksheetType[]): DisplayItem[] {
return list.map((w) => ({
...w,
ageRangeLabel:
w.ageRange && w.ageRange.length === 2
? `${w.ageRange[0]}-${w.ageRange[1]}`
: '',
difficultyLabel: w.difficulty ? DIFF_LABEL[w.difficulty] || '' : '',
}));
}
Page({
data: {
title: '分类',
sourceItems: [] as WorksheetType[],
items: [] as DisplayItem[],
ageFilters: [
{ key: 'all', label: '全部' },
{ key: '3-4', label: '3-4岁' },
{ key: '4-5', label: '4-5岁' },
{ key: '5-6', label: '5-6岁' },
{ key: '6-7', label: '6-7岁' },
{ key: '7-8', label: '7-8岁' },
],
diffFilters: [
{ key: 'all', label: '全部' },
{ key: 'beginner', label: '⭐ 入门' },
{ key: 'basic', label: '⭐⭐ 基础' },
{ key: 'intermediate', label: '⭐⭐⭐ 进阶' },
],
activeAge: 'all' as AgeFilterKey,
activeDiff: 'all' as DiffFilterKey,
},
onLoad(options: {
category?: string;
minAge?: string;
maxAge?: string;
title?: string;
}) {
const category = options.category || 'all';
let base = getWorksheetsByCategory(category);
const minAge = options.minAge != null ? Number(options.minAge) : NaN;
const maxAge = options.maxAge != null ? Number(options.maxAge) : NaN;
if (!Number.isNaN(minAge) && !Number.isNaN(maxAge)) {
const map = new Map<string, WorksheetType>();
const lo = Math.min(minAge, maxAge);
const hi = Math.max(minAge, maxAge);
for (let age = lo; age <= hi; age += 1) {
getWorksheetsByAge(base, age).forEach((w) =>
map.set(w.id, w),
);
}
base = Array.from(map.values());
}
const title = options.title
? decodeURIComponent(options.title)
: '分类';
this.setData({
title,
sourceItems: base,
});
wx.setNavigationBarTitle({ title });
this.applyFilters();
},
applyFilters() {
const { sourceItems, activeAge, activeDiff } = this.data;
let next = filterByAgeBand(sourceItems, activeAge);
next = filterByDifficulty(next, activeDiff);
this.setData({ items: toDisplayList(next) });
},
onAgeFilter(e: WechatMiniprogram.TouchEvent) {
const key = e.currentTarget.dataset.key as AgeFilterKey;
if (!key) return;
this.setData({ activeAge: key }, () => this.applyFilters());
},
onDiffFilter(e: WechatMiniprogram.TouchEvent) {
const key = e.currentTarget.dataset.key as DiffFilterKey;
if (!key) return;
this.setData({ activeDiff: key }, () => this.applyFilters());
},
buildWorksheetUrl(w: WorksheetType): string {
let url = getWorksheetPath(w);
if (!url) return '';
if (w.mode) {
url += url.includes('?') ? `&mode=${w.mode}` : `?mode=${w.mode}`;
}
return url;
},
onCardTap(e: WechatMiniprogram.TouchEvent) {
const id = e.currentTarget.dataset.id as string;
const w = this.data.items.find((item) => item.id === id);
if (!w) return;
const url = this.buildWorksheetUrl(w);
if (!url) {
wx.showToast({ title: '暂无法打开', icon: 'none' });
return;
}
wx.navigateTo({ url });
},
});
+58
View File
@@ -0,0 +1,58 @@
<view class="category-page">
<view class="category-page__header">
<text class="category-page__header-title">{{title}}</text>
</view>
<view class="category-page__filters">
<view class="category-page__filter-row">
<text class="category-page__filter-label">年龄</text>
<view class="category-page__pills">
<view
wx:for="{{ageFilters}}"
wx:key="key"
class="category-page__pill {{activeAge === item.key ? 'category-page__pill--active' : ''}}"
data-key="{{item.key}}"
bindtap="onAgeFilter"
>
{{item.label}}
</view>
</view>
</view>
<view class="category-page__filter-row">
<text class="category-page__filter-label">难度</text>
<view class="category-page__pills">
<view
wx:for="{{diffFilters}}"
wx:key="key"
class="category-page__pill {{activeDiff === item.key ? 'category-page__pill--active' : ''}}"
data-key="{{item.key}}"
bindtap="onDiffFilter"
>
{{item.label}}
</view>
</view>
</view>
</view>
<view wx:if="{{items.length}}" class="category-page__list">
<view
wx:for="{{items}}"
wx:key="id"
class="category-page__card-wrap"
>
<detail-card
title="{{item.title}}"
preview-bg="{{item.previewBg}}"
preview-text="{{item.previewText}}"
age-range="{{item.ageRangeLabel}}"
difficulty="{{item.difficultyLabel}}"
data-id="{{item.id}}"
bind:tap="onCardTap"
/>
</view>
</view>
<view wx:else class="category-page__empty">
<text class="category-page__empty-text">暂无符合条件的练习纸</text>
</view>
</view>
-6
View File
@@ -74,11 +74,5 @@
title="测试页面入口"
size="large"
custom-class="centered-title" />
<van-cell
title="形状打印"
clickable
data-name="shapePrint"
is-link
url="/demoPages/shapePrint/index" />
</van-cell-group>
</view>
@@ -0,0 +1,6 @@
{
"navigationBarTitleText": "我的收藏",
"navigationBarTextStyle": "black",
"navigationBarBackgroundColor": "#ffffff",
"backgroundColor": "#F6F6F6"
}
@@ -0,0 +1,88 @@
.favorites-page {
min-height: 100vh;
background: #f6f6f6;
}
.favorites-page__title {
padding: 32rpx 32rpx 24rpx;
font-size: 40rpx;
font-weight: 600;
color: #333;
background: #fff;
}
.favorites-page__tabs {
display: flex;
flex-direction: row;
background: #fff;
border-bottom: 1rpx solid #eee;
}
.favorites-page__tab {
flex: 1;
text-align: center;
padding: 24rpx 0;
font-size: 30rpx;
color: #666;
position: relative;
}
.favorites-page__tab--active {
color: #333;
font-weight: 600;
}
.favorites-page__tab--active::after {
content: '';
position: absolute;
left: 50%;
bottom: 0;
transform: translateX(-50%);
width: 48rpx;
height: 6rpx;
background: #f7ee47;
border-radius: 3rpx;
}
.favorites-page__empty {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 120rpx 48rpx 0;
}
.favorites-page__emoji {
font-size: 96rpx;
line-height: 1.2;
margin-bottom: 32rpx;
}
.favorites-page__empty-msg {
margin-bottom: 48rpx;
}
.favorites-page__empty-line {
display: block;
font-size: 28rpx;
color: #666;
text-align: center;
line-height: 1.6;
margin-bottom: 8rpx;
}
.favorites-page__btn {
width: 280rpx;
height: 80rpx;
line-height: 80rpx;
background: #f7ee47;
color: #333;
font-size: 30rpx;
font-weight: 600;
border-radius: 40rpx;
border: none;
}
.favorites-page__btn::after {
border: none;
}
+27
View File
@@ -0,0 +1,27 @@
type FavoritesTab = 'favorites' | 'downloads';
Page({
data: {
activeTab: 'favorites' as FavoritesTab,
favoriteList: [] as unknown[],
downloadList: [] as unknown[],
},
onShow() {
const tab = wx.getStorageSync('favorites_active_tab') as FavoritesTab;
if (tab === 'downloads' || tab === 'favorites') {
this.setData({ activeTab: tab });
wx.removeStorageSync('favorites_active_tab');
}
},
onTabSwitch(e: WechatMiniprogram.TouchEvent) {
const tab = e.currentTarget.dataset.tab as FavoritesTab;
if (tab !== 'favorites' && tab !== 'downloads') return;
this.setData({ activeTab: tab });
},
onDiscoverTap() {
wx.switchTab({ url: '/pages/home/home' });
},
});
@@ -0,0 +1,38 @@
<view class="favorites-page">
<view class="favorites-page__title">我的收藏</view>
<view class="favorites-page__tabs">
<view
class="favorites-page__tab {{activeTab === 'favorites' ? 'favorites-page__tab--active' : ''}}"
data-tab="favorites"
bindtap="onTabSwitch"
>
收藏的题型
</view>
<view
class="favorites-page__tab {{activeTab === 'downloads' ? 'favorites-page__tab--active' : ''}}"
data-tab="downloads"
bindtap="onTabSwitch"
>
下载历史
</view>
</view>
<view wx:if="{{activeTab === 'favorites' && favoriteList.length === 0}}" class="favorites-page__empty">
<text class="favorites-page__emoji">🦆</text>
<view class="favorites-page__empty-msg">
<text class="favorites-page__empty-line">鸭丫还没找到收藏呢</text>
<text class="favorites-page__empty-line">去发现好玩的练习纸吧~</text>
</view>
<button class="favorites-page__btn" bindtap="onDiscoverTap">去发现</button>
</view>
<view wx:elif="{{activeTab === 'downloads' && downloadList.length === 0}}" class="favorites-page__empty">
<text class="favorites-page__emoji">🦆</text>
<view class="favorites-page__empty-msg">
<text class="favorites-page__empty-line">还没有下载记录哦</text>
<text class="favorites-page__empty-line">快去生成练习纸吧~</text>
</view>
<button class="favorites-page__btn" bindtap="onDiscoverTap">去发现</button>
</view>
</view>
+6
View File
@@ -0,0 +1,6 @@
{
"navigationBarTitleText": "打印指南",
"navigationBarTextStyle": "black",
"navigationBarBackgroundColor": "#f7ee47",
"backgroundColor": "#F6F6F6"
}
+150
View File
@@ -0,0 +1,150 @@
.guide-page {
min-height: 100vh;
background: #f6f6f6;
padding-bottom: 48rpx;
}
.guide-page__hero {
background: linear-gradient(180deg, #f7ee47 0%, #e6d520 100%);
padding: 60rpx 40rpx;
display: flex;
flex-direction: column;
align-items: center;
}
.guide-page__hero-icon {
font-size: 80rpx;
margin-bottom: 24rpx;
}
.guide-page__hero-title {
font-size: 40rpx;
font-weight: 700;
color: #333;
}
.guide-page__steps {
position: relative;
padding: 40rpx 32rpx 24rpx 32rpx;
margin: 0 24rpx;
margin-top: -24rpx;
background: #fff;
border-radius: 24rpx;
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.06);
}
.guide-page__timeline-line {
position: absolute;
left: 62rpx;
top: 100rpx;
bottom: 100rpx;
width: 4rpx;
background: #eee;
z-index: 0;
}
.guide-page__step {
position: relative;
z-index: 1;
display: flex;
flex-direction: row;
margin-bottom: 48rpx;
}
.guide-page__step--last {
margin-bottom: 0;
}
.guide-page__step-num-wrap {
flex-shrink: 0;
width: 64rpx;
margin-right: 24rpx;
display: flex;
justify-content: center;
}
.guide-page__step-num {
width: 60rpx;
height: 60rpx;
border-radius: 50%;
background: #f7ee47;
font-size: 28rpx;
font-weight: 700;
color: #333;
display: flex;
align-items: center;
justify-content: center;
}
.guide-page__step-body {
flex: 1;
padding-top: 4rpx;
}
.guide-page__step-title {
display: block;
font-size: 32rpx;
font-weight: 600;
color: #333;
margin-bottom: 12rpx;
}
.guide-page__step-desc {
display: block;
font-size: 28rpx;
color: #666;
line-height: 1.55;
margin-bottom: 16rpx;
}
.guide-page__tip {
background: #fff8e1;
border-radius: 16rpx;
padding: 20rpx 24rpx;
font-size: 26rpx;
color: #666;
line-height: 1.5;
}
.guide-page__tip-line {
display: block;
margin-bottom: 8rpx;
}
.guide-page__tip-line:last-child {
margin-bottom: 0;
}
.guide-page__faq-title {
font-size: 32rpx;
font-weight: 700;
color: #333;
padding: 40rpx 32rpx 16rpx;
}
.guide-page__faq {
padding: 0 24rpx;
}
.guide-page__faq-item {
background: #fff;
border-radius: 16rpx;
padding: 28rpx 32rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
}
.guide-page__faq-q {
display: block;
font-size: 28rpx;
font-weight: 700;
color: #333;
margin-bottom: 12rpx;
}
.guide-page__faq-a {
display: block;
font-size: 26rpx;
color: #999;
line-height: 1.55;
}
+7
View File
@@ -0,0 +1,7 @@
Page({
data: {},
onLoad() {
// 静态说明页,无额外逻辑
},
});
+63
View File
@@ -0,0 +1,63 @@
<view class="guide-page">
<view class="guide-page__hero">
<text class="guide-page__hero-icon">🖨️</text>
<text class="guide-page__hero-title">3 步完成打印</text>
</view>
<view class="guide-page__steps">
<view class="guide-page__timeline-line" />
<view class="guide-page__step">
<view class="guide-page__step-num-wrap">
<view class="guide-page__step-num">1</view>
</view>
<view class="guide-page__step-body">
<text class="guide-page__step-title">保存到相册</text>
<text class="guide-page__step-desc">在练习纸预览页点击「保存」或「保存到相册」,授权后即可保存图片。</text>
<view class="guide-page__tip">提示:建议先关闭深色模式截图,打印对比度更好。</view>
</view>
</view>
<view class="guide-page__step">
<view class="guide-page__step-num-wrap">
<view class="guide-page__step-num">2</view>
</view>
<view class="guide-page__step-body">
<text class="guide-page__step-title">连接打印机</text>
<text class="guide-page__step-desc">确保打印机与手机处于同一 Wi-Fi,或按打印机说明完成蓝牙配对。</text>
<view class="guide-page__tip">家用喷墨 / 激光打印机均可;A4 纸竖向打印即可。</view>
</view>
</view>
<view class="guide-page__step guide-page__step--last">
<view class="guide-page__step-num-wrap">
<view class="guide-page__step-num">3</view>
</view>
<view class="guide-page__step-body">
<text class="guide-page__step-title">从相册打印</text>
<text class="guide-page__step-desc">打开手机相册,选中刚保存的练习纸图片,点击分享或打印。</text>
<view class="guide-page__tip">
<text class="guide-page__tip-line">iPhone:分享 → 打印 → 选择打印机。</text>
<text class="guide-page__tip-line">Android:更多 → 打印 或 使用系统相册自带的打印入口。</text>
</view>
</view>
</view>
</view>
<view class="guide-page__faq-title">常见问题</view>
<view class="guide-page__faq">
<view class="guide-page__faq-item">
<text class="guide-page__faq-q">保存失败怎么办?</text>
<text class="guide-page__faq-a">请在小程序设置中开启相册写入权限,并检查手机存储空间是否充足。</text>
</view>
<view class="guide-page__faq-item">
<text class="guide-page__faq-q">打印出来太小/太大?</text>
<text class="guide-page__faq-a">在打印预览中选择「适应页面」或调整缩放比例,通常 100% 即可。</text>
</view>
<view class="guide-page__faq-item">
<text class="guide-page__faq-q">没有打印机?</text>
<text class="guide-page__faq-a">可将图片发到电脑或拷贝至 U 盘,在打印店完成打印。</text>
</view>
</view>
</view>
+11
View File
@@ -0,0 +1,11 @@
{
"navigationBarTitleText": "涂鸦丫",
"navigationBarTextStyle": "black",
"navigationBarBackgroundColor": "#FEF6E7",
"backgroundColor": "#FEF6E7",
"enablePullDownRefresh": false,
"usingComponents": {
"worksheet-card": "/components/shared/worksheet-card/worksheet-card",
"category-tabs": "/components/shared/category-tabs/category-tabs"
}
}
+172
View File
@@ -0,0 +1,172 @@
/* 首页体验配色:主题 #FFD709 / 正文 #605B50 / 底 #FEF6E7 / 按钮字 #322E25 / 标签 #91F78E */
.home-page {
min-height: 100vh;
background: #fef6e7;
padding-bottom: 48rpx;
box-sizing: border-box;
color: #605b50;
}
.home-header {
background: #fef6e7;
padding: 24rpx 24rpx 20rpx;
}
.home-brand {
display: flex;
flex-direction: row;
align-items: center;
gap: 16rpx;
margin-bottom: 24rpx;
}
.home-brand__duck {
font-size: 48rpx;
line-height: 1;
}
.home-brand__name {
font-size: 36rpx;
font-weight: 700;
color: #322e25;
}
.home-search {
background: rgba(255, 255, 255, 0.72);
border-radius: 40rpx;
padding: 22rpx 32rpx;
border: 1rpx solid rgba(255, 215, 9, 0.35);
}
.home-search__placeholder {
font-size: 28rpx;
color: rgba(96, 91, 80, 0.55);
}
.home-body {
padding: 24rpx 24rpx 0;
}
.home-section {
margin-bottom: 40rpx;
}
.home-section--hot {
margin-bottom: 32rpx;
}
.home-section__title-row {
margin-bottom: 20rpx;
}
.home-section__title {
font-size: 32rpx;
font-weight: 700;
color: #605b50;
}
.home-section__head {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
margin-bottom: 20rpx;
}
.home-section__head-left {
display: flex;
flex-direction: row;
align-items: center;
gap: 12rpx;
}
.home-section__icon {
font-size: 36rpx;
line-height: 1;
}
.home-section__name {
font-size: 32rpx;
font-weight: 700;
color: #605b50;
}
.home-section__more {
font-size: 26rpx;
color: #322e25;
font-weight: 500;
}
.home-hot__scroll {
width: 100%;
white-space: nowrap;
}
.home-hot__row {
display: inline-flex;
flex-direction: row;
gap: 24rpx;
padding-bottom: 8rpx;
}
.home-hot__item {
display: inline-block;
width: 240rpx;
vertical-align: top;
}
.home-hot__card-wrap {
width: 240rpx;
padding: 4rpx;
border-radius: 28rpx;
box-sizing: border-box;
background: linear-gradient(145deg, rgba(255, 215, 9, 0.85), rgba(254, 246, 231, 0.95));
}
/* category-tabs:仅首页通过 apply-shared + .home-page 生效 */
.home-page .category-tabs {
background: #fef6e7;
}
.home-page .category-tabs__pill {
background: rgba(96, 91, 80, 0.08);
color: #605b50;
}
.home-page .category-tabs__pill--active {
background: #ffd709;
color: #322e25;
font-weight: 600;
}
/* worksheet-card:仅首页 */
.home-page .worksheet-card {
box-shadow: 0 8rpx 28rpx rgba(50, 46, 37, 0.08);
}
.home-page .worksheet-card__title {
color: #605b50;
}
.home-page .worksheet-card__preview-text {
color: #605b50;
}
.home-page .worksheet-card__tag--age,
.home-page .worksheet-card__tag--diff {
background: #91f78e;
color: #322e25;
}
.home-grid {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 20rpx;
}
.home-grid__cell {
width: calc((100% - 40rpx) / 3);
box-sizing: border-box;
}
+88
View File
@@ -0,0 +1,88 @@
import { ALL_WORKSHEETS, getWorksheetsByCategory, getWorksheetPath } from '../../core/data/worksheets';
import { CATEGORIES } from '../../core/data/categories';
import { WorksheetType } from '../../core/models/worksheet';
const CATEGORY_TABS = [
{ id: 'all', name: '全部' },
...CATEGORIES.map((c) => ({ id: c.id, name: c.name })),
];
Page({
data: {
tabs: CATEGORY_TABS,
activeTab: 'all',
sections: [] as Array<{ title: string; icon: string; category: string; items: WorksheetType[] }>,
hotItems: [] as WorksheetType[],
filteredItems: null as WorksheetType[] | null,
},
onLoad() {
const sections = CATEGORIES.filter((cat) => {
const items = getWorksheetsByCategory(cat.id);
return items.length > 0;
}).map((cat) => ({
title: cat.name,
icon: cat.icon,
category: cat.id,
items: getWorksheetsByCategory(cat.id).slice(0, 3),
}));
const hotItems = ALL_WORKSHEETS.filter((w) => w.page).slice(0, 4);
this.setData({ sections, hotItems });
},
onTabChange(e: WechatMiniprogram.CustomEvent) {
const activeTab = e.detail.id;
if (activeTab === 'all') {
this.setData({ activeTab, filteredItems: null });
} else {
const filteredItems = getWorksheetsByCategory(activeTab);
this.setData({ activeTab, filteredItems });
}
},
onCardTap(e: WechatMiniprogram.TouchEvent) {
const id = e.currentTarget.dataset.id as string | undefined;
if (!id) return;
const item = ALL_WORKSHEETS.find((w) => w.id === id);
if (!item) return;
const path = getWorksheetPath(item);
if (path) {
wx.navigateTo({ url: path });
}
},
onHotCardTap(e: WechatMiniprogram.TouchEvent) {
const id = e.currentTarget.dataset.id as string | undefined;
if (!id) return;
const item = ALL_WORKSHEETS.find((w) => w.id === id);
if (!item) return;
const path = getWorksheetPath(item);
if (path) {
wx.navigateTo({ url: path });
}
},
onSeeMore(e: WechatMiniprogram.TouchEvent) {
const { category } = e.currentTarget.dataset;
wx.navigateTo({
url: `/pages/category/category?category=${category}`,
});
},
onShareAppMessage() {
return {
title: '涂鸦丫 - 快来生成宝宝的专属学习卡',
path: '/pages/home/home',
imageUrl: 'https://cdn.joeyone.cn/doodle/share-img/index-share-v2.png',
};
},
onShareTimeline() {
return {
title: '涂鸦丫 - 快来生成宝宝的专属学习卡',
query: '',
};
},
});
+105
View File
@@ -0,0 +1,105 @@
<wxs module="fmt">
function ageLabel(arr) {
if (!arr || arr.length < 2) {
return '';
}
return arr[0] + '-' + arr[1] + '岁';
}
module.exports.ageLabel = ageLabel;
</wxs>
<view class="home-page">
<view class="home-header">
<view class="home-brand">
<text class="home-brand__duck">🦆</text>
<text class="home-brand__name">涂鸦丫</text>
</view>
<view class="home-search">
<text class="home-search__placeholder">搜索练习单(即将上线)</text>
</view>
</view>
<category-tabs tabs="{{tabs}}" active-tab="{{activeTab}}" bind:change="onTabChange" />
<view class="home-body">
<block wx:if="{{activeTab != 'all'}}">
<view class="home-grid">
<view
wx:for="{{filteredItems}}"
wx:key="id"
class="home-grid__cell"
bindtap="onCardTap"
data-id="{{item.id}}"
>
<worksheet-card
title="{{item.title}}"
preview-bg="{{item.previewBg}}"
preview-text="{{item.previewText}}"
img="{{item.img}}"
age-range="{{fmt.ageLabel(item.ageRange)}}"
difficulty="{{item.difficulty}}"
/>
</view>
</view>
</block>
<block wx:else>
<view class="home-section home-section--hot">
<view class="home-section__title-row">
<text class="home-section__title">🔥 热门下载</text>
</view>
<scroll-view class="home-hot__scroll" scroll-x enable-flex>
<view class="home-hot__row">
<view
wx:for="{{hotItems}}"
wx:key="id"
class="home-hot__item"
bindtap="onHotCardTap"
data-id="{{item.id}}"
>
<view class="home-hot__card-wrap">
<worksheet-card
title="{{item.title}}"
preview-bg="{{item.previewBg}}"
preview-text="{{item.previewText}}"
img="{{item.img}}"
age-range="{{fmt.ageLabel(item.ageRange)}}"
difficulty="{{item.difficulty}}"
/>
</view>
</view>
</view>
</scroll-view>
</view>
<view wx:for="{{sections}}" wx:key="category" class="home-section">
<view class="home-section__head">
<view class="home-section__head-left">
<text class="home-section__icon">{{item.icon}}</text>
<text class="home-section__name">{{item.title}}</text>
</view>
<text class="home-section__more" bindtap="onSeeMore" data-category="{{item.category}}">更多</text>
</view>
<view class="home-grid">
<view
wx:for="{{item.items}}"
wx:for-item="w"
wx:key="id"
class="home-grid__cell"
bindtap="onCardTap"
data-id="{{w.id}}"
>
<worksheet-card
title="{{w.title}}"
preview-bg="{{w.previewBg}}"
preview-text="{{w.previewText}}"
img="{{w.img}}"
age-range="{{fmt.ageLabel(w.ageRange)}}"
difficulty="{{w.difficulty}}"
/>
</view>
</view>
</view>
</block>
</view>
</view>
+6
View File
@@ -0,0 +1,6 @@
{
"navigationBarTitleText": "我的",
"navigationBarTextStyle": "black",
"navigationBarBackgroundColor": "#ffffff",
"backgroundColor": "#F6F6F6"
}
+115
View File
@@ -0,0 +1,115 @@
.profile-page {
min-height: 100vh;
background: #f6f6f6;
padding-bottom: 48rpx;
}
.profile-page__header {
display: flex;
flex-direction: row;
align-items: center;
background: #f7ee47;
padding: 48rpx 32rpx;
}
.profile-page__avatar {
width: 112rpx;
height: 112rpx;
border-radius: 50%;
background: #fff;
display: flex;
align-items: center;
justify-content: center;
font-size: 56rpx;
flex-shrink: 0;
}
.profile-page__header-info {
margin-left: 28rpx;
display: flex;
flex-direction: column;
justify-content: center;
}
.profile-page__name {
font-size: 36rpx;
font-weight: 700;
color: #333;
margin-bottom: 12rpx;
}
.profile-page__stat {
font-size: 26rpx;
color: #555;
}
.profile-page__group {
background: #fff;
border-radius: 32rpx;
margin: 24rpx 24rpx 0;
box-shadow: 0 4rpx 24rpx rgba(0, 0, 0, 0.06);
overflow: hidden;
}
.profile-page__item {
display: flex;
flex-direction: row;
align-items: center;
padding: 28rpx 32rpx;
border-bottom: 1rpx solid #f0f0f0;
}
.profile-page__item--last {
border-bottom: none;
}
.profile-page__icon {
font-size: 36rpx;
margin-right: 20rpx;
width: 44rpx;
text-align: center;
}
.profile-page__label {
flex: 1;
font-size: 30rpx;
color: #333;
}
.profile-page__badge {
font-size: 20rpx;
color: #fff;
background: #ff4757;
padding: 4rpx 12rpx;
border-radius: 8rpx;
margin-right: 12rpx;
}
.profile-page__arrow {
font-size: 36rpx;
color: #ccc;
font-weight: 300;
}
.profile-page__footer {
display: flex;
flex-direction: column;
align-items: center;
padding: 64rpx 32rpx 32rpx;
}
.profile-page__footer-emoji {
font-size: 72rpx;
margin-bottom: 16rpx;
}
.profile-page__footer-slogan {
font-size: 26rpx;
color: #999;
margin-bottom: 12rpx;
}
.profile-page__footer-version {
font-size: 22rpx;
color: #ccc;
}
+46
View File
@@ -0,0 +1,46 @@
Page({
data: {
generateCount: 0,
version: '',
},
onLoad() {
const info = wx.getAccountInfoSync();
const version = info.miniProgram.version || '开发版';
this.setData({ version });
},
onGoToDownloads() {
wx.setStorageSync('favorites_active_tab', 'downloads');
wx.switchTab({ url: '/pages/favorites/favorites' });
},
onGoToFavorites() {
wx.setStorageSync('favorites_active_tab', 'favorites');
wx.switchTab({ url: '/pages/favorites/favorites' });
},
onGoToPrintPlan() {
wx.showToast({ title: '功能开发中', icon: 'none' });
},
onGoToStats() {
wx.showToast({ title: '功能开发中', icon: 'none' });
},
onGoToGuide() {
wx.navigateTo({ url: '/pages/guide/guide' });
},
onGoToPrintSettings() {
wx.showToast({ title: '功能开发中', icon: 'none' });
},
onGoToFeedback() {
wx.showToast({ title: '功能开发中', icon: 'none' });
},
onGoToAbout() {
wx.showToast({ title: '涂鸦丫练习纸', icon: 'none' });
},
});
+62
View File
@@ -0,0 +1,62 @@
<view class="profile-page">
<view class="profile-page__header">
<view class="profile-page__avatar">🦆</view>
<view class="profile-page__header-info">
<text class="profile-page__name">微信用户</text>
<text class="profile-page__stat">已生成 {{generateCount}} 张练习纸</text>
</view>
</view>
<view class="profile-page__group">
<view class="profile-page__item" bindtap="onGoToDownloads">
<text class="profile-page__icon">📥</text>
<text class="profile-page__label">下载历史</text>
<text class="profile-page__arrow"></text>
</view>
<view class="profile-page__item" bindtap="onGoToFavorites">
<text class="profile-page__icon">❤️</text>
<text class="profile-page__label">我的收藏</text>
<text class="profile-page__arrow"></text>
</view>
<view class="profile-page__item" bindtap="onGoToPrintPlan">
<text class="profile-page__icon">📋</text>
<text class="profile-page__label">每日打印计划</text>
<view class="profile-page__badge">即将上线</view>
<text class="profile-page__arrow"></text>
</view>
<view class="profile-page__item profile-page__item--last" bindtap="onGoToStats">
<text class="profile-page__icon">📊</text>
<text class="profile-page__label">学习统计</text>
<text class="profile-page__arrow"></text>
</view>
</view>
<view class="profile-page__group">
<view class="profile-page__item" bindtap="onGoToGuide">
<text class="profile-page__icon">🖨️</text>
<text class="profile-page__label">打印指南</text>
<text class="profile-page__arrow"></text>
</view>
<view class="profile-page__item" bindtap="onGoToPrintSettings">
<text class="profile-page__icon">⚙️</text>
<text class="profile-page__label">打印设置</text>
<text class="profile-page__arrow"></text>
</view>
<view class="profile-page__item" bindtap="onGoToFeedback">
<text class="profile-page__icon">💬</text>
<text class="profile-page__label">意见反馈</text>
<text class="profile-page__arrow"></text>
</view>
<view class="profile-page__item profile-page__item--last" bindtap="onGoToAbout">
<text class="profile-page__icon">️</text>
<text class="profile-page__label">关于涂鸦丫</text>
<text class="profile-page__arrow"></text>
</view>
</view>
<view class="profile-page__footer">
<text class="profile-page__footer-emoji">🦆</text>
<text class="profile-page__footer-slogan">陪孩子一起,把学习变成游戏</text>
<text class="profile-page__footer-version">v{{version}}</text>
</view>
</view>