feat: worksheet发布功能开发

This commit is contained in:
R524809
2026-04-24 17:49:11 +08:00
parent 5106dbbd7c
commit bb69d5c49d
24 changed files with 2540 additions and 1014 deletions
+165
View File
@@ -3,6 +3,12 @@ import { downloadPrint } from '../utils/downloadPrint';
import { BaseDrawService } from '../core/draw/baseDraw';
import tracker from '../utils/tracker';
import { defaultShareConfig } from '../config/config';
import {
buildWorksheetPreviewCloudPath,
isDebugPublishEnabled,
type DebugPublishConfirmDetail,
type DebugPublishMeta,
} from '../utils/debugPublish';
/**
* Canvas 相关的页面实例属性
@@ -17,6 +23,8 @@ export interface PageCanvasInstance {
setData(data: any, callback?: () => void): void;
route: string;
getShareOptions(): ShareOptions;
selectComponent(selector: string): any;
getPublishMeta?(): DebugPublishMeta;
}
/**
@@ -34,6 +42,10 @@ export interface CanvasDataState {
data: any;
setData(data: any, callback?: () => void): void;
route: string;
isDevEnv?: boolean;
debugPublishVisible?: boolean;
debugPublishLoading?: boolean;
debugPublishMeta?: DebugPublishMeta | null;
}
/**
@@ -275,6 +287,159 @@ export function getPageCommonMethods(config: PageCommonMethodsConfig = {}) {
}
},
syncDebugPublishEnv(this: PageCanvasInstance) {
this.setData({ isDevEnv: isDebugPublishEnabled() });
},
onOpenDebugPublish(this: PageCanvasInstance) {
if (!isDebugPublishEnabled()) return;
if (typeof this.getPublishMeta !== 'function') {
wx.showToast({
title: '页面未实现发布元数据',
icon: 'none',
});
return;
}
try {
const meta = this.getPublishMeta();
this.setData({
isDevEnv: true,
debugPublishVisible: true,
debugPublishMeta: meta,
});
} catch (error) {
wx.showToast({
title:
error instanceof Error
? error.message
: '准备发布数据失败',
icon: 'none',
});
}
},
onCloseDebugPublish(this: PageCanvasInstance) {
this.setData({ debugPublishVisible: false });
},
/**
* 确认发布
*/
async onConfirmDebugPublish(
this: PageCanvasInstance,
e: WechatMiniprogram.CustomEvent<DebugPublishConfirmDetail>,
) {
const previewCard = this.selectComponent('#previewCard');
const debugPublishTools =
this.selectComponent('#debugPublishTools');
const baseMeta =
this.data.debugPublishMeta ||
(typeof this.getPublishMeta === 'function'
? this.getPublishMeta()
: null);
if (!previewCard?.exportToTempFile) {
wx.showToast({
title: '预览组件不可用',
icon: 'none',
});
return;
}
if (!debugPublishTools?.processImage) {
wx.showToast({
title: '发布工具不可用',
icon: 'none',
});
return;
}
if (!baseMeta) {
wx.showToast({
title: '缺少发布元数据',
icon: 'none',
});
return;
}
const detail = e.detail;
const publishMeta: DebugPublishMeta = {
...baseMeta,
title: detail.meta.title,
subtitle: detail.meta.subtitle,
tags: detail.meta.tags,
status: detail.meta.status,
};
this.setData({ debugPublishLoading: true });
wx.showLoading({ title: '发布中...' });
try {
const sourcePath = await previewCard.exportToTempFile({
fileType: 'jpg',
quality: 1,
});
console.log('sourcePath', sourcePath);
const processed = await debugPublishTools.processImage({
sourcePath,
settings: detail.settings,
});
console.log('processed', processed);
const cloudPath = buildWorksheetPreviewCloudPath(
publishMeta.category,
publishMeta.id,
);
console.log('cloudPath', cloudPath);
const uploadRes = await wx.cloud.uploadFile({
cloudPath,
filePath: processed.tempFilePath,
});
console.log('uploadRes', uploadRes);
const cloudCall = (await wx.cloud.callFunction({
name: 'worksheetsPublish',
data: {
...publishMeta,
previewImg: uploadRes.fileID,
},
})) as {
result?: { success?: boolean; message?: string };
};
console.log('cloudCall', cloudCall);
if (!cloudCall.result?.success) {
throw new Error(
cloudCall.result?.message || '云端写入失败',
);
}
console.log('success');
this.setData({
debugPublishVisible: false,
debugPublishMeta: {
...publishMeta,
previewImg: uploadRes.fileID,
},
});
wx.showToast({
title: `发布成功 ${Math.round(processed.size / 1024)}KB`,
icon: 'success',
});
} catch (error) {
console.log('onConfirmDebugPublish error', error);
wx.showToast({
title: error instanceof Error ? error.message : '发布失败',
icon: 'none',
});
} finally {
wx.hideLoading();
this.setData({ debugPublishLoading: false });
}
},
/**
* 初始化页面信息
*
@@ -0,0 +1,7 @@
{
"component": true,
"styleIsolation": "isolated",
"usingComponents": {
"van-popup": "@vant/weapp/popup/index"
}
}
@@ -0,0 +1,196 @@
.debug-publish-tools {
.debug-publish-tools__fab {
position: fixed;
right: 24rpx;
bottom: calc(136rpx + env(safe-area-inset-bottom));
z-index: 40;
display: inline-flex;
align-items: center;
gap: 10rpx;
padding: 20rpx 28rpx;
border-radius: 999rpx;
background: rgba(50, 46, 37, 0.92);
box-shadow: 0 12rpx 32rpx rgba(0, 0, 0, 0.18);
}
.debug-publish-tools__fab--hover {
transform: scale(0.98);
opacity: 0.92;
}
.debug-publish-tools__fab-icon {
font-size: 28rpx;
line-height: 1;
}
.debug-publish-tools__fab-text {
font-size: 24rpx;
font-weight: 700;
color: #ffffff;
line-height: 1;
}
.debug-publish-tools__popup {
width: 680rpx;
max-height: 82vh;
overflow: hidden;
}
.debug-publish-tools__content {
max-height: 82vh;
padding: 56rpx 32rpx 32rpx;
box-sizing: border-box;
overflow-y: auto;
}
.debug-publish-tools__title {
display: block;
margin-bottom: 28rpx;
font-size: 36rpx;
font-weight: 800;
color: #322e25;
text-align: center;
}
.debug-publish-tools__section + .debug-publish-tools__section {
margin-top: 28rpx;
}
.debug-publish-tools__section-title {
display: block;
margin-bottom: 18rpx;
font-size: 26rpx;
font-weight: 700;
color: #7c766a;
}
.debug-publish-tools__meta-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16rpx;
}
.debug-publish-tools__meta-item,
.debug-publish-tools__path-box,
.debug-publish-tools__field {
padding: 18rpx 20rpx;
border-radius: 20rpx;
background: #f7f1e6;
}
.debug-publish-tools__path-box {
margin-top: 16rpx;
}
.debug-publish-tools__meta-label,
.debug-publish-tools__field-label {
display: block;
font-size: 22rpx;
font-weight: 700;
color: #8a8478;
}
.debug-publish-tools__meta-value,
.debug-publish-tools__path {
display: block;
margin-top: 8rpx;
font-size: 24rpx;
color: #322e25;
line-height: 1.5;
word-break: break-all;
}
.debug-publish-tools__field + .debug-publish-tools__field {
margin-top: 16rpx;
}
.debug-publish-tools__input {
width: 100%;
margin-top: 12rpx;
min-height: 44rpx;
font-size: 26rpx;
color: #322e25;
}
.debug-publish-tools__chip-row {
display: flex;
gap: 16rpx;
margin-top: 14rpx;
}
.debug-publish-tools__chip-row--wrap {
flex-wrap: wrap;
}
.debug-publish-tools__chip {
min-width: 120rpx;
padding: 16rpx 22rpx;
border-radius: 999rpx;
background: #ebe3d4;
font-size: 24rpx;
font-weight: 700;
color: #605b50;
text-align: center;
box-sizing: border-box;
}
.debug-publish-tools__chip--active {
background: #f7ce00;
color: #453900;
}
.debug-publish-tools__hint {
display: block;
margin-top: 10rpx;
font-size: 22rpx;
line-height: 1.6;
color: #8a8478;
}
.debug-publish-tools__actions {
display: flex;
gap: 20rpx;
margin-top: 32rpx;
}
.debug-publish-tools__btn {
flex: 1;
height: 88rpx;
line-height: 88rpx;
border-radius: 999rpx;
font-size: 28rpx;
font-weight: 700;
border: none;
padding: 0;
}
.debug-publish-tools__btn::after {
border: none;
}
.debug-publish-tools__btn--ghost {
background: #f0eadf;
color: #605b50;
}
.debug-publish-tools__btn--primary {
background: linear-gradient(135deg, #f7ee47 0%, #f0ca08 100%);
color: #453900;
}
.debug-publish-tools__processor {
position: fixed;
left: -9999px;
top: -9999px;
width: 2px;
height: 2px;
overflow: hidden;
opacity: 0;
pointer-events: none;
}
.debug-publish-tools__processor-canvas {
width: 2px;
height: 2px;
}
}
@@ -0,0 +1,350 @@
import {
DEBUG_PUBLISH_DEFAULT_QUALITY,
DEBUG_PUBLISH_DEFAULT_WIDTH,
DEBUG_PUBLISH_MAX_SIZE_KB,
clampPublishQuality,
clampPublishWidth,
getCropRatios,
normalizeTagsInput,
type DebugCropMode,
type DebugProcessImageParams,
type DebugProcessImageResult,
type DebugPublishConfirmDetail,
type DebugPublishMeta,
} from '../../utils/debugPublish';
/**
* Debug 发布工具组件。
*
* 组件只负责两件事:
* 1. 展示调试发布的 UI(悬浮按钮 + 弹窗表单)
* 2. 用隐藏 canvas 处理预览图的裁剪与压缩
*
* 真正的上传云存储、调用云函数写库仍由页面层处理,
* 这样同一套组件可以复用到多个绘制页面。
*/
Component({
properties: {
enabled: {
type: Boolean,
value: false,
},
visible: {
type: Boolean,
value: false,
},
loading: {
type: Boolean,
value: false,
},
meta: {
type: Object,
value: {},
},
},
data: {
formTitle: '',
formSubtitle: '',
formTags: '',
formStatus: 'draft',
formCropMode: 'header-footer',
formQuality: DEBUG_PUBLISH_DEFAULT_QUALITY,
formWidth: DEBUG_PUBLISH_DEFAULT_WIDTH,
maxSizeKB: DEBUG_PUBLISH_MAX_SIZE_KB,
},
observers: {
visible(visible: boolean) {
if (visible) {
this.syncFormState(
(this.properties as any).meta as DebugPublishMeta | null,
);
}
},
meta(meta: DebugPublishMeta | null) {
if ((this.properties as any).visible) {
this.syncFormState(meta);
}
},
},
lifetimes: {
ready() {
this.initProcessorCanvas();
},
},
methods: {
/** 打开弹窗时,用当前题型元数据重置表单。 */
syncFormState(meta: DebugPublishMeta | null) {
this.setData({
formTitle: meta?.title ?? '',
formSubtitle: meta?.subtitle ?? '',
formTags: meta?.tags?.join(', ') ?? '',
formStatus: meta?.status ?? 'draft',
formCropMode: 'header-footer',
formQuality: DEBUG_PUBLISH_DEFAULT_QUALITY,
formWidth: DEBUG_PUBLISH_DEFAULT_WIDTH,
});
},
/** 点击悬浮发布按钮。挂载时机已由页面层控制,这里无需再二次判断 enabled。 */
onOpen() {
this.triggerEvent('open');
},
/** 关闭发布弹窗。 */
onClose() {
this.triggerEvent('close');
},
onTitleInput(e: WechatMiniprogram.Input) {
this.setData({ formTitle: (e.detail.value ?? '').trim() });
},
onSubtitleInput(e: WechatMiniprogram.Input) {
this.setData({ formSubtitle: (e.detail.value ?? '').trim() });
},
onTagsInput(e: WechatMiniprogram.Input) {
this.setData({ formTags: e.detail.value ?? '' });
},
onSelectStatus(e: WechatMiniprogram.TouchEvent) {
const value = e.currentTarget.dataset.value as string;
if (!value) return;
this.setData({ formStatus: value });
},
onSelectCropMode(e: WechatMiniprogram.TouchEvent) {
const value = e.currentTarget.dataset.value as DebugCropMode;
if (!value) return;
this.setData({ formCropMode: value });
},
onQualityChange(e: WechatMiniprogram.CustomEvent) {
this.setData({
formQuality: clampPublishQuality(Number(e.detail.value)),
});
},
onWidthChange(e: WechatMiniprogram.CustomEvent) {
this.setData({
formWidth: clampPublishWidth(Number(e.detail.value)),
});
},
/** 校验弹窗输入,并把结果回传给页面层。 */
onConfirm() {
const meta = (this.properties as any)
.meta as DebugPublishMeta | null;
if (!meta) {
wx.showToast({
title: '缺少发布元数据',
icon: 'none',
});
return;
}
const title = this.data.formTitle.trim();
const subtitle = this.data.formSubtitle.trim();
if (!title || !subtitle) {
wx.showToast({
title: '标题和副标题不能为空',
icon: 'none',
});
return;
}
const detail: DebugPublishConfirmDetail = {
meta: {
title,
subtitle,
tags: normalizeTagsInput(this.data.formTags),
status: this.data.formStatus as DebugPublishMeta['status'],
},
settings: {
cropMode: this.data.formCropMode as DebugCropMode,
quality: clampPublishQuality(this.data.formQuality),
width: clampPublishWidth(this.data.formWidth),
maxSizeKB: this.data.maxSizeKB,
},
};
this.triggerEvent('confirm', detail);
},
/** 初始化隐藏处理 canvas,后续所有裁剪压缩都在这里完成。 */
initProcessorCanvas() {
if ((this as any)._processorCanvasReadyPromise) return;
(this as any)._processorCanvasReadyPromise = new Promise<void>(
(resolve, reject) => {
this.createSelectorQuery()
.select('#processorCanvas')
.fields({ node: true, size: true })
.exec((res) => {
if (!res[0] || !res[0].node) {
reject(new Error('处理画布初始化失败'));
return;
}
(this as any)._processorCanvas = res[0].node;
(this as any)._processorCtx = (
this as any
)._processorCanvas.getContext('2d');
resolve();
});
},
);
},
/** 确保隐藏 canvas 已经 ready。 */
async ensureProcessorCanvasReady() {
if (!(this as any)._processorCanvasReadyPromise) {
this.initProcessorCanvas();
}
await (this as any)._processorCanvasReadyPromise;
},
/** 用当前 canvas 上下文加载临时图片文件。 */
loadImage(
canvas: WechatMiniprogram.Canvas,
src: string,
): Promise<WechatMiniprogram.Image> {
return new Promise((resolve, reject) => {
const image = canvas.createImage();
image.onload = () => resolve(image);
image.onerror = reject;
image.src = src;
});
},
/** 将处理后的 canvas 导出成 jpg 临时文件。 */
exportCanvasToTempFile(
canvas: WechatMiniprogram.Canvas,
width: number,
height: number,
quality: number,
): Promise<string> {
return new Promise((resolve, reject) => {
wx.canvasToTempFilePath({
canvas,
fileType: 'jpg',
quality,
destWidth: width,
destHeight: height,
success: (res) => resolve(res.tempFilePath),
fail: (err) => reject(err),
});
});
},
/**
* 读取导出文件大小,用于控制最大体积。
* 开发者工具中 canvasToTempFilePath 可能返回 HTTP URL
* wx.getFileSystemManager().getFileInfo() 不支持 HTTP 路径,
* 此时返回 -1 表示无法获取大小,调用方应跳过体积检测。
*/
getFileSize(filePath: string): Promise<number> {
return new Promise((resolve) => {
wx.getFileSystemManager().getFileInfo({
filePath,
success: (res) => resolve(res.size),
fail: () => resolve(-1),
});
});
},
/**
* 裁剪并压缩入口图。
*
* 流程:
* 1. 加载 preview-card 导出的完整 A4 图片
* 2. 按裁剪模式裁掉页眉 / 页脚
* 3. 按目标宽度重绘到隐藏 canvas
* 4. 若体积超出阈值,则逐步降低 jpg quality
*/
async processImage(
params: DebugProcessImageParams,
): Promise<DebugProcessImageResult> {
await this.ensureProcessorCanvasReady();
const canvas = (this as any)
._processorCanvas as WechatMiniprogram.Canvas;
const ctx = (this as any)._processorCtx as RenderingContext;
if (!canvas || !ctx) {
throw new Error('处理画布不可用');
}
const image = await this.loadImage(canvas, params.sourcePath);
const { topRatio, bottomRatio } = getCropRatios(
params.settings.cropMode,
);
const sourceWidth = image.width;
const sourceHeight = image.height;
const cropTop = Math.round(sourceHeight * topRatio);
const cropBottom = Math.round(sourceHeight * bottomRatio);
const extractHeight = sourceHeight - cropTop - cropBottom;
if (extractHeight <= 0) {
throw new Error('裁剪区域无效');
}
const targetWidth = clampPublishWidth(params.settings.width);
const targetHeight = Math.round(
(extractHeight * targetWidth) / sourceWidth,
);
canvas.width = targetWidth;
canvas.height = targetHeight;
ctx.clearRect(0, 0, targetWidth, targetHeight);
ctx.drawImage(
image,
0,
cropTop,
sourceWidth,
extractHeight,
0,
0,
targetWidth,
targetHeight,
);
let currentQuality =
clampPublishQuality(params.settings.quality) / 100;
let outputPath = '';
let outputSize = 0;
while (currentQuality >= 0.35) {
outputPath = await this.exportCanvasToTempFile(
canvas,
targetWidth,
targetHeight,
currentQuality,
);
outputSize = await this.getFileSize(outputPath);
// -1 表示无法获取大小(开发者工具 HTTP 路径),直接跳过体积循环
if (
outputSize < 0 ||
outputSize <= params.settings.maxSizeKB * 1024
) {
break;
}
currentQuality =
Math.round((currentQuality - 0.05) * 100) / 100;
}
return {
tempFilePath: outputPath,
size: outputSize,
width: targetWidth,
height: targetHeight,
quality: Math.round(currentQuality * 100),
};
},
},
});
@@ -0,0 +1,183 @@
<view class="debug-publish-tools">
<view
class="debug-publish-tools__fab"
hover-class="debug-publish-tools__fab--hover"
hover-start-time="0"
hover-stay-time="70"
catchtap="onOpen">
<text class="debug-publish-tools__fab-icon">📤</text>
<text class="debug-publish-tools__fab-text">发布到云端</text>
</view>
<van-popup
show="{{visible}}"
position="center"
round
closeable="{{!loading}}"
close-on-click-overlay="{{!loading}}"
bind:close="onClose"
custom-class="debug-publish-tools__popup">
<view class="debug-publish-tools__content">
<text class="debug-publish-tools__title">Debug 发布</text>
<view wx:if="{{meta}}" class="debug-publish-tools__section">
<text class="debug-publish-tools__section-title">基础信息</text>
<view class="debug-publish-tools__meta-grid">
<view class="debug-publish-tools__meta-item">
<text class="debug-publish-tools__meta-label">ID</text>
<text class="debug-publish-tools__meta-value">{{meta.id}}</text>
</view>
<view class="debug-publish-tools__meta-item">
<text class="debug-publish-tools__meta-label">分类</text>
<text class="debug-publish-tools__meta-value">{{meta.category}}</text>
</view>
<view class="debug-publish-tools__meta-item">
<text class="debug-publish-tools__meta-label">年龄</text>
<text class="debug-publish-tools__meta-value">
{{meta.ageMin}}-{{meta.ageMax}} 岁
</text>
</view>
<view class="debug-publish-tools__meta-item">
<text class="debug-publish-tools__meta-label">难度</text>
<text class="debug-publish-tools__meta-value">{{meta.difficulty}}</text>
</view>
</view>
<view class="debug-publish-tools__path-box">
<text class="debug-publish-tools__meta-label">路径</text>
<text class="debug-publish-tools__path">{{meta.path}}</text>
</view>
</view>
<view class="debug-publish-tools__section">
<text class="debug-publish-tools__section-title">资料信息</text>
<view class="debug-publish-tools__field">
<text class="debug-publish-tools__field-label">标题</text>
<input
class="debug-publish-tools__input"
value="{{formTitle}}"
maxlength="20"
bindinput="onTitleInput" />
</view>
<view class="debug-publish-tools__field">
<text class="debug-publish-tools__field-label">副标题</text>
<input
class="debug-publish-tools__input"
value="{{formSubtitle}}"
maxlength="40"
bindinput="onSubtitleInput" />
</view>
<view class="debug-publish-tools__field">
<text class="debug-publish-tools__field-label">标签</text>
<input
class="debug-publish-tools__input"
value="{{formTags}}"
placeholder="多个标签用逗号分隔"
maxlength="80"
bindinput="onTagsInput" />
</view>
<view class="debug-publish-tools__field">
<text class="debug-publish-tools__field-label">状态</text>
<view class="debug-publish-tools__chip-row">
<view
class="debug-publish-tools__chip {{formStatus === 'draft' ? 'debug-publish-tools__chip--active' : ''}}"
data-value="draft"
bindtap="onSelectStatus">
draft
</view>
<view
class="debug-publish-tools__chip {{formStatus === 'active' ? 'debug-publish-tools__chip--active' : ''}}"
data-value="active"
bindtap="onSelectStatus">
active
</view>
<view
class="debug-publish-tools__chip {{formStatus === 'hidden' ? 'debug-publish-tools__chip--active' : ''}}"
data-value="hidden"
bindtap="onSelectStatus">
hidden
</view>
</view>
</view>
</view>
<view class="debug-publish-tools__section">
<text class="debug-publish-tools__section-title">图片处理</text>
<view class="debug-publish-tools__field">
<text class="debug-publish-tools__field-label">裁剪模式</text>
<view class="debug-publish-tools__chip-row debug-publish-tools__chip-row--wrap">
<view
class="debug-publish-tools__chip {{formCropMode === 'header-footer' ? 'debug-publish-tools__chip--active' : ''}}"
data-value="header-footer"
bindtap="onSelectCropMode">
裁剪头尾
</view>
<view
class="debug-publish-tools__chip {{formCropMode === 'header-only' ? 'debug-publish-tools__chip--active' : ''}}"
data-value="header-only"
bindtap="onSelectCropMode">
仅裁头部
</view>
<view
class="debug-publish-tools__chip {{formCropMode === 'none' ? 'debug-publish-tools__chip--active' : ''}}"
data-value="none"
bindtap="onSelectCropMode">
不裁剪
</view>
</view>
</view>
<view class="debug-publish-tools__field">
<text class="debug-publish-tools__field-label">
压缩质量 {{formQuality}}
</text>
<slider
min="40"
max="95"
step="5"
value="{{formQuality}}"
activeColor="#f7ce00"
backgroundColor="#ece4d2"
bindchange="onQualityChange" />
</view>
<view class="debug-publish-tools__field">
<text class="debug-publish-tools__field-label">
输出宽度 {{formWidth}}px
</text>
<slider
min="400"
max="1000"
step="20"
value="{{formWidth}}"
activeColor="#f7ce00"
backgroundColor="#ece4d2"
bindchange="onWidthChange" />
</view>
<text class="debug-publish-tools__hint">
默认按文档规则裁掉页眉和页脚,并尽量压缩到 {{maxSizeKB}}KB 内。
</text>
</view>
<view class="debug-publish-tools__actions">
<button
class="debug-publish-tools__btn debug-publish-tools__btn--ghost"
disabled="{{loading}}"
bindtap="onClose">
取消
</button>
<button
class="debug-publish-tools__btn debug-publish-tools__btn--primary"
loading="{{loading}}"
disabled="{{loading}}"
bindtap="onConfirm">
确认发布
</button>
</view>
</view>
</van-popup>
<view class="debug-publish-tools__processor">
<canvas
id="processorCanvas"
type="2d"
class="debug-publish-tools__processor-canvas" />
</view>
</view>
+119 -8
View File
@@ -1,8 +1,119 @@
/**
* 桥接文件 - 保持向后兼容
* 实际数据已迁移到 core/data/worksheets.ts
*/
export {
FOCUS_FUNCTION_TYPES,
type FocusFunctionType,
} from '../core/data/worksheets';
export interface FocusFunctionType {
id: string;
page?: string;
title: string;
desc: string;
icon: string;
mode?: string;
img?: string;
}
export const FOCUS_FUNCTION_TYPES: FocusFunctionType[] = [
{
id: 'color-shape-match',
page: 'focusDraw',
title: '根据颜色画图形',
desc: '根据颜色画出对应图形',
icon: '🎯',
img: '/assets/focusEntrance/color-shape-match.png',
},
{
id: 'shape-symbol',
page: 'focusDraw',
title: '图形符号配对',
desc: '根据图形画对应符号',
icon: '🔗',
img: '/assets/focusEntrance/shape-symbol.png',
},
{
id: 'shape-recognition',
page: 'focusDraw',
title: '识别形状',
desc: '识别形状,涂一涂',
icon: '🔍',
img: '/assets/focusEntrance/shape-recognition.png',
},
{
id: 'position-coloring',
page: 'focusDraw',
title: '方位涂涂乐',
desc: '观察位置,在方格中涂色',
icon: '📍',
img: '/assets/focusEntrance/position-coloring.png',
},
{
id: 'color-pattern',
page: 'focusDraw',
title: '颜色找规律',
desc: '观察颜色规律,在空白图形中涂色',
icon: '🎨',
img: '/assets/focusEntrance/color-pattern.png',
},
{
id: 'match-connect',
page: 'focusDraw',
title: '连连看',
desc: '根据物品连一连',
icon: '🔗',
img: '/assets/focusEntrance/match-connect.png',
},
{
id: 'line-recognition',
page: 'focusDraw',
title: '线条识别',
desc: '认识不同线条,画出颜色对应的线条',
icon: '📏',
img: '/assets/focusEntrance/line-recognition.png',
},
{
id: 'grid-reasoning',
page: 'focusDraw',
title: '方格推理',
desc: '推理出合并方格并连线',
icon: '🧩',
img: '/assets/focusEntrance/grid-reasoning.png',
},
{
id: 'code-connect',
page: 'focusDraw',
title: '译码连线',
desc: '按数字顺序将数字对应颜色连线',
icon: '🔢',
img: '/assets/focusEntrance/code-connect.png',
},
{
id: 'dot-connect',
page: 'focusDraw',
title: '数字点连线',
desc: '按数字顺序连点成图',
icon: '🔗',
img: '/assets/focusEntrance/dot-connect.png',
},
{
id: 'grid-drawing-3x3',
page: 'focusDraw',
title: '格子仿画 3×3',
desc: '简单有趣,培养专注力',
icon: '🎨',
mode: '3x3',
img: '/assets/focusEntrance/grid-drawing-3x3.png',
},
{
id: 'grid-drawing-5x5',
page: 'focusDraw',
title: '格子仿画 5×5',
desc: '创意挑战,提升观察力',
icon: '🎨',
mode: '5x5',
img: '/assets/focusEntrance/grid-drawing-5x5.png',
},
{
id: 'grid-drawing-7x7',
page: 'focusDraw',
title: '格子仿画 7×7',
desc: '大师挑战,锻炼耐心',
icon: '🎨',
mode: '7x7',
img: '/assets/focusEntrance/grid-drawing-7x7.png',
},
];
+217 -5
View File
@@ -1,5 +1,217 @@
/**
* 桥接文件 - 保持向后兼容
* 实际数据已迁移到 core/data/worksheets.ts
*/
export { MATH_FUNCTION_TYPES, type MathFunctionType } from '../core/data/worksheets';
export interface MathFunctionType {
id: string;
page?: string;
title: string;
desc: string;
icon?: string;
img?: string;
}
export const MATH_FUNCTION_TYPES: MathFunctionType[] = [
{
id: 'number-find',
page: 'mathDraw',
title: '找数字,涂一涂',
desc: '在数字方阵中找出目标数字并涂色',
img: '/assets/mathEntrance/number-find.png',
},
{
id: 'number-write',
page: 'mathDraw',
title: '看数字,写一写',
desc: '按笔画顺序练习书写数字',
img: '/assets/mathEntrance/number-write.png',
},
{
id: 'number-coloring',
page: 'mathDraw',
title: '按数字,涂颜色',
desc: '按指定数字给对应圆圈涂色',
icon: '🎨',
img: '/assets/mathEntrance/number-coloring.png',
},
{
id: 'counting-matching',
page: 'mathDraw',
title: '数一数,连一连',
desc: '连线配对数字和对应数量图形',
icon: '🔗',
img: '/assets/mathEntrance/count-match.png',
},
{
id: 'number-object-match',
page: 'mathDraw',
title: '数物连线',
desc: '连线相同数量的物品和数字',
icon: '🔗',
img: '/assets/mathEntrance/number-object-match.png',
},
{
id: 'number-object-fill',
page: 'mathDraw',
title: '数物填写',
desc: '数物品数量,填写对应数字',
icon: '✏️',
img: '/assets/mathEntrance/number-object-fill.png',
},
{
id: 'counting-select',
page: 'mathDraw',
title: '数一数,选一选',
desc: '数出物品数量,圈出正确答案',
icon: '✓',
img: '/assets/mathEntrance/counting-select.png',
},
{
id: 'counting-fill',
page: 'mathDraw',
title: '数一数,填一填',
desc: '数出物品数量,填写数字',
icon: '✏️',
img: '/assets/mathEntrance/counting-fill.png',
},
{
id: 'compare',
page: 'mathDraw',
title: '数一数,比大小',
desc: '比较数量,填入 ><=',
icon: '⚖️',
img: '/assets/mathEntrance/compare.png',
},
{
id: 'number-sort',
page: 'mathDraw',
title: '数字排序',
desc: '写出正确的数字顺序',
icon: '🔢',
img: '/assets/mathEntrance/number-sort.png',
},
{
id: 'missing-number',
page: 'mathDraw',
title: '填上缺少的数字',
desc: '在数列中找出并填写缺失数字',
icon: '❓',
img: '/assets/mathEntrance/missing-number.png',
},
{
id: 'number-decompose',
page: 'mathDraw',
title: '10以内数的分与合',
desc: '把数字分一分,合一合',
icon: '🔢',
img: '/assets/mathEntrance/number-decompose.png',
},
{
id: 'number-decompose-20',
page: 'mathDraw',
title: '20以内数的分与合',
desc: '把数字分一分,合一合',
icon: '🔢',
img: '/assets/mathEntrance/number-decompose-20.png',
},
{
id: 'one-digit-addition',
page: 'mathDraw',
title: '一位数加法',
desc: '通过圆点学习一位数加法运算',
icon: '',
img: '/assets/mathEntrance/one-digit-addition.png',
},
{
id: 'addition-5',
page: 'mathDraw',
title: '5以内加法',
desc: '图形化展示 5 以内加法',
icon: '',
img: '/assets/mathEntrance/addition-5.png',
},
{
id: 'addition-10',
page: 'mathDraw',
title: '10以内加法',
desc: '图形化展示 10 以内加法',
icon: '',
img: '/assets/mathEntrance/addition-10.png',
},
{
id: 'subtraction-10',
page: 'mathDraw',
title: '10以内减法',
desc: '图形化展示 10 以内减法',
icon: '',
img: '/assets/mathEntrance/subtraction-10.png',
},
{
id: 'addition-subtraction-10',
page: 'mathDraw',
title: '10以内加减法',
desc: '加减法混合运算',
icon: '±',
img: '/assets/mathEntrance/addition-subtraction-10.png',
},
{
id: 'make-ten',
page: 'mathDraw',
title: '凑十法练习',
desc: '20 以内进位加法',
icon: '',
img: '/assets/mathEntrance/make-ten.png',
},
{
id: 'break-ten',
page: 'mathDraw',
title: '破十法练习',
desc: '20 以内退位减法',
icon: '',
img: '/assets/mathEntrance/break-ten.png',
},
{
id: 'flat-ten',
page: 'mathDraw',
title: '平十法练习',
desc: '20 以内退位减法',
icon: '',
img: '/assets/mathEntrance/flat-ten.png',
},
{
id: 'borrow-ten',
page: 'mathDraw',
title: '借十法练习',
desc: '20 以上退位减法',
icon: '',
img: '/assets/mathEntrance/borrow-ten.png',
},
{
id: 'practice-addition',
page: 'mathDraw',
title: '加法运算',
desc: '10/20/50/100 以内加法',
icon: '',
img: '/assets/mathEntrance/practice-addition.png',
},
{
id: 'practice-subtraction',
page: 'mathDraw',
title: '减法运算',
desc: '10/20/50/100 以内减法',
icon: '',
img: '/assets/mathEntrance/practice-subtraction.png',
},
{
id: 'practice-mixed',
page: 'mathDraw',
title: '混合运算',
desc: '10/20/50/100 以内加减法混合',
icon: '±',
img: '/assets/mathEntrance/practice-mixed.png',
},
{
id: 'multiplication-table',
page: 'mathDraw',
title: '九九乘法表',
desc: '学习九九乘法口诀',
icon: '✖️',
img: '/assets/mathEntrance/multiplication-table.png',
},
];
-545
View File
@@ -1,545 +0,0 @@
import type {
GeneratorType,
LayoutConfig,
TemplateType,
WorksheetDefinition,
WorksheetType,
} from '../models/worksheet';
import { difficultyToStars } from './difficulty';
/**
* MathFunctionType - 保持向后兼容(mathPageMixin / mathIndex
*/
export interface MathFunctionType {
id: string;
page?: string;
title: string;
desc: string;
icon?: string;
img?: string;
}
/**
* FocusFunctionType - 保持向后兼容(focusPageMixin / focusIndex
*/
export interface FocusFunctionType {
id: string;
page?: string;
title: string;
desc: string;
icon: string;
mode?: string;
img?: string;
}
interface SheetEngine {
subcategory: string;
template: TemplateType;
generator: GeneratorType;
generatorConfig?: Record<string, unknown>;
layoutConfig?: LayoutConfig;
tags?: string[];
sortOrder: number;
}
function legacyPath(ui: WorksheetType): string | null {
if (!ui.page) return null;
if (ui.subpackage) {
return `/${ui.subpackage}/${ui.page}/${ui.page}`;
}
return `/pages/${ui.page}/${ui.page}`;
}
function sheet(ui: WorksheetType, engine: SheetEngine): WorksheetDefinition {
const ageMin = ui.ageRange?.[0] ?? 3;
const ageMax = ui.ageRange?.[1] ?? 8;
return {
...ui,
subcategory: engine.subcategory,
ageMin,
ageMax,
difficultyLevel: difficultyToStars(ui.difficulty),
previewImage: ui.img ?? '',
tags: engine.tags?.length ? engine.tags : [engine.subcategory],
isNew: false,
isHot: false,
sortOrder: engine.sortOrder,
downloadCount: 0,
status: 'active',
template: engine.template,
generator: engine.generator,
generatorConfig: engine.generatorConfig ?? {},
layoutConfig: engine.layoutConfig ?? { showBorder: true },
userConfigurable: null,
legacyPage: legacyPath(ui),
};
}
const L = {
gridEx: { showBorder: true, fontSize: 18 } satisfies LayoutConfig,
card: { showBorder: true, columns: 2, fontSize: 16 } satisfies LayoutConfig,
trace: {
showBorder: true,
showInstruction: true,
fontSize: 20,
} satisfies LayoutConfig,
};
// 现有功能清单 §二:数感启蒙 26 种 + §三 13 种 + §四 §五 语文 2
// prettier-ignore
export const ALL_WORKSHEETS: WorksheetDefinition[] = [
sheet(
{ id: 'number-find', title: '找数字,涂一涂', desc: '在数字方阵中找出目标数字并涂色', category: 'math', page: 'mathDraw', subpackage: 'mathPages', img: '/assets/mathEntrance/number-find.png', ageRange: [3, 5], difficulty: 'beginner', previewBg: '#fff3e0', previewText: '🔍 1 2 3' },
{ subcategory: 'number-sense', template: 'grid-coloring', generator: 'counting', sortOrder: 1, layoutConfig: { ...L.gridEx }, generatorConfig: { functionId: 'number-find' } },
),
sheet(
{ id: 'number-write', title: '看数字,写一写', desc: '按笔画顺序练习书写数字', category: 'math', page: 'mathDraw', subpackage: 'mathPages', img: '/assets/mathEntrance/number-write.png', ageRange: [3, 5], difficulty: 'beginner', previewBg: '#fff3e0', previewText: '1 2 3' },
{ subcategory: 'number-sense', template: 'tracing-writing', generator: 'counting', sortOrder: 2, layoutConfig: { ...L.trace }, generatorConfig: { functionId: 'number-write' } },
),
sheet(
{ id: 'number-coloring', title: '按数字,涂颜色', desc: '按指定数字给对应圆圈涂色', category: 'math', page: 'mathDraw', subpackage: 'mathPages', icon: '🎨', img: '/assets/mathEntrance/number-coloring.png', ageRange: [3, 5], difficulty: 'beginner', previewBg: '#fce4ec', previewText: '🎨' },
{ subcategory: 'number-sense', template: 'grid-coloring', generator: 'counting', sortOrder: 3, layoutConfig: { ...L.gridEx }, generatorConfig: { functionId: 'number-coloring' } },
),
sheet(
{ id: 'counting-matching', title: '数一数,连一连', desc: '连线配对数字和对应数量图形', category: 'math', page: 'mathDraw', subpackage: 'mathPages', icon: '🔗', img: '/assets/mathEntrance/count-match.png', ageRange: [3, 5], difficulty: 'beginner', previewBg: '#e8f5e9', previewText: '3↔🍎🍎🍎' },
{ subcategory: 'counting', template: 'match-connect', generator: 'counting', sortOrder: 4, layoutConfig: { ...L.gridEx }, generatorConfig: { functionId: 'counting-matching' } },
),
sheet(
{ id: 'number-object-match', title: '数物连线', desc: '连线相同数量的物品和数字', category: 'math', page: 'mathDraw', subpackage: 'mathPages', icon: '🔗', img: '/assets/mathEntrance/number-object-match.png', ageRange: [3, 5], difficulty: 'beginner', previewBg: '#e8f5e9', previewText: '🔗' },
{ subcategory: 'counting', template: 'match-connect', generator: 'counting', sortOrder: 5, generatorConfig: { functionId: 'number-object-match' } },
),
sheet(
{ id: 'number-object-fill', title: '数物填写', desc: '数物品数量,填写对应数字', category: 'math', page: 'mathDraw', subpackage: 'mathPages', icon: '✏️', img: '/assets/mathEntrance/number-object-fill.png', ageRange: [3, 5], difficulty: 'beginner', previewBg: '#e8f5e9', previewText: '✏️' },
{ subcategory: 'counting', template: 'grid-exercise', generator: 'counting', sortOrder: 6, generatorConfig: { functionId: 'number-object-fill' } },
),
sheet(
{ id: 'counting-select', title: '数一数,选一选', desc: '数出物品数量,圈出正确答案', category: 'math', page: 'mathDraw', subpackage: 'mathPages', icon: '✓', img: '/assets/mathEntrance/counting-select.png', ageRange: [3, 5], difficulty: 'beginner', previewBg: '#e8f5e9', previewText: '✓' },
{ subcategory: 'counting', template: 'grid-exercise', generator: 'counting', sortOrder: 7, generatorConfig: { functionId: 'counting-select' } },
),
sheet(
{ id: 'counting-fill', title: '数一数,填一填', desc: '数出物品数量,填写数字', category: 'math', page: 'mathDraw', subpackage: 'mathPages', icon: '✏️', img: '/assets/mathEntrance/counting-fill.png', ageRange: [3, 5], difficulty: 'beginner', previewBg: '#e8f5e9', previewText: '✏️' },
{ subcategory: 'counting', template: 'grid-exercise', generator: 'counting', sortOrder: 8, generatorConfig: { functionId: 'counting-fill' } },
),
sheet(
{ id: 'compare', title: '数一数,比大小', desc: '比较数量,填入 ><=', category: 'math', page: 'mathDraw', subpackage: 'mathPages', icon: '⚖️', img: '/assets/mathEntrance/compare.png', ageRange: [4, 6], difficulty: 'basic', previewBg: '#e3f2fd', previewText: '3 ○ 5' },
{ subcategory: 'counting', template: 'grid-exercise', generator: 'comparison', sortOrder: 9, generatorConfig: { functionId: 'compare' } },
),
sheet(
{ id: 'number-sort', title: '数字排序', desc: '写出正确的数字顺序', category: 'math', page: 'mathDraw', subpackage: 'mathPages', icon: '🔢', img: '/assets/mathEntrance/number-sort.png', ageRange: [4, 6], difficulty: 'basic', previewBg: '#fff3e0', previewText: '1→2→3' },
{ subcategory: 'sequence', template: 'sequence-pattern', generator: 'number-sequence', sortOrder: 10, generatorConfig: { functionId: 'number-sort' } },
),
sheet(
{ id: 'missing-number', title: '填上缺少的数字', desc: '在数列中找出并填写缺失数字', category: 'math', page: 'mathDraw', subpackage: 'mathPages', icon: '❓', img: '/assets/mathEntrance/missing-number.png', ageRange: [4, 6], difficulty: 'basic', previewBg: '#fff3e0', previewText: '1_3_5' },
{ subcategory: 'sequence', template: 'sequence-pattern', generator: 'number-sequence', sortOrder: 11, generatorConfig: { functionId: 'missing-number' } },
),
sheet(
{ id: 'number-decompose', title: '10以内数的分与合', desc: '把数字分一分,合一合', category: 'math', page: 'mathDraw', subpackage: 'mathPages', icon: '🔢', img: '/assets/mathEntrance/number-decompose.png', ageRange: [4, 6], difficulty: 'beginner', previewBg: '#fff3e0', previewText: '8=?+?' },
{ subcategory: 'decompose', template: 'grid-exercise', generator: 'number-decompose', sortOrder: 12, generatorConfig: { max: 10 } },
),
sheet(
{ id: 'number-decompose-20', title: '20以内数的分与合', desc: '把数字分一分,合一合', category: 'math', page: 'mathDraw', subpackage: 'mathPages', icon: '🔢', img: '/assets/mathEntrance/number-decompose-20.png', ageRange: [5, 7], difficulty: 'basic', previewBg: '#fff3e0', previewText: '15=?+?' },
{ subcategory: 'decompose', template: 'grid-exercise', generator: 'number-decompose', sortOrder: 13, generatorConfig: { max: 20 } },
),
sheet(
{ id: 'one-digit-addition', title: '一位数加法', desc: '通过圆点学习一位数加法运算', category: 'math', page: 'mathDraw', subpackage: 'mathPages', icon: '', img: '/assets/mathEntrance/one-digit-addition.png', ageRange: [4, 6], difficulty: 'basic', previewBg: '#e3f2fd', previewText: '2+3=?' },
{ subcategory: 'arithmetic-visual', template: 'grid-exercise', generator: 'arithmetic', sortOrder: 14, generatorConfig: { operators: ['+'], maxNumber: 9, showDots: true } },
),
sheet(
{ id: 'addition-5', title: '5以内加法', desc: '图形化展示 5 以内加法', category: 'math', page: 'mathDraw', subpackage: 'mathPages', icon: '', img: '/assets/mathEntrance/addition-5.png', ageRange: [3, 5], difficulty: 'beginner', previewBg: '#e3f2fd', previewText: '2+1=?' },
{ subcategory: 'arithmetic-visual', template: 'grid-exercise', generator: 'arithmetic', sortOrder: 15, generatorConfig: { operators: ['+'], maxNumber: 5, showDots: true } },
),
sheet(
{ id: 'addition-10', title: '10以内加法', desc: '图形化展示 10 以内加法', category: 'math', page: 'mathDraw', subpackage: 'mathPages', icon: '', img: '/assets/mathEntrance/addition-10.png', ageRange: [4, 6], difficulty: 'basic', previewBg: '#e3f2fd', previewText: '3+5=?' },
{ subcategory: 'arithmetic-visual', template: 'grid-exercise', generator: 'arithmetic', sortOrder: 16, generatorConfig: { operators: ['+'], maxNumber: 10, showDots: true } },
),
sheet(
{ id: 'subtraction-10', title: '10以内减法', desc: '图形化展示 10 以内减法', category: 'math', page: 'mathDraw', subpackage: 'mathPages', icon: '', img: '/assets/mathEntrance/subtraction-10.png', ageRange: [4, 6], difficulty: 'basic', previewBg: '#e3f2fd', previewText: '8-3=?' },
{ subcategory: 'arithmetic-visual', template: 'grid-exercise', generator: 'arithmetic', sortOrder: 17, generatorConfig: { operators: ['-'], maxNumber: 10, showDots: true } },
),
sheet(
{ id: 'addition-subtraction-10', title: '10以内加减法', desc: '加减法混合运算', category: 'math', page: 'mathDraw', subpackage: 'mathPages', icon: '±', img: '/assets/mathEntrance/addition-subtraction-10.png', ageRange: [5, 7], difficulty: 'basic', previewBg: '#e3f2fd', previewText: '±' },
{ subcategory: 'arithmetic-visual', template: 'grid-exercise', generator: 'arithmetic', sortOrder: 18, generatorConfig: { operators: ['+', '-'], maxNumber: 10, showDots: true } },
),
sheet(
{ id: 'make-ten', title: '凑十法练习', desc: '20 以内进位加法', category: 'math', page: 'mathDraw', subpackage: 'mathPages', icon: '', img: '/assets/mathEntrance/make-ten.png', ageRange: [5, 7], difficulty: 'intermediate', previewBg: '#e3f2fd', previewText: '7+?=10' },
{ subcategory: 'methods-ten', template: 'grid-exercise', generator: 'arithmetic', sortOrder: 19, generatorConfig: { method: 'make-ten' } },
),
sheet(
{ id: 'break-ten', title: '破十法练习', desc: '20 以内退位减法', category: 'math', page: 'mathDraw', subpackage: 'mathPages', icon: '', img: '/assets/mathEntrance/break-ten.png', ageRange: [5, 7], difficulty: 'intermediate', previewBg: '#e3f2fd', previewText: '15-8=?' },
{ subcategory: 'methods-ten', template: 'grid-exercise', generator: 'arithmetic', sortOrder: 20, generatorConfig: { method: 'break-ten' } },
),
sheet(
{ id: 'flat-ten', title: '平十法练习', desc: '20 以内退位减法', category: 'math', page: 'mathDraw', subpackage: 'mathPages', icon: '', img: '/assets/mathEntrance/flat-ten.png', ageRange: [5, 7], difficulty: 'intermediate', previewBg: '#e3f2fd', previewText: '14-6=?' },
{ subcategory: 'methods-ten', template: 'grid-exercise', generator: 'arithmetic', sortOrder: 21, generatorConfig: { method: 'flat-ten' } },
),
sheet(
{ id: 'borrow-ten', title: '借十法练习', desc: '20 以上退位减法', category: 'math', page: 'mathDraw', subpackage: 'mathPages', icon: '', img: '/assets/mathEntrance/borrow-ten.png', ageRange: [6, 8], difficulty: 'advanced', previewBg: '#e3f2fd', previewText: '32-8=?' },
{ subcategory: 'methods-ten', template: 'grid-exercise', generator: 'arithmetic', sortOrder: 22, generatorConfig: { method: 'borrow-ten' } },
),
sheet(
{ id: 'practice-addition', title: '加法运算', desc: '10/20/50/100 以内加法', category: 'math', page: 'mathDraw', subpackage: 'mathPages', icon: '', img: '/assets/mathEntrance/practice-addition.png', ageRange: [5, 8], difficulty: 'intermediate', previewBg: '#e3f2fd', previewText: '25+18=?' },
{ subcategory: 'practice', template: 'grid-exercise', generator: 'arithmetic', sortOrder: 23, generatorConfig: { operators: ['+'], preset: 'vertical' } },
),
sheet(
{ id: 'practice-subtraction', title: '减法运算', desc: '10/20/50/100 以内减法', category: 'math', page: 'mathDraw', subpackage: 'mathPages', icon: '', img: '/assets/mathEntrance/practice-subtraction.png', ageRange: [5, 8], difficulty: 'intermediate', previewBg: '#e3f2fd', previewText: '43-17=?' },
{ subcategory: 'practice', template: 'grid-exercise', generator: 'arithmetic', sortOrder: 24, generatorConfig: { operators: ['-'], preset: 'vertical' } },
),
sheet(
{ id: 'practice-mixed', title: '混合运算', desc: '10/20/50/100 以内加减法混合', category: 'math', page: 'mathDraw', subpackage: 'mathPages', icon: '±', img: '/assets/mathEntrance/practice-mixed.png', ageRange: [5, 8], difficulty: 'intermediate', previewBg: '#e3f2fd', previewText: '±' },
{ subcategory: 'practice', template: 'grid-exercise', generator: 'arithmetic', sortOrder: 25, generatorConfig: { operators: ['+', '-'], preset: 'vertical' } },
),
sheet(
{ id: 'multiplication-table', title: '九九乘法表', desc: '学习九九乘法口诀', category: 'math', page: 'mathDraw', subpackage: 'mathPages', icon: '✖️', img: '/assets/mathEntrance/multiplication-table.png', ageRange: [6, 8], difficulty: 'intermediate', previewBg: '#f3e5f5', previewText: '3×4=12' },
{ subcategory: 'practice', template: 'grid-exercise', generator: 'arithmetic', sortOrder: 26, generatorConfig: { operators: ['×'], mode: 'multiplication-table' } },
),
sheet(
{ id: 'color-shape-match', title: '根据颜色画图形', desc: '根据颜色画出对应图形', category: 'focus', page: 'focusDraw', subpackage: 'focusPages', icon: '🎯', img: '/assets/focusEntrance/color-shape-match.png', ageRange: [4, 6], difficulty: 'basic', previewBg: '#f3e5f5', previewText: '🎯' },
{ subcategory: 'focus-visual', template: 'grid-exercise', generator: 'shape-grid', sortOrder: 27, generatorConfig: { functionId: 'color-shape-match' } },
),
sheet(
{ id: 'shape-symbol', title: '图形符号配对', desc: '根据图形画对应符号', category: 'focus', page: 'focusDraw', subpackage: 'focusPages', icon: '🔗', img: '/assets/focusEntrance/shape-symbol.png', ageRange: [4, 6], difficulty: 'basic', previewBg: '#e8eaf6', previewText: '△→♠' },
{ subcategory: 'focus-visual', template: 'match-connect', generator: 'shape-grid', sortOrder: 28, generatorConfig: { functionId: 'shape-symbol' } },
),
sheet(
{ id: 'shape-recognition', title: '识别形状', desc: '识别形状,涂一涂', category: 'focus', page: 'focusDraw', subpackage: 'focusPages', icon: '🔍', img: '/assets/focusEntrance/shape-recognition.png', ageRange: [3, 5], difficulty: 'beginner', previewBg: '#fce4ec', previewText: '△ ○ □' },
{ subcategory: 'focus-visual', template: 'grid-coloring', generator: 'shape-grid', sortOrder: 29, generatorConfig: { functionId: 'shape-recognition' } },
),
sheet(
{ id: 'position-coloring', title: '方位涂涂乐', desc: '观察位置,在方格中涂色', category: 'focus', page: 'focusDraw', subpackage: 'focusPages', icon: '📍', img: '/assets/focusEntrance/position-coloring.png', ageRange: [4, 6], difficulty: 'basic', previewBg: '#e8f5e9', previewText: '📍' },
{ subcategory: 'focus-grid', template: 'grid-coloring', generator: 'shape-grid', sortOrder: 30, generatorConfig: { functionId: 'position-coloring' } },
),
sheet(
{ id: 'color-pattern', title: '颜色找规律', desc: '观察颜色规律,在空白图形中涂色', category: 'focus', page: 'focusDraw', subpackage: 'focusPages', icon: '🎨', img: '/assets/focusEntrance/color-pattern.png', ageRange: [4, 6], difficulty: 'basic', previewBg: '#fff8e1', previewText: '🎨' },
{ subcategory: 'focus-grid', template: 'grid-coloring', generator: 'color-pattern', sortOrder: 31, generatorConfig: { functionId: 'color-pattern' } },
),
sheet(
{ id: 'match-connect', title: '连连看', desc: '根据物品连一连', category: 'focus', page: 'focusDraw', subpackage: 'focusPages', icon: '🔗', img: '/assets/focusEntrance/match-connect.png', ageRange: [3, 6], difficulty: 'beginner', previewBg: '#e0f7fa', previewText: '🔗' },
{ subcategory: 'focus-connect', template: 'match-connect', generator: 'shape-grid', sortOrder: 32, generatorConfig: { functionId: 'match-connect' } },
),
sheet(
{ id: 'line-recognition', title: '线条识别', desc: '认识不同线条,画出颜色对应的线条', category: 'focus', page: 'focusDraw', subpackage: 'focusPages', icon: '📏', img: '/assets/focusEntrance/line-recognition.png', ageRange: [3, 5], difficulty: 'beginner', previewBg: '#f1f8e9', previewText: '〰️' },
{ subcategory: 'focus-visual', template: 'tracing-writing', generator: 'shape-grid', sortOrder: 33, generatorConfig: { functionId: 'line-recognition' } },
),
sheet(
{ id: 'grid-reasoning', title: '方格推理', desc: '推理出合并方格并连线', category: 'focus', page: 'focusDraw', subpackage: 'focusPages', icon: '🧩', img: '/assets/focusEntrance/grid-reasoning.png', ageRange: [5, 7], difficulty: 'intermediate', previewBg: '#e8eaf6', previewText: '🧩' },
{ subcategory: 'focus-logic', template: 'special-graphic', generator: 'shape-grid', sortOrder: 34, generatorConfig: { functionId: 'grid-reasoning' } },
),
sheet(
{ id: 'code-connect', title: '译码连线', desc: '按数字顺序将数字对应颜色连线', category: 'focus', page: 'focusDraw', subpackage: 'focusPages', icon: '🔢', img: '/assets/focusEntrance/code-connect.png', ageRange: [5, 7], difficulty: 'intermediate', previewBg: '#fff3e0', previewText: '🔢' },
{ subcategory: 'focus-connect', template: 'match-connect', generator: 'dot-connect', sortOrder: 35, generatorConfig: { functionId: 'code-connect' } },
),
sheet(
{ id: 'dot-connect', title: '数字点连线', desc: '按数字顺序连点成图', category: 'focus', page: 'focusDraw', subpackage: 'focusPages', icon: '🔗', img: '/assets/focusEntrance/dot-connect.png', ageRange: [3, 6], difficulty: 'beginner', previewBg: '#e3f2fd', previewText: '1→2→3' },
{ subcategory: 'focus-connect', template: 'match-connect', generator: 'dot-connect', sortOrder: 36, generatorConfig: { functionId: 'dot-connect' } },
),
sheet(
{ id: 'grid-drawing-3x3', title: '格子仿画 3×3', desc: '简单有趣,培养专注力', category: 'focus', page: 'focusDraw', subpackage: 'focusPages', icon: '🎨', mode: '3x3', img: '/assets/focusEntrance/grid-drawing-3x3.png', ageRange: [3, 5], difficulty: 'beginner', previewBg: '#fce4ec', previewText: '▦' },
{ subcategory: 'grid-copy', template: 'grid-exercise', generator: 'shape-grid', sortOrder: 37, generatorConfig: { grid: '3x3' } },
),
sheet(
{ id: 'grid-drawing-5x5', title: '格子仿画 5×5', desc: '创意挑战,提升观察力', category: 'focus', page: 'focusDraw', subpackage: 'focusPages', icon: '🎨', mode: '5x5', img: '/assets/focusEntrance/grid-drawing-5x5.png', ageRange: [4, 6], difficulty: 'basic', previewBg: '#fce4ec', previewText: '▦' },
{ subcategory: 'grid-copy', template: 'grid-exercise', generator: 'shape-grid', sortOrder: 38, generatorConfig: { grid: '5x5' } },
),
sheet(
{ id: 'grid-drawing-7x7', title: '格子仿画 7×7', desc: '大师挑战,锻炼耐心', category: 'focus', page: 'focusDraw', subpackage: 'focusPages', icon: '🎨', mode: '7x7', img: '/assets/focusEntrance/grid-drawing-7x7.png', ageRange: [5, 8], difficulty: 'intermediate', previewBg: '#fce4ec', previewText: '▦' },
{ subcategory: 'grid-copy', template: 'grid-exercise', generator: 'shape-grid', sortOrder: 39, generatorConfig: { grid: '7x7' } },
),
sheet(
{ id: 'word-recognition', title: '识字卡', desc: '输入或选字生成涂色识字卡(grid/find 模板)', category: 'chinese', page: 'index', subpackage: '', icon: '📖', img: '', ageRange: [3, 6], difficulty: 'beginner', previewBg: '#fce4ec', previewText: '大 小' },
{ subcategory: 'literacy', template: 'card-layout', generator: 'custom', sortOrder: 40, generatorConfig: { functionId: 'word-recognition' }, layoutConfig: { ...L.card } },
),
sheet(
{ id: 'copybook', title: '练字帖', desc: '选字生成田字格笔顺练字帖', category: 'chinese', page: 'copyBook', subpackage: '', icon: '✏️', img: '', ageRange: [4, 7], difficulty: 'basic', previewBg: '#fff8e1', previewText: '横竖撇' },
{ subcategory: 'copybook', template: 'tracing-writing', generator: 'character-tracing', sortOrder: 41, generatorConfig: { functionId: 'copybook' }, layoutConfig: { ...L.trace } },
),
sheet(
{
id: 'letter-tracing-single',
title: '看图描红',
desc: '单字母配图、例句与六行四线三格描红',
category: 'english',
page: 'letterTracing',
subpackage: 'englishPages',
icon: '🔠',
img: '',
ageRange: [4, 7],
difficulty: 'beginner',
previewBg: '#e8f4ff',
previewText: 'Aa',
},
{
subcategory: 'letter-tracing',
template: 'tracing-writing',
generator: 'letter-tracing',
sortOrder: 42,
layoutConfig: {
...L.trace,
instructionText: '看一看,读一读,再描一描',
},
generatorConfig: {
mode: 'letter-tracing-single',
letterCase: 'upper',
selectedLetter: 'A',
repetitions: 5,
fadePattern: 'gradient',
},
},
),
sheet(
{
id: 'letter-tracing-two-column',
title: '两列练习',
desc: '左 A–M、右 NZ,每组包含大小写',
category: 'english',
page: 'letterTracing',
subpackage: 'englishPages',
icon: '🔤',
img: '',
ageRange: [4, 7],
difficulty: 'beginner',
previewBg: '#e8f4ff',
previewText: 'A|N',
},
{
subcategory: 'letter-tracing',
template: 'tracing-writing',
generator: 'letter-tracing',
sortOrder: 43,
layoutConfig: {
...L.trace,
columns: 2,
instructionText: '左 A–M、右 NZ,大小写配对描红',
},
generatorConfig: {
mode: 'letter-tracing-two-column',
letterCase: 'both',
repetitions: 4,
fadePattern: 'gradient',
},
},
),
sheet(
{
id: 'letter-tracing-upper-lower',
title: '字母总览',
desc: 'Uppercase / Lowercase 分区,每区四行(7+7+6+6',
category: 'english',
page: 'letterTracing',
subpackage: 'englishPages',
icon: '🔡',
img: '',
ageRange: [4, 7],
difficulty: 'beginner',
previewBg: '#e8f4ff',
previewText: 'Aa Bb',
},
{
subcategory: 'letter-tracing',
template: 'tracing-writing',
generator: 'letter-tracing',
sortOrder: 44,
layoutConfig: {
...L.trace,
columns: 13,
instructionText: '认读全部大写与小写字母',
},
generatorConfig: {
mode: 'letter-tracing-upper-lower',
letterCase: 'both',
repetitions: 1,
fadePattern: 'first-only',
},
},
),
sheet(
{
id: 'letter-tracing-case-pairing',
title: '大小写练习',
desc: '半组字母左大写右小写,每行四个',
category: 'english',
page: 'letterTracing',
subpackage: 'englishPages',
icon: '✏️',
img: '',
ageRange: [4, 7],
difficulty: 'beginner',
previewBg: '#e8f4ff',
previewText: 'A→Z',
},
{
subcategory: 'letter-tracing',
template: 'tracing-writing',
generator: 'letter-tracing',
sortOrder: 45,
layoutConfig: {
...L.trace,
instructionText: '左栏大写、右栏小写,逐行对应描红',
},
generatorConfig: {
mode: 'letter-tracing-case-pairing',
letterCase: 'both',
alphabetHalf: 'A-M',
repetitions: 4,
fadePattern: 'gradient',
},
},
),
sheet(
{
id: 'letter-tracing-three',
title: '三字母精练',
desc: '每页聚焦 3 个字母,大写 + 小写深度书写',
category: 'english',
page: 'letterTracing',
subpackage: 'englishPages',
icon: '🔤',
img: '',
ageRange: [4, 7],
difficulty: 'basic',
previewBg: '#e8f4ff',
previewText: 'ABC',
},
{
subcategory: 'letter-tracing',
template: 'tracing-writing',
generator: 'letter-tracing',
sortOrder: 46,
layoutConfig: {
...L.trace,
instructionText: '每页 3 个字母,逐字精练',
},
generatorConfig: {
mode: 'letter-tracing-three',
letterCase: 'both',
repetitions: 5,
fadePattern: 'gradient',
tripleLetters: ['A', 'B', 'C'],
},
},
),
sheet(
{
id: 'letter-tracing-half',
title: '单字母逐行',
desc: '每行一个字母,13 字母半表逐字练习',
category: 'english',
page: 'letterTracing',
subpackage: 'englishPages',
icon: '📝',
img: '',
ageRange: [4, 7],
difficulty: 'basic',
previewBg: '#e8f4ff',
previewText: 'AM',
},
{
subcategory: 'letter-tracing',
template: 'tracing-writing',
generator: 'letter-tracing',
sortOrder: 47,
layoutConfig: {
...L.trace,
instructionText: '每行一个字母,逐字反复练习',
},
generatorConfig: {
mode: 'letter-tracing-half',
letterCase: 'both',
alphabetHalf: 'A-M',
repetitions: 5,
fadePattern: 'first-only',
},
},
),
sheet(
{
id: 'letter-tracing-daily-checkin',
title: '每日打卡',
desc: '四宫格每日字母打卡,闭合四线三格练习',
category: 'english',
page: 'letterTracing',
subpackage: 'englishPages',
icon: '🗓️',
img: '',
ageRange: [4, 7],
difficulty: 'basic',
previewBg: '#e8f4ff',
previewText: 'ABCD',
},
{
subcategory: 'letter-tracing',
template: 'tracing-writing',
generator: 'letter-tracing',
sortOrder: 48,
layoutConfig: {
...L.trace,
instructionText: '四宫格每日打卡字母描红',
},
generatorConfig: {
mode: 'letter-tracing-daily-checkin',
letterCase: 'both',
repetitions: 5,
fadePattern: 'first-only',
dailyLetters: ['A', 'B', 'C', 'D'],
},
},
),
];
export const MATH_FUNCTION_TYPES: MathFunctionType[] = ALL_WORKSHEETS.filter(
(w) => w.category === 'math',
)
.sort((a, b) => a.sortOrder - b.sortOrder)
.map((w) => ({
id: w.id,
page: w.page,
title: w.title,
desc: w.desc,
icon: w.icon,
img: w.img,
}));
export const FOCUS_FUNCTION_TYPES: FocusFunctionType[] = ALL_WORKSHEETS.filter(
(w) => w.category === 'focus',
)
.sort((a, b) => a.sortOrder - b.sortOrder)
.map((w) => ({
id: w.id,
page: w.page,
title: w.title,
desc: w.desc,
icon: w.icon ?? '・',
mode: w.mode,
img: w.img,
}));
export function getWorksheetsByCategory(
category: string,
): WorksheetDefinition[] {
if (category === 'all') return ALL_WORKSHEETS;
return ALL_WORKSHEETS.filter((w) => w.category === category);
}
export function getWorksheetsByAge<T extends WorksheetType>(
worksheets: readonly T[],
age: number,
): T[] {
return worksheets.filter((w) => {
if (!w.ageRange) return true;
return age >= w.ageRange[0] && age <= w.ageRange[1];
});
}
export function getWorksheetPath(worksheet: WorksheetType): string {
if (!worksheet.page) return '';
if (worksheet.subpackage) {
return `/${worksheet.subpackage}/${worksheet.page}/${worksheet.page}?id=${worksheet.id}`;
}
return `/pages/${worksheet.page}/${worksheet.page}`;
}
export function getWorksheetById(id: string): WorksheetDefinition | undefined {
return ALL_WORKSHEETS.find((w) => w.id === id);
}
+8
View File
@@ -1,3 +1,11 @@
export type CategoryId =
| 'math'
| 'chinese'
| 'english'
| 'puzzle'
| 'craft'
| 'pinyin';
/** Tab / 首页用的分类展示模型 */
export interface CategoryType {
id: string;
+9 -165
View File
@@ -1,106 +1,26 @@
import { CategoryId } from './category';
/**
* 题型与模板引擎相关模型:对齐技术架构 §5.4 与云开发 worksheets 集合 §3.3
*/
/** 小程序现状:专注力单独成类;云端 category 枚举无 focus 时可映射为 puzzle + subcategory */
export type WorksheetCategory =
| 'math'
| 'focus'
| 'chinese'
| 'english'
| 'puzzle'
| 'craft';
/** 云 worksheets.category 严格枚举(§3.3 */
export type WorksheetCloudCategory =
| 'math'
| 'chinese'
| 'english'
| 'puzzle'
| 'craft';
export type DifficultyLevel =
| 'beginner'
| 'basic'
| 'intermediate'
| 'advanced';
export type WorksheetStatus = 'active' | 'draft' | 'hidden';
/** 排版模板(技术架构 §5.4) */
export type TemplateType =
| 'grid-exercise'
| 'match-connect'
| 'grid-coloring'
| 'card-layout'
| 'tracing-writing'
| 'full-page-asset'
| 'sequence-pattern'
| 'special-graphic';
/** 数据生成器(技术架构 §5.4) */
export type GeneratorType =
| 'arithmetic'
| 'number-sequence'
| 'number-decompose'
| 'counting'
| 'comparison'
| 'shape-grid'
| 'color-pattern'
| 'character-tracing'
| 'letter-tracing'
| 'pinyin-tracing'
| 'static-asset'
| 'maze'
| 'dot-connect'
| 'clock'
| 'custom';
export interface LayoutConfig {
columns?: number;
rows?: number;
fontSize?: number;
showBorder?: boolean;
showTitle?: boolean;
padding?: number;
itemSpacing?: number;
showInstruction?: boolean;
instructionText?: string;
}
export interface UserConfigField {
key: string;
label: string;
type: 'select' | 'slider' | 'switch';
options?: { label: string; value: unknown }[];
min?: number;
max?: number;
defaultValue: unknown;
}
/** 与架构文档 WorksheetConfig 一致,供云端下发 / 本地内置 */
export interface WorksheetConfig {
id: string;
title: string;
desc: string;
category: WorksheetCloudCategory;
subcategory: string;
subtitle: string;
category: CategoryId;
subcategory?: string;
ageMin: number;
ageMax: number;
difficulty: 1 | 2 | 3 | 4;
previewImage: string;
previewImg: string;
tags: string[];
isNew: boolean;
isHot: boolean;
sortOrder: number;
status: WorksheetStatus;
template: TemplateType;
generator: GeneratorType;
generatorConfig: Record<string, unknown>;
layoutConfig: LayoutConfig;
userConfigurable?: UserConfigField[] | null;
legacyPage?: string | null;
downloadCount: number;
sortOrder?: number;
status: 'active' | 'draft' | 'hidden';
downloads: number;
likes: number;
}
/** 云集合 `worksheets` 文档(§3.3,含数据库字段) */
@@ -109,79 +29,3 @@ export interface WorksheetRecord extends WorksheetConfig {
createdAt: Date;
updatedAt: Date;
}
/** 列表卡片 / 导航用(兼容存量页面) */
export interface WorksheetType {
id: string;
title: string;
desc: string;
category: WorksheetCategory;
page?: string;
subpackage?: string;
icon?: string;
img?: string;
ageRange?: [number, number];
difficulty?: DifficultyLevel;
mode?: string;
previewBg?: string;
previewText?: string;
}
/** 内置兜底 + 模板引擎字段(本地 core/data/worksheets */
export interface WorksheetDefinition extends WorksheetType {
subcategory: string;
ageMin: number;
ageMax: number;
/** 14,对应云 worksheets.difficulty */
difficultyLevel: 1 | 2 | 3 | 4;
previewImage: string;
tags: string[];
isNew: boolean;
isHot: boolean;
sortOrder: number;
downloadCount: number;
status: WorksheetStatus;
template: TemplateType;
generator: GeneratorType;
generatorConfig: Record<string, unknown>;
layoutConfig: LayoutConfig;
userConfigurable?: UserConfigField[] | null;
legacyPage?: string | null;
}
/** 写入云库时的 categoryfocus 归为 puzzle,子类区分 */
export function toCloudCategory(
category: WorksheetCategory,
): WorksheetCloudCategory {
if (category === 'focus') return 'puzzle';
return category;
}
/** 由内置定义生成云侧 worksheets 文档形状(不含 _id/时间,供同步层补全) */
export function worksheetDefinitionToConfig(
def: WorksheetDefinition,
): Omit<WorksheetRecord, '_id' | 'createdAt' | 'updatedAt'> {
return {
id: def.id,
title: def.title,
desc: def.desc,
category: toCloudCategory(def.category),
subcategory: def.subcategory,
ageMin: def.ageMin,
ageMax: def.ageMax,
difficulty: def.difficultyLevel,
previewImage: def.previewImage,
tags: def.tags,
isNew: def.isNew,
isHot: def.isHot,
sortOrder: def.sortOrder,
downloadCount: def.downloadCount,
status: def.status,
template: def.template,
generator: def.generator,
generatorConfig: def.generatorConfig,
layoutConfig: def.layoutConfig,
userConfigurable: def.userConfigurable ?? null,
legacyPage: def.legacyPage ?? null,
};
}
@@ -425,7 +425,7 @@ export function generateLetterTracing(
};
}
/** 与 WorksheetDefinition.generatorConfig 合并后的运行时配置 */
/** 与页面内模式配置合并后的运行时配置 */
export function mergeLetterTracingConfig(
base: Partial<LetterTracingGeneratorConfig>,
overrides: Partial<LetterTracingGeneratorConfig>,
@@ -0,0 +1,123 @@
import type { LetterTracingMode } from './generators/letter-tracing-generator';
import type { DebugPublishMeta } from '../../utils/debugPublish';
import { inferGradeFromAge } from '../../utils/debugPublish';
interface ModeDefinition {
id: LetterTracingMode;
icon: string;
title: string;
subtitle: string;
difficulty: 1 | 2 | 3 | 4;
tags: string[];
sortOrder: number;
}
const MODES = [
{
id: 'letter-tracing-single',
icon: 'start-a',
title: '默认字帖',
subtitle: '配图、例句与描红',
difficulty: 1,
tags: ['字母描红', '英语启蒙', '看图描红'],
sortOrder: 42,
},
{
id: 'letter-tracing-upper-lower',
icon: 'draw-o',
title: '基础描红',
subtitle: 'Uppercase / Lowercase 总览',
difficulty: 1,
tags: ['字母描红', '英语启蒙', '字母总览'],
sortOrder: 44,
},
{
id: 'letter-tracing-case-pairing',
icon: 'font-size',
title: '大小写对照',
subtitle: '半组字母左大写右小写',
difficulty: 1,
tags: ['字母描红', '英语启蒙', '大小写练习'],
sortOrder: 45,
},
{
id: 'letter-tracing-two-column',
icon: 'two-columns',
title: '两列描红',
subtitle: '左 AM、右 NZ 配对描红',
difficulty: 1,
tags: ['字母描红', '英语启蒙', '两列练习'],
sortOrder: 43,
},
{
id: 'letter-tracing-half',
icon: 'square-half',
title: '13字母半表',
subtitle: '每行一个字母,13 字母半表',
difficulty: 2,
tags: ['字母描红', '英语启蒙', '单字母逐行'],
sortOrder: 47,
},
{
id: 'letter-tracing-three',
icon: 'ABC-list',
title: '三字母精练',
subtitle: '每页聚焦 3 个字母深度书写',
difficulty: 2,
tags: ['字母描红', '英语启蒙', '三字母精练'],
sortOrder: 46,
},
{
id: 'letter-tracing-daily-checkin',
icon: 'draw-o',
title: '每日打卡',
subtitle: '四宫格每日字母打卡练习',
difficulty: 2,
tags: ['字母描红', '英语启蒙', '每日打卡'],
sortOrder: 48,
},
] as const satisfies ReadonlyArray<ModeDefinition>;
type Mode = (typeof MODES)[number];
const MODE_BY_ID = Object.fromEntries(
MODES.map((m) => [m.id, m]),
) as Record<string, Mode>;
/** 页面渲染用:模式选择器列表 */
export const LETTER_TRACING_MODE_OPTIONS = MODES;
/** 页面 pageInfoLookup 用 */
export function getModeInfo(id: string) {
const m = MODE_BY_ID[id];
return m ? { title: m.title, desc: m.subtitle } : undefined;
}
/** 判断 id 是否有效 */
export function isValidMode(id: string): boolean {
return id in MODE_BY_ID;
}
/** 发布用:从当前 mode 生成 DebugPublishMeta */
export function getPublishMetaByMode(id: string): DebugPublishMeta | null {
const m = MODE_BY_ID[id];
if (!m) return null;
return {
id: m.id,
title: m.title,
subtitle: m.subtitle,
category: 'english',
subcategory: 'letter-tracing',
path: `/englishPages/letterTracing/letterTracing?id=${m.id}`,
ageMin: 4,
ageMax: 7,
grade: inferGradeFromAge(4, 7),
difficulty: m.difficulty,
previewImg: '',
tags: [...m.tags],
isNew: false,
isHot: false,
sortOrder: m.sortOrder,
status: 'draft',
};
}
@@ -10,6 +10,7 @@
"share-guide-popup": "../../components/share-guide-popup/share-guide-popup",
"draw-ad": "../../components/draw-ad/draw-ad",
"preview-footer-actions": "../../components3.0/preview-footer-actions/preview-footer-actions",
"debug-publish-tools": "../../components3.0/debug-publish-tools/debug-publish-tools",
"toy-icon": "../../toy/icon/icon",
"preview-card": "../../components3.0/preview-card/preview-card"
}
@@ -1,7 +1,6 @@
import LetterTracingDraw from './draw/letterTracingDraw';
import { createPage, type CanvasDataState } from '../../base/pageMixin';
import { defaultShareConfig } from '../../config/config';
import { getWorksheetById } from '../../core/data/worksheets';
import {
generateLetterTracing,
mergeLetterTracingConfig,
@@ -9,6 +8,12 @@ import {
type LetterTracingGeneratorConfig,
type LetterTracingMode,
} from './generators/letter-tracing-generator';
import {
getModeInfo,
getPublishMetaByMode,
isValidMode,
LETTER_TRACING_MODE_OPTIONS,
} from './letterTracing.config';
import { LETTERS_UPPER, LETTERS_PAIRS } from '../shared/data/alphabet';
import {
DEFAULT_LETTER_PROFILE,
@@ -16,75 +21,24 @@ import {
type FontProfile,
} from '../shared/data/fontProfiles';
import { loadLetterFont } from '../shared/draw/drawTools';
/** 练习模式定义(顺序与产品文档一致),同时用于模式选择器和页面元信息 */
const MODES = [
{
id: 'letter-tracing-single',
icon: 'start-a',
label: '默认字帖',
desc: '配图、例句与描红',
},
{
id: 'letter-tracing-upper-lower',
icon: 'draw-o',
// label: '字母总览',
label: '基础描红',
desc: 'Uppercase / Lowercase 总览',
},
{
id: 'letter-tracing-case-pairing',
icon: 'font-size',
label: '大小写对照',
desc: '半组字母左大写右小写',
},
{
id: 'letter-tracing-two-column',
icon: 'two-columns',
label: '两列描红',
desc: '左 AM、右 NZ 配对描红',
},
{
id: 'letter-tracing-half',
icon: 'square-half',
label: '13字母半表',
desc: '每行一个字母,13 字母半表',
},
{
id: 'letter-tracing-three',
icon: 'ABC-list',
label: '三字母精练',
desc: '每页聚焦 3 个字母深度书写',
},
{
id: 'letter-tracing-daily-checkin',
icon: 'draw-o',
label: '每日打卡',
desc: '四宫格每日字母打卡练习',
},
] as const;
const MODE_MAP = Object.fromEntries(MODES.map((m) => [m.id, m])) as Record<
string,
(typeof MODES)[number]
>;
import type { DebugPublishMeta } from '../../utils/debugPublish';
/** 三字母精练分组:26 字母每 3 个一组 */
const TRIPLE_GROUPS: { label: string; letters: string[] }[] = [];
const TRIPLE_GROUPS: { title: string; letters: string[] }[] = [];
for (let i = 0; i < LETTERS_UPPER.length; i += 3) {
const group = LETTERS_UPPER.slice(i, i + 3);
const label =
const title =
group.length > 1 ? `${group[0]}${group[group.length - 1]}` : group[0];
TRIPLE_GROUPS.push({ label, letters: [...group] });
TRIPLE_GROUPS.push({ title, letters: [...group] });
}
/** 每日打卡分组:4 个字母一组 */
const DAILY_GROUPS: { label: string; letters: string[] }[] = [];
const DAILY_GROUPS: { title: string; letters: string[] }[] = [];
for (let i = 0; i < LETTERS_UPPER.length; i += 4) {
const group = LETTERS_UPPER.slice(i, i + 4);
const label =
const title =
group.length > 1 ? `${group[0]}${group[group.length - 1]}` : group[0];
DAILY_GROUPS.push({ label, letters: [...group] });
DAILY_GROUPS.push({ title, letters: [...group] });
}
function getDailyGroupIndex(letters?: string[]): number {
@@ -93,11 +47,7 @@ function getDailyGroupIndex(letters?: string[]): number {
return idx >= 0 ? idx : 0;
}
/** 根据 worksheetId 查找页面元信息 */
function pageInfoLookup(id: string) {
const m = MODE_MAP[id];
return m ? { title: m.label, desc: m.desc } : undefined;
}
const pageInfoLookup = getModeInfo;
type PageData = CanvasDataState & {
worksheetId: string;
@@ -110,13 +60,17 @@ type PageData = CanvasDataState & {
showNextLetter: boolean;
showTripleGroupPicker: boolean;
showDailyGroupPicker: boolean;
tripleGroups: { label: string; letters: string[] }[];
tripleGroups: { title: string; letters: string[] }[];
selectedTripleGroupIdx: number;
dailyGroups: { label: string; letters: string[] }[];
dailyGroups: { title: string; letters: string[] }[];
selectedDailyGroupIdx: number;
letterGrid: string[];
modeOptions: ReadonlyArray<{ id: string; label: string; desc: string }>;
modeOptions: typeof LETTER_TRACING_MODE_OPTIONS;
isPreviewFavorite: boolean;
isDevEnv: boolean;
debugPublishVisible: boolean;
debugPublishLoading: boolean;
debugPublishMeta: DebugPublishMeta | null;
};
createPage(
@@ -148,8 +102,12 @@ createPage(
dailyGroups: DAILY_GROUPS,
selectedDailyGroupIdx: 0,
letterGrid: LETTERS_PAIRS,
modeOptions: [...MODES],
modeOptions: LETTER_TRACING_MODE_OPTIONS,
isPreviewFavorite: false,
isDevEnv: false,
debugPublishVisible: false,
debugPublishLoading: false,
debugPublishMeta: null,
} as unknown as PageData,
/** 页面加载:从路由参数获取 worksheetId、letter、case、font 并应用,同时预加载字体 */
@@ -164,9 +122,10 @@ createPage(
}
loadLetterFont(this.fontProfile).catch(() => {});
const worksheetId =
options.id && MODE_MAP[options.id]
options.id && isValidMode(options.id)
? options.id
: 'letter-tracing-single';
this.syncDebugPublishEnv();
this.applyWorksheet(worksheetId);
const updates: Partial<PageData> = {};
@@ -216,11 +175,11 @@ createPage(
});
},
/** 构建运行时生成器配置:合并 worksheet 预设 + 当前页面状态 */
/** 构建运行时生成器配置:合并页面模式预设 + 当前页面状态 */
buildRuntimeConfig(): LetterTracingGeneratorConfig {
const def = getWorksheetById(this.data.worksheetId);
const base = (def?.generatorConfig ??
{}) as Partial<LetterTracingGeneratorConfig>;
const base: Partial<LetterTracingGeneratorConfig> = {
mode: this.data.traceMode,
};
const letterCase =
this.data.traceMode === 'letter-tracing-single'
? (base.letterCase ?? 'upper')
@@ -321,6 +280,14 @@ createPage(
});
},
getPublishMeta(): DebugPublishMeta {
const meta = getPublishMetaByMode(this.data.worksheetId);
if (!meta) {
throw new Error('当前题型配置不存在');
}
return meta;
},
/** 用户在三字母分组选择器中选择了一组 */
onSelectTripleGroup(e: WechatMiniprogram.TouchEvent) {
const idx = Number(e.currentTarget.dataset.idx);
@@ -338,9 +305,9 @@ createPage(
},
/**
* 应用指定的 worksheet 配置到页面状态
* 根据 worksheet 的 generatorConfig 决定 UI 控件的显隐(字母选择器、大小写切换等)
* @param worksheetId - 要应用的 worksheet ID
* 应用指定的页面模式配置到页面状态
* 根据当前模式的运行时默认参数决定 UI 控件的显隐(字母选择器、大小写切换等)
* @param worksheetId - 要应用的模式 ID
* @param options.preserveLetter - 是否保留当前选中的字母(模式切换时使用)
* @param options.redraw - 是否立即重绘 Canvas
*/
@@ -351,11 +318,12 @@ createPage(
redraw?: boolean;
},
) {
const def = getWorksheetById(worksheetId);
console.log('def', def);
const base = (def?.generatorConfig ??
{}) as Partial<LetterTracingGeneratorConfig>;
const merged = mergeLetterTracingConfig(base, {});
if (!isValidMode(worksheetId)) return;
const merged = mergeLetterTracingConfig(
{ mode: worksheetId as LetterTracingMode },
{},
);
const traceMode = merged.mode;
const showLetterPicker = traceMode === 'letter-tracing-single';
@@ -397,10 +365,7 @@ createPage(
selectedDailyGroupIdx,
},
() => {
this.initPageInfo(
worksheetId,
MODE_MAP[worksheetId]?.label ?? '字母描红',
);
this.initPageInfo(worksheetId, '字母描红');
if (this.drawService) {
this.drawService.options.title = this.data.pageTitle;
@@ -57,14 +57,14 @@
<view class="lt-chip-row lt-chip-row--wrap lt-chip-row--triple">
<view
wx:for="{{tripleGroups}}"
wx:key="label"
wx:key="title"
class="lt-chip lt-chip--triple {{selectedTripleGroupIdx === index ? 'lt-chip--active' : ''}}"
hover-class="lt-chip--pressed"
hover-start-time="0"
hover-stay-time="70"
data-idx="{{index}}"
bind:tap="onSelectTripleGroup">
{{item.label}}
{{item.title}}
</view>
</view>
</view>
@@ -74,14 +74,14 @@
<view class="lt-chip-row lt-chip-row--wrap lt-chip-row--triple">
<view
wx:for="{{dailyGroups}}"
wx:key="label"
wx:key="title"
class="lt-chip lt-chip--triple {{selectedDailyGroupIdx === index ? 'lt-chip--active' : ''}}"
hover-class="lt-chip--pressed"
hover-start-time="0"
hover-stay-time="70"
data-idx="{{index}}"
bind:tap="onSelectDailyGroup">
{{item.label}}
{{item.title}}
</view>
</view>
</view>
@@ -119,7 +119,7 @@
size="42rpx"
color="{{worksheetId === item.id ? '#453900' : '#605b50'}}"
custom-class="lt-mode-card__icon" />
<text class="lt-mode-card__label">{{item.label}}</text>
<text class="lt-mode-card__label">{{item.title}}</text>
</view>
</view>
</view>
@@ -133,6 +133,16 @@
bind:primary="exportToPrint"
bind:secondary="onShare" />
<debug-publish-tools
wx:if="{{isDevEnv && hasContent}}"
id="debugPublishTools"
visible="{{debugPublishVisible}}"
loading="{{debugPublishLoading}}"
meta="{{debugPublishMeta}}"
bind:open="onOpenDebugPublish"
bind:close="onCloseDebugPublish"
bind:confirm="onConfirmDebugPublish" />
<share-guide-popup
show="{{showShareDialog}}"
bind:onClose="onCloseShareDialog"
+639 -71
View File
@@ -4,14 +4,19 @@ export type CategoryItem = {
id: string;
title: string;
subtitle: string;
icon: string;
img?: string;
icon?: string;
/** 无图时在列表中用 icon 占位 */
previewImg?: string;
ageBand: string;
difficulty: string;
ageMin?: number;
ageMax?: number;
difficulty: 1 | 2 | 3 | 4;
difficultyLabel: string;
path: string;
available: boolean;
likes: number;
downloads: number;
/** 列表底部展示的日期(静态数据用 id 派生稳定值) */
date: string;
};
@@ -27,23 +32,17 @@ export type CategoryDataset = {
categories: CategoryGroup[];
};
const DIFFICULTY_MAP: Record<string, string> = {
beginner: '入门',
basic: '基础',
intermediate: '进阶',
advanced: '挑战',
const DIFFICULTY_LABELS: Record<CategoryItem['difficulty'], string> = {
1: '入门',
2: '基础',
3: '进阶',
4: '挑战',
};
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;
@@ -53,80 +52,649 @@ function statsFromId(id: string): { likes: number; downloads: number } {
return { likes, downloads };
}
type ItemInput = Omit<CategoryItem, 'likes' | 'downloads' | 'date'>;
function dateFromId(id: string): string {
let h = 0;
for (let i = 0; i < id.length; i++) h = (h * 31 + id.charCodeAt(i)) | 0;
const days = Math.abs(h % 500);
const d = new Date('2024-01-01');
d.setDate(d.getDate() + days);
return d.toISOString().slice(0, 10);
}
type ItemInput = Omit<
CategoryItem,
'likes' | 'downloads' | 'date' | 'difficultyLabel'
>;
function item(input: ItemInput): CategoryItem {
const { likes, downloads } = statsFromId(input.id);
return { ...input, likes, downloads, date: TODAY };
return {
...input,
likes,
downloads,
date: dateFromId(input.id),
difficultyLabel: DIFFICULTY_LABELS[input.difficulty],
};
}
const CATEGORY_ITEMS_BY_ID: Record<string, CategoryItem[]> = {
math: [
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 }),
item({
id: 'number-find',
title: '数字,涂一涂',
subtitle: '在数字方阵中找出目标数字并涂色',
icon: '🔍',
previewImg: '/assets/entrancePicture/math/number-find.png',
ageBand: age(3, 5),
difficulty: 1,
path: '/mathPages/mathDraw/mathDraw?id=number-find',
available: true,
}),
item({
id: 'number-write',
title: '看数字,写一写',
subtitle: '按笔画顺序练习书写数字',
icon: '✏️',
previewImg: '/assets/entrancePicture/math/number-write.png',
ageBand: age(3, 5),
difficulty: 1,
path: '/mathPages/mathDraw/mathDraw?id=number-write',
available: true,
}),
item({
id: 'number-coloring',
title: '按数字,涂颜色',
subtitle: '按指定数字给对应圆圈涂色',
icon: '🎨',
previewImg: '/assets/entrancePicture/math/number-coloring.png',
ageBand: age(3, 5),
difficulty: 1,
path: '/mathPages/mathDraw/mathDraw?id=number-coloring',
available: true,
}),
item({
id: 'counting-matching',
title: '数一数,连一连',
subtitle: '连线配对数字和对应数量图形',
icon: '🔗',
previewImg: '/assets/entrancePicture/math/count-match.png',
ageBand: age(3, 5),
difficulty: 1,
path: '/mathPages/mathDraw/mathDraw?id=counting-matching',
available: true,
}),
item({
id: 'number-object-match',
title: '数物连线',
subtitle: '连线相同数量的物品和数字',
icon: '🔗',
previewImg: '/assets/entrancePicture/math/number-object-match.png',
ageBand: age(3, 5),
difficulty: 1,
path: '/mathPages/mathDraw/mathDraw?id=number-object-match',
available: true,
}),
item({
id: 'number-object-fill',
title: '数物填写',
subtitle: '数物品数量,填写对应数字',
icon: '✏️',
previewImg: '/assets/entrancePicture/math/number-object-fill.png',
ageBand: age(3, 5),
difficulty: 1,
path: '/mathPages/mathDraw/mathDraw?id=number-object-fill',
available: true,
}),
item({
id: 'counting-select',
title: '数一数,选一选',
subtitle: '数出物品数量,圈出正确答案',
icon: '✓',
previewImg: '/assets/entrancePicture/math/counting-select.png',
ageBand: age(3, 5),
difficulty: 1,
path: '/mathPages/mathDraw/mathDraw?id=counting-select',
available: true,
}),
item({
id: 'counting-fill',
title: '数一数,填一填',
subtitle: '数出物品数量,填写数字',
icon: '✏️',
previewImg: '/assets/entrancePicture/math/counting-fill.png',
ageBand: age(3, 5),
difficulty: 1,
path: '/mathPages/mathDraw/mathDraw?id=counting-fill',
available: true,
}),
item({
id: 'compare',
title: '数一数,比大小',
subtitle: '比较数量,填入 ><=',
icon: '⚖️',
previewImg: '/assets/entrancePicture/math/compare.png',
ageBand: age(4, 6),
difficulty: 2,
path: '/mathPages/mathDraw/mathDraw?id=compare',
available: true,
}),
item({
id: 'number-sort',
title: '数字排序',
subtitle: '写出正确的数字顺序',
icon: '🔢',
previewImg: '/assets/entrancePicture/math/number-sort.png',
ageBand: age(4, 6),
difficulty: 2,
path: '/mathPages/mathDraw/mathDraw?id=number-sort',
available: true,
}),
item({
id: 'missing-number',
title: '填上缺少的数字',
subtitle: '在数列中找出并填写缺失数字',
icon: '❓',
previewImg: '/assets/entrancePicture/math/missing-number.png',
ageBand: age(4, 6),
difficulty: 2,
path: '/mathPages/mathDraw/mathDraw?id=missing-number',
available: true,
}),
item({
id: 'number-decompose',
title: '10以内数的分与合',
subtitle: '把数字分一分,合一合',
icon: '🔢',
previewImg: '/assets/entrancePicture/math/number-decompose.png',
ageBand: age(4, 6),
difficulty: 1,
path: '/mathPages/mathDraw/mathDraw?id=number-decompose',
available: true,
}),
item({
id: 'number-decompose-20',
title: '20以内数的分与合',
subtitle: '把数字分一分,合一合',
icon: '🔢',
previewImg: '/assets/entrancePicture/math/number-decompose-20.png',
ageBand: age(5, 7),
difficulty: 2,
path: '/mathPages/mathDraw/mathDraw?id=number-decompose-20',
available: true,
}),
item({
id: 'one-digit-addition',
title: '一位数加法',
subtitle: '通过圆点学习一位数加法运算',
icon: '',
previewImg: '/assets/entrancePicture/math/one-digit-addition.png',
ageBand: age(4, 6),
difficulty: 2,
path: '/mathPages/mathDraw/mathDraw?id=one-digit-addition',
available: true,
}),
item({
id: 'addition-5',
title: '5以内加法',
subtitle: '图形化展示 5 以内加法',
icon: '',
previewImg: '/assets/entrancePicture/math/addition-5.png',
ageBand: age(3, 5),
difficulty: 1,
path: '/mathPages/mathDraw/mathDraw?id=addition-5',
available: true,
}),
item({
id: 'addition-10',
title: '10以内加法',
subtitle: '图形化展示 10 以内加法',
icon: '',
previewImg: '/assets/entrancePicture/math/addition-10.png',
ageBand: age(4, 6),
difficulty: 2,
path: '/mathPages/mathDraw/mathDraw?id=addition-10',
available: true,
}),
item({
id: 'subtraction-10',
title: '10以内减法',
subtitle: '图形化展示 10 以内减法',
icon: '',
previewImg: '/assets/entrancePicture/math/subtraction-10.png',
ageBand: age(4, 6),
difficulty: 2,
path: '/mathPages/mathDraw/mathDraw?id=subtraction-10',
available: true,
}),
item({
id: 'addition-subtraction-10',
title: '10以内加减法',
subtitle: '加减法混合运算',
icon: '±',
previewImg:
'/assets/entrancePicture/math/addition-subtraction-10.png',
ageBand: age(5, 7),
difficulty: 2,
path: '/mathPages/mathDraw/mathDraw?id=addition-subtraction-10',
available: true,
}),
item({
id: 'make-ten',
title: '凑十法练习',
subtitle: '20 以内进位加法',
icon: '',
previewImg: '/assets/entrancePicture/math/make-ten.png',
ageBand: age(5, 7),
difficulty: 3,
path: '/mathPages/mathDraw/mathDraw?id=make-ten',
available: true,
}),
item({
id: 'break-ten',
title: '破十法练习',
subtitle: '20 以内退位减法',
icon: '',
previewImg: '/assets/entrancePicture/math/break-ten.png',
ageBand: age(5, 7),
difficulty: 3,
path: '/mathPages/mathDraw/mathDraw?id=break-ten',
available: true,
}),
item({
id: 'flat-ten',
title: '平十法练习',
subtitle: '20 以内退位减法',
icon: '',
previewImg: '/assets/entrancePicture/math/flat-ten.png',
ageBand: age(5, 7),
difficulty: 3,
path: '/mathPages/mathDraw/mathDraw?id=flat-ten',
available: true,
}),
item({
id: 'borrow-ten',
title: '借十法练习',
subtitle: '20 以上退位减法',
icon: '',
previewImg: '/assets/entrancePicture/math/borrow-ten.png',
ageBand: age(6, 8),
difficulty: 4,
path: '/mathPages/mathDraw/mathDraw?id=borrow-ten',
available: true,
}),
item({
id: 'practice-addition',
title: '加法运算',
subtitle: '10/20/50/100 以内加法',
icon: '',
previewImg: '/assets/entrancePicture/math/practice-addition.png',
ageBand: age(5, 8),
difficulty: 3,
path: '/mathPages/mathDraw/mathDraw?id=practice-addition',
available: true,
}),
item({
id: 'practice-subtraction',
title: '减法运算',
subtitle: '10/20/50/100 以内减法',
icon: '',
previewImg: '/assets/entrancePicture/math/practice-subtraction.png',
ageBand: age(5, 8),
difficulty: 3,
path: '/mathPages/mathDraw/mathDraw?id=practice-subtraction',
available: true,
}),
item({
id: 'practice-mixed',
title: '混合运算',
subtitle: '10/20/50/100 以内加减法混合',
icon: '±',
previewImg: '/assets/entrancePicture/math/practice-mixed.png',
ageBand: age(5, 8),
difficulty: 3,
path: '/mathPages/mathDraw/mathDraw?id=practice-mixed',
available: true,
}),
item({
id: 'multiplication-table',
title: '九九乘法表',
subtitle: '学习九九乘法口诀',
icon: '✖️',
previewImg: '/assets/entrancePicture/math/multiplication-table.png',
ageBand: age(6, 8),
difficulty: 3,
path: '/mathPages/mathDraw/mathDraw?id=multiplication-table',
available: true,
}),
],
pinyin: [
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 }),
item({
id: 'pinyin-initials',
title: '声母描红',
subtitle: '23个声母认读与书写练习',
icon: '🔤',
ageBand: age(5, 7),
difficulty: 2,
path: '',
available: false,
}),
item({
id: 'pinyin-finals',
title: '韵母描红',
subtitle: '24个韵母认读与书写练习',
icon: '🔤',
ageBand: age(5, 7),
difficulty: 2,
path: '',
available: false,
}),
item({
id: 'pinyin-overall',
title: '整体认读音节',
subtitle: '16个整体认读音节练习',
icon: '🔤',
ageBand: age(5, 7),
difficulty: 3,
path: '',
available: false,
}),
],
puzzle: [
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 }),
item({
id: 'color-shape-match',
title: '根据颜色画图形',
subtitle: '根据颜色画出对应图形',
icon: '🎯',
previewImg: '/assets/entrancePicture/focus/color-shape-match.png',
ageBand: age(4, 6),
difficulty: 2,
path: '/focusPages/focusDraw/focusDraw?id=color-shape-match',
available: true,
}),
item({
id: 'shape-symbol',
title: '图形符号配对',
subtitle: '根据图形画对应符号',
icon: '🔗',
previewImg: '/assets/entrancePicture/focus/shape-symbol.png',
ageBand: age(4, 6),
difficulty: 2,
path: '/focusPages/focusDraw/focusDraw?id=shape-symbol',
available: true,
}),
item({
id: 'shape-recognition',
title: '识别形状',
subtitle: '识别形状,涂一涂',
icon: '🔍',
previewImg: '/assets/entrancePicture/focus/shape-recognition.png',
ageBand: age(3, 5),
difficulty: 1,
path: '/focusPages/focusDraw/focusDraw?id=shape-recognition',
available: true,
}),
item({
id: 'position-coloring',
title: '方位涂涂乐',
subtitle: '观察位置,在方格中涂色',
icon: '📍',
previewImg: '/assets/entrancePicture/focus/position-coloring.png',
ageBand: age(4, 6),
difficulty: 2,
path: '/focusPages/focusDraw/focusDraw?id=position-coloring',
available: true,
}),
item({
id: 'color-pattern',
title: '颜色找规律',
subtitle: '观察颜色规律,在空白图形中涂色',
icon: '🎨',
previewImg: '/assets/entrancePicture/focus/color-pattern.png',
ageBand: age(4, 6),
difficulty: 2,
path: '/focusPages/focusDraw/focusDraw?id=color-pattern',
available: true,
}),
item({
id: 'match-connect',
title: '连连看',
subtitle: '根据物品连一连',
icon: '🔗',
previewImg: '/assets/entrancePicture/focus/match-connect.png',
ageBand: age(3, 6),
difficulty: 1,
path: '/focusPages/focusDraw/focusDraw?id=match-connect',
available: true,
}),
item({
id: 'line-recognition',
title: '线条识别',
subtitle: '认识不同线条,画出颜色对应的线条',
icon: '📏',
previewImg: '/assets/entrancePicture/focus/line-recognition.png',
ageBand: age(3, 5),
difficulty: 1,
path: '/focusPages/focusDraw/focusDraw?id=line-recognition',
available: true,
}),
item({
id: 'grid-reasoning',
title: '方格推理',
subtitle: '推理出合并方格并连线',
icon: '🧩',
previewImg: '/assets/entrancePicture/focus/grid-reasoning.png',
ageBand: age(5, 7),
difficulty: 3,
path: '/focusPages/focusDraw/focusDraw?id=grid-reasoning',
available: true,
}),
item({
id: 'code-connect',
title: '译码连线',
subtitle: '按数字顺序将数字对应颜色连线',
icon: '🔢',
previewImg: '/assets/entrancePicture/focus/code-connect.png',
ageBand: age(5, 7),
difficulty: 3,
path: '/focusPages/focusDraw/focusDraw?id=code-connect',
available: true,
}),
item({
id: 'dot-connect',
title: '数字点连线',
subtitle: '按数字顺序连点成图',
icon: '🔗',
previewImg: '/assets/entrancePicture/focus/dot-connect.png',
ageBand: age(3, 6),
difficulty: 1,
path: '/focusPages/focusDraw/focusDraw?id=dot-connect',
available: true,
}),
item({
id: 'grid-drawing-3x3',
title: '格子仿画 3×3',
subtitle: '简单有趣,培养专注力',
icon: '🎨',
previewImg: '/assets/entrancePicture/focus/grid-drawing-3x3.png',
ageBand: age(3, 5),
difficulty: 1,
path: '/focusPages/focusDraw/focusDraw?id=grid-drawing-3x3',
available: true,
}),
item({
id: 'grid-drawing-5x5',
title: '格子仿画 5×5',
subtitle: '创意挑战,提升观察力',
icon: '🎨',
previewImg: '/assets/entrancePicture/focus/grid-drawing-5x5.png',
ageBand: age(4, 6),
difficulty: 2,
path: '/focusPages/focusDraw/focusDraw?id=grid-drawing-5x5',
available: true,
}),
item({
id: 'grid-drawing-7x7',
title: '格子仿画 7×7',
subtitle: '大师挑战,锻炼耐心',
icon: '🎨',
previewImg: '/assets/entrancePicture/focus/grid-drawing-7x7.png',
ageBand: age(5, 8),
difficulty: 3,
path: '/focusPages/focusDraw/focusDraw?id=grid-drawing-7x7',
available: true,
}),
],
chinese: [
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 }),
item({
id: 'word-recognition',
title: '识字卡',
subtitle: '输入或选字生成涂色识字卡',
icon: '📖',
ageBand: age(3, 6),
difficulty: 1,
path: '/pages/index/index',
available: true,
}),
item({
id: 'copybook',
title: '练字帖',
subtitle: '选字生成田字格笔顺练字帖',
icon: '✏️',
ageBand: age(4, 7),
difficulty: 2,
path: '/pages/copyBook/copyBook',
available: true,
}),
],
english: [
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-single2', title: '看图描红', subtitle: '单字母配图、例句与六行四线三格描红', icon: '🔠', img: '/assets/entrancePicture/english/letter-tracing-single2.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、右 N-Z,大小写配对描红', 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 }),
item({ id: 'letter-tracing-daily-checkin', title: '每日打卡', subtitle: '四宫格每日字母打卡练习', icon: '🔤', img: '/assets/entrancePicture/english/letter-tracing-daily-checkin.jpg', ageBand: age(4, 7), difficulty: d('basic'), path: '/englishPages/letterTracing/letterTracing?id=letter-tracing-daily-checkin', available: true }),
item({
id: 'letter-tracing-single',
title: '看图描红',
subtitle: '单字母配图、例句与六行四线三格描红',
icon: '🔠',
previewImg:
'/assets/entrancePicture/english/letter-tracing-single.jpg',
ageBand: age(4, 7),
difficulty: 1,
path: '/englishPages/letterTracing/letterTracing?id=letter-tracing-single',
available: true,
}),
item({
id: 'letter-tracing-single2',
title: '看图描红',
subtitle: '单字母配图、例句与六行四线三格描红',
icon: '🔠',
previewImg:
'/assets/entrancePicture/english/letter-tracing-single2.jpg',
ageBand: age(4, 7),
difficulty: 1,
path: '/englishPages/letterTracing/letterTracing?id=letter-tracing-single',
available: true,
}),
item({
id: 'letter-tracing-upper-lower',
title: '字母总览',
subtitle: 'Uppercase / Lowercase 分区总览',
icon: '🔡',
previewImg:
'/assets/entrancePicture/english/letter-tracing-upper-lower.jpg',
ageBand: age(4, 7),
difficulty: 1,
path: '/englishPages/letterTracing/letterTracing?id=letter-tracing-upper-lower',
available: true,
}),
item({
id: 'letter-tracing-case-pairing',
title: '大小写练习',
subtitle: '半组字母左大写右小写,逐行对照描红',
icon: '✏️',
previewImg:
'/assets/entrancePicture/english/letter-tracing-case-pairing.jpg',
ageBand: age(4, 7),
difficulty: 1,
path: '/englishPages/letterTracing/letterTracing?id=letter-tracing-case-pairing',
available: true,
}),
item({
id: 'letter-tracing-two-column',
title: '两列练习',
subtitle: '左 A-M、右 N-Z,大小写配对描红',
icon: '🔤',
previewImg:
'/assets/entrancePicture/english/letter-tracing-two-column.jpg',
ageBand: age(4, 7),
difficulty: 1,
path: '/englishPages/letterTracing/letterTracing?id=letter-tracing-two-column',
available: true,
}),
item({
id: 'letter-tracing-half',
title: '单字母逐行',
subtitle: '13 字母半表逐字练习',
icon: '📝',
previewImg:
'/assets/entrancePicture/english/letter-tracing-half.jpg',
ageBand: age(4, 7),
difficulty: 2,
path: '/englishPages/letterTracing/letterTracing?id=letter-tracing-half',
available: true,
}),
item({
id: 'letter-tracing-three',
title: '三字母精练',
subtitle: '每页 3 个字母,大写 + 小写深度书写',
icon: '🔤',
previewImg:
'/assets/entrancePicture/english/letter-tracing-three.jpg',
ageBand: age(4, 7),
difficulty: 2,
path: '/englishPages/letterTracing/letterTracing?id=letter-tracing-three',
available: true,
}),
item({
id: 'letter-tracing-daily-checkin',
title: '每日打卡',
subtitle: '四宫格每日字母打卡练习',
icon: '🔤',
previewImg:
'/assets/entrancePicture/english/letter-tracing-daily-checkin.jpg',
ageBand: age(4, 7),
difficulty: 2,
path: '/englishPages/letterTracing/letterTracing?id=letter-tracing-daily-checkin',
available: true,
}),
],
craft: [
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 }),
item({
id: 'craft-coloring',
title: '涂色卡',
subtitle: '动物、交通、节日主题涂色',
icon: '🎨',
ageBand: age(3, 6),
difficulty: 1,
path: '',
available: false,
}),
item({
id: 'craft-origami',
title: '折纸模板',
subtitle: '打印后即可折叠的趣味模板',
icon: '🪭',
ageBand: age(4, 7),
difficulty: 2,
path: '',
available: false,
}),
item({
id: 'craft-stickers',
title: '贴纸打印',
subtitle: '奖励贴纸与装饰贴纸',
icon: '⭐',
ageBand: age(3, 8),
difficulty: 1,
path: '',
available: false,
}),
],
};
+7 -5
View File
@@ -7,11 +7,13 @@ type CategoryTab = {
icon: string;
};
const SIDEBAR_TABS: CategoryTab[] = CATEGORY_LIST_WITH_ALL.map(({ id, name, icon }) => ({
id,
name,
icon,
}));
const SIDEBAR_TABS: CategoryTab[] = CATEGORY_LIST_WITH_ALL.map(
({ id, name, icon }) => ({
id,
name,
icon,
}),
);
function buildAllItems(): CategoryItem[] {
const items: CategoryItem[] = [];
+3 -3
View File
@@ -59,9 +59,9 @@
bindtap="onTapItem">
<view class="cat-card__thumb">
<image
wx:if="{{item.img}}"
wx:if="{{item.previewImg}}"
class="cat-card__thumb-img"
src="{{item.img}}"
src="{{item.previewImg}}"
mode="aspectFill" />
<text wx:else class="cat-card__thumb-icon"
>{{item.icon}}</text
@@ -81,7 +81,7 @@
>{{item.ageBand}}</text
>
<text class="tag tag--diff"
>{{item.difficulty}}</text
>{{item.difficultyLabel}}</text
>
</view>
<view class="cat-card__footer">
+4 -4
View File
@@ -1,12 +1,13 @@
import { CATEGORY_LIST_WITH_ALL } from '../../core/data/categories';
import { AGE_BANDS } from '../../core/data/difficulty';
import { CategoryId } from '../../core/models/category';
export type HomeDisplayItem = {
id: string;
title: string;
subtitle: string;
description?: string;
category: 'math' | 'chinese' | 'english' | 'puzzle' | 'craft';
category: CategoryId;
ageBand: string;
difficulty: '入门' | '基础' | '进阶' | '挑战';
icon: string;
@@ -44,9 +45,8 @@ export type HomeDisplayDataset = {
sections: HomeDisplaySection[];
};
const HOME_CATEGORY_TABS: HomeDisplayDataset['categoryTabs'] = CATEGORY_LIST_WITH_ALL.map(
({ id, name }) => ({ id, name }),
);
const HOME_CATEGORY_TABS: HomeDisplayDataset['categoryTabs'] =
CATEGORY_LIST_WITH_ALL.map(({ id, name }) => ({ id, name }));
const HOME_AGE_BANDS = AGE_BANDS.map((item, index) => ({
key: item.key,
+156
View File
@@ -0,0 +1,156 @@
import type {
WorksheetCloudCategory,
WorksheetStatus,
} from '../core/models/worksheet';
/**
* 调试发布时的裁剪模式。
* - header-footer: 同 node-tools 入口图脚本,默认裁掉页眉和页脚
* - header-only: 仅裁掉页眉,保留底部品牌区
* - none: 不裁剪,直接压缩整张 A4 预览图
*/
export type DebugCropMode = 'header-footer' | 'header-only' | 'none';
/**
* 发布到 worksheets 集合的核心元数据。
* 这里对齐《小程序云开发方案》中的题型字段设计,供页面侧生成 payload 使用。
*/
export interface DebugPublishMeta {
id: string;
title: string;
subtitle: string;
category: WorksheetCloudCategory;
subcategory?: string;
path: string;
ageMin: number;
ageMax: number;
grade: number;
difficulty: 1 | 2 | 3 | 4;
previewImg?: string;
tags: string[];
isNew: boolean;
isHot: boolean;
sortOrder?: number;
status: WorksheetStatus;
}
/** 发布弹窗里可调的图片处理参数。 */
export interface DebugPublishSettings {
cropMode: DebugCropMode;
quality: number;
width: number;
maxSizeKB: number;
}
/** 弹窗确认后回传给页面的表单结果。 */
export interface DebugPublishConfirmDetail {
meta: Pick<DebugPublishMeta, 'title' | 'subtitle' | 'tags' | 'status'>;
settings: DebugPublishSettings;
}
/** 图片处理组件收到的输入参数。 */
export interface DebugProcessImageParams {
sourcePath: string;
settings: DebugPublishSettings;
}
/** 图片处理完成后返回给页面的信息。 */
export interface DebugProcessImageResult {
tempFilePath: string;
size: number;
width: number;
height: number;
quality: number;
}
/** 默认压缩质量,与文档里的入口图建议值保持一致。 */
export const DEBUG_PUBLISH_DEFAULT_QUALITY = 90;
/** 默认入口图宽度,沿用 node-tools 脚本的 600px。 */
export const DEBUG_PUBLISH_DEFAULT_WIDTH = 600;
/** 默认体积上限,超过后会继续降质压缩。 */
export const DEBUG_PUBLISH_MAX_SIZE_KB = 200;
/** 页眉裁剪比例:110 / 842,来源于现有入口图脚本。 */
export const DEBUG_CROP_TOP_RATIO = 110 / 842;
/** 页脚裁剪比例:52 / 842,来源于现有入口图脚本。 */
export const DEBUG_CROP_BOTTOM_RATIO = 52 / 842;
/** 仅开发版开放 Debug 发布能力。 */
export function isDebugPublishEnabled(): boolean {
const accountInfo = wx.getAccountInfoSync();
return accountInfo.miniProgram.envVersion === 'develop';
}
/** 限制压缩质量范围,避免过高或过低。 */
export function clampPublishQuality(value: number): number {
return Math.min(95, Math.max(40, Math.round(value)));
}
/** 限制输出宽度范围,防止导出过小或过大。 */
export function clampPublishWidth(value: number): number {
return Math.min(1000, Math.max(400, Math.round(value)));
}
/** 根据裁剪模式计算顶部 / 底部裁剪比例。 */
export function getCropRatios(mode: DebugCropMode): {
topRatio: number;
bottomRatio: number;
} {
if (mode === 'header-only') {
return {
topRatio: DEBUG_CROP_TOP_RATIO,
bottomRatio: 0,
};
}
if (mode === 'none') {
return {
topRatio: 0,
bottomRatio: 0,
};
}
return {
topRatio: DEBUG_CROP_TOP_RATIO,
bottomRatio: DEBUG_CROP_BOTTOM_RATIO,
};
}
/** 统一构造预览图在云存储中的路径。 */
export function buildWorksheetPreviewCloudPath(
category: WorksheetCloudCategory,
id: string,
): string {
return `assets/previews/${category}/${id}.jpg`;
}
/** 将输入框中的标签文本拆成标签数组,兼容英文逗号、中文逗号、顿号和空白。 */
export function normalizeTagsInput(raw: string): string[] {
return raw
.split(/[,,、\s]+/)
.map((item) => item.trim())
.filter(Boolean);
}
/**
* 由年龄段推断年级值。
* 当前先用“年龄中心点 -> 年级”的简单映射,后续如有更细分规则可独立替换。
*/
export function inferGradeFromAge(ageMin: number, ageMax: number): number {
const centerAge = Math.round((ageMin + ageMax) / 2);
const ageGradeMap: Record<number, number> = {
2: -4,
3: -3,
4: -2,
5: -1,
6: 0,
7: 1,
8: 2,
9: 3,
10: 4,
11: 5,
12: 6,
};
return ageGradeMap[centerAge] ?? 0;
}