diff --git a/96qiAacPdaLu78de7d9f9dfe65085df526e0ad9e2fea.jpg b/96qiAacPdaLu78de7d9f9dfe65085df526e0ad9e2fea.jpg deleted file mode 100644 index 4134ca0..0000000 Binary files a/96qiAacPdaLu78de7d9f9dfe65085df526e0ad9e2fea.jpg and /dev/null differ diff --git a/miniprogram/app.json b/miniprogram/app.json index 284a7e2..a4f16c1 100644 --- a/miniprogram/app.json +++ b/miniprogram/app.json @@ -52,7 +52,8 @@ "name": "chinesePages", "pages": [ "wordColoring/wordColoring", - "handwritingSheet/handwritingSheet" + "handwritingSheet/handwritingSheet", + "penControlSheet/penControlSheet" ], "independent": false } diff --git a/miniprogram/assets/fonts/iconfont.less b/miniprogram/assets/fonts/iconfont.less index e3c3ffa..6408d51 100644 --- a/miniprogram/assets/fonts/iconfont.less +++ b/miniprogram/assets/fonts/iconfont.less @@ -34,7 +34,7 @@ content: "\e634"; } -.toy-icon-iconify-material-symbols_check-rounded:before { +.toy-icon-check:before { content: "\e632"; } diff --git a/miniprogram/chinesePages/penControlSheet/data/penControlPatterns.ts b/miniprogram/chinesePages/penControlSheet/data/penControlPatterns.ts new file mode 100644 index 0000000..268b766 --- /dev/null +++ b/miniprogram/chinesePages/penControlSheet/data/penControlPatterns.ts @@ -0,0 +1,268 @@ +/** 控笔图形:100×100 坐标系,Y 轴向下 */ + +export type PenControlCategory = 'line' | 'curve' | 'shape' | 'combo'; + +export const PEN_CONTROL_CATEGORY_LABEL: Record = { + line: '直线', + curve: '曲线', + shape: '形状', + combo: '组合', +}; + +export type PenControlRenderMode = 'stroke' | 'fill' | 'both'; + +export interface PenControlPattern { + id: string; + name: string; + category: PenControlCategory; + categoryLabel?: string; + tags: string[]; + paths: string[]; + render: PenControlRenderMode; + strokeScale?: number; +} + +const RAW_PEN_CONTROL_PATTERNS: PenControlPattern[] = [ + { + id: 'horizontal-lines', + name: '横线', + category: 'line', + tags: ['基础', '直线'], + render: 'stroke', + paths: [ + 'M 14 26 L 86 26', + 'M 14 42 L 86 42', + 'M 14 58 L 86 58', + 'M 14 74 L 86 74', + ], + }, + { + id: 'vertical-lines', + name: '竖线', + category: 'line', + tags: ['基础', '直线'], + render: 'stroke', + paths: [ + 'M 26 14 L 26 86', + 'M 42 14 L 42 86', + 'M 58 14 L 58 86', + 'M 74 14 L 74 86', + ], + }, + { + id: 'diagonal-lr', + name: '左斜线', + category: 'line', + tags: ['斜线'], + render: 'stroke', + paths: [ + 'M 14 14 L 86 86', + 'M 14 32 L 68 86', + 'M 32 14 L 86 68', + 'M 14 50 L 50 86', + 'M 50 14 L 86 50', + ], + }, + { + id: 'diagonal-rl', + name: '右斜线', + category: 'line', + tags: ['斜线'], + render: 'stroke', + paths: [ + 'M 86 14 L 14 86', + 'M 86 32 L 32 86', + 'M 68 14 L 14 68', + 'M 86 50 L 50 86', + 'M 50 14 L 14 50', + ], + }, + { + id: 'zigzag', + name: '锯齿线', + category: 'line', + tags: ['折线'], + render: 'stroke', + paths: ['M 14 18 L 26 82 L 38 18 L 50 82 L 62 18 L 74 82 L 86 18'], + }, + { + id: 'loop-single', + name: '回字圈', + category: 'shape', + tags: ['形状'], + render: 'stroke', + paths: [ + 'M 14 14 L 86 14 L 86 86 L 14 86 Z', + 'M 28 28 L 72 28 L 72 72 L 28 72 Z', + 'M 42 42 L 58 42 L 58 58 L 42 58 Z', + ], + }, + { + id: 'spiral', + name: '蜗牛线', + category: 'combo', + tags: ['组合'], + render: 'stroke', + strokeScale: 0.95, + paths: [ + 'M 88 50 Q 88 86, 50 86 Q 14 86, 14 50 Q 14 18, 50 18 Q 80 18, 80 50 Q 80 74, 50 74 Q 26 74, 26 50 Q 26 30, 50 30 Q 68 30, 68 50 Q 68 62, 50 62 Q 38 62, 38 50 Q 38 44, 50 44 Q 56 44, 56 50', + ], + }, + { + id: 'mountain', + name: '山峰线', + category: 'line', + tags: ['折线'], + render: 'stroke', + paths: ['M 14 76 L 32 28 L 50 64 L 68 28 L 86 76'], + }, + { + id: 'stair', + name: '台阶线', + category: 'line', + tags: ['折线'], + render: 'stroke', + paths: [ + 'M 14 58 L 14 43 L 38 43 L 38 28 L 62 28 L 62 13 L 86 13', + 'M 14 88 L 14 73 L 38 73 L 38 58 L 62 58 L 62 43 L 86 43', + ], + }, + { + id: 'corner-turn', + name: '转折线', + category: 'line', + tags: ['折线'], + render: 'stroke', + paths: [ + 'M 86 86 L 14 86 L 14 14 L 86 14 L 86 74 L 26 74 L 26 26 L 74 26 L 74 62 L 38 62 L 38 38 L 62 38 L 62 50', + ], + }, + { + id: 'wave', + name: '波浪线', + category: 'curve', + tags: ['曲线'], + render: 'stroke', + paths: ['M 14 50 Q 32 18, 50 50 Q 68 82, 86 50'], + }, + { + id: 'horizontal-curve', + name: '横曲线', + category: 'curve', + tags: ['曲线'], + render: 'stroke', + paths: [ + 'M 14 24 Q 20 8, 26 24 Q 32 40, 38 24 Q 44 8, 50 24 Q 56 40, 62 24 Q 68 8, 74 24 Q 80 40, 86 24', + 'M 14 50 Q 20 34, 26 50 Q 32 66, 38 50 Q 44 34, 50 50 Q 56 66, 62 50 Q 68 34, 74 50 Q 80 66, 86 50', + 'M 14 76 Q 20 60, 26 76 Q 32 92, 38 76 Q 44 60, 50 76 Q 56 92, 62 76 Q 68 60, 74 76 Q 80 92, 86 76', + ], + }, + { + id: 'vertical-curve', + name: '竖曲线', + category: 'curve', + tags: ['曲线'], + render: 'stroke', + paths: [ + 'M 24 14 Q 8 20, 24 26 Q 40 32, 24 38 Q 8 44, 24 50 Q 40 56, 24 62 Q 8 68, 24 74 Q 40 80, 24 86', + 'M 50 14 Q 34 20, 50 26 Q 66 32, 50 38 Q 34 44, 50 50 Q 66 56, 50 62 Q 34 68, 50 74 Q 66 80, 50 86', + 'M 76 14 Q 60 20, 76 26 Q 92 32, 76 38 Q 60 44, 76 50 Q 92 56, 76 62 Q 60 68, 76 74 Q 92 80, 76 86', + ], + }, + { + id: 's-curve', + name: 'S 弯', + category: 'curve', + tags: ['曲线'], + render: 'stroke', + strokeScale: 1.1, + paths: ['M 80 16 Q 18 18, 50 50 Q 82 82, 20 84'], + }, + { + id: 'arc-up', + name: '左弧线', + category: 'curve', + tags: ['弧线'], + render: 'stroke', + paths: [ + 'M 52 13 Q 13 13, 13 52', + 'M 69 30 Q 30 30, 30 69', + 'M 86 47 Q 47 47, 47 86', + ], + }, + { + id: 'arc-down', + name: '右弧线', + category: 'curve', + tags: ['弧线'], + render: 'stroke', + paths: [ + 'M 13 52 Q 52 52, 52 13', + 'M 30 69 Q 69 69, 69 30', + 'M 47 86 Q 86 86, 86 47', + ], + }, + + { + id: 'figure-eight', + name: '长8线', + category: 'combo', + tags: ['组合'], + render: 'stroke', + strokeScale: 0.95, + paths: [ + 'M 20 50 C 10 41, 13 20, 27 13 C 41 6, 42 28, 34 40 C 30 45, 25 49, 20 50 C 30 59, 27 80, 14 87 C 5 92, 5 72, 8 60 C 11 55, 15 51, 20 50', + 'M 50 50 C 40 41, 43 20, 57 13 C 71 6, 72 28, 64 40 C 60 45, 55 49, 50 50 C 60 59, 57 80, 43 87 C 32 92, 31 72, 36 60 C 40 55, 45 51, 50 50', + 'M 80 50 C 70 41, 73 20, 87 13 C 99 7, 100 28, 94 40 C 90 45, 85 49, 80 50 C 90 59, 87 80, 73 87 C 62 92, 61 72, 66 60 C 70 55, 75 51, 80 50', + ], + }, + { + id: 'cross-hatch', + name: '小太阳', + category: 'combo', + tags: ['组合'], + render: 'stroke', + strokeScale: 0.9, + paths: [ + 'M 62 50 C 62 56.6, 56.6 62, 50 62 C 43.4 62, 38 56.6, 38 50 C 38 43.4, 43.4 38, 50 38 C 56.6 38, 62 43.4, 62 50 Z', + 'M 50 30 L 50 12', + 'M 50 70 L 50 88', + 'M 30 50 L 12 50', + 'M 70 50 L 88 50', + 'M 36 36 L 22 22', + 'M 64 36 L 78 22', + 'M 36 64 L 22 78', + 'M 64 64 L 78 78', + ], + }, +]; + +export const PEN_CONTROL_PATTERNS: PenControlPattern[] = + RAW_PEN_CONTROL_PATTERNS.map((p) => ({ + ...p, + categoryLabel: PEN_CONTROL_CATEGORY_LABEL[p.category], + })); + +const PATTERN_BY_ID = Object.fromEntries( + PEN_CONTROL_PATTERNS.map((p) => [p.id, p]), +) as Record; + +export function getPenControlPattern( + id: string, +): PenControlPattern | undefined { + return PATTERN_BY_ID[id]; +} + +export const PEN_CONTROL_PATTERN_IDS = PEN_CONTROL_PATTERNS.map((p) => p.id); + +export function pickRandomPatternIds( + count: number, + exclude: string[] = [], +): string[] { + const pool = PEN_CONTROL_PATTERNS.map((p) => p.id).filter( + (id) => !exclude.includes(id), + ); + const n = Math.max(0, Math.min(count, pool.length)); + const shuffled = [...pool].sort(() => Math.random() - 0.5); + return shuffled.slice(0, n); +} diff --git a/miniprogram/chinesePages/penControlSheet/draw/penControlDrawService.ts b/miniprogram/chinesePages/penControlSheet/draw/penControlDrawService.ts new file mode 100644 index 0000000..bf81c4b --- /dev/null +++ b/miniprogram/chinesePages/penControlSheet/draw/penControlDrawService.ts @@ -0,0 +1,169 @@ +import { BaseDrawService } from '../../../core/draw/baseDraw'; +import { drawTianZiGrid } from '../../shared/drawUtils'; +import { getPenControlPattern } from '../data/penControlPatterns'; +import type { PenControlSheetData } from '../generators/penControlGenerator'; +import { + drawPenControlInCell, + type PenControlCellStyle, +} from '../shared/penControlDrawUtils'; + +/** 控笔练习固定 8 列 × 10 行 */ +const LAYOUT = { + topGap: 17, + leftMargin: 40, + rightMargin: 40, + bottomMargin: 40, + targetCols: 8, + targetRows: 10, + minColGap: 6, + rowGap: 8, +} as const; + +export default class PenControlDrawService extends BaseDrawService { + constructor( + canvas: Canvas, + ctx: RenderingContext, + options?: Record, + ) { + super(canvas, ctx, { + title: '控笔组合练习', + subTitle: '每种图形占两行田字格', + ...options, + }); + } + + getMaxGridLayout(): { maxRow: number; maxCol: number } { + return { maxRow: LAYOUT.targetRows, maxCol: LAYOUT.targetCols }; + } + + computeSheetLayout(maxRow: number, maxCol: number) { + const content = this.getContentRect(); + const { minColGap, rowGap } = LAYOUT; + + const cellByWidth = + (content.width - (maxCol - 1) * minColGap) / maxCol; + const cellByHeight = + (content.height - (maxRow - 1) * rowGap) / maxRow; + const cellSize = Math.max( + 16, + Math.floor(Math.min(cellByWidth, cellByHeight)), + ); + + const actualColGap = + maxCol > 1 + ? (content.width - maxCol * cellSize) / (maxCol - 1) + : 0; + const actualRowGap = + maxRow > 1 + ? Math.min( + rowGap, + (content.height - maxRow * cellSize) / (maxRow - 1), + ) + : 0; + + return { + maxRow, + maxCol, + cellSize, + startX: content.left, + startY: content.top, + colGap: actualColGap, + rowGap: actualRowGap, + }; + } + + async draw(data: PenControlSheetData) { + this.prepareDraw(); + await this.drawHeaderAndDivider(); + const layout = this.computeSheetLayout( + data.layout.maxRow, + data.layout.maxCol, + ); + this.drawSheet({ ...data, layout }); + this.drawPrintFooter(); + } + + private getContentRect() { + const topGap = LAYOUT.topGap; + const leftMargin = LAYOUT.leftMargin; + const rightMargin = LAYOUT.rightMargin; + const bottomMargin = LAYOUT.bottomMargin; + const contentTop = this.currentY + topGap; + const contentWidth = this.canvasWidth - leftMargin - rightMargin; + const contentHeight = + this.canvasHeight - contentTop - bottomMargin; + + return { + top: contentTop, + left: leftMargin, + width: contentWidth, + height: contentHeight, + }; + } + + private drawSheet(data: PenControlSheetData) { + const { ctx } = this; + const { layout, blocks } = data; + const { + startX, + startY, + cellSize, + colGap, + rowGap, + maxCol, + maxRow, + } = layout; + + for (let row = 0; row < maxRow; row++) { + for (let col = 0; col < maxCol; col++) { + const cx = startX + col * (cellSize + colGap) + cellSize / 2; + const cy = startY + row * (cellSize + rowGap) + cellSize / 2; + drawTianZiGrid({ ctx, cx, cy, size: cellSize }); + } + } + + let globalRow = 0; + for (const block of blocks) { + for (const rowSpec of block.rows) { + if (globalRow >= maxRow) break; + + const pattern = getPenControlPattern(block.patternId); + if (pattern) { + for (let col = 0; col < maxCol; col++) { + const cx = + startX + col * (cellSize + colGap) + cellSize / 2; + const cy = + startY + + globalRow * (cellSize + rowGap) + + cellSize / 2; + const style: PenControlCellStyle = + rowSpec.cells[col] ?? 'guide'; + const dashed = + rowSpec.role === 'pair-second' && col >= 1; + drawPenControlInCell( + ctx, + pattern, + cx, + cy, + cellSize, + style, + { dashed }, + ); + } + } + + globalRow++; + } + + if (globalRow >= maxRow) break; + } + } + + /** 供页面在绘制前计算 layout */ + buildLayoutForGenerate(): ReturnType< + PenControlDrawService['computeSheetLayout'] + > { + const { maxRow, maxCol } = this.getMaxGridLayout(); + return this.computeSheetLayout(maxRow, maxCol); + } +} diff --git a/miniprogram/chinesePages/penControlSheet/generators/penControlGenerator.ts b/miniprogram/chinesePages/penControlSheet/generators/penControlGenerator.ts new file mode 100644 index 0000000..25cd82f --- /dev/null +++ b/miniprogram/chinesePages/penControlSheet/generators/penControlGenerator.ts @@ -0,0 +1,123 @@ +import { + getPenControlPattern, + PEN_CONTROL_PATTERN_IDS, +} from '../data/penControlPatterns'; +import type { PenControlCellStyle } from '../shared/penControlDrawUtils'; + +export const PEN_CONTROL_MODE = 'pen-control-mix' as const; +export type PenControlMode = typeof PEN_CONTROL_MODE; + +export const ROWS_PER_PATTERN = 2; +export const MIN_PATTERNS = 0; +export const MAX_PATTERNS = 5; +export const DEFAULT_PATTERN_COUNT = 5; + +export interface PenControlRowSpec { + role: 'pair-first' | 'pair-second'; + cells: PenControlCellStyle[]; +} + +export interface PenControlPatternBlock { + patternId: string; + patternName: string; + rows: [PenControlRowSpec, PenControlRowSpec]; +} + +export interface PenControlSheetLayout { + maxRow: number; + maxCol: number; + cellSize: number; + startX: number; + startY: number; + colGap: number; + rowGap: number; +} + +export interface PenControlSheetData { + mode: PenControlMode; + patternCount: number; + blocks: PenControlPatternBlock[]; + layout: PenControlSheetLayout; +} + +export function clampPatternCount( + requested: number, + maxRow: number, +): number { + const capByRows = Math.floor(maxRow / ROWS_PER_PATTERN); + const capped = Math.min( + MAX_PATTERNS, + Math.max(MIN_PATTERNS, requested), + Math.max(MIN_PATTERNS, capByRows), + ); + return capped; +} + +function buildRowCells( + maxCol: number, + _role: 'pair-first' | 'pair-second', +): PenControlCellStyle[] { + const cells: PenControlCellStyle[] = []; + for (let c = 0; c < maxCol; c++) { + cells.push(c === 0 ? 'reference' : 'guide'); + } + return cells; +} + +function normalizePatternIds( + patternIds: string[], + patternCount: number, +): string[] { + const valid = patternIds.filter((id) => + PEN_CONTROL_PATTERN_IDS.includes(id), + ); + const unique: string[] = []; + for (const id of valid) { + if (!unique.includes(id)) unique.push(id); + } + + if (unique.length >= patternCount) { + return unique.slice(0, patternCount); + } + + const rest = PEN_CONTROL_PATTERN_IDS.filter((id) => !unique.includes(id)); + const shuffled = [...rest].sort(() => Math.random() - 0.5); + while (unique.length < patternCount && shuffled.length > 0) { + unique.push(shuffled.shift()!); + } + return unique.slice(0, patternCount); +} + +export function generatePenControlMix(options: { + patternIds: string[]; + patternCount: number; + layout: PenControlSheetLayout; +}): PenControlSheetData { + const { layout } = options; + const patternCount = clampPatternCount( + options.patternCount, + layout.maxRow, + ); + const ids = normalizePatternIds(options.patternIds, patternCount); + + const blocks: PenControlPatternBlock[] = ids.map((patternId) => { + const pattern = getPenControlPattern(patternId); + const pairFirst = buildRowCells(layout.maxCol, 'pair-first'); + const pairSecond = buildRowCells(layout.maxCol, 'pair-second'); + return { + patternId, + patternName: pattern?.name ?? patternId, + rows: [ + { role: 'pair-first', cells: pairFirst }, + { role: 'pair-second', cells: pairSecond }, + ], + }; + }); + + return { + mode: PEN_CONTROL_MODE, + patternCount: blocks.length, + blocks, + layout, + }; +} diff --git a/miniprogram/chinesePages/penControlSheet/penControlSheet.config.ts b/miniprogram/chinesePages/penControlSheet/penControlSheet.config.ts new file mode 100644 index 0000000..9b87340 --- /dev/null +++ b/miniprogram/chinesePages/penControlSheet/penControlSheet.config.ts @@ -0,0 +1,69 @@ +import type { DebugPublishMeta } from '../../utils/debugPublish'; +import { inferGradeFromAge } from '../../utils/debugPublish'; +import { PEN_CONTROL_MODE } from './generators/penControlGenerator'; + +interface PenControlWorksheetDefinition { + id: typeof PEN_CONTROL_MODE; + icon: string; + title: string; + subtitle: string; + ageMin: number; + ageMax: number; + difficulty: 1 | 2 | 3 | 4; + tags: string[]; + sortOrder: number; +} + +export const PEN_CONTROL_WORKSHEET_DEFINITIONS = [ + { + id: PEN_CONTROL_MODE, + icon: 'edit', + title: '控笔组合练习', + subtitle: '3–6 种图形,每种占两行田字格', + ageMin: 3, + ageMax: 6, + difficulty: 1, + tags: ['控笔', '运笔', '田字格', '学前'], + sortOrder: 40, + }, +] as const satisfies ReadonlyArray; + +type PenControlWorksheetRow = (typeof PEN_CONTROL_WORKSHEET_DEFINITIONS)[number]; + +const WORKSHEET_BY_ID = Object.fromEntries( + PEN_CONTROL_WORKSHEET_DEFINITIONS.map((m) => [m.id, m]), +) as Record; + +export const PEN_CONTROL_WORKSHEET_ID = PEN_CONTROL_WORKSHEET_DEFINITIONS[0].id; + +export function getModeInfo(id: string) { + const m = WORKSHEET_BY_ID[id]; + return m ? { title: m.title, desc: m.subtitle } : undefined; +} + +export function isValidMode(id: string): boolean { + return id in WORKSHEET_BY_ID; +} + +export function getPublishMetaByMode(id: string): DebugPublishMeta | null { + const m = WORKSHEET_BY_ID[id]; + if (!m) return null; + return { + id: m.id, + title: m.title, + subtitle: m.subtitle, + category: 'chinese', + subcategory: 'pen-control', + path: `/chinesePages/penControlSheet/penControlSheet?id=${m.id}`, + ageMin: m.ageMin, + ageMax: m.ageMax, + grade: inferGradeFromAge(m.ageMin, m.ageMax), + difficulty: m.difficulty, + previewImg: '', + tags: [...m.tags], + isNew: true, + isHot: false, + sortOrder: m.sortOrder, + status: 'draft', + }; +} diff --git a/miniprogram/chinesePages/penControlSheet/penControlSheet.json b/miniprogram/chinesePages/penControlSheet/penControlSheet.json new file mode 100644 index 0000000..091b342 --- /dev/null +++ b/miniprogram/chinesePages/penControlSheet/penControlSheet.json @@ -0,0 +1,16 @@ +{ + "navigationStyle": "custom", + "navigationBarTitleText": "控笔组合练习", + "navigationBarBackgroundColor": "#F8F0E0", + "navigationBarTextStyle": "black", + "backgroundColor": "#FEF6E7", + "enablePullDownRefresh": false, + "usingComponents": { + "nav-bar": "../../components3.0/nav-bar/nav-bar", + "share-guide-popup": "../../components/share-guide-popup/share-guide-popup", + "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" + } +} diff --git a/miniprogram/chinesePages/penControlSheet/penControlSheet.less b/miniprogram/chinesePages/penControlSheet/penControlSheet.less new file mode 100644 index 0000000..dabe4b3 --- /dev/null +++ b/miniprogram/chinesePages/penControlSheet/penControlSheet.less @@ -0,0 +1,123 @@ +@import '../../style/theme.less'; + +page { + background-color: @bg-page; +} + +.pc-page { + min-height: 100vh; + padding: 0 @page-padding-x; + padding-bottom: calc(200rpx + env(safe-area-inset-bottom)); + box-sizing: border-box; +} + +.pc-main { + padding-top: 24rpx; + display: flex; + flex-direction: column; + gap: 32rpx; +} + +.pc-section { + display: flex; + flex-direction: column; + gap: 20rpx; +} + +.pc-section-header { + display: flex; + align-items: center; + justify-content: space-between; +} + +.pc-section-title { + font-size: 30rpx; + font-weight: 700; + color: #6d3b00; + padding-left: 8rpx; +} + +.pc-pattern-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 24rpx; +} + +.pc-pattern-card { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 8rpx; + padding: 28rpx 12rpx; + border-radius: @radius-lg; + background: rgba(255, 255, 255, 0.6); + border: 4rpx solid rgba(179, 172, 159, 0.1); + box-shadow: @shadow; + transition: transform 0.12s, box-shadow 0.12s, border-color 0.12s, + background 0.12s; +} + +.pc-pattern-card--active { + background: #ffffff; + border-color: @brand; + box-shadow: @shadow; +} + +.pc-pattern-card--pressed { + transform: scale(0.95); +} + +.pc-pattern-card__name { + font-size: 28rpx; + font-weight: 700; + color: @text-title; + line-height: 1.3; +} + +.pc-pattern-card__cat { + font-size: 20rpx; + color: @text-secondary; + text-align: center; + line-height: 1.3; + text-transform: capitalize; +} + +.pc-pattern-card--active .pc-pattern-card__name { + color: @text-title; +} + +.pc-pattern-card__check { + position: absolute; + top: 50%; + left: 18rpx; + transform: translateY(-50%); + display: flex; + align-items: center; + justify-content: center; + width: 32rpx; + height: 32rpx; + border-radius: 50%; + background-color: #9cd343; +} + +.pc-shuffle-btn { + display: flex; + align-items: center; + justify-content: center; + gap: 12rpx; + padding: 24rpx; + border-radius: 24rpx; + background: #f8f0e0; +} + +.pc-shuffle-btn--hover { + opacity: 0.85; +} + +.pc-shuffle-btn__text { + font-size: 28rpx; + font-weight: 600; + color: @text-secondary; +} \ No newline at end of file diff --git a/miniprogram/chinesePages/penControlSheet/penControlSheet.ts b/miniprogram/chinesePages/penControlSheet/penControlSheet.ts new file mode 100644 index 0000000..9b3118b --- /dev/null +++ b/miniprogram/chinesePages/penControlSheet/penControlSheet.ts @@ -0,0 +1,235 @@ +import PenControlDrawService from './draw/penControlDrawService'; +import { + PEN_CONTROL_PATTERNS, + pickRandomPatternIds, +} from './data/penControlPatterns'; +import { + DEFAULT_PATTERN_COUNT, + generatePenControlMix, + MAX_PATTERNS, +} from './generators/penControlGenerator'; +import { + getModeInfo, + getPublishMetaByMode, + isValidMode, + PEN_CONTROL_WORKSHEET_ID, +} from './penControlSheet.config'; +import { createPage, type CanvasDataState } from '../../base/pageMixin'; +import { defaultShareConfig } from '../../config/config'; +import type { DebugPublishMeta } from '../../utils/debugPublish'; +import { + addFavorite, + removeFavorite, + batchCheckFavorited, +} from '../../utils/favorites'; + +const pageInfoLookup = getModeInfo; + +function pickRandomPatternSet(count: number, current: string[]): string[] { + const next = pickRandomPatternIds(count, current); + if (next.length >= count) return next.slice(0, count); + return pickRandomPatternIds(count); +} + +function buildSelectedMap(ids: string[]): Record { + const map: Record = {}; + for (const id of ids) map[id] = true; + return map; +} + +type PageData = CanvasDataState & { + worksheetId: string; + selectedPatternIds: string[]; + selectedMap: Record; + patternList: typeof PEN_CONTROL_PATTERNS; + isPreviewFavorite: boolean; + isDevEnv: boolean; + debugPublishVisible: boolean; + debugPublishLoading: boolean; + debugPublishMeta: DebugPublishMeta | null; +}; + +createPage( + { + canvas: null as Canvas | null, + ctx: null as RenderingContext | null, + boxHeight: 0, + boxWidth: 0, + drawService: null as PenControlDrawService | null, + _favoritedMap: {} as Record, + + data: { + pageTitle: '控笔组合练习', + functionId: PEN_CONTROL_WORKSHEET_ID, + hasContent: false, + showShareDialog: false, + worksheetId: PEN_CONTROL_WORKSHEET_ID, + selectedPatternIds: [] as string[], + selectedMap: {} as Record, + patternList: PEN_CONTROL_PATTERNS, + isPreviewFavorite: false, + isDevEnv: false, + debugPublishVisible: false, + debugPublishLoading: false, + debugPublishMeta: null, + } as unknown as PageData, + + onLoad(options: { id?: string }) { + this.syncDebugPublishEnv(); + console.log('PEN_CONTROL_PATTERNS', PEN_CONTROL_PATTERNS); + const worksheetId = + options.id && isValidMode(options.id) + ? options.id + : PEN_CONTROL_WORKSHEET_ID; + + const initialPatterns = PEN_CONTROL_PATTERNS.slice( + 0, + DEFAULT_PATTERN_COUNT, + ).map((p) => p.id); + + console.log('initialPatterns', initialPatterns); + + this.setData({ + worksheetId, + functionId: worksheetId, + selectedPatternIds: initialPatterns, + selectedMap: buildSelectedMap(initialPatterns), + }); + + this.initPageInfo(worksheetId, '控笔练习'); + this.loadFavoritedMap(); + }, + + onCanvasReady(e: WechatMiniprogram.CustomEvent) { + this.initCanvasFromComponent(e.detail, { + createDrawService: ( + canvas: Canvas, + ctx: RenderingContext, + opts?: Record, + ) => new PenControlDrawService(canvas, ctx, opts), + drawServiceOptions: { + title: this.data.pageTitle, + }, + onCanvasReady: () => { + this.drawCanvas(); + }, + }); + }, + + async drawCanvas() { + if (!this.drawService) return; + + try { + const layout = ( + this.drawService as PenControlDrawService + ).buildLayoutForGenerate(); + const data = generatePenControlMix({ + patternIds: this.data.selectedPatternIds, + patternCount: this.data.selectedPatternIds.length, + layout, + }); + + const ids = data.blocks.map((b) => b.patternId); + if (ids.join(',') !== this.data.selectedPatternIds.join(',')) { + this.setData({ + selectedPatternIds: ids, + selectedMap: buildSelectedMap(ids), + }); + } + + await (this.drawService as PenControlDrawService).draw(data); + this.setData({ hasContent: true }); + } catch (e) { + console.error('penControlSheet draw failed', e); + this.setData({ hasContent: false }); + } + }, + + onTogglePattern(e: WechatMiniprogram.TouchEvent) { + const id = e.currentTarget.dataset.id as string; + if (!id) return; + + let ids = [...this.data.selectedPatternIds]; + const idx = ids.indexOf(id); + + if (idx >= 0) { + ids.splice(idx, 1); + } else { + if (ids.length >= MAX_PATTERNS) { + wx.showToast({ + title: `最多选择 ${MAX_PATTERNS} 个`, + icon: 'none', + }); + return; + } + ids.push(id); + } + + this.setData( + { + selectedPatternIds: ids, + selectedMap: buildSelectedMap(ids), + }, + () => this.drawCanvas(), + ); + }, + + onPreviewRefresh() { + const count = + this.data.selectedPatternIds.length || DEFAULT_PATTERN_COUNT; + const next = pickRandomPatternSet( + count, + this.data.selectedPatternIds, + ); + this.setData( + { + selectedPatternIds: next, + selectedMap: buildSelectedMap(next), + }, + () => this.drawCanvas(), + ); + }, + + onShufflePatterns() { + this.onPreviewRefresh(); + }, + + async onPreviewFavorite() { + const next = !this.data.isPreviewFavorite; + this.setData({ isPreviewFavorite: next }); + const id = this.data.worksheetId; + if (id) { + this._favoritedMap[id] = next; + if (next) { + addFavorite(id); + } else { + removeFavorite(id); + } + } + wx.showToast({ + title: next ? '收藏成功' : '已取消收藏', + icon: 'none', + }); + }, + + async loadFavoritedMap() { + const ids = [PEN_CONTROL_WORKSHEET_ID]; + this._favoritedMap = await batchCheckFavorited(ids); + if (this._favoritedMap[this.data.worksheetId]) { + this.setData({ isPreviewFavorite: true }); + } + }, + + getPublishMeta(): DebugPublishMeta { + const meta = getPublishMetaByMode(this.data.worksheetId); + if (!meta) { + throw new Error('当前题型配置不存在'); + } + return meta; + }, + }, + { + shareConfig: defaultShareConfig, + pageInfoLookup, + }, +); diff --git a/miniprogram/chinesePages/penControlSheet/penControlSheet.wxml b/miniprogram/chinesePages/penControlSheet/penControlSheet.wxml new file mode 100644 index 0000000..3696a1f --- /dev/null +++ b/miniprogram/chinesePages/penControlSheet/penControlSheet.wxml @@ -0,0 +1,77 @@ + + + + + + + + + 选择图形 ({{selectedPatternIds.length}}/5) + + + + + + + {{item.name}} + {{item.categoryLabel}} + + + + + + + 换一批图形 + + + + + + + + + diff --git a/miniprogram/chinesePages/penControlSheet/shared/penControlDrawUtils.ts b/miniprogram/chinesePages/penControlSheet/shared/penControlDrawUtils.ts new file mode 100644 index 0000000..6ff7ef3 --- /dev/null +++ b/miniprogram/chinesePages/penControlSheet/shared/penControlDrawUtils.ts @@ -0,0 +1,103 @@ +import { TRACING_COLORS } from '../../../core/data/tracingStyles'; +import { drawSvgPath } from '../../shared/drawUtils'; +import type { + PenControlPattern, + PenControlRenderMode, +} from '../data/penControlPatterns'; + +export const PEN_CONTROL_VIEW_SIZE = 100; + +export interface PenControlTransform { + xOffset: number; + yOffset: number; + scale: number; +} + +export function getPenControlTransform( + width: number, + height: number, + padding: number, +): PenControlTransform { + const availableWidth = width - 2 * padding; + const availableHeight = height - 2 * padding; + const scale = Math.min( + availableWidth / PEN_CONTROL_VIEW_SIZE, + availableHeight / PEN_CONTROL_VIEW_SIZE, + ); + const scaledW = PEN_CONTROL_VIEW_SIZE * scale; + const scaledH = PEN_CONTROL_VIEW_SIZE * scale; + const xOffset = padding + (availableWidth - scaledW) / 2; + const yOffset = padding + (availableHeight - scaledH) / 2; + return { xOffset, yOffset, scale }; +} + +export type PenControlCellStyle = 'reference' | 'guide'; + +function colorForStyle(style: PenControlCellStyle): string { + return style === 'reference' ? TRACING_COLORS.strong : TRACING_COLORS.guide; +} + +function drawPathWithMode( + ctx: RenderingContext, + pathD: string, + render: PenControlRenderMode, + color: string, + lineWidth: number, + dashed: boolean, +) { + ctx.beginPath(); + drawSvgPath(ctx, pathD); + + if (render === 'fill' || render === 'both') { + ctx.fillStyle = color; + ctx.fill(); + } + if (render === 'stroke' || render === 'both') { + ctx.strokeStyle = color; + ctx.lineWidth = lineWidth; + if (dashed) { + // setLineDash 接收一个数组,表示虚线的样式。 + // 第一个参数 (lineWidth * 2.6):表示每段实线的长度 + // 第二个参数 (lineWidth * 2.4):表示每段虚线的间隙长度 + // 旧虚线效果不明显,调整为更清晰的虚线样式 + ctx.setLineDash([lineWidth * 1.8, lineWidth * 2.3]); + } else { + ctx.setLineDash([]); + } + ctx.stroke(); + } +} + +/** + * 在田字格中心绘制控笔图形 + */ +export function drawPenControlInCell( + ctx: RenderingContext, + pattern: PenControlPattern, + cx: number, + cy: number, + cellSize: number, + style: PenControlCellStyle, + options?: { dashed?: boolean }, +) { + const color = colorForStyle(style); + const padding = cellSize * 0.1; + const transform = getPenControlTransform(cellSize, cellSize, padding); + const lineWidth = + (cellSize * 0.0236 * (pattern.strokeScale ?? 1)) / transform.scale; + const dashed = options?.dashed === true; + + ctx.save(); + ctx.lineCap = 'round'; + ctx.lineJoin = 'round'; + ctx.translate(cx - cellSize / 2, cy - cellSize / 2); + ctx.translate(transform.xOffset, transform.yOffset); + ctx.scale(transform.scale, transform.scale); + + for (const pathD of pattern.paths) { + drawPathWithMode(ctx, pathD, pattern.render, color, lineWidth, dashed); + } + + ctx.setLineDash([]); + ctx.restore(); +} diff --git a/miniprogram/chinesePages/shared/drawUtils.ts b/miniprogram/chinesePages/shared/drawUtils.ts index f1d763f..435644b 100644 --- a/miniprogram/chinesePages/shared/drawUtils.ts +++ b/miniprogram/chinesePages/shared/drawUtils.ts @@ -42,10 +42,10 @@ export function getScalingTransform( } /** - * SVG 路径解析与绘制(支持 M/L/Q/Z 命令) + * SVG 路径解析与绘制(支持 M/L/Q/C/Z 命令) */ export function drawSvgPath(ctx: RenderingContext, pathD: string) { - const commandRegex = /([MLQZ])([^MLQZ]*?)(?=[MLQZ]|$)/gi; + const commandRegex = /([MLQCZ])([^MLQCZ]*?)(?=[MLQCZ]|$)/gi; const commands: Array<{ cmd: string; coords: string }> = []; let match; while ((match = commandRegex.exec(pathD)) !== null) { @@ -69,6 +69,11 @@ export function drawSvgPath(ctx: RenderingContext, pathD: string) { case 'Q': if (v.length >= 4) ctx.quadraticCurveTo(v[0], v[1], v[2], v[3]); break; + case 'C': + if (v.length >= 6) { + ctx.bezierCurveTo(v[0], v[1], v[2], v[3], v[4], v[5]); + } + break; case 'Z': ctx.closePath(); break; diff --git a/miniprogram/config/config.ts b/miniprogram/config/config.ts index 51d921e..1f03e9b 100644 --- a/miniprogram/config/config.ts +++ b/miniprogram/config/config.ts @@ -32,4 +32,4 @@ export const downloadFreeBatchSize = 4; * - develop:为 true 时启用下载配额/分享/广告(便于联调);为 false 时不限制 * - 体验版 / 正式版:始终启用,不受此开关影响 */ -export const enableDownloadLimitInDevelop = true; +export const enableDownloadLimitInDevelop = false; diff --git a/miniprogram/focusPages/focusDraw/focusDraw.config.ts b/miniprogram/focusPages/focusDraw/focusDraw.config.ts index 923b6a6..f1d6fcb 100644 --- a/miniprogram/focusPages/focusDraw/focusDraw.config.ts +++ b/miniprogram/focusPages/focusDraw/focusDraw.config.ts @@ -136,7 +136,8 @@ export const FOCUS_WORKSHEET_DEFINITIONS = [ }, ] as const satisfies ReadonlyArray; -type FocusWorksheetDefinitionItem = (typeof FOCUS_WORKSHEET_DEFINITIONS)[number]; +type FocusWorksheetDefinitionItem = + (typeof FOCUS_WORKSHEET_DEFINITIONS)[number]; const FOCUS_WORKSHEET_BY_ID = Object.fromEntries( FOCUS_WORKSHEET_DEFINITIONS.map((item) => [item.id, item]), diff --git a/miniprogram/focusPages/focusDraw/focusDraw.ts b/miniprogram/focusPages/focusDraw/focusDraw.ts index dbbbbfb..f44ed25 100644 --- a/miniprogram/focusPages/focusDraw/focusDraw.ts +++ b/miniprogram/focusPages/focusDraw/focusDraw.ts @@ -6,9 +6,16 @@ import { type FocusTypeConfig, type FocusTypeAction, } from './registry'; -import { getPublishMetaByFocusState, FOCUS_WORKSHEET_DEFINITIONS } from './focusDraw.config'; +import { + getPublishMetaByFocusState, + FOCUS_WORKSHEET_DEFINITIONS, +} from './focusDraw.config'; import type { DebugPublishMeta } from '../../utils/debugPublish'; -import { addFavorite, removeFavorite, batchCheckFavorited } from '../../utils/favorites'; +import { + addFavorite, + removeFavorite, + batchCheckFavorited, +} from '../../utils/favorites'; const TYPE_LIST = FOCUS_TYPE_CONFIGS.map((t) => ({ id: t.id, @@ -124,9 +131,10 @@ createFocusPage({ selectedTypeId: id, functionId: id, pageTitle: title, - isPreviewFavorite: !!this._favoritedMap[ - getPublishMetaByFocusState(id, id, mode)?.id || id - ], + isPreviewFavorite: + !!this._favoritedMap[ + getPublishMetaByFocusState(id, id, mode)?.id || id + ], showActions: !!typeConfig.actions, currentActions: typeConfig.actions || [], actionsTitle: typeConfig.actionsTitle || '选择模式', @@ -163,7 +171,8 @@ createFocusPage({ this.setData({ currentMode: value, pageTitle: title, - isPreviewFavorite: !!this._favoritedMap[this.getWorksheetStatsIdFor(value)], + isPreviewFavorite: + !!this._favoritedMap[this.getWorksheetStatsIdFor(value)], }); this.initPageInfo(this.data.functionId, title); diff --git a/miniprogram/mathPages/mathDraw/mathDraw.ts b/miniprogram/mathPages/mathDraw/mathDraw.ts index 1a106da..5190200 100644 --- a/miniprogram/mathPages/mathDraw/mathDraw.ts +++ b/miniprogram/mathPages/mathDraw/mathDraw.ts @@ -6,9 +6,16 @@ import { type MathTypeConfig, type MathTypeAction, } from './registry'; -import { getPublishMetaByMathState, MATH_WORKSHEET_DEFINITIONS } from './mathDraw.config'; +import { + getPublishMetaByMathState, + MATH_WORKSHEET_DEFINITIONS, +} from './mathDraw.config'; import type { DebugPublishMeta } from '../../utils/debugPublish'; -import { addFavorite, removeFavorite, batchCheckFavorited } from '../../utils/favorites'; +import { + addFavorite, + removeFavorite, + batchCheckFavorited, +} from '../../utils/favorites'; const TYPE_LIST = MATH_TYPE_CONFIGS.map((t) => ({ id: t.id, @@ -135,9 +142,10 @@ createMathPage({ selectedTypeId: id, functionId: id, pageTitle: title, - isPreviewFavorite: !!this._favoritedMap[ - getPublishMetaByMathState(id, id, mode)?.id || id - ], + isPreviewFavorite: + !!this._favoritedMap[ + getPublishMetaByMathState(id, id, mode)?.id || id + ], showActions: !!typeConfig.actions, currentActions: typeConfig.actions || [], actionsTitle: typeConfig.actionsTitle || '选择模式', @@ -178,7 +186,8 @@ createMathPage({ this.setData({ currentMode: value, pageTitle: title, - isPreviewFavorite: !!this._favoritedMap[this.getWorksheetStatsIdFor(value)], + isPreviewFavorite: + !!this._favoritedMap[this.getWorksheetStatsIdFor(value)], }); this.initPageInfo(this.data.functionId, title); diff --git a/miniprogram/pages/age/age.config.ts b/miniprogram/pages/age/age.config.ts index 2671793..ceeac43 100644 --- a/miniprogram/pages/age/age.config.ts +++ b/miniprogram/pages/age/age.config.ts @@ -153,6 +153,12 @@ export const AGE_WEEK_PLANS: Record = { subtitle: '自定义田字格练字帖', path: '/chinesePages/handwritingSheet/handwritingSheet?id=hanzi-sheet', }, + { + _id: 'pen-control-mix', + title: '控笔组合练习', + subtitle: '3–6 种图形,每种占两行', + path: '/chinesePages/penControlSheet/penControlSheet?id=pen-control-mix', + }, { _id: 'letter-tracing-single', title: '默认字帖', diff --git a/miniprogram/pages/category/category.data.ts b/miniprogram/pages/category/category.data.ts index 96b5059..770301b 100644 --- a/miniprogram/pages/category/category.data.ts +++ b/miniprogram/pages/category/category.data.ts @@ -568,6 +568,16 @@ const CATEGORY_ITEMS_BY_ID: Record = { path: '/pages/copyBook/copyBook', available: true, }), + item({ + id: 'pen-control-mix', + title: '控笔组合练习', + subtitle: '3–6 种图形,每种占两行田字格', + icon: '✍️', + ageBand: age(3, 6), + difficulty: 1, + path: '/chinesePages/penControlSheet/penControlSheet?id=pen-control-mix', + available: true, + }), ], english: [ item({ diff --git a/miniprogram/supportPages/worksheetSync/worksheetSync.ts b/miniprogram/supportPages/worksheetSync/worksheetSync.ts index 163d261..f3eb93b 100644 --- a/miniprogram/supportPages/worksheetSync/worksheetSync.ts +++ b/miniprogram/supportPages/worksheetSync/worksheetSync.ts @@ -51,7 +51,6 @@ function inferGradeFromAge(ageMin: number, ageMax: number): number { return ageGradeMap[centerAge] ?? 0; } - type ConfigSource = { label: string; data: readonly LocalDef[]; @@ -398,9 +397,10 @@ Page({ const startedAt = Date.now(); try { - const queryRes = await callCloudFunction< - Array<{ _id: string }> - >('worksheetsQuery', {}); + const queryRes = await callCloudFunction>( + 'worksheetsQuery', + {}, + ); if (!queryRes.success || !queryRes.data) { throw new Error(queryRes.message || '查询 worksheet 列表失败'); @@ -443,8 +443,7 @@ Page({ this.setData({ statusText: text }); wx.showToast({ title: '已重置', icon: 'success' }); } catch (error) { - const msg = - error instanceof Error ? error.message : '重置失败'; + const msg = error instanceof Error ? error.message : '重置失败'; this.setData({ statusText: msg }); wx.showToast({ title: msg, icon: 'none' }); } finally { diff --git a/project.private.config.json b/project.private.config.json index 25c13c2..17b2662 100644 --- a/project.private.config.json +++ b/project.private.config.json @@ -1,148 +1,162 @@ { - "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html", - "projectname": "doodle-mini", - "setting": { - "compileHotReLoad": true, - "urlCheck": true, - "coverView": false, - "lazyloadPlaceholderEnable": false, - "skylineRenderEnable": true, - "preloadBackgroundData": false, - "autoAudits": false, - "useApiHook": true, - "useApiHostProcess": true, - "showShadowRootInWxmlPanel": false, - "useStaticServer": false, - "useLanDebug": false, - "showES6CompileOption": false, - "checkInvalidKey": true, - "ignoreDevUnusedFiles": true, - "bigPackageSizeSupport": false - }, - "libVersion": "3.7.12", - "condition": { - "miniprogram": { - "list": [ - { - "name": "chinesePages/handwritingSheet/handwritingSheet", - "pathName": "chinesePages/handwritingSheet/handwritingSheet", - "query": "id=hanzi-sheet", - "scene": null, - "launchMode": "default" - }, - { - "name": "chinesePages/handwritingSheet/handwritingSheet", - "pathName": "chinesePages/handwritingSheet/handwritingSheet", - "query": "id=hanzi-sheet", - "launchMode": "default", - "scene": null - }, - { - "name": "pinyinPages/pinyinDictation/pinyinDictation", - "pathName": "pinyinPages/pinyinDictation/pinyinDictation", - "query": "", - "launchMode": "default", - "scene": null - }, - { - "name": "chinesePages/handwritingSheet/handwritingSheet", - "pathName": "chinesePages/handwritingSheet/handwritingSheet", - "query": "", - "launchMode": "default", - "scene": null - }, - { - "name": "chinesePages/wordColoring/wordColoring", - "pathName": "chinesePages/wordColoring/wordColoring", - "query": "", - "launchMode": "default", - "scene": null - }, - { - "name": "pages/profile/profile", - "pathName": "pages/profile/profile", - "query": "", - "launchMode": "default", - "scene": null - }, - { - "name": "pages/age/age", - "pathName": "pages/age/age", - "query": "", - "launchMode": "default", - "scene": null - }, - { - "name": "supportPages/debug/debug", - "pathName": "supportPages/debug/debug", - "query": "", - "launchMode": "default", - "scene": null - }, - { - "name": "englishPages/letterTracing/letterTracing", - "pathName": "englishPages/letterTracing/letterTracing", - "query": "", - "launchMode": "default", - "scene": null - }, - { - "name": "supportPages/debug/debug", - "pathName": "supportPages/debug/debug", - "query": "", - "launchMode": "default", - "scene": null - }, - { - "name": "pages/category/category", - "pathName": "pages/category/category", - "query": "", - "launchMode": "default", - "scene": null - }, - { - "name": "mathIndex", - "pathName": "mathPages/mathDraw/mathDraw", - "query": "", - "launchMode": "default", - "scene": null - }, - { - "name": "focusDraw", - "pathName": "focusPages/focusDraw/focusDraw", - "query": "", - "launchMode": "default", - "scene": null - }, - { - "name": "oldFocusIndex", - "pathName": "supportPages/focusIndex/focusIndex", - "query": "", - "launchMode": "default", - "scene": null - }, - { - "name": "oldMathIndex", - "pathName": "supportPages/mathIndex/mathIndex", - "query": "", - "launchMode": "default", - "scene": null - }, - { - "name": "pages/index/index", - "pathName": "pages/index/index", - "query": "", - "launchMode": "default", - "scene": null - }, - { - "name": "englishPages/letterTracing/letterTracing", - "pathName": "englishPages/letterTracing/letterTracing", - "query": "id=letter-tracing-single", - "launchMode": "default", - "scene": null - } - ] + "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html", + "projectname": "doodle-mini", + "setting": { + "compileHotReLoad": true, + "urlCheck": true, + "coverView": false, + "lazyloadPlaceholderEnable": false, + "skylineRenderEnable": true, + "preloadBackgroundData": false, + "autoAudits": false, + "useApiHook": true, + "useApiHostProcess": true, + "showShadowRootInWxmlPanel": false, + "useStaticServer": false, + "useLanDebug": false, + "showES6CompileOption": false, + "checkInvalidKey": true, + "ignoreDevUnusedFiles": true, + "bigPackageSizeSupport": false + }, + "libVersion": "3.7.12", + "condition": { + "miniprogram": { + "list": [ + { + "name": "chinesePages/penControlSheet/penControlSheet", + "pathName": "chinesePages/penControlSheet/penControlSheet", + "query": "", + "scene": null, + "launchMode": "default" + }, + { + "name": "chinesePages/penControlSheet/penControlSheet?id=pen-control-mix", + "pathName": "chinesePages/penControlSheet/penControlSheet?id=pen-control-mix", + "query": "", + "launchMode": "default", + "scene": null + }, + { + "name": "chinesePages/handwritingSheet/handwritingSheet", + "pathName": "chinesePages/handwritingSheet/handwritingSheet", + "query": "id=hanzi-sheet", + "launchMode": "default", + "scene": null + }, + { + "name": "chinesePages/handwritingSheet/handwritingSheet", + "pathName": "chinesePages/handwritingSheet/handwritingSheet", + "query": "id=hanzi-sheet", + "launchMode": "default", + "scene": null + }, + { + "name": "pinyinPages/pinyinDictation/pinyinDictation", + "pathName": "pinyinPages/pinyinDictation/pinyinDictation", + "query": "", + "launchMode": "default", + "scene": null + }, + { + "name": "chinesePages/handwritingSheet/handwritingSheet", + "pathName": "chinesePages/handwritingSheet/handwritingSheet", + "query": "", + "launchMode": "default", + "scene": null + }, + { + "name": "chinesePages/wordColoring/wordColoring", + "pathName": "chinesePages/wordColoring/wordColoring", + "query": "", + "launchMode": "default", + "scene": null + }, + { + "name": "pages/profile/profile", + "pathName": "pages/profile/profile", + "query": "", + "launchMode": "default", + "scene": null + }, + { + "name": "pages/age/age", + "pathName": "pages/age/age", + "query": "", + "launchMode": "default", + "scene": null + }, + { + "name": "supportPages/debug/debug", + "pathName": "supportPages/debug/debug", + "query": "", + "launchMode": "default", + "scene": null + }, + { + "name": "englishPages/letterTracing/letterTracing", + "pathName": "englishPages/letterTracing/letterTracing", + "query": "", + "launchMode": "default", + "scene": null + }, + { + "name": "supportPages/debug/debug", + "pathName": "supportPages/debug/debug", + "query": "", + "launchMode": "default", + "scene": null + }, + { + "name": "pages/category/category", + "pathName": "pages/category/category", + "query": "", + "launchMode": "default", + "scene": null + }, + { + "name": "mathIndex", + "pathName": "mathPages/mathDraw/mathDraw", + "query": "", + "launchMode": "default", + "scene": null + }, + { + "name": "focusDraw", + "pathName": "focusPages/focusDraw/focusDraw", + "query": "", + "launchMode": "default", + "scene": null + }, + { + "name": "oldFocusIndex", + "pathName": "supportPages/focusIndex/focusIndex", + "query": "", + "launchMode": "default", + "scene": null + }, + { + "name": "oldMathIndex", + "pathName": "supportPages/mathIndex/mathIndex", + "query": "", + "launchMode": "default", + "scene": null + }, + { + "name": "pages/index/index", + "pathName": "pages/index/index", + "query": "", + "launchMode": "default", + "scene": null + }, + { + "name": "englishPages/letterTracing/letterTracing", + "pathName": "englishPages/letterTracing/letterTracing", + "query": "id=letter-tracing-single", + "launchMode": "default", + "scene": null } + ] } -} + } +} \ No newline at end of file