feat: 新增幼儿测字表
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
import { WORDS } from '../../../core/data/words';
|
||||
|
||||
export const WORD_TEST_GROUP_SIZE = 50;
|
||||
|
||||
export interface WordTestCategory {
|
||||
id: string;
|
||||
icon: string;
|
||||
name: string;
|
||||
parentName?: string;
|
||||
words: string[];
|
||||
}
|
||||
|
||||
export interface WordTestGroup {
|
||||
index: number;
|
||||
label: string;
|
||||
words: string[];
|
||||
}
|
||||
|
||||
const EXCLUDED_CATEGORY_IDS = new Set([4, 5]);
|
||||
|
||||
/** 默认分类:一年级上册 */
|
||||
export const DEFAULT_WORD_TEST_CATEGORY_ID = '26-一年级上册';
|
||||
|
||||
export function buildWordTestCategories(): WordTestCategory[] {
|
||||
const result: WordTestCategory[] = [];
|
||||
|
||||
for (const cat of WORDS) {
|
||||
if (EXCLUDED_CATEGORY_IDS.has(cat.categoryId)) continue;
|
||||
|
||||
if (cat.sections?.length) {
|
||||
for (const section of cat.sections) {
|
||||
result.push({
|
||||
id: `${cat.categoryId}-${section.sectionName}`,
|
||||
icon: cat.icon,
|
||||
name: section.sectionName,
|
||||
parentName: cat.categoryName,
|
||||
words: section.words,
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cat.words?.length) {
|
||||
result.push({
|
||||
id: String(cat.categoryId),
|
||||
icon: cat.icon,
|
||||
name: cat.categoryName,
|
||||
words: cat.words,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function buildWordTestGroups(words: string[]): WordTestGroup[] {
|
||||
if (!words.length) return [];
|
||||
|
||||
const groups: WordTestGroup[] = [];
|
||||
for (let i = 0; i < words.length; i += WORD_TEST_GROUP_SIZE) {
|
||||
const index = Math.floor(i / WORD_TEST_GROUP_SIZE) + 1;
|
||||
groups.push({
|
||||
index,
|
||||
label: `第${index}组`,
|
||||
words: words.slice(i, i + WORD_TEST_GROUP_SIZE),
|
||||
});
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
|
||||
export function findWordTestCategory(
|
||||
categories: WordTestCategory[],
|
||||
id: string,
|
||||
): WordTestCategory | undefined {
|
||||
return categories.find((cat) => cat.id === id);
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
import { BaseDrawService } from '../../../core/draw/baseDraw';
|
||||
|
||||
const COLS = 5;
|
||||
const ROWS = 10;
|
||||
|
||||
const LAYOUT = {
|
||||
topGap: 10,
|
||||
leftMargin: 36,
|
||||
rightMargin: 36,
|
||||
bottomMargin: 40,
|
||||
instructionGap: 18,
|
||||
gridTopGap: 12,
|
||||
instructionFont: 'bold 16px "KaiTi", "STKaiti", "Microsoft Yahei", serif',
|
||||
instructionBoxSize: 12,
|
||||
instructionCheckSize: 14,
|
||||
instructionSymbolGap: 3,
|
||||
rowGap: 10,
|
||||
charBoxGap: 12,
|
||||
boxSize: 14,
|
||||
} as const;
|
||||
|
||||
export interface WordTestSheetDrawData {
|
||||
words: string[];
|
||||
pageNumber: number;
|
||||
}
|
||||
|
||||
export default class WordTestDrawService extends BaseDrawService {
|
||||
constructor(
|
||||
canvas: Canvas,
|
||||
ctx: RenderingContext,
|
||||
options?: Record<string, unknown>,
|
||||
) {
|
||||
super(canvas, ctx, {
|
||||
title: '测字表',
|
||||
...options,
|
||||
});
|
||||
}
|
||||
|
||||
async draw(data: WordTestSheetDrawData) {
|
||||
this.prepareDraw();
|
||||
await this.drawHeaderAndDivider();
|
||||
this.drawInstruction();
|
||||
this.drawWordGrid(data.words);
|
||||
this.drawPageNumber(data.pageNumber);
|
||||
this.drawPrintFooter();
|
||||
}
|
||||
|
||||
private getContentRect() {
|
||||
const contentTop = this.currentY;
|
||||
const contentWidth =
|
||||
this.canvasWidth - LAYOUT.leftMargin - LAYOUT.rightMargin;
|
||||
const contentHeight =
|
||||
this.canvasHeight - contentTop - LAYOUT.bottomMargin;
|
||||
|
||||
return {
|
||||
top: contentTop,
|
||||
left: LAYOUT.leftMargin,
|
||||
width: contentWidth,
|
||||
height: contentHeight,
|
||||
};
|
||||
}
|
||||
|
||||
private drawInstruction() {
|
||||
const { ctx, canvasWidth } = this;
|
||||
const y = this.currentY + LAYOUT.instructionGap;
|
||||
const {
|
||||
instructionFont,
|
||||
instructionBoxSize,
|
||||
instructionCheckSize,
|
||||
instructionSymbolGap,
|
||||
} = LAYOUT;
|
||||
|
||||
const prefix = '认识的字';
|
||||
const middle = '里打';
|
||||
|
||||
ctx.save();
|
||||
ctx.font = instructionFont;
|
||||
ctx.fillStyle = '#322E25';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.textAlign = 'left';
|
||||
|
||||
const prefixW = ctx.measureText(prefix).width;
|
||||
const middleW = ctx.measureText(middle).width;
|
||||
const totalW =
|
||||
prefixW +
|
||||
instructionSymbolGap +
|
||||
instructionBoxSize +
|
||||
instructionSymbolGap +
|
||||
middleW +
|
||||
instructionSymbolGap +
|
||||
instructionCheckSize;
|
||||
|
||||
let x = (canvasWidth - totalW) / 2;
|
||||
|
||||
ctx.fillText(prefix, x, y);
|
||||
x += prefixW + instructionSymbolGap;
|
||||
|
||||
this.drawBox(
|
||||
ctx,
|
||||
x,
|
||||
y - instructionBoxSize / 2,
|
||||
instructionBoxSize,
|
||||
instructionBoxSize,
|
||||
);
|
||||
x += instructionBoxSize + instructionSymbolGap;
|
||||
|
||||
ctx.fillText(middle, x, y);
|
||||
x += middleW + instructionSymbolGap;
|
||||
|
||||
this.drawCheckMark(ctx, x, y, instructionCheckSize);
|
||||
|
||||
const metrics = ctx.measureText(prefix);
|
||||
const instructionFontSize = 16;
|
||||
const halfH = Math.max(
|
||||
metrics.actualBoundingBoxAscent ?? instructionFontSize * 0.5,
|
||||
metrics.actualBoundingBoxDescent ?? instructionFontSize * 0.5,
|
||||
);
|
||||
const contentStartY = y + halfH + LAYOUT.gridTopGap;
|
||||
|
||||
ctx.restore();
|
||||
|
||||
this.currentY = contentStartY;
|
||||
}
|
||||
|
||||
/** 绘制对勾(替代文字 V) */
|
||||
private drawCheckMark(
|
||||
ctx: RenderingContext,
|
||||
leftX: number,
|
||||
centerY: number,
|
||||
size: number,
|
||||
) {
|
||||
ctx.save();
|
||||
ctx.strokeStyle = '#322E25';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.lineCap = 'round';
|
||||
ctx.lineJoin = 'round';
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(leftX + size * 0.12, centerY + size * 0.02);
|
||||
ctx.lineTo(leftX + size * 0.38, centerY + size * 0.32);
|
||||
ctx.lineTo(leftX + size * 0.88, centerY - size * 0.34);
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
private getCharVerticalMetrics(
|
||||
ctx: RenderingContext,
|
||||
char: string,
|
||||
fontSize: number,
|
||||
) {
|
||||
ctx.textBaseline = 'alphabetic';
|
||||
const metrics = ctx.measureText(char);
|
||||
const ascent = metrics.actualBoundingBoxAscent ?? fontSize * 0.82;
|
||||
const descent = metrics.actualBoundingBoxDescent ?? fontSize * 0.18;
|
||||
return { ascent, descent };
|
||||
}
|
||||
|
||||
/** 以 centerY 为视觉中心,绘制汉字与右侧方框 */
|
||||
private drawCharWithBox(
|
||||
ctx: RenderingContext,
|
||||
char: string,
|
||||
startX: number,
|
||||
centerY: number,
|
||||
fontSize: number,
|
||||
) {
|
||||
const { charBoxGap, boxSize } = LAYOUT;
|
||||
const { ascent, descent } = this.getCharVerticalMetrics(
|
||||
ctx,
|
||||
char,
|
||||
fontSize,
|
||||
);
|
||||
|
||||
ctx.textBaseline = 'alphabetic';
|
||||
const textY = centerY + (ascent - descent) / 2;
|
||||
ctx.fillText(char, startX, textY);
|
||||
|
||||
const boxX = startX + ctx.measureText(char).width + charBoxGap;
|
||||
const boxY = centerY - boxSize / 2;
|
||||
this.drawBox(ctx, boxX, boxY, boxSize, boxSize);
|
||||
}
|
||||
|
||||
private drawWordGrid(words: string[]) {
|
||||
const { ctx } = this;
|
||||
const content = this.getContentRect();
|
||||
const colWidth = content.width / COLS;
|
||||
const rowHeight = content.height / ROWS;
|
||||
const fontSize = 20;
|
||||
|
||||
ctx.save();
|
||||
ctx.font = `${fontSize}px "KaiTi", "STKaiti", "Microsoft Yahei", serif`;
|
||||
ctx.fillStyle = '#322E25';
|
||||
ctx.textAlign = 'left';
|
||||
ctx.textBaseline = 'alphabetic';
|
||||
|
||||
for (let row = 0; row < ROWS; row++) {
|
||||
for (let col = 0; col < COLS; col++) {
|
||||
const index = row * COLS + col;
|
||||
const char = words[index];
|
||||
if (!char) continue;
|
||||
|
||||
const cellLeft = content.left + col * colWidth;
|
||||
const cellCenterY =
|
||||
content.top + row * rowHeight + rowHeight / 2;
|
||||
|
||||
const charWidth = ctx.measureText(char).width;
|
||||
const groupWidth =
|
||||
charWidth + LAYOUT.charBoxGap + LAYOUT.boxSize;
|
||||
const startX = cellLeft + (colWidth - groupWidth) / 2;
|
||||
|
||||
this.drawCharWithBox(ctx, char, startX, cellCenterY, fontSize);
|
||||
}
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
private drawPageNumber(pageNumber: number) {
|
||||
const { ctx, canvasWidth, canvasHeight } = this;
|
||||
|
||||
ctx.save();
|
||||
ctx.font = '12px "Microsoft Yahei", sans-serif';
|
||||
ctx.fillStyle = '#7C766A';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillText(String(pageNumber), canvasWidth / 2, canvasHeight - 28);
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import type { DebugPublishMeta } from '../../utils/debugPublish';
|
||||
import { inferGradeFromAge } from '../../utils/debugPublish';
|
||||
import { WORD_TEST_WORKSHEET_DEFINITIONS } from '../../config/worksheets/wordTest';
|
||||
|
||||
type WordTestWorksheetRow = (typeof WORD_TEST_WORKSHEET_DEFINITIONS)[number];
|
||||
|
||||
const WORKSHEET_BY_ID = Object.fromEntries(
|
||||
WORD_TEST_WORKSHEET_DEFINITIONS.map((m) => [m.id, m]),
|
||||
) as Record<string, WordTestWorksheetRow>;
|
||||
|
||||
export const WORD_TEST_WORKSHEET_ID = WORD_TEST_WORKSHEET_DEFINITIONS[0].id;
|
||||
|
||||
export function getModeInfo(id: string) {
|
||||
const m = WORKSHEET_BY_ID[id];
|
||||
return m ? { title: m.title, desc: m.subtitle } : undefined;
|
||||
}
|
||||
|
||||
export function isValidMode(id: string): boolean {
|
||||
return id in WORKSHEET_BY_ID;
|
||||
}
|
||||
|
||||
export function getPublishMetaByMode(id: string): DebugPublishMeta | null {
|
||||
const m = WORKSHEET_BY_ID[id];
|
||||
if (!m) return null;
|
||||
return {
|
||||
id: m.id,
|
||||
title: m.title,
|
||||
subtitle: m.subtitle,
|
||||
category: 'chinese',
|
||||
subcategory: 'word-test',
|
||||
path: `/chinesePages/wordTestSheet/wordTestSheet?id=${m.id}`,
|
||||
ageMin: m.ageMin,
|
||||
ageMax: m.ageMax,
|
||||
grade: inferGradeFromAge(m.ageMin, m.ageMax),
|
||||
difficulty: m.difficulty,
|
||||
previewImg: '',
|
||||
tags: [...m.tags],
|
||||
isNew: true,
|
||||
isHot: false,
|
||||
sortOrder: m.sortOrder,
|
||||
status: 'draft',
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"navigationStyle": "custom",
|
||||
"navigationBarTitleText": "测字表",
|
||||
"navigationBarBackgroundColor": "#F8F0E0",
|
||||
"navigationBarTextStyle": "black",
|
||||
"backgroundColor": "#FEF6E7",
|
||||
"enablePullDownRefresh": false,
|
||||
"usingComponents": {
|
||||
"nav-bar": "../../components3.0/nav-bar/nav-bar",
|
||||
"share-guide-popup": "../../components/share-guide-popup/share-guide-popup",
|
||||
"preview-footer-actions": "../../components3.0/preview-footer-actions/preview-footer-actions",
|
||||
"debug-publish-tools": "../../components3.0/debug-publish-tools/debug-publish-tools",
|
||||
"toy-icon": "../../toy/icon/icon",
|
||||
"preview-card": "../../components3.0/preview-card/preview-card"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
@import '../../style/theme.less';
|
||||
|
||||
page {
|
||||
background-color: @bg-page;
|
||||
}
|
||||
|
||||
.wt-page {
|
||||
min-height: 100vh;
|
||||
padding: 0 @page-padding-x;
|
||||
padding-bottom: calc(200rpx + env(safe-area-inset-bottom));
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.wt-main {
|
||||
padding-top: 24rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 32rpx;
|
||||
}
|
||||
|
||||
.wt-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.wt-section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.wt-section-title {
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
color: #6d3b00;
|
||||
padding-left: 8rpx;
|
||||
}
|
||||
|
||||
.wt-category-grid,
|
||||
.wt-group-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.wt-category-card,
|
||||
.wt-group-card {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8rpx;
|
||||
padding: 28rpx 12rpx;
|
||||
border-radius: @radius-lg;
|
||||
background: rgba(255, 255, 255, 0.6);
|
||||
border: 4rpx solid rgba(179, 172, 159, 0.1);
|
||||
box-shadow: @shadow;
|
||||
transition: transform 0.12s, box-shadow 0.12s, border-color 0.12s,
|
||||
background 0.12s;
|
||||
}
|
||||
|
||||
.wt-group-card {
|
||||
padding: 24rpx 12rpx;
|
||||
}
|
||||
|
||||
.wt-category-card--active,
|
||||
.wt-group-card--active {
|
||||
background: #ffffff;
|
||||
border-color: @brand;
|
||||
box-shadow: @shadow;
|
||||
}
|
||||
|
||||
.wt-category-card--pressed,
|
||||
.wt-group-card--pressed {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.wt-category-card__icon {
|
||||
font-size: 36rpx;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.wt-category-card__name,
|
||||
.wt-group-card__name {
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
color: @text-title;
|
||||
line-height: 1.3;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.wt-category-card__cat {
|
||||
font-size: 20rpx;
|
||||
color: @text-secondary;
|
||||
text-align: center;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.wt-category-card__check,
|
||||
.wt-group-card__check {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 18rpx;
|
||||
transform: translateY(-50%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
border-radius: 50%;
|
||||
background-color: #9cd343;
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
import WordTestDrawService from './draw/wordTestDrawService';
|
||||
import {
|
||||
buildWordTestCategories,
|
||||
buildWordTestGroups,
|
||||
DEFAULT_WORD_TEST_CATEGORY_ID,
|
||||
findWordTestCategory,
|
||||
type WordTestCategory,
|
||||
type WordTestGroup,
|
||||
} from './data/wordTestCategories';
|
||||
import {
|
||||
getModeInfo,
|
||||
getPublishMetaByMode,
|
||||
isValidMode,
|
||||
WORD_TEST_WORKSHEET_ID,
|
||||
} from './wordTestSheet.config';
|
||||
import { createPage, type CanvasDataState } from '../../base/pageMixin';
|
||||
import { defaultShareConfig } from '../../config/config';
|
||||
import type { DebugPublishMeta } from '../../utils/debugPublish';
|
||||
import {
|
||||
addFavorite,
|
||||
removeFavorite,
|
||||
batchCheckFavorited,
|
||||
} from '../../utils/favorites';
|
||||
|
||||
const pageInfoLookup = getModeInfo;
|
||||
|
||||
function buildSelectedMap(id: string | null): Record<string, boolean> {
|
||||
if (!id) return {};
|
||||
return { [id]: true };
|
||||
}
|
||||
|
||||
type PageData = CanvasDataState & {
|
||||
worksheetId: string;
|
||||
categoryList: WordTestCategory[];
|
||||
groupList: WordTestGroup[];
|
||||
selectedCategoryId: string;
|
||||
selectedGroupIndex: number;
|
||||
selectedCategoryMap: Record<string, boolean>;
|
||||
selectedGroupMap: Record<string, boolean>;
|
||||
isPreviewFavorite: boolean;
|
||||
isDevEnv: boolean;
|
||||
debugPublishVisible: boolean;
|
||||
debugPublishLoading: boolean;
|
||||
debugPublishMeta: DebugPublishMeta | null;
|
||||
};
|
||||
|
||||
createPage(
|
||||
{
|
||||
canvas: null as Canvas | null,
|
||||
ctx: null as RenderingContext | null,
|
||||
boxHeight: 0,
|
||||
boxWidth: 0,
|
||||
drawService: null as WordTestDrawService | null,
|
||||
_favoritedMap: {} as Record<string, boolean>,
|
||||
|
||||
data: {
|
||||
pageTitle: '测字表',
|
||||
functionId: WORD_TEST_WORKSHEET_ID,
|
||||
hasContent: false,
|
||||
showShareDialog: false,
|
||||
worksheetId: WORD_TEST_WORKSHEET_ID,
|
||||
categoryList: [] as WordTestCategory[],
|
||||
groupList: [] as WordTestGroup[],
|
||||
selectedCategoryId: DEFAULT_WORD_TEST_CATEGORY_ID,
|
||||
selectedGroupIndex: 1,
|
||||
selectedCategoryMap: buildSelectedMap(
|
||||
DEFAULT_WORD_TEST_CATEGORY_ID,
|
||||
),
|
||||
selectedGroupMap: buildSelectedMap('1'),
|
||||
isPreviewFavorite: false,
|
||||
isDevEnv: false,
|
||||
debugPublishVisible: false,
|
||||
debugPublishLoading: false,
|
||||
debugPublishMeta: null,
|
||||
} as unknown as PageData,
|
||||
|
||||
onLoad(options: { id?: string }) {
|
||||
this.syncDebugPublishEnv();
|
||||
|
||||
const worksheetId =
|
||||
options.id && isValidMode(options.id)
|
||||
? options.id
|
||||
: WORD_TEST_WORKSHEET_ID;
|
||||
|
||||
const categoryList = buildWordTestCategories();
|
||||
const defaultCategory =
|
||||
findWordTestCategory(
|
||||
categoryList,
|
||||
DEFAULT_WORD_TEST_CATEGORY_ID,
|
||||
) || categoryList[0];
|
||||
const selectedCategoryId = defaultCategory?.id || '';
|
||||
const groupList = buildWordTestGroups(defaultCategory?.words || []);
|
||||
|
||||
this.setData({
|
||||
worksheetId,
|
||||
functionId: worksheetId,
|
||||
categoryList,
|
||||
groupList,
|
||||
selectedCategoryId,
|
||||
selectedGroupIndex: 1,
|
||||
selectedCategoryMap: buildSelectedMap(selectedCategoryId),
|
||||
selectedGroupMap: buildSelectedMap('1'),
|
||||
});
|
||||
|
||||
this.initPageInfo(worksheetId, '测字表');
|
||||
this.loadFavoritedMap();
|
||||
},
|
||||
|
||||
onCanvasReady(e: WechatMiniprogram.CustomEvent) {
|
||||
const category = this.getSelectedCategory();
|
||||
this.initCanvasFromComponent(e.detail, {
|
||||
createDrawService: (
|
||||
canvas: Canvas,
|
||||
ctx: RenderingContext,
|
||||
opts?: Record<string, unknown>,
|
||||
) => new WordTestDrawService(canvas, ctx, opts),
|
||||
drawServiceOptions: {
|
||||
title: this.getSheetTitle(category),
|
||||
},
|
||||
onCanvasReady: () => {
|
||||
this.drawCanvas();
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
getSelectedCategory(): WordTestCategory | undefined {
|
||||
return findWordTestCategory(
|
||||
this.data.categoryList,
|
||||
this.data.selectedCategoryId,
|
||||
);
|
||||
},
|
||||
|
||||
getSelectedGroup(): WordTestGroup | undefined {
|
||||
return (this.data.groupList as WordTestGroup[]).find(
|
||||
(group: WordTestGroup) =>
|
||||
group.index === this.data.selectedGroupIndex,
|
||||
);
|
||||
},
|
||||
|
||||
getSheetTitle(category?: WordTestCategory): string {
|
||||
if (!category) return '测字表';
|
||||
return `${category.name}(测字表)`;
|
||||
},
|
||||
|
||||
async drawCanvas() {
|
||||
if (!this.drawService) return;
|
||||
|
||||
const group = this.getSelectedGroup();
|
||||
if (!group || group.words.length === 0) {
|
||||
this.setData({ hasContent: false });
|
||||
return;
|
||||
}
|
||||
|
||||
const category = this.getSelectedCategory();
|
||||
const title = this.getSheetTitle(category);
|
||||
|
||||
try {
|
||||
(this.drawService as WordTestDrawService).options.title = title;
|
||||
await (this.drawService as WordTestDrawService).draw({
|
||||
words: group.words,
|
||||
pageNumber: group.index,
|
||||
});
|
||||
this.setData({ hasContent: true });
|
||||
} catch (e) {
|
||||
console.error('wordTestSheet draw failed', e);
|
||||
this.setData({ hasContent: false });
|
||||
}
|
||||
},
|
||||
|
||||
onSelectCategory(e: WechatMiniprogram.TouchEvent) {
|
||||
const id = e.currentTarget.dataset.id as string;
|
||||
if (!id || id === this.data.selectedCategoryId) return;
|
||||
|
||||
const category = findWordTestCategory(this.data.categoryList, id);
|
||||
if (!category) return;
|
||||
|
||||
const groupList = buildWordTestGroups(category.words);
|
||||
|
||||
this.setData(
|
||||
{
|
||||
selectedCategoryId: id,
|
||||
selectedCategoryMap: buildSelectedMap(id),
|
||||
groupList,
|
||||
selectedGroupIndex: 1,
|
||||
selectedGroupMap: buildSelectedMap('1'),
|
||||
},
|
||||
() => this.drawCanvas(),
|
||||
);
|
||||
},
|
||||
|
||||
onSelectGroup(e: WechatMiniprogram.TouchEvent) {
|
||||
const index = Number(e.currentTarget.dataset.index);
|
||||
if (!index || index === this.data.selectedGroupIndex) return;
|
||||
|
||||
this.setData(
|
||||
{
|
||||
selectedGroupIndex: index,
|
||||
selectedGroupMap: buildSelectedMap(String(index)),
|
||||
},
|
||||
() => this.drawCanvas(),
|
||||
);
|
||||
},
|
||||
|
||||
onPreviewRefresh() {
|
||||
const groupList = this.data.groupList as WordTestGroup[];
|
||||
if (!groupList.length) {
|
||||
this.drawCanvas();
|
||||
return;
|
||||
}
|
||||
|
||||
const currentIdx = groupList.findIndex(
|
||||
(group) => group.index === this.data.selectedGroupIndex,
|
||||
);
|
||||
const nextIdx =
|
||||
currentIdx < 0 ? 0 : (currentIdx + 1) % groupList.length;
|
||||
const nextGroup = groupList[nextIdx];
|
||||
|
||||
this.setData(
|
||||
{
|
||||
selectedGroupIndex: nextGroup.index,
|
||||
selectedGroupMap: buildSelectedMap(String(nextGroup.index)),
|
||||
},
|
||||
() => this.drawCanvas(),
|
||||
);
|
||||
},
|
||||
|
||||
async onPreviewFavorite() {
|
||||
const next = !this.data.isPreviewFavorite;
|
||||
this.setData({ isPreviewFavorite: next });
|
||||
const id = this.data.worksheetId;
|
||||
if (id) {
|
||||
this._favoritedMap[id] = next;
|
||||
if (next) {
|
||||
addFavorite(id);
|
||||
} else {
|
||||
removeFavorite(id);
|
||||
}
|
||||
}
|
||||
wx.showToast({
|
||||
title: next ? '收藏成功' : '已取消收藏',
|
||||
icon: 'none',
|
||||
});
|
||||
},
|
||||
|
||||
async loadFavoritedMap() {
|
||||
const ids = [WORD_TEST_WORKSHEET_ID];
|
||||
this._favoritedMap = await batchCheckFavorited(ids);
|
||||
if (this._favoritedMap[this.data.worksheetId]) {
|
||||
this.setData({ isPreviewFavorite: true });
|
||||
}
|
||||
},
|
||||
|
||||
getPublishMeta(): DebugPublishMeta {
|
||||
const meta = getPublishMetaByMode(this.data.worksheetId);
|
||||
if (!meta) {
|
||||
throw new Error('当前题型配置不存在');
|
||||
}
|
||||
return meta;
|
||||
},
|
||||
},
|
||||
{
|
||||
shareConfig: defaultShareConfig,
|
||||
pageInfoLookup,
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,83 @@
|
||||
<nav-bar title="测字表" />
|
||||
|
||||
<view class="wt-page">
|
||||
<view class="wt-main">
|
||||
<preview-card
|
||||
id="previewCard"
|
||||
showRefresh="{{true}}"
|
||||
showFavorite="{{true}}"
|
||||
favorited="{{isPreviewFavorite}}"
|
||||
bind:canvas-ready="onCanvasReady"
|
||||
bind:refresh="onPreviewRefresh"
|
||||
bind:favorite="onPreviewFavorite" />
|
||||
|
||||
<view wx:if="{{groupList.length > 0}}" class="wt-section">
|
||||
<view class="wt-section-header">
|
||||
<text class="wt-section-title">选择组</text>
|
||||
</view>
|
||||
<view class="wt-group-grid">
|
||||
<view
|
||||
wx:for="{{groupList}}"
|
||||
wx:key="index"
|
||||
class="wt-group-card {{selectedGroupMap[item.index] ? 'wt-group-card--active' : ''}}"
|
||||
data-index="{{item.index}}"
|
||||
hover-class="wt-group-card--pressed"
|
||||
hover-start-time="0"
|
||||
hover-stay-time="70"
|
||||
bindtap="onSelectGroup">
|
||||
<view
|
||||
wx:if="{{selectedGroupMap[item.index]}}"
|
||||
class="wt-group-card__check">
|
||||
<toy-icon name="check" size="20rpx" color="#fff" />
|
||||
</view>
|
||||
<text class="wt-group-card__name">{{item.label}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="wt-section">
|
||||
<view class="wt-section-header">
|
||||
<text class="wt-section-title">汉字分类</text>
|
||||
</view>
|
||||
<view class="wt-category-grid">
|
||||
<view
|
||||
wx:for="{{categoryList}}"
|
||||
wx:key="id"
|
||||
class="wt-category-card {{selectedCategoryMap[item.id] ? 'wt-category-card--active' : ''}}"
|
||||
data-id="{{item.id}}"
|
||||
hover-class="wt-category-card--pressed"
|
||||
hover-start-time="0"
|
||||
hover-stay-time="70"
|
||||
bindtap="onSelectCategory">
|
||||
<text class="wt-category-card__icon">{{item.icon}}</text>
|
||||
<text class="wt-category-card__name">{{item.name}}</text>
|
||||
<text
|
||||
wx:if="{{item.parentName}}"
|
||||
class="wt-category-card__cat"
|
||||
>{{item.parentName}}</text
|
||||
>
|
||||
</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" />
|
||||
|
||||
<share-guide-popup
|
||||
show="{{showShareDialog}}"
|
||||
bind:onClose="onCloseShareDialog"
|
||||
bind:onShareSuccess="onShareSuccess" />
|
||||
Reference in New Issue
Block a user