feat: 字母描红页样式开发基本完成

This commit is contained in:
R524809
2026-04-09 17:43:22 +08:00
parent 653737c481
commit a911da6181
59 changed files with 1043 additions and 206 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+14
View File
@@ -5,6 +5,20 @@
"css_prefix_text": "icon-",
"description": "",
"glyphs": [
{
"icon_id": "47291112",
"name": "home-o",
"font_class": "home-o",
"unicode": "e621",
"unicode_decimal": 58913
},
{
"icon_id": "47291111",
"name": "home",
"font_class": "home",
"unicode": "e622",
"unicode_decimal": 58914
},
{
"icon_id": "47282109",
"name": "setting",
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,6 +1,6 @@
import { PAPER_SIZE } from '../constants/colors';
import { downloadPrint } from '../utils/downloadPrint';
import { BaseDrawService } from '../service/baseDraw';
import { BaseDrawService } from '../core/draw/baseDraw';
import tracker from '../utils/tracker';
import { defaultShareConfig } from '../config/config';
@@ -32,6 +32,10 @@
flex-shrink: 0;
}
.nav-bar__back--hidden {
visibility: hidden;
}
.nav-bar__back-icon {
font-size: 40rpx;
color: @color-nav-title;
@@ -57,4 +61,4 @@
.nav-bar__placeholder {
width: 100%;
flex-shrink: 0;
}
}
+44 -9
View File
@@ -1,5 +1,13 @@
import { NAV_INNER_PX } from '../../utils/navMetrics';
const HOME_TAB_URL = '/pages/home/home';
const TAB_BAR_ROUTES = new Set([
'pages/home/home',
'pages/age/age',
'pages/favorites/favorites',
'pages/profile/profile',
]);
Component({
options: { multipleSlots: true },
properties: {
@@ -9,26 +17,53 @@ Component({
data: {
statusBarHeight: 0,
navBlockHeight: 0,
showBackButton: false,
leftActionType: 'none',
},
lifetimes: {
attached() {
const { statusBarHeight } = wx.getWindowInfo();
const pages = getCurrentPages();
this.setData({
statusBarHeight,
navBlockHeight: statusBarHeight + NAV_INNER_PX,
showBackButton: pages.length > 1,
});
this.syncLeftAction();
},
},
pageLifetimes: {
show() {
this.syncLeftAction();
},
},
methods: {
handleBack() {
wx.navigateBack({
fail: () => {
wx.switchTab({ url: '/pages/home/home' });
},
});
syncLeftAction() {
const pages = getCurrentPages();
const currentRoute = pages[pages.length - 1]?.route || '';
const isTabBarPage = TAB_BAR_ROUTES.has(currentRoute);
let leftActionType: 'none' | 'back' | 'home' = 'none';
if (pages.length > 1) {
leftActionType = 'back';
} else if (!isTabBarPage) {
leftActionType = 'home';
}
this.setData({ leftActionType });
},
handleLeftAction() {
if (this.data.leftActionType === 'back') {
wx.navigateBack({
fail: () => {
wx.switchTab({ url: HOME_TAB_URL });
},
});
return;
}
if (this.data.leftActionType === 'home') {
wx.switchTab({ url: HOME_TAB_URL });
}
},
},
});
+11 -5
View File
@@ -1,10 +1,16 @@
<view class="nav-bar" style="padding-top: {{statusBarHeight}}px; {{background ? 'background:' + background + ';' : ''}}">
<view
class="nav-bar"
style="padding-top: {{statusBarHeight}}px; {{background ? 'background:' + background + ';' : ''}}">
<view class="nav-bar__inner">
<view
wx:if="{{showBackButton}}"
class="nav-bar__back"
bindtap="handleBack">
<text class="toy-icon toy-icon-arrow-left nav-bar__back-icon"></text>
class="nav-bar__back {{leftActionType === 'none' ? 'nav-bar__back--hidden' : ''}}"
bindtap="handleLeftAction">
<text
wx:if="{{leftActionType === 'back'}}"
class="toy-icon toy-icon-arrow-left nav-bar__back-icon"></text>
<text
wx:elif="{{leftActionType === 'home'}}"
class="toy-icon toy-icon-home-o nav-bar__back-icon"></text>
</view>
<slot name="left"></slot>
<text class="nav-bar__title">{{title}}</text>
@@ -6,7 +6,8 @@
bottom: 0;
width: 100%;
padding: 0 24rpx;
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
// padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
padding-bottom: env(safe-area-inset-bottom);
box-sizing: border-box;
z-index: 60;
@@ -22,23 +23,27 @@
gap: 16rpx;
}
/* flex 子项:占宽比例;min-width:0 避免子项溢出时把 flex 算崩 */
&__secondary {
flex: 4;
flex: 3.92;
min-width: 0;
}
&__primary {
flex: 6;
flex: 6.18;
min-width: 0;
}
&__secondary .toy-btn,
&__primary .toy-btn {
width: 100%;
height: 88rpx;
/*
* 以下类通过 toy-button 的 custom-class 作用在内部原生 button 上。
* 不能写 &__primary .toy-btn:小程序样式无法穿透进子自定义组件。
*/
.preview-footer-actions__toy-btn {
border-radius: 28rpx;
font-size: 30rpx;
}
&__primary .toy-btn--primary {
.preview-footer-actions__toy-btn--primary {
box-shadow: 0 4rpx 12rpx rgba(255, 215, 0, 0.3);
}
}
@@ -9,12 +9,21 @@ Component({
properties: {
primaryText: {
type: String,
value: '保存到作品库',
value: '保存到相册',
},
primaryIcon: {
type: String,
value: 'download',
},
secondaryText: {
type: String,
value: '分享',
},
/** 须对应 iconfont 中 toy-icon-{name};历史上常用 wechat 已映射为 wechat-o */
secondaryIcon: {
type: String,
value: 'line-share',
},
disabled: {
type: Boolean,
value: false,
@@ -28,7 +37,31 @@ Component({
value: 'share',
},
},
data: {
/** 经归一化后的图标名,供 wxml 绑定(避免父级传 wechat 等 iconfont 中不存在的 name */
resolvedSecondaryIcon: 'line-share',
},
observers: {
secondaryIcon() {
this.syncSecondaryIconGlyph();
},
},
lifetimes: {
attached() {
this.syncSecondaryIconGlyph();
},
},
methods: {
syncSecondaryIconGlyph() {
const raw = (this.data.secondaryIcon ?? '').trim();
const aliases: Record<string, string> = {
wechat: 'wechat-o',
};
const name = aliases[raw] || raw;
this.setData({
resolvedSecondaryIcon: name || 'line-share',
});
},
onPrimaryTap() {
if (this.data.disabled || this.data.loading) return;
this.triggerEvent('primary');
@@ -1,23 +1,33 @@
<view class="preview-footer-actions">
<view class="preview-footer-actions__bar">
<toy-button
class="preview-footer-actions__secondary"
type="secondary"
icon="line-share"
icon-size="40rpx"
open-type="{{ shareOpenType }}"
bind:click="onSecondaryTap">
{{ secondaryText }}
</toy-button>
<toy-button
class="preview-footer-actions__primary"
type="primary"
icon="download"
icon-size="40rpx"
disabled="{{ disabled }}"
loading="{{ loading }}"
bind:click="onPrimaryTap">
{{ primaryText }}
</toy-button>
<view class="preview-footer-actions__secondary">
<toy-button
type="secondary"
icon="{{ resolvedSecondaryIcon }}"
icon-size="40rpx"
block
width="100%"
height="88rpx"
open-type="{{ shareOpenType }}"
custom-class="preview-footer-actions__toy-btn"
bind:click="onSecondaryTap">
{{ secondaryText }}
</toy-button>
</view>
<view class="preview-footer-actions__primary">
<toy-button
type="primary"
icon="{{ primaryIcon }}"
icon-size="40rpx"
block
width="100%"
height="88rpx"
disabled="{{ disabled }}"
loading="{{ loading }}"
custom-class="preview-footer-actions__toy-btn preview-footer-actions__toy-btn--primary"
bind:click="onPrimaryTap">
{{ primaryText }}
</toy-button>
</view>
</view>
</view>
@@ -1,4 +1,4 @@
import { PAPER_SIZE } from '../constants/colors';
import { PAPER_SIZE } from '../../constants/colors';
import { drawBaseHeader, drawBaseMiniHeader } from './baseHeaderDraw';
/**
@@ -1,4 +1,4 @@
import { getMiniCodeImage, getImage } from '../utils/index';
import { getMiniCodeImage, getImage } from '../../utils/index';
/**
* 3
@@ -10,7 +10,6 @@
"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",
"toy-button": "../../toy/button/button",
"toy-icon": "../../toy/icon/icon"
}
}
@@ -15,19 +15,20 @@ page {
padding-top: 24rpx;
display: flex;
flex-direction: column;
gap: @section-gap;
/* space-y-8 = 32px → 64rpx */
gap: 64rpx;
}
/* ===== 预览卡 ===== */
/* ===== 预览卡 (rounded-xl shadow-sm p-8 border) ===== */
.lt-preview-wrap {
position: relative;
}
.lt-preview-card {
background: @bg-white;
border-radius: @radius;
border-radius: @radius-xl;
box-shadow: @shadow;
padding: 48rpx @page-padding-inner-x 32rpx;
padding: 48rpx;
border: @border;
box-sizing: border-box;
display: flex;
@@ -38,12 +39,14 @@ page {
.lt-preview-kicker {
display: block;
text-align: center;
font-size: 22rpx;
font-weight: 700;
color: @text-gray;
/* text-xs = 12px → 24rpx */
font-size: @fs-section-head-title;
font-weight: bold;
color: @text-secondary;
// 设置字母间距,使文本字符之间有更宽的间隔,提升可读性和美观性
letter-spacing: 0.12em;
text-transform: uppercase;
margin-bottom: 24rpx;
margin-bottom: 40rpx;
}
.lt-canvas-wrap {
@@ -63,118 +66,170 @@ page {
box-shadow: @shadow;
}
/* ===== 参数面板 ===== */
/* ===== 参数面板 (bg-surface-container rounded-xl p-6 space-y-6) ===== */
.lt-params {
background: @bg-header;
border-radius: @radius;
padding: 40rpx @page-padding-inner-x;
/* surface-container = #f0e7d6 */
background: @bg-card;
border-radius: @radius-xl;
padding: 48rpx;
box-sizing: border-box;
display: flex;
flex-direction: column;
gap: 40rpx;
border: @border;
gap: 48rpx;
box-shadow: @shadow;
}
/* 标题行 (gap-2 = 8px → 16rpx) */
.lt-params-head {
display: flex;
flex-direction: row;
align-items: center;
gap: 12rpx;
gap: 16rpx;
}
.lt-params-emoji {
font-size: 36rpx;
/* text-xl = 20px → 40rpx */
font-size: 40rpx;
line-height: 1;
}
.lt-params-title {
/* text-lg = 18px → 36rpx */
font-size: @fs-section-head-title;
font-weight: 700;
color: @text-title;
letter-spacing: @ls-nav-title;
}
/* 参数区块内间距 (space-y-3 = 12px → 24rpx) */
.param-block {
display: flex;
flex-direction: column;
/* space-y-3 = 12px → 24rpx */
gap: 24rpx;
}
/* 字段标签 (text-sm font-bold text-on-surface-variant ml-1) */
.lt-field-label {
display: block;
font-size: 26rpx;
/* text-sm = 14px → 28rpx */
font-size: 28rpx;
font-weight: 700;
/* on-surface-variant = #605b50 */
color: @text-secondary;
margin-bottom: 16rpx;
margin-left: 4rpx;
/* ml-1 = 4px → 8rpx */
margin-left: 8rpx;
}
/* ===== 练习类型2×2 网格选择器 ===== */
/* ===== 练习类型 Tab (grid-cols-2 gap-2 bg-surface-container-high p-1.5 rounded-lg) ===== */
.lt-mode-seg {
display: grid;
grid-template-columns: 1fr 1fr;
/* gap-2 = 8px → 16rpx */
gap: 16rpx;
/* p-1.5 = 6px → 12rpx */
padding: 12rpx;
/* surface-container-high = #eae2d0 */
background: #eae2d0;
border-radius: 32rpx;
box-sizing: border-box;
}
/* 未选中 item (py-2.5 px-3 text-sm font-medium rounded-lg text-on-surface-variant) */
.lt-mode-item {
display: flex;
align-items: center;
justify-content: center;
padding: 24rpx 16rpx;
border-radius: @radius-sm;
background: @bg-white;
border: 2rpx solid rgba(50, 46, 37, 0.1);
box-shadow: 0 2rpx 8rpx rgba(50, 46, 37, 0.04);
transition: all 0.2s ease;
/* py-2.5 = 10px → 20rpx; px-3 = 12px → 24rpx */
padding: 20rpx 24rpx;
/* rounded-lg = 8px → 16rpx */
border-radius: 32rpx;
background: transparent;
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
// transition: all 1s ease;
box-sizing: border-box;
}
/* 选中 item (bg-primary-container text-on-primary-container font-bold shadow-sm) */
.lt-mode-item--active {
/* primary-container = #ffd709 */
background: @brand;
border-color: rgba(255, 215, 9, 0.55);
box-shadow: @shadow;
box-shadow: 0 2rpx 8rpx rgba(50, 46, 37, 0.08);
}
.lt-mode-item__text {
font-size: 26rpx;
font-weight: 600;
color: @text-secondary;
line-height: 1.35;
/* text-sm = 14px → 28rpx */
font-size: 28rpx;
/* 未选中 font-medium = 500 */
font-weight: 500;
/* on-surface-variant = #605b50 */
color: @text-secondary; //#5b4b00
line-height: 40rpx;
text-align: center;
}
/* 选中态字色 (on-primary-container = #5b4b00, font-bold = 700) */
.lt-mode-item--active .lt-mode-item__text {
/* on-primary-container = #5b4b00 */
color: @text-selected-btn;
font-weight: 700;
}
/* ===== 字母选择网格7 列 ===== */
/* ===== 字母选择网格 (grid-cols-6 gap-2) ===== */
.lt-letter-grid {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 12rpx;
/* 稿 grid-cols-6 */
grid-template-columns: repeat(6, 1fr);
/* gap-2 = 8px → 16rpx */
gap: 16rpx;
}
/* 字母格子 (aspect-square rounded-lg bg-surface-container-lowest) */
.lt-letter-cell {
aspect-ratio: 1;
display: flex;
align-items: center;
justify-content: center;
border-radius: @radius-sm;
border-radius: 32rpx;
/* surface-container-lowest = #ffffff */
background: @bg-white;
border: 2rpx solid rgba(50, 46, 37, 0.1);
box-shadow: 0 2rpx 8rpx rgba(50, 46, 37, 0.04);
transition: all 0.15s ease;
box-sizing: border-box;
/* 不用 :active:小程序 view 上背景色常在松手后才刷新;用 hover-class 才能按下即变色,并做过渡 */
transition:
background-color 0.2s ease,
transform 0.2s ease,
box-shadow 0.2s ease;
}
/* 按下态(hover-class):与选中态同色,松手后由 --active 保持 */
.lt-letter-cell--pressed {
transform: scale(0.95);
background: @brand;
box-shadow: 0 2rpx 8rpx rgba(50, 46, 37, 0.08);
}
/* 选中字母 (bg-[#FFD700] text-[#6c5a00] shadow-sm) */
.lt-letter-cell--active {
background: @brand;
border-color: rgba(255, 215, 9, 0.55);
box-shadow: @shadow;
transform: scale(1.05);
box-shadow: 0 2rpx 8rpx rgba(50, 46, 37, 0.08);
// box-shadow: @btn-shadow-pressed;
}
.lt-letter-text {
font-size: 32rpx;
/* text-lg = 18px → 36rpx */
font-size: 36rpx;
font-weight: 700;
/* on-surface-variant = #605b50 */
color: @text-secondary;
line-height: 1;
transition: color 0.2s ease;
}
.lt-letter-cell--active .lt-letter-text {
/* #6c5a00 */
color: @text-selected-btn;
}
.lt-letter-cell--pressed .lt-letter-text {
color: @text-selected-btn;
}
@@ -182,36 +237,90 @@ page {
.lt-chip-row {
display: flex;
flex-direction: row;
/* gap-2 = 8px → 16rpx */
gap: 16rpx;
}
/* 未选中 chip (同练习类型 item 风格) */
.lt-chip {
flex: 1;
text-align: center;
padding: 24rpx 20rpx;
/* py-2.5 px-3 */
padding: 20rpx 24rpx;
/* text-sm = 14px → 28rpx */
font-size: 28rpx;
font-weight: 600;
font-weight: 500;
line-height: 40rpx;
color: @text-secondary;
background: @bg-white;
border-radius: @radius-sm;
border: 2rpx solid rgba(50, 46, 37, 0.1);
box-shadow: 0 2rpx 8rpx rgba(50, 46, 37, 0.04);
background: #fff;
/* rounded-lg */
border-radius: 32rpx;
transition:
background-color 0.2s cubic-bezier(0.4, 0, 0.2, 1),
transform 0.2s cubic-bezier(0.4, 0, 0.2, 1),
color 0.2s cubic-bezier(0.4, 0, 0.2, 1),
box-shadow 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
/* 按下态(hover-class),未选与已选 chip 共用 */
.lt-chip--pressed {
transform: scale(0.96);
background: @brand;
color: @text-selected-btn;
font-weight: 700;
box-shadow: 0 2rpx 8rpx rgba(50, 46, 37, 0.08);
}
/* 选中 chip (同练习类型选中态) */
.lt-chip--active {
background: @brand;
color: @text-selected-btn;
font-weight: 700;
border-color: rgba(255, 215, 9, 0.55);
box-shadow: @shadow;
box-shadow: 0 2rpx 8rpx rgba(50, 46, 37, 0.08);
}
/* ===== 换一批(toy-button outlined 覆盖) ===== */
/* 换一批:自定义描边按钮(对齐 stitch 粗边框 + 图标文案横排,避免 toy-button 覆盖成本) */
.lt-shuffle-btn {
border-width: 6rpx !important;
border-color: @bg-header !important;
height: 96rpx !important;
font-size: 32rpx !important;
box-sizing: border-box;
width: 100%;
height: 108rpx;
padding: 0 32rpx;
border-radius: 32rpx;
border: 8rpx solid @bg-card;
background: transparent;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
gap: 12rpx;
/* 与字母格/chip 一致:view 上勿依赖 :active 做背景或稳定缩放,用 hover-class */
transition:
transform 0.2s cubic-bezier(0.4, 0, 0.2, 1),
opacity 0.2s cubic-bezier(0.4, 0, 0.2, 1),
background-color 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
/* 稿 active:scale-[0.98],按下时略铺底便于看出过渡 */
.lt-shuffle-btn--hover {
transform: scale(0.98);
opacity: 0.92;
background-color: rgba(234, 226, 208, 0.55);
}
/* toy-icon 自定义组件宿主节点默认 display:inline,须强制为 inline-flex 方可参与 flex 对齐 */
.lt-shuffle-btn toy-icon {
display: inline-flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.lt-shuffle-btn__text {
font-size: 36rpx;
font-weight: 700;
line-height: 1;
color: @text-secondary;
transition: color 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
.lt-spacer-bottom {
@@ -1,4 +1,4 @@
import LetterTracingDraw from '../shared/service/letterTracingDraw';
import LetterTracingDraw from '../shared/draw/letterTracingDraw';
import { createPage, type CanvasDataState } from '../../base/pageMixin';
import { defaultShareConfig } from '../../config/config';
import { getWorksheetById } from '../../core/data/worksheets';
@@ -81,36 +81,15 @@ createPage(
options.id && PAGE_META[options.id]
? options.id
: 'letter-tracing-single';
const def = getWorksheetById(worksheetId);
const base = (def?.generatorConfig ??
{}) as Partial<LetterTracingGeneratorConfig>;
const merged = mergeLetterTracingConfig(base, {});
const traceMode = merged.mode;
const showLetterPicker = traceMode === 'single-letter';
const showCaseToggle =
traceMode === 'full-alphabet' || traceMode === 'two-column';
const showNextLetter = traceMode === 'single-letter';
this.setData({
worksheetId,
functionId: worksheetId,
traceMode,
selectedLetter: normalizePick(merged.selectedLetter),
letterCaseLower: merged.letterCase === 'lower',
showLetterPicker,
showCaseToggle,
showNextLetter,
});
this.initPageInfo(worksheetId, PAGE_META[worksheetId]?.title ?? '字母描红');
this.applyWorksheet(worksheetId);
},
onSelectMode(e: WechatMiniprogram.TouchEvent) {
const id = e.currentTarget.dataset.id as string | undefined;
if (!id || id === this.data.worksheetId) return;
wx.redirectTo({
url: `/englishPages/letterTracing/letterTracing?id=${id}`,
this.applyWorksheet(id, {
preserveLetter: true,
redraw: true,
});
},
@@ -184,6 +163,61 @@ createPage(
this.setData({ selectedLetter: next });
this.drawCanvas();
},
applyWorksheet(
worksheetId: string,
options?: {
preserveLetter?: boolean;
redraw?: boolean;
},
) {
const def = getWorksheetById(worksheetId);
const base = (def?.generatorConfig ??
{}) as Partial<LetterTracingGeneratorConfig>;
const merged = mergeLetterTracingConfig(base, {});
const traceMode = merged.mode;
const showLetterPicker = traceMode === 'single-letter';
const showCaseToggle =
traceMode === 'full-alphabet' || traceMode === 'two-column';
const showNextLetter = traceMode === 'single-letter';
const selectedLetter = options?.preserveLetter
? this.data.selectedLetter
: normalizePick(merged.selectedLetter);
const letterCaseLower =
traceMode === 'single-letter'
? merged.letterCase === 'lower'
: this.data.letterCaseLower;
this.setData(
{
worksheetId,
functionId: worksheetId,
traceMode,
selectedLetter,
letterCaseLower,
showLetterPicker,
showCaseToggle,
showNextLetter,
},
() => {
this.initPageInfo(
worksheetId,
PAGE_META[worksheetId]?.title ?? '字母描红',
);
if (this.drawService) {
this.drawService.options.title = this.data.pageTitle;
this.drawService.options.subTitle =
this.data.subTitle || '';
}
if (options?.redraw) {
this.drawCanvas();
}
},
);
},
},
{
shareConfig: defaultShareConfig,
@@ -43,6 +43,9 @@
wx:key="*this"
class="lt-letter-cell {{selectedLetter === item ? 'lt-letter-cell--active' : ''}}"
data-letter="{{item}}"
hover-class="lt-letter-cell--pressed"
hover-start-time="0"
hover-stay-time="70"
bind:tap="onSelectLetter">
<text class="lt-letter-text">{{item}}</text>
</view>
@@ -54,11 +57,17 @@
<view class="lt-chip-row">
<view
class="lt-chip {{!letterCaseLower ? 'lt-chip--active' : ''}}"
hover-class="lt-chip--pressed"
hover-start-time="0"
hover-stay-time="70"
bind:tap="onSelectCaseUpper">
大写 A–Z
</view>
<view
class="lt-chip {{letterCaseLower ? 'lt-chip--active' : ''}}"
hover-class="lt-chip--pressed"
hover-start-time="0"
hover-stay-time="70"
bind:tap="onSelectCaseLower">
小写 a–z
</view>
@@ -66,27 +75,25 @@
</view>
</view>
<toy-button
<view
wx:if="{{showNextLetter}}"
type="default"
variant="outlined"
block
icon="refresh"
custom-class="lt-shuffle-btn"
bind:click="onNextLetter">
换一批
</toy-button>
class="lt-shuffle-btn"
hover-class="lt-shuffle-btn--hover"
hover-start-time="0"
hover-stay-time="70"
bindtap="onNextLetter">
<toy-icon
name="refresh"
size="40rpx"
color="#605b50"
custom-class="lt-shuffle-btn__icon" />
<text class="lt-shuffle-btn__text">换一批</text>
</view>
<draw-ad type="mathDraw"></draw-ad>
<view class="lt-spacer-bottom"></view>
</view>
</view>
<preview-footer-actions
primary-text="保存到相册"
primary-icon="download"
secondary-text="分享"
secondary-icon="wechat"
disabled="{{!hasContent}}"
bind:primary="exportToPrint"
bind:secondary="onShare" />
@@ -1,4 +1,4 @@
import { BaseDrawService } from '../../../service/baseDraw';
import { BaseDrawService } from '../../../core/draw/baseDraw';
import type {
LetterTracingData,
TracingRow,
@@ -169,7 +169,13 @@ export default class LetterTracingDraw extends BaseDrawService {
const rowH = Math.max(36, (rest - 8) / rows.length);
const rowWidth = cw - M * 2;
for (let r = 0; r < rows.length; r++) {
this.drawTracingRow(rows[r], M, y + r * (rowH + 10), rowH, rowWidth);
this.drawTracingRow(
rows[r],
M,
y + r * (rowH + 10),
rowH,
rowWidth,
);
}
}
@@ -212,7 +218,15 @@ export default class LetterTracingDraw extends BaseDrawService {
if (idx >= chars.length) break;
const cx = M + c * (cellW + 4);
this.drawFourLines(cx, y, cellW, cellH);
this.drawCharInCell(chars[idx], cx, y, cellW, cellH, 0.85, true);
this.drawCharInCell(
chars[idx],
cx,
y,
cellW,
cellH,
0.85,
true,
);
}
y += cellH + 6;
}
@@ -2,7 +2,7 @@
* 译码连线绘制服务
*/
import { BaseDrawService } from '../../../service/baseDraw';
import { BaseDrawService } from '../../../core/draw/baseDraw';
/**
* 译码连线数据
@@ -2,7 +2,7 @@
* 颜色找规律绘制服务
*/
import { BaseDrawService } from '../../../service/baseDraw';
import { BaseDrawService } from '../../../core/draw/baseDraw';
import {
drawShapeSymbolShape,
DrawShapeOptions,
@@ -2,7 +2,7 @@
* 根据颜色画图形绘制服务
*/
import { BaseDrawService } from '../../../service/baseDraw';
import { BaseDrawService } from '../../../core/draw/baseDraw';
import { drawShapeSymbolShape } from '../shapes/shapeSymbolShapes';
/**
@@ -2,7 +2,7 @@
* 数字点连线绘制服务
*/
import { BaseDrawService } from '../../../service/baseDraw';
import { BaseDrawService } from '../../../core/draw/baseDraw';
/**
* 数字点连线数据
@@ -3,7 +3,7 @@
* 合并了基础绘制和内容绘制逻辑
*/
import { BaseDrawService } from '../../../service/baseDraw';
import { BaseDrawService } from '../../../core/draw/baseDraw';
import { GridCell, GridConfig } from '../types/gridTypes';
/**
@@ -2,7 +2,7 @@
* 方格推理绘制服务
*/
import { BaseDrawService } from '../../../service/baseDraw';
import { BaseDrawService } from '../../../core/draw/baseDraw';
/**
* 3x3网格中的位置坐标
@@ -2,7 +2,7 @@
* 线条识别绘制服务
*/
import { BaseDrawService } from '../../../service/baseDraw';
import { BaseDrawService } from '../../../core/draw/baseDraw';
import { getImage } from '../../../utils/index';
/**
@@ -2,7 +2,7 @@
* 连连看绘制服务
*/
import { BaseDrawService } from '../../../service/baseDraw';
import { BaseDrawService } from '../../../core/draw/baseDraw';
import { getImage } from '../../../utils/index';
/**
@@ -2,7 +2,7 @@
* 方位涂涂乐绘制服务
*/
import { BaseDrawService } from '../../../service/baseDraw';
import { BaseDrawService } from '../../../core/draw/baseDraw';
import { getImage } from '../../../utils/index';
/**
@@ -2,7 +2,7 @@
* 图形符号配对绘制服务
*/
import { BaseDrawService } from '../../../service/baseDraw';
import { BaseDrawService } from '../../../core/draw/baseDraw';
import { drawShapeSymbolShape } from '../shapes/shapeSymbolShapes';
/**
@@ -2,7 +2,7 @@
* 加减法计算绘制服务
*/
import { BaseDrawService } from '../../../service/baseDraw';
import { BaseDrawService } from '../../../core/draw/baseDraw';
import { getImage } from '../../../utils/index';
/**
@@ -2,7 +2,7 @@
* 破十法练习绘制服务
*/
import { BaseDrawService } from '../../../service/baseDraw';
import { BaseDrawService } from '../../../core/draw/baseDraw';
/**
* 破十法数据
@@ -2,7 +2,7 @@
* 计算练习题绘制服务
*/
import { BaseDrawService } from '../../../service/baseDraw';
import { BaseDrawService } from '../../../core/draw/baseDraw';
/**
* 计算练习题数据
@@ -2,7 +2,7 @@
* 数一数比大小绘制服务
*/
import { BaseDrawService } from '../../../service/baseDraw';
import { BaseDrawService } from '../../../core/draw/baseDraw';
import { getImage } from '../../../utils/index';
/**
@@ -2,7 +2,7 @@
* 数一数连一连绘制服务
*/
import { BaseDrawService } from '../../../service/baseDraw';
import { BaseDrawService } from '../../../core/draw/baseDraw';
import { getImage } from '../../../utils/index';
import { getRandomNumberColor } from '../../../constants/colors';
@@ -3,7 +3,7 @@
* 支持两种模式:'select'(选一选)和 'fill'(填一填)
*/
import { BaseDrawService } from '../../../service/baseDraw';
import { BaseDrawService } from '../../../core/draw/baseDraw';
import { getImage } from '../../../utils/index';
import { getRandomNumberColor } from '../../../constants/colors';
@@ -2,7 +2,7 @@
* 平十法练习绘制服务
*/
import { BaseDrawService } from '../../../service/baseDraw';
import { BaseDrawService } from '../../../core/draw/baseDraw';
/**
* 平十法数据
@@ -2,7 +2,7 @@
* 凑十法练习绘制服务
*/
import { BaseDrawService } from '../../../service/baseDraw';
import { BaseDrawService } from '../../../core/draw/baseDraw';
/**
* 凑十法数据
@@ -2,7 +2,7 @@
* 填上缺少的数字绘制服务
*/
import { BaseDrawService } from '../../../service/baseDraw';
import { BaseDrawService } from '../../../core/draw/baseDraw';
/**
* 填上缺少的数字数据
@@ -2,7 +2,7 @@
* 九九乘法表绘制服务
*/
import { BaseDrawService } from '../../../service/baseDraw';
import { BaseDrawService } from '../../../core/draw/baseDraw';
import { getRandomUniqueNumberColors } from '../../../constants/colors';
/**
@@ -2,7 +2,7 @@
* 按数字涂颜色绘制服务
*/
import { BaseDrawService } from '../../../service/baseDraw';
import { BaseDrawService } from '../../../core/draw/baseDraw';
import { getNumberColors } from '../../../constants/colors';
/**
@@ -2,7 +2,7 @@
* 10以内数的分与合绘制服务
*/
import { BaseDrawService } from '../../../service/baseDraw';
import { BaseDrawService } from '../../../core/draw/baseDraw';
import { getImage } from '../../../utils/index';
import { getRandomNumberColor } from '../../../constants/colors';
@@ -1,4 +1,4 @@
import { BaseDrawService } from '../../../service/baseDraw';
import { BaseDrawService } from '../../../core/draw/baseDraw';
import { drawNumberPreview } from './numberPreviewDraw';
import { drawNumberWriteContent } from './numberWriteDraw';
@@ -2,7 +2,7 @@
* 数物对应绘制服务
*/
import { BaseDrawService } from '../../../service/baseDraw';
import { BaseDrawService } from '../../../core/draw/baseDraw';
import { getImage } from '../../../utils/index';
/**
@@ -2,7 +2,7 @@
* 数字排序绘制服务
*/
import { BaseDrawService } from '../../../service/baseDraw';
import { BaseDrawService } from '../../../core/draw/baseDraw';
/**
* 数字排序数据
@@ -2,7 +2,7 @@
* 一位数加法绘制服务
*/
import { BaseDrawService } from '../../../service/baseDraw';
import { BaseDrawService } from '../../../core/draw/baseDraw';
import { getRandomNumberColor } from '../../../constants/colors';
/**
+1 -1
View File
@@ -1,4 +1,4 @@
import { BaseDrawService } from './baseDraw';
import { BaseDrawService } from '../core/draw/baseDraw';
import { POSITION_TEMPLATES } from './findWordTemplate';
// ==================== Debug 开关 ====================
+1 -1
View File
@@ -1,4 +1,4 @@
import { BaseDrawService } from './baseDraw';
import { BaseDrawService } from '../core/draw/baseDraw';
import { ShapeCard } from '../constants/shapes';
import { drawShape } from './drawShape';
+1 -1
View File
@@ -1,4 +1,4 @@
import { BaseDrawService } from './baseDraw';
import { BaseDrawService } from '../core/draw/baseDraw';
/**
* 计算示例区域圆的中心点
* @param canvasWidth 画布宽度(逻辑像素)
+1 -1
View File
@@ -1,4 +1,4 @@
import { BaseDrawService } from './baseDraw';
import { BaseDrawService } from '../core/draw/baseDraw';
import { CharacterItem } from '../types/characterType';
/**
+1
View File
@@ -24,6 +24,7 @@
@bg-gray: #f5edd8;
@bg-muted: #f5f5f5; // 分龄 Tab 未选中等弱对比底(Stitch)
@bg-week-head: #fafafa; // 分龄周卡头部(Stitch stone-50
@bg-card: #f0e7d6;
// ========== 文本色 ==========
@text-title: #322e25;
+35 -17
View File
@@ -3,21 +3,30 @@
/* ── design tokens(严格对齐 stitch-ui/buttonUi/code.html 色谱) ── */
/* primary 色系 */
@btn-primary-solid: #ffd709; /* primary-container / primary-fixed */
@btn-primary-gradient-from: #ffd709; /* primary-fixed */
@btn-primary-gradient-to: #efc900; /* primary-fixed-dim */
@btn-primary-text: #5b4b00; /* on-primary-container */
@btn-primary-pressed-bg: #5e4e00; /* primary-dim */
@btn-primary-pressed-text: #fff2cd; /* on-primary */
@btn-primary-solid: #ffd709;
/* primary-container / primary-fixed */
@btn-primary-gradient-from: #ffd709;
/* primary-fixed */
@btn-primary-gradient-to: #efc900;
/* primary-fixed-dim */
@btn-primary-text: #5b4b00;
/* on-primary-container */
@btn-primary-pressed-bg: #5e4e00;
/* primary-dim */
@btn-primary-pressed-text: #fff2cd;
/* on-primary */
@btn-primary-ring: rgba(108, 90, 0, 0.10);
/* secondary 色系 */
@btn-secondary-bg: #9cd343; /* tertiary-container / tertiary-fixed */
@btn-secondary-bg: #9cd343;
/* tertiary-container / tertiary-fixed */
@btn-secondary-text-on-tint: #5a8c1a;
/* error 色系 */
@btn-error-bg: #f95630; /* error-container */
@btn-error-text: #520c00; /* on-error-container */
@btn-error-bg: #f95630;
/* error-container */
@btn-error-text: #520c00;
/* on-error-container */
/* surface 参考 */
@btn-surface-variant: #e5dcc9;
@@ -45,8 +54,8 @@
@btn-disabled-bg: @bg-gray;
@btn-disabled-color: @text-disable;
/* ── reset native button chrome ── */
wx-button:not([size=mini]) {
/* ── reset native button chrome(全宽块级按钮由 .toy-btn--block 自行控制宽度) ── */
wx-button.toy-btn:not([size=mini]):not(.toy-btn--block) {
margin-left: auto;
margin-right: auto;
width: auto;
@@ -252,17 +261,21 @@ wx-button:not([size=mini]) {
}
}
/* ═══════ block ═══════ */
/* ═══════ block(铺满父级 flex 槽位,避免被 wx-button 默认 width:auto 限制) ═══════ */
&--block {
display: flex;
width: 100%;
max-width: 100%;
margin-left: 0;
margin-right: 0;
box-sizing: border-box;
}
/* ═══════ states ═══════ */
/* 通用按下态:缩放 0.95 */
/* 通用按下态:缩放 0.9 */
&--active {
transform: scale(0.95);
transform: scale(0.9);
box-shadow: @btn-shadow-pressed;
}
@@ -312,6 +325,11 @@ wx-button:not([size=mini]) {
}
@keyframes toy-btn-spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
+7
View File
@@ -171,6 +171,7 @@ Component({
visualType,
variant,
disabled,
pressed,
);
const resolvedIconSize =
this.normalizeUnit(iconSize) ||
@@ -214,10 +215,16 @@ Component({
type: string,
variant: string,
disabled: boolean,
pressed: boolean,
) {
if (disabled) return '#b8b2a6';
if (iconColor) return iconColor;
/* 与 .toy-btn--primary.toy-btn--active 的 color@btn-primary-pressed-text)一致 */
if (pressed && variant === 'filled' && type === 'primary') {
return '#fff2cd';
}
if (variant === 'outlined' || variant === 'ghost') {
const outlinedMap: Record<string, string> = {
primary: '#6c5a00',
+6 -2
View File
@@ -24,9 +24,13 @@
display: block;
}
/* 内层 text 显示 iconfont 字形 */
/* 内层 text 承载 ::before 字形,固定 1em 方盒便于与 flex 行内文案垂直对齐 */
> text {
display: inline-block;
display: inline-flex;
align-items: center;
justify-content: center;
width: 1em;
height: 1em;
line-height: 1;
font-style: normal;
}
+2 -4
View File
@@ -66,10 +66,8 @@ Component({
const styleSegments = [style];
if (normalizedSize) {
styleSegments.push(`font-size:${normalizedSize}`);
if (isImage) {
styleSegments.push(`width:${normalizedSize}`);
styleSegments.push(`height:${normalizedSize}`);
}
styleSegments.push(`width:${normalizedSize}`);
styleSegments.push(`height:${normalizedSize}`);
}
if (color && !isImage) {
styleSegments.push(`color:${color}`);
+54
View File
@@ -0,0 +1,54 @@
# Design System Strategy: Doodle Mini
## 1. Overview & Creative North Star: "The Sun-Drenched Studio"
The creative direction for this design system is **"The Sun-Drenched Studio."** We are moving away from the cold, clinical "educational app" look and toward a space that feels like a physical, tactile craft room bathed in morning light.
Instead of a rigid, boxed-in grid, we embrace **Organic Asymmetry**. Elements should feel like they were placed on a table by hand, not snapped to a pixel-perfect wireframe. We break the "template" look by using exaggerated corner radii, overlapping "paper" layers, and a hierarchy that prioritizes warmth and play over traditional data density.
## 2. Colors: Tonal Radiance
The palette is anchored by `primary` (#6c5a00) and `primary_container` (#ffd709), creating a "Yaya Yellow" experience that is vibrant yet sophisticated.
* **The "No-Line" Rule:** Under no circumstances should 1px solid borders be used to separate content. We define boundaries exclusively through background shifts. Use `surface_container_low` (#f8f0e0) for main content areas and `surface` (#fef6e7) for the global backdrop.
* **Surface Hierarchy & Nesting:** Treat the UI as stacked sheets of heavy-weight construction paper.
* **Level 0:** `background` (#fef6e7) The desk surface.
* **Level 1:** `surface_container` (#f0e7d6) The worksheet base.
* **Level 2:** `surface_container_lowest` (#ffffff) Active drawing cards or "Paper" modals.
* **The Glass & Gradient Rule:** For navigation bars or floating tool palettes, use Glassmorphism. Apply `surface_container_low` at 80% opacity with a `20px` backdrop blur. This ensures the "sunny" background bleeds through, maintaining the "Sun-Drenched" atmosphere.
* **Signature Textures:** Main CTAs should not be flat. Apply a subtle linear gradient from `primary_fixed` (#ffd709) to `primary_fixed_dim` (#efc900) at a 145-degree angle to give buttons a "pillowy" 3D volume.
## 3. Typography: The Friendly Guide
We use **Plus Jakarta Sans** exclusively. Its geometric yet soft terminals provide a modern, legible, and approachable voice.
* **Display & Headlines:** Use `display-lg` (3.5rem) for "Doodle Mini" brand moments. Headlines (`headline-md`) should use a tighter letter-spacing (-0.02em) to feel "hugged" and friendly.
* **Body & Labels:** Use `body-lg` for all instructional text. In a children's context, readability is paramount; ensure a line height of at least 1.6x the font size.
* **Hierarchy as Personality:** Use `on_secondary_container` (#6d3b00) for sub-headers to provide a warm, "chocolatey" contrast against the yellow, moving away from harsh blacks.
## 4. Elevation & Depth: Tonal Layering
Depth in this system is a result of light and shadow, not lines and strokes.
* **The Layering Principle:** To lift a component, place a `surface_container_highest` (#e5dcc9) element inside a `surface_container_low` (#f8f0e0) environment. This creates a "recessed" or "elevated" feel through color value alone.
* **Ambient Shadows:** Use shadows sparingly. When an object "floats" (like a floating action button), use a shadow color derived from `on_surface` (#322e25) at 6% opacity, with a blur of `32px` and a Y-offset of `8px`. It should look like a soft glow, not a drop shadow.
* **The "Ghost Border" Fallback:** If a separator is required for accessibility in input fields, use `outline_variant` (#b3ac9f) at **15% opacity**. It should be felt, not seen.
## 5. Components: Tactile & Safe
Every component must feel "squishy" and safe to touch.
* **Buttons:**
* **Primary:** Uses `primary_container` (#ffd709) with a `full` (9999px) radius.
* **States:** On press, scale the button down to 96% size to simulate a physical "click" into the screen.
* **Cards & Lists:** Forbid divider lines. Separate list items using a `spacing-3` (1rem) gap and alternating background colors between `surface_container_low` and `surface_container`.
* **Input Fields:** Use `surface_container_highest` for the input track. Corners must be `xl` (3rem). Labels sit *above* the field in `title-sm` to ensure children can always see the context.
* **Selection Chips:** Use `secondary_container` (#ffc697) for selected states. The roundedness must be `full` to mimic smooth river stones.
* **The "Joy" Progress Bar:** A custom component for Doodle Mini. Use a thick track (`1.5rem` height) in `surface_container_highest` with the progress indicator using a gradient of `tertiary` (#006b1b) to `tertiary_fixed` (#91f78e).
## 6. Dos and Donts
### Do:
* **Do** use `xl` and `full` corner radii for almost everything. Sharp corners are "scary."
* **Do** lean into white space. Use `spacing-12` (4rem) between major sections to let the design breathe.
* **Do** use `on_tertiary_container` (dark green) for success states; it feels organic and "earthy" against the yellow.
### Dont:
* **Dont** use pure black (#000000). Use `on_surface` (#322e25) for all dark text to maintain the warm, sunny tone.
* **Dont** use 1px dividers. If you feel you need one, use a `surface_variant` (#e5dcc9) color block that is `4px` thick with `full` rounded caps.
* **Dont** crowd the interface. If a screen feels busy, increase the background "paper" size and use a scrollable layout instead of shrinking elements.
+239
View File
@@ -0,0 +1,239 @@
<!DOCTYPE html>
<html lang="zh-CN"><head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<script id="tailwind-config">
tailwind.config = {
darkMode: "class",
theme: {
extend: {
"colors": {
"outline-variant": "#b3ac9f",
"tertiary-container": "#91f78e",
"on-tertiary": "#d1ffc8",
"on-error": "#ffefec",
"surface-container-high": "#eae2d0",
"inverse-surface": "#110e06",
"surface-container-lowest": "#ffffff",
"on-surface": "#322e25",
"surface-variant": "#e5dcc9",
"secondary-fixed": "#ffc697",
"tertiary-fixed": "#91f78e",
"secondary-dim": "#794200",
"tertiary-fixed-dim": "#83e881",
"surface-dim": "#dcd4c0",
"on-primary": "#fff2cd",
"primary-dim": "#5e4e00",
"surface": "#fef6e7",
"secondary-fixed-dim": "#ffb470",
"surface-container-low": "#f8f0e0",
"error-container": "#f95630",
"inverse-on-surface": "#a29c8f",
"error": "#b02500",
"outline": "#7c766a",
"on-primary-fixed": "#453900",
"background": "#fef6e7",
"on-secondary-fixed-variant": "#7a4300",
"tertiary-dim": "#005d16",
"secondary-container": "#ffc697",
"on-tertiary-fixed": "#00480f",
"primary": "#6c5a00",
"on-secondary-fixed": "#512a00",
"on-error-container": "#520c00",
"surface-container": "#f0e7d6",
"on-primary-fixed-variant": "#665500",
"on-secondary-container": "#6d3b00",
"on-secondary": "#fff0e6",
"inverse-primary": "#ffd709",
"primary-container": "#ffd709",
"surface-bright": "#fef6e7",
"primary-fixed": "#ffd709",
"tertiary": "#006b1b",
"on-tertiary-container": "#005e17",
"primary-fixed-dim": "#efc900",
"error-dim": "#b92902",
"surface-tint": "#6c5a00",
"on-surface-variant": "#605b50",
"on-tertiary-fixed-variant": "#00691a",
"secondary": "#8a4c00",
"on-background": "#322e25",
"surface-container-highest": "#e5dcc9",
"on-primary-container": "#5b4b00"
},
"borderRadius": {
"DEFAULT": "1rem",
"lg": "2rem",
"xl": "3rem",
"full": "9999px"
},
"fontFamily": {
"headline": ["Plus Jakarta Sans"],
"body": ["Plus Jakarta Sans"],
"label": ["Plus Jakarta Sans"]
}
},
},
}
</script>
<style>
@media print {
.no-print { display: none; }
body { background: white; }
.a4-page {
box-shadow: none !important;
margin: 0 !important;
border: none !important;
}
}
.material-symbols-outlined {
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
}
</style>
<style>
body {
min-height: max(884px, 100dvh);
}
</style>
</head>
<body class="bg-surface-container-low font-body text-on-surface antialiased min-h-screen flex flex-col items-center py-10">
<!-- TopAppBar Shell (Shared Component) -->
<header class="w-full pt-8 pb-4 bg-[#fef6e7] dark:bg-stone-950 border-b-4 border-[#e5dcc9] dark:border-stone-800 rounded-b-none no-print">
<div class="max-w-[210mm] mx-auto px-12 flex flex-row items-center justify-between">
<div class="flex items-center gap-3">
<span class="material-symbols-outlined text-[#6c5a00] text-3xl" data-icon="draw">draw</span>
<h1 class="text-2xl font-bold text-[#6c5a00] dark:text-yellow-500 font-['Plus_Jakarta_Sans'] tracking-tight leading-[1.6]">Doodle Mini</h1>
</div>
<div class="flex items-center gap-6">
<nav class="hidden md:flex items-center gap-8">
<a class="text-[#6c5a00] dark:text-yellow-400 font-bold font-['Plus_Jakarta_Sans']" href="#">Studio</a>
<a class="text-[#322e25] dark:text-stone-400 hover:bg-[#f0e7d6] dark:hover:bg-stone-800 transition-transform duration-200 p-2 rounded-lg font-['Plus_Jakarta_Sans']" href="#">Templates</a>
<a class="text-[#322e25] dark:text-stone-400 hover:bg-[#f0e7d6] dark:hover:bg-stone-800 transition-transform duration-200 p-2 rounded-lg font-['Plus_Jakarta_Sans']" href="#">Gallery</a>
</nav>
<div class="bg-primary-container text-on-primary-container px-4 py-1.5 rounded-full text-sm font-bold active:scale-95 transition-all cursor-pointer">
ID: #8821
</div>
</div>
</div>
</header>
<!-- Main Canvas (A4 Paper) -->
<main class="a4-page bg-surface-container-lowest w-[210mm] min-h-[297mm] shadow-[0_20px_50px_rgba(0,0,0,0.05)] mt-12 p-12 relative overflow-hidden flex flex-col">
<!-- Worksheet Header Section -->
<div class="border-b-4 border-on-surface/5 pb-8 mb-12">
<!-- Top Section -->
<div class="flex justify-between items-start mb-10">
<div class="flex items-center gap-4">
<div class="w-16 h-16 bg-primary-container rounded-xl flex items-center justify-center rotate-3">
<img alt="Doodle Mini Logo" class="w-12 h-12" data-alt="minimalist playful kid drawing logo with brush stroke yellow and brown colors on a clean white background" src="https://lh3.googleusercontent.com/aida-public/AB6AXuBdSOaW7wfndomKJ5FM9GgaqBCTAJXenyZWLXZDNDtt2PjtkDd-Ndlw5qes3qbkNfmDVVb4ovEYTvOG9U7Y5oA4HzAk0diN9fMxamhc5RV-TzkkbM1QmmC4qJ3xFn4GhxY6mwChzKp3o8i3BGpINikxJpcO5ZznuhL4ZuyrrlkzEBR2teL-Cke62jO1E7BTm4W9lB0JoH2kMePNVobUQYzgcqE_SEvgXbcn5yZmoevDnMgC37cdjm5aAIyVW11dNEaBM7j0Isek1y4"/>
</div>
<div>
<div class="text-xl font-black text-primary tracking-tighter">Doodle Mini</div>
<div class="text-[10px] text-outline font-bold tracking-widest uppercase">Educational Studio</div>
</div>
</div>
<div class="text-right">
<div class="bg-surface-container px-3 py-1 rounded-lg text-[11px] font-mono font-bold text-on-surface-variant">MATH-ADD-10-01</div>
</div>
</div>
<!-- Middle Section (Title) -->
<div class="text-center mb-10">
<h2 class="text-5xl font-black text-on-surface tracking-tight mb-2">10以内加法</h2>
<div class="h-1.5 w-24 bg-primary-container mx-auto rounded-full"></div>
</div>
<!-- Bottom Section (Fields) -->
<div class="flex justify-between items-end px-4">
<div class="flex items-end gap-2 flex-1">
<span class="text-lg font-bold text-on-surface-variant whitespace-nowrap">姓名:</span>
<div class="border-b-2 border-surface-container-highest flex-1 h-8 mb-1"></div>
</div>
<div class="flex items-end gap-2 flex-1 mx-8">
<span class="text-lg font-bold text-on-surface-variant whitespace-nowrap">日期:</span>
<div class="border-b-2 border-surface-container-highest flex-1 h-8 mb-1"></div>
</div>
<div class="flex items-end gap-2 w-32">
<span class="text-lg font-bold text-on-surface-variant whitespace-nowrap">得分:</span>
<div class="border-b-2 border-surface-container-highest flex-1 h-8 mb-1"></div>
</div>
</div>
</div>
<!-- Math Problems Content Area (Bento Preview) -->
<div class="grid grid-cols-2 gap-x-16 gap-y-10 flex-1">
<!-- Left Column Problems -->
<div class="space-y-12">
<div class="flex items-center justify-between text-3xl font-bold tracking-widest text-on-surface/80">
<span>3 + 4 =</span>
<div class="w-24 h-16 border-2 border-surface-container-highest rounded-2xl bg-surface-container-low/30"></div>
</div>
<div class="flex items-center justify-between text-3xl font-bold tracking-widest text-on-surface/80">
<span>5 + 2 =</span>
<div class="w-24 h-16 border-2 border-surface-container-highest rounded-2xl bg-surface-container-low/30"></div>
</div>
<div class="flex items-center justify-between text-3xl font-bold tracking-widest text-on-surface/80">
<span>1 + 8 =</span>
<div class="w-24 h-16 border-2 border-surface-container-highest rounded-2xl bg-surface-container-low/30"></div>
</div>
<div class="flex items-center justify-between text-3xl font-bold tracking-widest text-on-surface/80">
<span>6 + 3 =</span>
<div class="w-24 h-16 border-2 border-surface-container-highest rounded-2xl bg-surface-container-low/30"></div>
</div>
<div class="flex items-center justify-between text-3xl font-bold tracking-widest text-on-surface/80">
<span>2 + 7 =</span>
<div class="w-24 h-16 border-2 border-surface-container-highest rounded-2xl bg-surface-container-low/30"></div>
</div>
</div>
<!-- Right Column Problems -->
<div class="space-y-12">
<div class="flex items-center justify-between text-3xl font-bold tracking-widest text-on-surface/80">
<span>4 + 4 =</span>
<div class="w-24 h-16 border-2 border-surface-container-highest rounded-2xl bg-surface-container-low/30"></div>
</div>
<div class="flex items-center justify-between text-3xl font-bold tracking-widest text-on-surface/80">
<span>0 + 9 =</span>
<div class="w-24 h-16 border-2 border-surface-container-highest rounded-2xl bg-surface-container-low/30"></div>
</div>
<div class="flex items-center justify-between text-3xl font-bold tracking-widest text-on-surface/80">
<span>7 + 1 =</span>
<div class="w-24 h-16 border-2 border-surface-container-highest rounded-2xl bg-surface-container-low/30"></div>
</div>
<div class="flex items-center justify-between text-3xl font-bold tracking-widest text-on-surface/80">
<span>3 + 5 =</span>
<div class="w-24 h-16 border-2 border-surface-container-highest rounded-2xl bg-surface-container-low/30"></div>
</div>
<div class="flex items-center justify-between text-3xl font-bold tracking-widest text-on-surface/80">
<span>5 + 5 =</span>
<div class="w-24 h-16 border-2 border-surface-container-highest rounded-2xl bg-surface-container-low/30"></div>
</div>
</div>
</div>
<!-- Visual Decoration for Kids (Not visible in printing ideally but adds to UI) -->
<div class="absolute bottom-8 right-12 opacity-10 grayscale no-print">
<span class="material-symbols-outlined text-8xl" data-icon="rocket_launch">rocket_launch</span>
</div>
<div class="absolute bottom-8 left-12 opacity-10 grayscale no-print">
<span class="material-symbols-outlined text-8xl" data-icon="auto_awesome">auto_awesome</span>
</div>
</main>
<!-- BottomNavBar Shell (Shared Component) -->
<footer class="fixed bottom-0 left-0 w-full z-50 flex justify-around items-center px-6 pb-6 pt-2 print:hidden bg-[#f8f0e0]/80 backdrop-blur-xl shadow-[0_-8px_32px_rgba(50,46,37,0.06)] rounded-t-[3rem]">
<button class="flex flex-col items-center justify-center bg-[#ffd709] text-[#6c5a00] rounded-full p-3 transition-transform active:scale-96">
<span class="material-symbols-outlined" data-icon="brush">brush</span>
<span class="font-['Plus_Jakarta_Sans'] text-xs font-bold">Studio</span>
</button>
<button class="flex flex-col items-center justify-center text-[#322e25] p-3 hover:opacity-80 transition-opacity active:scale-96">
<span class="material-symbols-outlined" data-icon="description">description</span>
<span class="font-['Plus_Jakarta_Sans'] text-xs font-bold">Templates</span>
</button>
<button class="flex flex-col items-center justify-center text-[#322e25] p-3 hover:opacity-80 transition-opacity active:scale-96">
<span class="material-symbols-outlined" data-icon="auto_awesome_motion">auto_awesome_motion</span>
<span class="font-['Plus_Jakarta_Sans'] text-xs font-bold">Gallery</span>
</button>
</footer>
<!-- FAB for Print Action -->
<button class="no-print fixed right-8 bottom-28 w-16 h-16 bg-primary-container text-on-primary-container rounded-full shadow-lg flex items-center justify-center active:scale-95 transition-all hover:brightness-105 z-[60]" onclick="window.print()">
<span class="material-symbols-outlined text-3xl" data-icon="print">print</span>
</button>
</body></html>
+1
View File
@@ -0,0 +1 @@
<FIFE Image failed to fetch>