feat: 涂色识字
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -5,10 +5,45 @@
|
||||
"css_prefix_text": "icon-",
|
||||
"description": "",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "47521297",
|
||||
"name": "more-vert",
|
||||
"font_class": "more-vert",
|
||||
"unicode": "e633",
|
||||
"unicode_decimal": 58931
|
||||
},
|
||||
{
|
||||
"icon_id": "47521296",
|
||||
"name": "more-horiz",
|
||||
"font_class": "more-horiz",
|
||||
"unicode": "e634",
|
||||
"unicode_decimal": 58932
|
||||
},
|
||||
{
|
||||
"icon_id": "47520659",
|
||||
"name": "iconify-material-symbols_check-rounded",
|
||||
"font_class": "iconify-material-symbols_check-rounded",
|
||||
"unicode": "e632",
|
||||
"unicode_decimal": 58930
|
||||
},
|
||||
{
|
||||
"icon_id": "47519510",
|
||||
"name": "grid",
|
||||
"font_class": "grid",
|
||||
"unicode": "e631",
|
||||
"unicode_decimal": 58929
|
||||
},
|
||||
{
|
||||
"icon_id": "47519398",
|
||||
"name": "grid-o",
|
||||
"font_class": "grid-o",
|
||||
"unicode": "e630",
|
||||
"unicode_decimal": 58928
|
||||
},
|
||||
{
|
||||
"icon_id": "47510723",
|
||||
"name": "iconify-tabler_mood-empty",
|
||||
"font_class": "iconify-tabler_mood-empty",
|
||||
"name": "mood-empty",
|
||||
"font_class": "mood-empty",
|
||||
"unicode": "e62f",
|
||||
"unicode_decimal": 58927
|
||||
},
|
||||
|
||||
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
File diff suppressed because one or more lines are too long
@@ -11,13 +11,8 @@
|
||||
"preview-footer-actions": "../../components3.0/preview-footer-actions/preview-footer-actions",
|
||||
"debug-publish-tools": "../../components3.0/debug-publish-tools/debug-publish-tools",
|
||||
"share-guide-popup": "../../components/share-guide-popup/share-guide-popup",
|
||||
"draw-ad": "../../components/draw-ad/draw-ad",
|
||||
"toy-icon": "../../toy/icon/icon",
|
||||
"toy-button": "../../toy/button-v2/button",
|
||||
"word-input": "../../components/word-input/word-input",
|
||||
"word-card": "../../components/word-card/word-card",
|
||||
"color-picker": "../../components/color-picker/color-picker",
|
||||
"word-picker": "../../components/word-picker/word-picker",
|
||||
"empty-state": "../../components/empty-state/empty-state"
|
||||
"word-picker": "../../components/word-picker/word-picker"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
.wc-main {
|
||||
padding: @page-padding-x;
|
||||
padding-bottom: calc(120rpx + env(safe-area-inset-bottom));
|
||||
padding-bottom: calc(200rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.wc-section {
|
||||
@@ -17,85 +17,246 @@
|
||||
|
||||
.wc-section-title {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
color: @text-secondary;
|
||||
letter-spacing: 0.04em;
|
||||
color: @text-title;
|
||||
letter-spacing: 0.02em;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
/* 输入行 */
|
||||
.wc-input-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
/* 内容设置面板 */
|
||||
.wc-content-panel {
|
||||
background: rgba(240, 231, 214, 0.5);
|
||||
border-radius: @radius-lg;
|
||||
padding: 32rpx;
|
||||
}
|
||||
|
||||
/* 文字卡片网格 */
|
||||
.wc-card-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
/* 表单字段 */
|
||||
.wc-field {
|
||||
margin-bottom: 32rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.wc-field-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 4rpx;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.wc-field-label {
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
color: @text-secondary;
|
||||
}
|
||||
|
||||
.wc-field-hint {
|
||||
font-size: 20rpx;
|
||||
font-weight: 500;
|
||||
color: rgba(96, 91, 80, 0.6);
|
||||
}
|
||||
|
||||
/* 输入框 */
|
||||
.wc-input-wrapper {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.wc-text-input {
|
||||
width: 100%;
|
||||
height: 96rpx;
|
||||
background: #ffffff;
|
||||
border: 2rpx solid rgba(179, 172, 159, 0.3);
|
||||
border-radius: @radius-lg;
|
||||
padding: 0 88rpx 0 28rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
color: @text-title;
|
||||
box-shadow: @shadow;
|
||||
}
|
||||
|
||||
.wc-input-clear {
|
||||
position: absolute;
|
||||
right: 20rpx;
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
/* 颜色自定义 */
|
||||
.wc-color-scroll {
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.wc-color-list {
|
||||
display: inline-flex;
|
||||
gap: 24rpx;
|
||||
padding-bottom: 12rpx;
|
||||
}
|
||||
|
||||
/* 操作按钮行 */
|
||||
.wc-action-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
.wc-color-item {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 空状态 */
|
||||
.wc-empty {
|
||||
padding: 48rpx 0;
|
||||
.wc-color-word {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
background: #ffffff;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-weight: 700;
|
||||
font-size: 28rpx;
|
||||
color: @text-title;
|
||||
box-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.05);
|
||||
border: 2rpx solid rgba(179, 172, 159, 0.1);
|
||||
}
|
||||
|
||||
.wc-color-dot {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 0 4rpx #ffffff, 0 2rpx 4rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.wc-color-dot:active {
|
||||
box-shadow: 0 0 0 4rpx #ffffff, 0 0 0 8rpx @brand, 0 2rpx 4rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.wc-color-add {
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
.wc-color-add-btn {
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
border-radius: 50%;
|
||||
border: 2rpx dashed rgba(179, 172, 159, 0.3);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* 分类标签 */
|
||||
.wc-category-scroll {
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.wc-category-list {
|
||||
display: inline-flex;
|
||||
gap: 16rpx;
|
||||
padding-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.wc-category-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10rpx;
|
||||
flex-shrink: 0;
|
||||
padding: 20rpx 28rpx;
|
||||
background: #ffffff;
|
||||
border: 2rpx solid rgba(179, 172, 159, 0.2);
|
||||
border-radius: 36rpx;
|
||||
box-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.05);
|
||||
transition: transform 0.12s;
|
||||
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
|
||||
.wc-category-tag-icon {
|
||||
font-size: 28rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.wc-category-tag-text {
|
||||
font-size: 22rpx;
|
||||
font-weight: 700;
|
||||
color: #6c5a00;
|
||||
}
|
||||
|
||||
.wc-category-tag--more {
|
||||
.wc-category-tag-icon {
|
||||
font-size: 24rpx;
|
||||
letter-spacing: 2rpx;
|
||||
color: #6c5a00;
|
||||
}
|
||||
}
|
||||
|
||||
/* 模板选择网格(2列) */
|
||||
.wc-mode-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 20rpx;
|
||||
gap: 32rpx;
|
||||
}
|
||||
|
||||
.wc-mode-card {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12rpx;
|
||||
padding: 32rpx 16rpx;
|
||||
gap: 16rpx;
|
||||
padding: 40rpx 16rpx;
|
||||
border-radius: @radius-lg;
|
||||
background: @bg-header;
|
||||
background: rgba(255, 255, 255, 0.6);
|
||||
border: 2rpx solid rgba(179, 172, 159, 0.1);
|
||||
box-shadow: @shadow;
|
||||
transition: transform 0.12s, box-shadow 0.12s;
|
||||
}
|
||||
|
||||
.wc-mode-card--active {
|
||||
background: @brand;
|
||||
box-shadow: @shadow, 0 0 0 4rpx fade(@brand, 30%);
|
||||
background: #ffffff;
|
||||
border: 4rpx solid @brand;
|
||||
box-shadow: @shadow;
|
||||
}
|
||||
|
||||
.wc-mode-card--pressed {
|
||||
transform: scale(0.96);
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.wc-mode-card__icon {
|
||||
margin-bottom: 4rpx;
|
||||
.wc-mode-card__icon-wrap {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 198, 151, 0.3);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.wc-mode-card__icon-wrap--active {
|
||||
background: @brand-light;
|
||||
}
|
||||
|
||||
.wc-mode-card__label {
|
||||
font-size: 26rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
color: @text-title;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.wc-mode-card--active .wc-mode-card__label {
|
||||
color: @text-selected-btn;
|
||||
}
|
||||
|
||||
.wc-mode-card__sub {
|
||||
font-size: 20rpx;
|
||||
color: @text-secondary;
|
||||
@@ -103,6 +264,19 @@
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.wc-mode-card--active .wc-mode-card__sub {
|
||||
color: fade(@text-selected-btn, 75%);
|
||||
.wc-mode-card--active .wc-mode-card__label {
|
||||
color: @text-title;
|
||||
}
|
||||
|
||||
.wc-mode-card__check {
|
||||
position: absolute;
|
||||
top: 12rpx;
|
||||
right: 12rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
border-radius: 50%;
|
||||
background-color: #6c5a00;
|
||||
}
|
||||
@@ -22,6 +22,13 @@ const COLOR_MAP: Record<string, string> = {
|
||||
粉: '#FF69B4', 棕: '#A52A2A', 灰: '#808080',
|
||||
};
|
||||
|
||||
const MAX_WORDS = 3;
|
||||
const CATEGORY_TAGS = WORDS.slice(0, MAX_WORDS).map((cat) => ({
|
||||
categoryId: cat.categoryId,
|
||||
icon: cat.icon,
|
||||
categoryName: cat.categoryName,
|
||||
}));
|
||||
|
||||
const pageInfoLookup = getModeInfo;
|
||||
|
||||
// PLACEHOLDER_TS_PART2
|
||||
@@ -30,10 +37,13 @@ type PageData = CanvasDataState & {
|
||||
worksheetId: string;
|
||||
cardList: CardItem[];
|
||||
selectedWords: string[];
|
||||
inputValue: string;
|
||||
showColorPopup: boolean;
|
||||
currentKey: number;
|
||||
currentColor: string;
|
||||
showSelectWordPopup: boolean;
|
||||
pickerCurrentTab: number;
|
||||
categoryTags: typeof CATEGORY_TAGS;
|
||||
modeOptions: typeof WORD_COLORING_MODE_OPTIONS;
|
||||
isPreviewFavorite: boolean;
|
||||
isDevEnv: boolean;
|
||||
@@ -59,10 +69,13 @@ createPage(
|
||||
worksheetId: 'word-coloring-grid',
|
||||
cardList: [] as CardItem[],
|
||||
selectedWords: [] as string[],
|
||||
inputValue: '',
|
||||
showColorPopup: false,
|
||||
currentKey: 0,
|
||||
currentColor: '',
|
||||
showSelectWordPopup: false,
|
||||
pickerCurrentTab: 0,
|
||||
categoryTags: CATEGORY_TAGS,
|
||||
modeOptions: WORD_COLORING_MODE_OPTIONS,
|
||||
isPreviewFavorite: false,
|
||||
isDevEnv: false,
|
||||
@@ -84,6 +97,7 @@ createPage(
|
||||
});
|
||||
this.initPageInfo(worksheetId, '涂色识字');
|
||||
this.loadFavoritedMap();
|
||||
this.refreshCardList();
|
||||
},
|
||||
|
||||
onCanvasReady(e: WechatMiniprogram.CustomEvent) {
|
||||
@@ -174,75 +188,83 @@ createPage(
|
||||
word,
|
||||
color: COLOR_MAP[word] || colors[i % colors.length].hex,
|
||||
}));
|
||||
this.setData({ cardList, selectedWords: selected }, () => {
|
||||
const inputValue = selected.join('');
|
||||
this.setData({ cardList, selectedWords: selected, inputValue }, () => {
|
||||
this.drawCanvas();
|
||||
});
|
||||
},
|
||||
|
||||
/** 清空文字卡片 */
|
||||
clearCardList() {
|
||||
this.setData({ cardList: [], selectedWords: [], hasContent: false });
|
||||
this.setData({ cardList: [], selectedWords: [], inputValue: '', hasContent: false });
|
||||
},
|
||||
|
||||
// PLACEHOLDER_TS_PART4
|
||||
|
||||
/** 输入确认:添加文字卡片 */
|
||||
onConfirmInput(e: WechatMiniprogram.Input) {
|
||||
const { cardList } = this.data;
|
||||
const { value } = e.detail;
|
||||
const characters = value.split('');
|
||||
const remainingSlots = 6 - cardList.length;
|
||||
if (remainingSlots <= 0) {
|
||||
wx.showToast({ title: '最多只能添加6个字哦', icon: 'none' });
|
||||
return;
|
||||
/** 输入框内容变化:同步 cardList */
|
||||
onInputChange(e: WechatMiniprogram.Input) {
|
||||
const value = e.detail.value as string;
|
||||
this.syncCardsFromInput(value);
|
||||
},
|
||||
|
||||
/** 输入框确认 */
|
||||
onInputConfirm(e: WechatMiniprogram.Input) {
|
||||
const value = e.detail.value as string;
|
||||
this.syncCardsFromInput(value);
|
||||
},
|
||||
|
||||
/** 清空输入框及卡片 */
|
||||
onClearInput() {
|
||||
this.setData({ inputValue: '', cardList: [], selectedWords: [], hasContent: false });
|
||||
if (this.drawService) {
|
||||
this.drawCanvas();
|
||||
}
|
||||
},
|
||||
|
||||
const usedColors = cardList.map((item) => item.color);
|
||||
const usedWords = cardList.map((item) => item.word);
|
||||
const allColors = WATER_COLORS.basic12.map((c) => c.hex);
|
||||
const available = allColors.filter((c) => !usedColors.includes(c));
|
||||
const shuffled = [...available].sort(() => Math.random() - 0.5);
|
||||
/** 将输入文本同步为 cardList */
|
||||
syncCardsFromInput(value: string) {
|
||||
const chars = [...new Set(value.split('').filter((c: string) => c.trim()))].slice(0, 6);
|
||||
const oldCardMap = new Map<string, string>(this.data.cardList.map((c: CardItem) => [c.word, c.color]));
|
||||
const allColors = WATER_COLORS.basic12.map((c: { hex: string }) => c.hex);
|
||||
const usedColors = new Set<string>();
|
||||
|
||||
const newChars: string[] = [];
|
||||
for (const char of characters) {
|
||||
if (newChars.length >= remainingSlots) break;
|
||||
if (!usedWords.includes(char) && !newChars.includes(char)) {
|
||||
newChars.push(char);
|
||||
const cardList = chars.map((char) => {
|
||||
const existing = oldCardMap.get(char);
|
||||
if (existing) {
|
||||
usedColors.add(existing);
|
||||
return { word: char, color: existing };
|
||||
}
|
||||
}
|
||||
|
||||
const newCards = newChars.map((char, i) => {
|
||||
const mapped = COLOR_MAP[char];
|
||||
const color =
|
||||
mapped && !usedColors.includes(mapped)
|
||||
? mapped
|
||||
: shuffled[i % shuffled.length];
|
||||
if (mapped && !usedColors.has(mapped)) {
|
||||
usedColors.add(mapped);
|
||||
return { word: char, color: mapped };
|
||||
}
|
||||
const available = allColors.filter((c) => !usedColors.has(c));
|
||||
const shuffled = [...available].sort(() => Math.random() - 0.5);
|
||||
const color = shuffled[0] || allColors[Math.floor(Math.random() * allColors.length)];
|
||||
usedColors.add(color);
|
||||
return { word: char, color };
|
||||
});
|
||||
|
||||
const updated = [...cardList, ...newCards];
|
||||
this.setData(
|
||||
{ cardList: updated, selectedWords: updated.map((c) => c.word) },
|
||||
{ inputValue: value, cardList, selectedWords: chars },
|
||||
() => this.drawCanvas(),
|
||||
);
|
||||
},
|
||||
|
||||
/** 删除单个文字卡片 */
|
||||
deleteWordCard(e: WechatMiniprogram.CustomEvent) {
|
||||
const { key } = e.detail;
|
||||
const updated = this.data.cardList.filter((_, i) => i !== key);
|
||||
this.setData(
|
||||
{ cardList: updated, selectedWords: updated.map((c) => c.word) },
|
||||
() => this.drawCanvas(),
|
||||
);
|
||||
},
|
||||
|
||||
/** 颜色选择相关 */
|
||||
onColorTap(e: WechatMiniprogram.CustomEvent) {
|
||||
const { key, color } = e.detail;
|
||||
/** 颜色自定义中的颜色点点击 */
|
||||
onColorDotTap(e: WechatMiniprogram.TouchEvent) {
|
||||
const key = Number(e.currentTarget.dataset.key);
|
||||
const color = e.currentTarget.dataset.color as string;
|
||||
this.setData({ showColorPopup: true, currentKey: key, currentColor: color });
|
||||
},
|
||||
|
||||
/** 分类标签点击:打开弹窗定位到对应分类 */
|
||||
onCategoryTap(e: WechatMiniprogram.TouchEvent) {
|
||||
const categoryId = Number(e.currentTarget.dataset.categoryId);
|
||||
this.setData({ showSelectWordPopup: true, pickerCurrentTab: categoryId });
|
||||
},
|
||||
|
||||
onCloseColorPopup() {
|
||||
this.setData({ showColorPopup: false, currentKey: 0, currentColor: '' });
|
||||
},
|
||||
@@ -259,7 +281,7 @@ createPage(
|
||||
|
||||
/** 文字选择弹窗 */
|
||||
openSelectWordPopup() {
|
||||
this.setData({ showSelectWordPopup: true });
|
||||
this.setData({ showSelectWordPopup: true, pickerCurrentTab: 0 });
|
||||
},
|
||||
|
||||
closeSelectWordPopup() {
|
||||
@@ -268,12 +290,30 @@ createPage(
|
||||
|
||||
onChangeWord(e: WechatMiniprogram.CustomEvent) {
|
||||
const selectedWords = e.detail.selectedWords as string[];
|
||||
const colors = [...WATER_COLORS.basic12];
|
||||
const cardList = selectedWords.map((word, i) => ({
|
||||
word,
|
||||
color: COLOR_MAP[word] || colors[i % colors.length].hex,
|
||||
}));
|
||||
this.setData({ showSelectWordPopup: false, cardList, selectedWords }, () => {
|
||||
const oldCardMap = new Map<string, string>(this.data.cardList.map((c: CardItem) => [c.word, c.color]));
|
||||
const allColors = WATER_COLORS.basic12.map((c: { hex: string }) => c.hex);
|
||||
const usedColors = new Set<string>();
|
||||
|
||||
const cardList = selectedWords.map((word) => {
|
||||
const existing = oldCardMap.get(word);
|
||||
if (existing) {
|
||||
usedColors.add(existing);
|
||||
return { word, color: existing };
|
||||
}
|
||||
const mapped = COLOR_MAP[word];
|
||||
if (mapped && !usedColors.has(mapped)) {
|
||||
usedColors.add(mapped);
|
||||
return { word, color: mapped };
|
||||
}
|
||||
const available = allColors.filter((c) => !usedColors.has(c));
|
||||
const shuffled = [...available].sort(() => Math.random() - 0.5);
|
||||
const color = shuffled[0] || allColors[Math.floor(Math.random() * allColors.length)];
|
||||
usedColors.add(color);
|
||||
return { word, color };
|
||||
});
|
||||
|
||||
const inputValue = selectedWords.join('');
|
||||
this.setData({ showSelectWordPopup: false, cardList, selectedWords, inputValue }, () => {
|
||||
this.drawCanvas();
|
||||
});
|
||||
},
|
||||
|
||||
@@ -11,67 +11,102 @@
|
||||
bind:refresh="onPreviewRefresh"
|
||||
bind:favorite="onPreviewFavorite" />
|
||||
|
||||
<!-- 文字输入区域 -->
|
||||
<!-- 内容设置 -->
|
||||
<view class="wc-section">
|
||||
<text class="wc-section-title">添加文字</text>
|
||||
<view class="wc-input-row">
|
||||
<word-input bind:onConfirm="onConfirmInput" width="380rpx" />
|
||||
<toy-button
|
||||
type="green"
|
||||
size="small"
|
||||
bind:click="openSelectWordPopup"
|
||||
width="180rpx">
|
||||
选择文字
|
||||
</toy-button>
|
||||
<text class="wc-section-title">内容设置</text>
|
||||
<view class="wc-content-panel">
|
||||
<!-- 自定义文字 -->
|
||||
<view class="wc-field">
|
||||
<view class="wc-field-header">
|
||||
<text class="wc-field-label">自定义文字</text>
|
||||
<text class="wc-field-hint">最多输入6个字</text>
|
||||
</view>
|
||||
<view class="wc-input-wrapper">
|
||||
<input
|
||||
class="wc-text-input"
|
||||
type="text"
|
||||
maxlength="6"
|
||||
placeholder="输入想要练习的汉字"
|
||||
value="{{inputValue}}"
|
||||
bindinput="onInputChange"
|
||||
bindconfirm="onInputConfirm" />
|
||||
<view
|
||||
wx:if="{{inputValue.length > 0}}"
|
||||
class="wc-input-clear"
|
||||
bindtap="onClearInput">
|
||||
<toy-icon
|
||||
name="close-bold"
|
||||
size="36rpx"
|
||||
color="#605b50" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 颜色自定义 -->
|
||||
<view class="wc-field">
|
||||
<view class="wc-field-header">
|
||||
<text class="wc-field-label">颜色自定义</text>
|
||||
<text class="wc-field-hint">点击圆点修改颜色</text>
|
||||
</view>
|
||||
<scroll-view scroll-x class="wc-color-scroll">
|
||||
<view class="wc-color-list">
|
||||
<view
|
||||
wx:for="{{cardList}}"
|
||||
wx:key="index"
|
||||
class="wc-color-item">
|
||||
<view class="wc-color-word">{{item.word}}</view>
|
||||
<view
|
||||
class="wc-color-dot"
|
||||
style="background-color: {{item.color}}"
|
||||
data-key="{{index}}"
|
||||
data-color="{{item.color}}"
|
||||
bindtap="onColorDotTap"></view>
|
||||
</view>
|
||||
<!-- 添加按钮 -->
|
||||
<view class="wc-color-item wc-color-add">
|
||||
<view
|
||||
class="wc-color-add-btn"
|
||||
bindtap="openSelectWordPopup">
|
||||
<toy-icon
|
||||
name="plus"
|
||||
size="36rpx"
|
||||
color="rgba(96,91,80,0.4)" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
<!-- 分类标签 -->
|
||||
<scroll-view scroll-x class="wc-category-scroll">
|
||||
<view class="wc-category-list">
|
||||
<view
|
||||
wx:for="{{categoryTags}}"
|
||||
wx:key="categoryId"
|
||||
class="wc-category-tag"
|
||||
data-category-id="{{item.categoryId}}"
|
||||
bindtap="onCategoryTap">
|
||||
<text class="wc-category-tag-icon"
|
||||
>{{item.icon}}</text
|
||||
>
|
||||
<text class="wc-category-tag-text"
|
||||
>{{item.categoryName}}</text
|
||||
>
|
||||
</view>
|
||||
<view
|
||||
class="wc-category-tag wc-category-tag--more"
|
||||
bindtap="openSelectWordPopup">
|
||||
<text class="wc-category-tag-icon">···</text>
|
||||
<text class="wc-category-tag-text">更多分类</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 文字卡片 -->
|
||||
<view wx:if="{{cardList.length > 0}}" class="wc-section">
|
||||
<view class="wc-card-grid">
|
||||
<word-card
|
||||
wx:for="{{cardList}}"
|
||||
wx:key="index"
|
||||
key="{{index}}"
|
||||
word="{{item.word}}"
|
||||
color="{{item.color}}"
|
||||
bind:onDelete="deleteWordCard"
|
||||
bind:onColorTap="onColorTap" />
|
||||
</view>
|
||||
</view>
|
||||
<view wx:elif="{{!hasContent}}" class="wc-empty">
|
||||
<empty-state
|
||||
title="还没有添加文字"
|
||||
desc="输入文字或点击选择文字,开始创建涂鸦卡"
|
||||
hint="提示:最多可以同时添加 6 个文字" />
|
||||
</view>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<!-- 模板选择 -->
|
||||
<view class="wc-section">
|
||||
<view class="wc-action-row">
|
||||
<toy-button
|
||||
type="primary"
|
||||
bind:click="refreshCardList"
|
||||
width="380rpx"
|
||||
icon="refresh"
|
||||
icon-class-prefix="toy-icon">
|
||||
随机生成
|
||||
</toy-button>
|
||||
<toy-button
|
||||
disabled="{{cardList.length <= 0}}"
|
||||
type="white"
|
||||
bind:click="clearCardList"
|
||||
width="180rpx"
|
||||
icon="clear"
|
||||
icon-class-prefix="toy-icon">
|
||||
清空
|
||||
</toy-button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 模板选择(2列卡片网格) -->
|
||||
<view class="wc-section">
|
||||
<text class="wc-section-title">选择模板</text>
|
||||
<text class="wc-section-title">模板选择</text>
|
||||
<view class="wc-mode-grid">
|
||||
<view
|
||||
wx:for="{{modeOptions}}"
|
||||
@@ -82,18 +117,23 @@
|
||||
hover-start-time="0"
|
||||
hover-stay-time="70"
|
||||
bindtap="onSelectMode">
|
||||
<toy-icon
|
||||
name="{{item.icon}}"
|
||||
size="42rpx"
|
||||
color="{{worksheetId === item.id ? '#453900' : '#605b50'}}"
|
||||
custom-class="wc-mode-card__icon" />
|
||||
<view
|
||||
class="wc-mode-card__icon-wrap {{worksheetId === item.id ? 'wc-mode-card__icon-wrap--active' : ''}}">
|
||||
<toy-icon
|
||||
name="{{item.icon}}"
|
||||
size="56rpx"
|
||||
color="{{worksheetId === item.id ? '#6c5a00' : '#8a4c00'}}" />
|
||||
</view>
|
||||
<text class="wc-mode-card__label">{{item.title}}</text>
|
||||
<text class="wc-mode-card__sub">{{item.subtitle}}</text>
|
||||
<view
|
||||
wx:if="{{worksheetId === item.id}}"
|
||||
class="wc-mode-card__check">
|
||||
<toy-icon name="check" size="28rpx" color="#fff" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<draw-ad type="mathDraw"></draw-ad>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -126,5 +166,6 @@
|
||||
<word-picker
|
||||
show="{{showSelectWordPopup}}"
|
||||
cardList="{{cardList}}"
|
||||
currentTab="{{pickerCurrentTab}}"
|
||||
bind:onClose="closeSelectWordPopup"
|
||||
bind:onChange="onChangeWord" />
|
||||
|
||||
@@ -68,7 +68,8 @@ Component({
|
||||
},
|
||||
|
||||
onTabChange(e: WechatMiniprogram.CustomEvent) {
|
||||
this.setData({ currentTab: e.detail });
|
||||
const index = typeof e.detail === 'object' ? e.detail.index : e.detail;
|
||||
this.setData({ currentTab: index });
|
||||
},
|
||||
|
||||
onWordClick(e: WechatMiniprogram.TouchEvent) {
|
||||
|
||||
@@ -230,14 +230,6 @@ page {
|
||||
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;
|
||||
|
||||
@@ -124,7 +124,7 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<draw-ad type="mathDraw"></draw-ad>
|
||||
<!-- <draw-ad type="mathDraw"></draw-ad> -->
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
@@ -145,13 +145,6 @@ page {
|
||||
background-color: rgba(234, 226, 208, 0.55);
|
||||
}
|
||||
|
||||
.fd-shuffle-btn toy-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.fd-shuffle-btn__text {
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
|
||||
@@ -200,13 +200,6 @@ page {
|
||||
background-color: rgba(234, 226, 208, 0.55);
|
||||
}
|
||||
|
||||
.md-shuffle-btn toy-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.md-shuffle-btn__text {
|
||||
font-size: 36rpx;
|
||||
font-weight: 700;
|
||||
|
||||
@@ -102,7 +102,7 @@ class FindWordDrawService extends BaseDrawService {
|
||||
// 找字模板没有 drawLegend 部分
|
||||
this.drawDivider();
|
||||
this.drawContent();
|
||||
await this.drawPrintFooter();
|
||||
// await this.drawPrintFooter();
|
||||
}
|
||||
|
||||
drawContent() {
|
||||
|
||||
@@ -79,7 +79,7 @@ class TextDrawService extends BaseDrawService {
|
||||
this.drawDivider();
|
||||
this.drawLegend();
|
||||
this.drawContent();
|
||||
await this.drawPrintFooter();
|
||||
// await this.drawPrintFooter();
|
||||
}
|
||||
|
||||
/** 绘制示例
|
||||
|
||||
@@ -273,7 +273,7 @@ class WordDrawService extends BaseDrawService {
|
||||
async drawContentEmpty() {
|
||||
this.clearContentArea();
|
||||
this.drawContent(null);
|
||||
await this.drawPrintFooter();
|
||||
// await this.drawPrintFooter();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -286,7 +286,7 @@ class WordDrawService extends BaseDrawService {
|
||||
|
||||
// 绘制田字格和练字内容
|
||||
this.drawContent(characters);
|
||||
await this.drawPrintFooter();
|
||||
// await this.drawPrintFooter();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
vertical-align: middle;
|
||||
flex-shrink: 0;
|
||||
font-size: inherit;
|
||||
color: inherit;
|
||||
line-height: 1;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
Component({
|
||||
options: {
|
||||
virtualHost: true,
|
||||
styleIsolation: 'apply-shared',
|
||||
addGlobalClass: true,
|
||||
},
|
||||
|
||||
@@ -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. Do’s and Don’ts
|
||||
|
||||
### 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.
|
||||
|
||||
### Don’t:
|
||||
* **Don’t** use pure black (#000000). Use `on_surface` (#322e25) for all dark text to maintain the warm, sunny tone.
|
||||
* **Don’t** use 1px dividers. If you feel you need one, use a `surface_variant` (#e5dcc9) color block that is `4px` thick with `full` rounded caps.
|
||||
* **Don’t** crowd the interface. If a screen feels busy, increase the background "paper" size and use a scrollable layout instead of shrinking elements.
|
||||
@@ -0,0 +1,305 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html class="light" 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&display=swap"
|
||||
rel="stylesheet" />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&display=swap"
|
||||
rel="stylesheet" />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&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": {
|
||||
"primary": "#6c5a00",
|
||||
"on-tertiary-fixed-variant": "#00691a",
|
||||
"on-secondary-fixed": "#512a00",
|
||||
"on-error": "#ffefec",
|
||||
"surface-dim": "#dcd4c0",
|
||||
"primary-fixed": "#ffd709",
|
||||
"on-tertiary-fixed": "#00480f",
|
||||
"outline": "#7c766a",
|
||||
"on-secondary": "#fff0e6",
|
||||
"on-tertiary": "#d1ffc8",
|
||||
"inverse-surface": "#110e06",
|
||||
"on-secondary-fixed-variant": "#7a4300",
|
||||
"on-tertiary-container": "#005e17",
|
||||
"background": "#fef6e7",
|
||||
"primary-dim": "#5e4e00",
|
||||
"surface-container-low": "#f8f0e0",
|
||||
"primary-fixed-dim": "#efc900",
|
||||
"tertiary-container": "#91f78e",
|
||||
"inverse-primary": "#ffd709",
|
||||
"surface": "#fef6e7",
|
||||
"error": "#b02500",
|
||||
"on-error-container": "#520c00",
|
||||
"on-surface-variant": "#605b50",
|
||||
"surface-container-lowest": "#ffffff",
|
||||
"secondary": "#8a4c00",
|
||||
"surface-variant": "#e5dcc9",
|
||||
"secondary-fixed-dim": "#ffb470",
|
||||
"on-primary-fixed": "#453900",
|
||||
"on-secondary-container": "#6d3b00",
|
||||
"tertiary": "#006b1b",
|
||||
"surface-tint": "#6c5a00",
|
||||
"surface-container": "#f0e7d6",
|
||||
"on-background": "#322e25",
|
||||
"surface-bright": "#fef6e7",
|
||||
"on-primary-container": "#5b4b00",
|
||||
"error-container": "#f95630",
|
||||
"outline-variant": "#b3ac9f",
|
||||
"surface-container-highest": "#e5dcc9",
|
||||
"secondary-fixed": "#ffc697",
|
||||
"secondary-dim": "#794200",
|
||||
"on-surface": "#322e25",
|
||||
"on-primary": "#fff2cd",
|
||||
"tertiary-fixed": "#91f78e",
|
||||
"inverse-on-surface": "#a29c8f",
|
||||
"error-dim": "#b92902",
|
||||
"primary-container": "#ffd709",
|
||||
"surface-container-high": "#eae2d0",
|
||||
"on-primary-fixed-variant": "#665500",
|
||||
"tertiary-fixed-dim": "#83e881",
|
||||
"tertiary-dim": "#005d16",
|
||||
"secondary-container": "#ffc697"
|
||||
},
|
||||
"borderRadius": {
|
||||
"DEFAULT": "1rem",
|
||||
"lg": "2rem",
|
||||
"xl": "3rem",
|
||||
"full": "9999px"
|
||||
},
|
||||
"fontFamily": {
|
||||
"headline": ["Plus Jakarta Sans", "sans-serif"],
|
||||
"display": ["Plus Jakarta Sans", "sans-serif"],
|
||||
"body": ["Plus Jakarta Sans", "sans-serif"],
|
||||
"label": ["Plus Jakarta Sans", "sans-serif"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
.material-symbols-outlined {
|
||||
font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Plus Jakarta Sans', sans-serif;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
body {
|
||||
min-height: max(884px, 100dvh);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="bg-surface text-on-surface min-h-screen pb-32 bg-[#f8f0e0]">
|
||||
<!-- TopAppBar -->
|
||||
<header
|
||||
class="fixed top-0 left-0 w-full z-50 flex items-center justify-between px-6 h-16 bg-[#f8f0e0]/80 backdrop-blur-[20px]">
|
||||
<button class="active:scale-95 transition-transform duration-200 text-[#6c5a00]">
|
||||
<span class="material-symbols-outlined text-2xl">arrow_back</span>
|
||||
</button>
|
||||
<h1 class="font-['Plus_Jakarta_Sans'] font-bold text-lg tracking-tight text-[#6c5a00]">涂色识字配置</h1>
|
||||
<button class="active:scale-95 transition-transform duration-200 text-[#6c5a00]">
|
||||
<span class="material-symbols-outlined text-2xl">help</span>
|
||||
</button>
|
||||
</header>
|
||||
<main class="pt-20 px-6 max-w-2xl mx-auto">
|
||||
<!-- Print Preview Section -->
|
||||
<section class="mb-10">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="text-on-secondary-container font-bold text-lg">打印预览</h2>
|
||||
<span class="text-on-surface-variant text-sm font-medium">A4 垂直布局</span>
|
||||
</div>
|
||||
<div
|
||||
class="relative bg-surface-container-lowest rounded-xl shadow-sm aspect-[1/1.414] overflow-hidden p-6 border-4 border-surface-container-highest">
|
||||
<div
|
||||
class="h-full w-full border border-dashed border-outline-variant flex flex-col items-center justify-center space-y-8 bg-white/50">
|
||||
<!-- Preview Content Simulation -->
|
||||
<div class="grid grid-cols-4 gap-4 w-full px-4">
|
||||
<div class="aspect-square flex flex-col items-center justify-center space-y-2">
|
||||
<span class="text-4xl font-bold text-on-surface/20">山</span>
|
||||
<div class="w-4 h-4 rounded-full bg-primary-container shadow-sm"></div>
|
||||
</div>
|
||||
<div class="aspect-square flex flex-col items-center justify-center space-y-2">
|
||||
<span class="text-4xl font-bold text-on-surface/20">水</span>
|
||||
<div class="w-4 h-4 rounded-full bg-tertiary-container shadow-sm"></div>
|
||||
</div>
|
||||
<div class="aspect-square flex flex-col items-center justify-center space-y-2">
|
||||
<span class="text-4xl font-bold text-on-surface/20">日</span>
|
||||
<div class="w-4 h-4 rounded-full bg-secondary-container shadow-sm"></div>
|
||||
</div>
|
||||
<div class="aspect-square flex flex-col items-center justify-center space-y-2">
|
||||
<span class="text-4xl font-bold text-on-surface/20">月</span>
|
||||
<div class="w-4 h-4 rounded-full bg-blue-200 shadow-sm"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-4/5 h-px bg-surface-container-highest"></div>
|
||||
<div class="grid grid-cols-5 gap-3 w-full px-4">
|
||||
<div class="w-full aspect-square border border-surface-container-highest rounded-full"></div>
|
||||
<div class="w-full aspect-square border border-surface-container-highest rounded-full"></div>
|
||||
<div class="w-full aspect-square border border-surface-container-highest rounded-full"></div>
|
||||
<div class="w-full aspect-square border border-surface-container-highest rounded-full"></div>
|
||||
<div class="w-full aspect-square border border-surface-container-highest rounded-full"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Floating Action Buttons -->
|
||||
<button
|
||||
class="absolute bottom-4 left-4 w-12 h-12 bg-surface-container-lowest/90 backdrop-blur shadow-lg rounded-full flex items-center justify-center text-primary active:scale-90 transition-all">
|
||||
<span class="material-symbols-outlined">refresh</span>
|
||||
</button>
|
||||
<button
|
||||
class="absolute bottom-4 right-4 w-12 h-12 bg-surface-container-lowest/90 backdrop-blur shadow-lg rounded-full flex items-center justify-center text-primary active:scale-90 transition-all">
|
||||
<span class="material-symbols-outlined" style="font-variation-settings: 'FILL' 1;">favorite</span>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
<!-- Custom Content Section -->
|
||||
<section class="mb-10">
|
||||
<h2 class="text-on-secondary-container font-bold text-lg mb-4">内容设置</h2>
|
||||
<div class="bg-surface-container/50 rounded-2xl p-5 space-y-5 h-auto">
|
||||
<div class="flex flex-col space-y-3">
|
||||
<div class="flex justify-between items-center px-1">
|
||||
<label class="text-on-surface-variant text-sm font-semibold">自定义文字</label>
|
||||
<span class="text-on-surface-variant/60 text-xs font-medium">最多输入6个字</span>
|
||||
</div>
|
||||
<div class="relative flex items-center">
|
||||
<input
|
||||
class="w-full h-14 bg-white border border-outline-variant/30 rounded-2xl pl-4 pr-12 text-on-surface text-base focus:ring-2 focus:ring-primary-container focus:border-transparent font-medium shadow-sm"
|
||||
maxlength="6" placeholder="输入想要练习的汉字" type="text" value="山水日月" />
|
||||
<button
|
||||
class="absolute right-3 p-1 rounded-full text-on-surface-variant hover:bg-surface-container-low active:scale-90 transition-all">
|
||||
<span class="material-symbols-outlined text-xl">close</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="mt-6 space-y-3">
|
||||
<div class="flex justify-between items-center px-1">
|
||||
<label class="text-on-surface-variant text-sm font-semibold">颜色自定义</label>
|
||||
<span class="text-on-surface-variant/60 text-xs font-medium">点击圆点修改颜色</span>
|
||||
</div>
|
||||
<div class="flex gap-4 overflow-x-auto no-scrollbar pb-2">
|
||||
<div class="flex-shrink-0 flex flex-col items-center gap-2">
|
||||
<div
|
||||
class="w-10 h-10 bg-white rounded-xl flex items-center justify-center font-bold text-lg text-on-surface shadow-sm border border-outline-variant/10">
|
||||
山</div>
|
||||
<button
|
||||
class="w-6 h-6 rounded-full bg-primary-container ring-2 ring-offset-2 ring-transparent active:ring-primary shadow-sm"></button>
|
||||
</div>
|
||||
<div class="flex-shrink-0 flex flex-col items-center gap-2">
|
||||
<div
|
||||
class="w-10 h-10 bg-white rounded-xl flex items-center justify-center font-bold text-lg text-on-surface shadow-sm border border-outline-variant/10">
|
||||
水</div>
|
||||
<button
|
||||
class="w-6 h-6 rounded-full bg-tertiary-container ring-2 ring-offset-2 ring-transparent active:ring-primary shadow-sm"></button>
|
||||
</div>
|
||||
<div class="flex-shrink-0 flex flex-col items-center gap-2">
|
||||
<div
|
||||
class="w-10 h-10 bg-white rounded-xl flex items-center justify-center font-bold text-lg text-on-surface shadow-sm border border-outline-variant/10">
|
||||
日</div>
|
||||
<button
|
||||
class="w-6 h-6 rounded-full bg-secondary-container ring-2 ring-offset-2 ring-transparent active:ring-primary shadow-sm"></button>
|
||||
</div>
|
||||
<div class="flex-shrink-0 flex flex-col items-center gap-2">
|
||||
<div
|
||||
class="w-10 h-10 bg-white rounded-xl flex items-center justify-center font-bold text-lg text-on-surface shadow-sm border border-outline-variant/10">
|
||||
月</div>
|
||||
<button
|
||||
class="w-6 h-6 rounded-full bg-blue-200 ring-2 ring-offset-2 ring-transparent active:ring-primary shadow-sm"></button>
|
||||
</div>
|
||||
<div class="flex-shrink-0 flex items-center justify-center">
|
||||
<button
|
||||
class="w-10 h-10 rounded-xl border border-dashed border-outline-variant/30 flex items-center justify-center text-on-surface-variant/40">
|
||||
<span class="material-symbols-outlined">add</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="-mx-1 px-1 overflow-x-auto no-scrollbar">
|
||||
<div class="flex gap-3 pb-1">
|
||||
<button
|
||||
class="flex-shrink-0 px-5 py-2.5 bg-white border border-outline-variant/20 rounded-xl text-[13px] font-bold text-primary shadow-sm active:scale-95 transition-all flex items-center gap-2 hover:bg-primary-container">
|
||||
<span class="material-symbols-outlined text-lg">draw</span>
|
||||
<span>基础笔画</span>
|
||||
</button>
|
||||
<button
|
||||
class="flex-shrink-0 px-5 py-2.5 bg-white border border-outline-variant/20 rounded-xl text-[13px] font-bold text-primary shadow-sm active:scale-95 transition-all flex items-center gap-2 hover:bg-primary-container">
|
||||
<span class="material-symbols-outlined text-lg">pin</span>
|
||||
<span>数字拼音</span>
|
||||
</button>
|
||||
<button
|
||||
class="flex-shrink-0 px-5 py-2.5 bg-white border border-outline-variant/20 rounded-xl text-[13px] font-bold text-primary shadow-sm active:scale-95 transition-all flex items-center gap-2 hover:bg-primary-container">
|
||||
<span class="material-symbols-outlined text-lg">nature_people</span>
|
||||
<span>动物植物</span>
|
||||
</button>
|
||||
<button
|
||||
class="flex-shrink-0 px-5 py-2.5 bg-white border border-outline-variant/20 rounded-xl text-[13px] font-bold text-primary shadow-sm active:scale-95 transition-all flex items-center gap-2 hover:bg-primary-container">
|
||||
<span class="material-symbols-outlined text-lg">more_horiz</span>
|
||||
<span>更多分类</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<!-- Template Selection Grid -->
|
||||
<section class="mb-10">
|
||||
<h2 class="text-on-secondary-container font-bold text-lg mb-4">模板选择</h2>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<!-- Grid Template Card -->
|
||||
<div
|
||||
class="relative bg-white p-6 rounded-2xl border-2 border-[#FFD700] shadow-sm flex flex-col items-center justify-center space-y-3">
|
||||
<div class="w-14 h-14 bg-primary-container rounded-full flex items-center justify-center text-primary">
|
||||
<span class="material-symbols-outlined text-3xl">grid_on</span>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<p class="font-bold text-on-surface text-base">网格模板</p>
|
||||
<p class="text-xs text-on-surface-variant/70 mt-0.5">规范田字格练习</p>
|
||||
</div>
|
||||
<div class="absolute top-2 right-2">
|
||||
<span class="material-symbols-outlined text-primary text-xl"
|
||||
style="font-variation-settings: 'FILL' 1;">check_circle</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Search Template Card -->
|
||||
<div
|
||||
class="bg-white/60 p-6 rounded-2xl border border-outline-variant/10 shadow-sm flex flex-col items-center justify-center space-y-3 active:scale-95 transition-all">
|
||||
<div class="w-14 h-14 bg-secondary-container/30 rounded-full flex items-center justify-center text-secondary">
|
||||
<span class="material-symbols-outlined text-3xl">search</span>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<p class="font-bold text-on-surface text-base">找字模板</p>
|
||||
<p class="text-xs text-on-surface-variant/70 mt-0.5">趣味视觉辨识练习</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<!-- Bottom Action Bar -->
|
||||
<div class="fixed bottom-0 left-0 w-full bg-[#f8f0e0]/90 backdrop-blur-[20px] px-6 py-6 flex gap-4 items-center z-50">
|
||||
<button
|
||||
class="w-[40%] h-14 bg-tertiary-container text-on-tertiary-container rounded-full font-bold flex items-center justify-center gap-2 active:scale-95 transition-all shadow-[0_4px_12px_rgba(0,107,27,0.1)]">
|
||||
<span class="material-symbols-outlined">share</span>
|
||||
<span>分享</span>
|
||||
</button>
|
||||
<button
|
||||
class="w-[60%] h-14 bg-gradient-to-br from-primary-fixed to-primary-fixed-dim text-on-primary-container rounded-full font-bold flex items-center justify-center gap-2 active:scale-95 transition-all shadow-[0_4px_12px_rgba(108,90,0,0.15)]">
|
||||
<span class="material-symbols-outlined">download</span>
|
||||
<span>保存到相册</span>
|
||||
</button>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 123 KiB |
Reference in New Issue
Block a user