feat: 首页跳转到分类页指定分类优化
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
import type { DebugPublishMeta } from '../../utils/debugPublish';
|
||||
import { inferGradeFromAge } from '../../utils/debugPublish';
|
||||
|
||||
interface HandwritingSheetWorksheetDefinition {
|
||||
id: string;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
ageMin: number;
|
||||
ageMax: number;
|
||||
difficulty: 1 | 2 | 3 | 4;
|
||||
tags: string[];
|
||||
sortOrder: number;
|
||||
}
|
||||
|
||||
export const HANDWRITING_SHEET_WORKSHEET_DEFINITIONS = [
|
||||
{
|
||||
id: 'handwriting-sheet',
|
||||
title: '自定义练字帖',
|
||||
subtitle: '自定义田字格练字帖',
|
||||
ageMin: 4,
|
||||
ageMax: 8,
|
||||
difficulty: 2,
|
||||
tags: ['练字', '字帖', '田字格', '汉字'],
|
||||
sortOrder: 35,
|
||||
},
|
||||
] as const satisfies ReadonlyArray<HandwritingSheetWorksheetDefinition>;
|
||||
|
||||
type HandwritingSheetWorksheetRow =
|
||||
(typeof HANDWRITING_SHEET_WORKSHEET_DEFINITIONS)[number];
|
||||
|
||||
const HANDWRITING_SHEET_WORKSHEET_BY_ID = Object.fromEntries(
|
||||
HANDWRITING_SHEET_WORKSHEET_DEFINITIONS.map((item) => [item.id, item]),
|
||||
) as Record<string, HandwritingSheetWorksheetRow>;
|
||||
|
||||
export const HANDWRITING_SHEET_WORKSHEET_ID =
|
||||
HANDWRITING_SHEET_WORKSHEET_DEFINITIONS[0].id;
|
||||
|
||||
export function getModeInfo(id: string) {
|
||||
const item = HANDWRITING_SHEET_WORKSHEET_BY_ID[id];
|
||||
return item ? { title: item.title, desc: item.subtitle } : undefined;
|
||||
}
|
||||
|
||||
export function isValidMode(id: string): boolean {
|
||||
return id in HANDWRITING_SHEET_WORKSHEET_BY_ID;
|
||||
}
|
||||
|
||||
export function getPublishMetaByMode(id: string): DebugPublishMeta | null {
|
||||
const item = HANDWRITING_SHEET_WORKSHEET_BY_ID[id];
|
||||
if (!item) return null;
|
||||
|
||||
return {
|
||||
id: item.id,
|
||||
title: item.title,
|
||||
subtitle: item.subtitle,
|
||||
category: 'chinese',
|
||||
subcategory: 'handwriting-sheet',
|
||||
path: `/chinesePages/handwritingSheet/handwritingSheet?id=${item.id}`,
|
||||
ageMin: item.ageMin,
|
||||
ageMax: item.ageMax,
|
||||
grade: inferGradeFromAge(item.ageMin, item.ageMax),
|
||||
difficulty: item.difficulty,
|
||||
previewImg: '',
|
||||
tags: [...item.tags],
|
||||
isNew: false,
|
||||
isHot: false,
|
||||
sortOrder: item.sortOrder,
|
||||
status: 'draft',
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "自定义练字贴",
|
||||
"navigationBarBackgroundColor": "#F8F0E0",
|
||||
"navigationBarTextStyle": "black",
|
||||
"backgroundColor": "#FEF6E7",
|
||||
"enablePullDownRefresh": false,
|
||||
"usingComponents": {
|
||||
"nav-bar": "../../components3.0/nav-bar/nav-bar",
|
||||
"preview-card": "../../components3.0/preview-card/preview-card",
|
||||
"preview-footer-actions": "../../components3.0/preview-footer-actions/preview-footer-actions",
|
||||
"debug-publish-tools": "../../components3.0/debug-publish-tools/debug-publish-tools",
|
||||
"word-picker": "../../components3.0/word-picker/word-picker",
|
||||
"share-guide-popup": "../../components/share-guide-popup/share-guide-popup",
|
||||
"toy-icon": "../../toy/icon/icon"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
@import '../../style/theme.less';
|
||||
|
||||
.cb-page {
|
||||
min-height: 100vh;
|
||||
background: @bg-page;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.cb-main {
|
||||
padding: @page-padding-x;
|
||||
padding-bottom: calc(200rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.cb-section {
|
||||
margin-top: @section-gap-sm;
|
||||
}
|
||||
|
||||
.cb-section-title {
|
||||
display: block;
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
color: @text-title;
|
||||
letter-spacing: 0.02em;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.cb-content-panel {
|
||||
background: rgba(240, 231, 214, 0.5);
|
||||
border-radius: @radius-lg;
|
||||
padding: 32rpx;
|
||||
}
|
||||
|
||||
.cb-field {
|
||||
margin-bottom: 32rpx;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.cb-field-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 4rpx;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.cb-field-label {
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
color: @text-secondary;
|
||||
}
|
||||
|
||||
.cb-field-hint {
|
||||
font-size: 20rpx;
|
||||
font-weight: 500;
|
||||
color: rgba(96, 91, 80, 0.6);
|
||||
}
|
||||
|
||||
.cb-input-wrapper {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.cb-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;
|
||||
}
|
||||
|
||||
.cb-input-clear {
|
||||
position: absolute;
|
||||
right: 20rpx;
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.cb-word-scroll {
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.cb-word-list {
|
||||
display: inline-flex;
|
||||
gap: 20rpx;
|
||||
padding-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.cb-word-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 12rpx;
|
||||
min-width: 88rpx;
|
||||
height: 72rpx;
|
||||
padding-left: 20rpx;
|
||||
padding-right: 12rpx;
|
||||
flex-shrink: 0;
|
||||
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);
|
||||
}
|
||||
|
||||
.cb-word-chip__text {
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
color: @text-title;
|
||||
}
|
||||
|
||||
.cb-word-chip__delete-hit {
|
||||
width: 44rpx;
|
||||
height: 44rpx;
|
||||
margin-right: -6rpx;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.cb-word-add {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.cb-word-add-btn {
|
||||
width: 72rpx;
|
||||
height: 72rpx;
|
||||
border-radius: 50%;
|
||||
border: 2rpx dashed rgba(179, 172, 159, 0.3);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(255, 255, 255, 0.55);
|
||||
}
|
||||
|
||||
.cb-category-scroll {
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.cb-category-list {
|
||||
display: inline-flex;
|
||||
gap: 16rpx;
|
||||
padding-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.cb-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);
|
||||
}
|
||||
}
|
||||
|
||||
.cb-category-tag-icon {
|
||||
font-size: 28rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.cb-category-tag-text {
|
||||
font-size: 22rpx;
|
||||
font-weight: 700;
|
||||
color: #6c5a00;
|
||||
}
|
||||
|
||||
.cb-category-tag--more {
|
||||
.cb-category-tag-icon {
|
||||
font-size: 24rpx;
|
||||
letter-spacing: 2rpx;
|
||||
color: #6c5a00;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,444 @@
|
||||
import WordDrawService from '../../service/wordDrawService';
|
||||
import { downloadPrint } from '../../utils/downloadPrint';
|
||||
import { getWordsSvgData } from '../../utils/getWordsSvgJson';
|
||||
import { WORDS } from '../../core/data/words';
|
||||
import { CharacterItem } from '../../types/characterType';
|
||||
import tracker from '../../utils/tracker';
|
||||
import { createPage } from '../../base/pageMixin';
|
||||
import { defaultShareConfig } from '../../config/config';
|
||||
import {
|
||||
addFavorite,
|
||||
removeFavorite,
|
||||
batchCheckFavorited,
|
||||
} from '../../utils/favorites';
|
||||
import type { DebugPublishMeta } from '../../utils/debugPublish';
|
||||
import {
|
||||
HANDWRITING_SHEET_WORKSHEET_DEFINITIONS,
|
||||
HANDWRITING_SHEET_WORKSHEET_ID,
|
||||
getModeInfo,
|
||||
getPublishMetaByMode,
|
||||
} from './handwritingSheet.config';
|
||||
|
||||
const MAX_WORDS = 11;
|
||||
const RANDOM_WORD_COUNT = 8;
|
||||
const DEFAULT_WORDS = '东西南北日月山河风雨';
|
||||
const CATEGORY_TAGS = WORDS.slice(0, 3).map((cat) => ({
|
||||
categoryId: cat.categoryId,
|
||||
icon: cat.icon,
|
||||
categoryName: cat.categoryName,
|
||||
}));
|
||||
|
||||
createPage(
|
||||
{
|
||||
canvas: null as Canvas | null,
|
||||
ctx: null as RenderingContext | null,
|
||||
wordDrawService: null as WordDrawService | null,
|
||||
svgWords: {} as Record<string, string[]>,
|
||||
maxRow: 0 as number,
|
||||
maxCol: 0 as number,
|
||||
_favoritedMap: {} as Record<string, boolean>,
|
||||
|
||||
data: {
|
||||
pageTitle: HANDWRITING_SHEET_WORKSHEET_DEFINITIONS[0].title,
|
||||
functionId: HANDWRITING_SHEET_WORKSHEET_ID,
|
||||
hasContent: false,
|
||||
words: [] as string[],
|
||||
inputWords: [] as string[],
|
||||
selectedWords: [] as string[],
|
||||
inputValue: '',
|
||||
showSelectWordPopup: false,
|
||||
pickerCurrentTab: 0,
|
||||
categoryTags: CATEGORY_TAGS,
|
||||
showShareDialog: false,
|
||||
isPreviewFavorite: false,
|
||||
isDevEnv: false,
|
||||
debugPublishVisible: false,
|
||||
debugPublishLoading: false,
|
||||
debugPublishMeta: null as DebugPublishMeta | null,
|
||||
},
|
||||
|
||||
async onLoad() {
|
||||
this.syncDebugPublishEnv();
|
||||
this.loadFavoritedMap();
|
||||
this.initPageInfo(
|
||||
HANDWRITING_SHEET_WORKSHEET_ID,
|
||||
HANDWRITING_SHEET_WORKSHEET_DEFINITIONS[0].title,
|
||||
);
|
||||
await this.loadSvgWords();
|
||||
this.initDefaultWords();
|
||||
},
|
||||
|
||||
async loadSvgWords() {
|
||||
try {
|
||||
wx.showToast({ title: '加载字体中...', icon: 'loading' });
|
||||
this.svgWords = await getWordsSvgData();
|
||||
wx.hideToast();
|
||||
|
||||
if (this.data.words.length > 0) {
|
||||
this.renderPracticeContent().catch(console.error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载SVG汉字数据失败:', error);
|
||||
wx.hideToast();
|
||||
wx.showModal({
|
||||
title: '加载失败',
|
||||
content: '无法加载汉字数据,请检查网络连接后重试',
|
||||
showCancel: true,
|
||||
cancelText: '取消',
|
||||
confirmText: '重试',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
this.loadSvgWords();
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
async onCanvasReady(e: WechatMiniprogram.CustomEvent) {
|
||||
const { canvas, ctx } = e.detail;
|
||||
if (!canvas || !ctx) return;
|
||||
|
||||
this.canvas = canvas;
|
||||
this.ctx = ctx;
|
||||
this.wordDrawService = new WordDrawService(canvas, ctx);
|
||||
|
||||
await this.wordDrawService.drawLayout();
|
||||
|
||||
const { maxRow, maxCol } = this.wordDrawService.getMaxGridLayout();
|
||||
this.maxRow = maxRow;
|
||||
this.maxCol = maxCol;
|
||||
|
||||
if (this.data.words.length > 0) {
|
||||
await this.renderPracticeContent();
|
||||
}
|
||||
},
|
||||
|
||||
onPreviewRefresh() {
|
||||
this.refreshWords();
|
||||
},
|
||||
|
||||
async onPreviewFavorite() {
|
||||
const next = !this.data.isPreviewFavorite;
|
||||
this.setData({ isPreviewFavorite: next });
|
||||
this._favoritedMap[HANDWRITING_SHEET_WORKSHEET_ID] = next;
|
||||
|
||||
if (next) {
|
||||
await addFavorite(HANDWRITING_SHEET_WORKSHEET_ID);
|
||||
} else {
|
||||
await removeFavorite(HANDWRITING_SHEET_WORKSHEET_ID);
|
||||
}
|
||||
|
||||
wx.showToast({
|
||||
title: next ? '收藏成功' : '已取消收藏',
|
||||
icon: 'none',
|
||||
});
|
||||
},
|
||||
|
||||
async loadFavoritedMap() {
|
||||
this._favoritedMap = await batchCheckFavorited([
|
||||
HANDWRITING_SHEET_WORKSHEET_ID,
|
||||
]);
|
||||
|
||||
if (this._favoritedMap[HANDWRITING_SHEET_WORKSHEET_ID]) {
|
||||
this.setData({ isPreviewFavorite: true });
|
||||
}
|
||||
},
|
||||
|
||||
onInputChange(e: WechatMiniprogram.Input) {
|
||||
const value = e.detail.value as string;
|
||||
this.syncWordsFromInput(value, false);
|
||||
},
|
||||
|
||||
onInputConfirm(e: WechatMiniprogram.Input) {
|
||||
const value = e.detail.value as string;
|
||||
this.syncWordsFromInput(value, true);
|
||||
},
|
||||
|
||||
onClearInput() {
|
||||
this.clearWords();
|
||||
},
|
||||
|
||||
syncWordsFromInput(value: string, showLimitToast: boolean) {
|
||||
const selectedWords = this.data.selectedWords as string[];
|
||||
const maxInputCount = Math.max(0, MAX_WORDS - selectedWords.length);
|
||||
const nextInputWords = this.splitToSingleChars(value).slice(
|
||||
0,
|
||||
maxInputCount,
|
||||
);
|
||||
const nextWords = [...nextInputWords, ...selectedWords];
|
||||
|
||||
if (
|
||||
showLimitToast &&
|
||||
nextInputWords.length < this.splitToSingleChars(value).length
|
||||
) {
|
||||
wx.showToast({
|
||||
title: `最多输入 ${maxInputCount} 个字`,
|
||||
icon: 'none',
|
||||
});
|
||||
}
|
||||
|
||||
this.setData(
|
||||
{
|
||||
inputValue: nextInputWords.join(''),
|
||||
inputWords: nextInputWords,
|
||||
words: nextWords,
|
||||
},
|
||||
() => this.renderPracticeContent().catch(console.error),
|
||||
);
|
||||
},
|
||||
|
||||
openSelectWordPopup() {
|
||||
this.setData({ showSelectWordPopup: true, pickerCurrentTab: 0 });
|
||||
},
|
||||
|
||||
closeSelectWordPopup() {
|
||||
this.setData({ showSelectWordPopup: false });
|
||||
},
|
||||
|
||||
onCategoryTap(e: WechatMiniprogram.TouchEvent) {
|
||||
const categoryId = Number(e.currentTarget.dataset.categoryId);
|
||||
this.setData({
|
||||
showSelectWordPopup: true,
|
||||
pickerCurrentTab: categoryId,
|
||||
});
|
||||
},
|
||||
|
||||
onChangeWord(e: WechatMiniprogram.CustomEvent) {
|
||||
const nextSelectedWords = e.detail.selectedWords as string[];
|
||||
const inputWords = this.data.inputWords as string[];
|
||||
|
||||
if (inputWords.length + nextSelectedWords.length > MAX_WORDS) {
|
||||
wx.showToast({
|
||||
title: `最多只能添加 ${MAX_WORDS} 个字`,
|
||||
icon: 'none',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.setData(
|
||||
{
|
||||
selectedWords: nextSelectedWords,
|
||||
words: [...inputWords, ...nextSelectedWords],
|
||||
showSelectWordPopup: false,
|
||||
},
|
||||
() => this.renderPracticeContent().catch(console.error),
|
||||
);
|
||||
},
|
||||
|
||||
deleteWordChip(e: WechatMiniprogram.TouchEvent) {
|
||||
const index = Number(e.currentTarget.dataset.index);
|
||||
if (Number.isNaN(index)) return;
|
||||
|
||||
const inputWords = [...(this.data.inputWords as string[])];
|
||||
const selectedWords = [...(this.data.selectedWords as string[])];
|
||||
|
||||
if (index < inputWords.length) {
|
||||
inputWords.splice(index, 1);
|
||||
this.setData(
|
||||
{
|
||||
inputWords,
|
||||
inputValue: inputWords.join(''),
|
||||
words: [...inputWords, ...selectedWords],
|
||||
},
|
||||
() => this.renderPracticeContent().catch(console.error),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedIndex = index - inputWords.length;
|
||||
if (selectedIndex < 0 || selectedIndex >= selectedWords.length)
|
||||
return;
|
||||
|
||||
selectedWords.splice(selectedIndex, 1);
|
||||
this.setData(
|
||||
{
|
||||
selectedWords,
|
||||
words: [...inputWords, ...selectedWords],
|
||||
},
|
||||
() => this.renderPracticeContent().catch(console.error),
|
||||
);
|
||||
},
|
||||
|
||||
clearWords() {
|
||||
this.setData(
|
||||
{
|
||||
words: [],
|
||||
inputWords: [],
|
||||
selectedWords: [],
|
||||
inputValue: '',
|
||||
showSelectWordPopup: false,
|
||||
},
|
||||
() => this.renderPracticeContent().catch(console.error),
|
||||
);
|
||||
},
|
||||
|
||||
initDefaultWords() {
|
||||
const defaults = this.getSupportedWords(
|
||||
this.splitToSingleChars(DEFAULT_WORDS),
|
||||
).slice(0, MAX_WORDS);
|
||||
|
||||
if (defaults.length === 0) {
|
||||
this.refreshWords();
|
||||
return;
|
||||
}
|
||||
|
||||
this.setData(
|
||||
{
|
||||
words: defaults,
|
||||
inputWords: defaults,
|
||||
selectedWords: [],
|
||||
inputValue: defaults.join(''),
|
||||
},
|
||||
() => this.renderPracticeContent().catch(console.error),
|
||||
);
|
||||
},
|
||||
|
||||
refreshWords() {
|
||||
const candidates = WORDS.slice(0, 3).reduce(
|
||||
(acc: string[], category) => acc.concat(category.words),
|
||||
[] as string[],
|
||||
);
|
||||
const shuffled = Array.from(new Set(candidates)).sort(
|
||||
() => Math.random() - 0.5,
|
||||
);
|
||||
const supported = this.getSupportedWords(shuffled).slice(
|
||||
0,
|
||||
RANDOM_WORD_COUNT,
|
||||
);
|
||||
|
||||
if (supported.length === 0) {
|
||||
wx.showToast({
|
||||
title: '随机到的字暂不支持,重试一下',
|
||||
icon: 'none',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.setData(
|
||||
{
|
||||
words: supported,
|
||||
inputWords: supported,
|
||||
selectedWords: [],
|
||||
inputValue: supported.join(''),
|
||||
},
|
||||
() => this.renderPracticeContent().catch(console.error),
|
||||
);
|
||||
},
|
||||
|
||||
splitToSingleChars(text: string): string[] {
|
||||
const matches = text.match(/[\u4e00-\u9fff]/g);
|
||||
return matches || [];
|
||||
},
|
||||
|
||||
async renderPracticeContent() {
|
||||
if (!this.canvas || !this.wordDrawService) return;
|
||||
|
||||
const words = this.data.words as string[];
|
||||
if (words.length === 0) {
|
||||
await this.wordDrawService.drawContentEmpty();
|
||||
this.setData({ hasContent: false });
|
||||
return;
|
||||
}
|
||||
|
||||
const supportedWords = this.getSupportedWords(words);
|
||||
if (supportedWords.length === 0) {
|
||||
wx.showToast({ title: '暂不支持这些汉字', icon: 'none' });
|
||||
await this.wordDrawService.drawContentEmpty();
|
||||
this.setData({ hasContent: false });
|
||||
return;
|
||||
}
|
||||
|
||||
let maxRow = this.maxRow;
|
||||
let maxCol = this.maxCol;
|
||||
if (!maxRow || !maxCol) {
|
||||
const layout = this.wordDrawService.getMaxGridLayout();
|
||||
this.maxRow = layout.maxRow;
|
||||
this.maxCol = layout.maxCol;
|
||||
maxRow = layout.maxRow;
|
||||
maxCol = layout.maxCol;
|
||||
}
|
||||
|
||||
const characterData = this.processCharacterLayout(
|
||||
supportedWords,
|
||||
maxRow,
|
||||
maxCol,
|
||||
);
|
||||
|
||||
await this.wordDrawService.drawPracticeContent(characterData);
|
||||
this.setData({ hasContent: characterData.length > 0 });
|
||||
},
|
||||
|
||||
getSupportedWords(words: string[]): string[] {
|
||||
return words.filter((word) => this.svgWords[word]);
|
||||
},
|
||||
|
||||
processCharacterLayout(
|
||||
words: string[],
|
||||
maxRow: number,
|
||||
maxCol: number,
|
||||
): CharacterItem[] {
|
||||
let rowIndex = 0;
|
||||
const characterData: CharacterItem[] = [];
|
||||
|
||||
words.forEach((word: string) => {
|
||||
const strokes = this.svgWords[word];
|
||||
const strokeCount = strokes.length;
|
||||
const totalCells = 1 + strokeCount;
|
||||
const totalRows = Math.ceil(totalCells / maxCol);
|
||||
rowIndex += totalRows;
|
||||
|
||||
if (rowIndex <= maxRow) {
|
||||
characterData.push({ character: word, strokes });
|
||||
}
|
||||
});
|
||||
|
||||
return characterData;
|
||||
},
|
||||
|
||||
async exportToPrint() {
|
||||
await downloadPrint(this.canvas, {
|
||||
errorToast: '请先生成练字贴',
|
||||
trackerName: '练字贴',
|
||||
});
|
||||
},
|
||||
|
||||
onShare() {},
|
||||
|
||||
onShareAppMessage() {
|
||||
tracker.reportShare('练字贴');
|
||||
return {
|
||||
...defaultShareConfig,
|
||||
path: `/chinesePages/handwritingSheet/handwritingSheet?id=${HANDWRITING_SHEET_WORKSHEET_ID}`,
|
||||
};
|
||||
},
|
||||
|
||||
onShareTimeline() {
|
||||
tracker.reportShare('练字贴');
|
||||
return {
|
||||
...defaultShareConfig,
|
||||
query: `id=${HANDWRITING_SHEET_WORKSHEET_ID}`,
|
||||
};
|
||||
},
|
||||
|
||||
onCloseShareDialog() {
|
||||
this.setData({ showShareDialog: false });
|
||||
},
|
||||
|
||||
onShareSuccess() {
|
||||
this.setData({ showShareDialog: false });
|
||||
},
|
||||
|
||||
getPublishMeta(): DebugPublishMeta {
|
||||
const meta = getPublishMetaByMode(HANDWRITING_SHEET_WORKSHEET_ID);
|
||||
if (!meta) {
|
||||
throw new Error('当前题型配置不存在');
|
||||
}
|
||||
return meta;
|
||||
},
|
||||
},
|
||||
{
|
||||
shareConfig: defaultShareConfig,
|
||||
pageInfoLookup: getModeInfo,
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,133 @@
|
||||
<nav-bar title="自定义练字贴" />
|
||||
|
||||
<view class="cb-page">
|
||||
<view class="cb-main">
|
||||
<preview-card
|
||||
id="previewCard"
|
||||
showRefresh="{{true}}"
|
||||
showFavorite="{{true}}"
|
||||
favorited="{{isPreviewFavorite}}"
|
||||
bind:canvas-ready="onCanvasReady"
|
||||
bind:refresh="onPreviewRefresh"
|
||||
bind:favorite="onPreviewFavorite" />
|
||||
|
||||
<view class="cb-section">
|
||||
<text class="cb-section-title">内容设置</text>
|
||||
<view class="cb-content-panel">
|
||||
<view class="cb-field">
|
||||
<view class="cb-field-header">
|
||||
<text class="cb-field-label">自定义文字</text>
|
||||
<text class="cb-field-hint">最多输入11个字</text>
|
||||
</view>
|
||||
<view class="cb-input-wrapper">
|
||||
<input
|
||||
class="cb-text-input"
|
||||
type="text"
|
||||
maxlength="20"
|
||||
placeholder="输入想要练习的汉字"
|
||||
value="{{inputValue}}"
|
||||
bindinput="onInputChange"
|
||||
bindconfirm="onInputConfirm" />
|
||||
<view
|
||||
wx:if="{{inputValue.length > 0}}"
|
||||
class="cb-input-clear"
|
||||
bindtap="onClearInput">
|
||||
<toy-icon
|
||||
name="close-bold"
|
||||
size="36rpx"
|
||||
color="#605b50" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="cb-field">
|
||||
<view class="cb-field-header">
|
||||
<text class="cb-field-label">已选文字</text>
|
||||
<text class="cb-field-hint">点击文字可删除</text>
|
||||
</view>
|
||||
<scroll-view scroll-x class="cb-word-scroll">
|
||||
<view class="cb-word-list">
|
||||
<view
|
||||
wx:for="{{words}}"
|
||||
wx:key="index"
|
||||
class="cb-word-chip">
|
||||
<text class="cb-word-chip__text">{{item}}</text>
|
||||
<view
|
||||
class="cb-word-chip__delete-hit"
|
||||
data-index="{{index}}"
|
||||
catchtap="deleteWordChip">
|
||||
<toy-icon
|
||||
name="close-bold"
|
||||
size="24rpx"
|
||||
color="rgba(96,91,80,0.5)" />
|
||||
</view>
|
||||
</view>
|
||||
<view class="cb-word-add">
|
||||
<view
|
||||
class="cb-word-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="cb-category-scroll">
|
||||
<view class="cb-category-list">
|
||||
<view
|
||||
wx:for="{{categoryTags}}"
|
||||
wx:key="categoryId"
|
||||
class="cb-category-tag"
|
||||
data-category-id="{{item.categoryId}}"
|
||||
bindtap="onCategoryTap">
|
||||
<text class="cb-category-tag-icon"
|
||||
>{{item.icon}}</text
|
||||
>
|
||||
<text class="cb-category-tag-text"
|
||||
>{{item.categoryName}}</text
|
||||
>
|
||||
</view>
|
||||
<view
|
||||
class="cb-category-tag cb-category-tag--more"
|
||||
bindtap="openSelectWordPopup">
|
||||
<text class="cb-category-tag-icon">···</text>
|
||||
<text class="cb-category-tag-text">更多分类</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<preview-footer-actions
|
||||
disabled="{{!hasContent}}"
|
||||
bind:primary="exportToPrint"
|
||||
bind:secondary="onShare" />
|
||||
|
||||
<debug-publish-tools
|
||||
wx:if="{{isDevEnv && hasContent}}"
|
||||
id="debugPublishTools"
|
||||
visible="{{debugPublishVisible}}"
|
||||
loading="{{debugPublishLoading}}"
|
||||
meta="{{debugPublishMeta}}"
|
||||
bind:open="onOpenDebugPublish"
|
||||
bind:close="onCloseDebugPublish"
|
||||
bind:confirm="onConfirmDebugPublish" />
|
||||
|
||||
<word-picker
|
||||
show="{{showSelectWordPopup}}"
|
||||
selectedWords="{{selectedWords}}"
|
||||
currentTab="{{pickerCurrentTab}}"
|
||||
max="11"
|
||||
bind:onClose="closeSelectWordPopup"
|
||||
bind:onChange="onChangeWord" />
|
||||
|
||||
<share-guide-popup
|
||||
show="{{showShareDialog}}"
|
||||
bind:onClose="onCloseShareDialog"
|
||||
bind:onShareSuccess="onShareSuccess" />
|
||||
Reference in New Issue
Block a user