From 78b7964860cee133202882ed854167414e3d6591 Mon Sep 17 00:00:00 2001 From: Felix Date: Fri, 9 Jan 2026 12:29:07 +0800 Subject: [PATCH] add confetti --- .../components/vx-confetti/vx-confetti.js | 448 ++++++++++++++++++ .../components/vx-confetti/vx-confetti.json | 4 + .../components/vx-confetti/vx-confetti.wxml | 1 + miniprogram/pages/assessment/assessment.ts | 7 +- miniprogram/pages/assessment/assessment.wxml | 3 +- .../pages/qa_exercise/qa_exercise.json | 3 +- miniprogram/pages/qa_exercise/qa_exercise.ts | 107 ++++- .../pages/qa_exercise/qa_exercise.wxml | 9 +- .../pages/qa_exercise/qa_exercise.wxss | 14 +- miniprogram/pages/upload/upload.ts | 3 +- miniprogram/pages/upload/upload.wxml | 9 +- 11 files changed, 587 insertions(+), 21 deletions(-) create mode 100644 miniprogram/components/vx-confetti/vx-confetti.js create mode 100644 miniprogram/components/vx-confetti/vx-confetti.json create mode 100644 miniprogram/components/vx-confetti/vx-confetti.wxml diff --git a/miniprogram/components/vx-confetti/vx-confetti.js b/miniprogram/components/vx-confetti/vx-confetti.js new file mode 100644 index 0000000..221a5d2 --- /dev/null +++ b/miniprogram/components/vx-confetti/vx-confetti.js @@ -0,0 +1,448 @@ +// canvas-confetti.js - 微信小程序兼容版本 +// 基于 https://github.com/catdad/canvas-confetti + +Component({ + properties: { + width: { + type: Number, + value: 300 + }, + height: { + type: Number, + value: 300 + }, + id: { + type: String, + value: 'confettiCanvas' + } + }, + + data: { + canvasId: '', + isCanvasReady: false, + canvasInitPromise: null, + animationFrameId: null, + animatingFettis: [], + defaults: { + particleCount: 50, + angle: 90, + spread: 45, + startVelocity: 45, + decay: 0.9, + gravity: 1, + drift: 0, + ticks: 200, + x: 0.5, + y: 0.5, + shapes: ['square', 'circle'], + colors: [ + '#26ccff', + '#a25afd', + '#ff5e7e', + '#88ff5a', + '#fcff42', + '#ffa62d', + '#ff36ff' + ], + scalar: 1, + flat: false + } + }, + + lifetimes: { + attached() { + this.setData({ + canvasId: this.properties.id + }); + // 创建初始化Promise + this.data.canvasInitPromise = new Promise((resolve) => { + this._canvasReadyResolver = resolve; + }); + }, + ready() { + // 在组件准备好后初始化canvas + this.initCanvas(); + }, + detached() { + this.reset(); + } + }, + + methods: { + initCanvas() { + const query = this.createSelectorQuery(); + query.select(`#${this.data.canvasId}`) + .fields({ node: true, size: true }) + .exec((res) => { + if (res && res[0]) { + const canvas = res[0].node; + const ctx = canvas.getContext('2d'); + + // 设置canvas大小 + canvas.width = this.properties.width; + canvas.height = this.properties.height; + + // 优化真机显示 - 考虑设备像素比 + try { + const deviceInfo = wx.getDeviceInfo(); + const dpr = deviceInfo.pixelRatio || 1; + canvas.width = this.properties.width * dpr; + canvas.height = this.properties.height * dpr; + ctx.scale(dpr, dpr); + } catch (e) { + console.error('设置DPR失败', e); + } + + this.canvas = canvas; + this.ctx = ctx; + + // 标记canvas已准备好 + this.setData({ isCanvasReady: true }); + + // 解析初始化Promise + if (this._canvasReadyResolver) { + this._canvasReadyResolver(); + } + + console.log('Canvas 已初始化完成', this.canvas.width, this.canvas.height); + } else { + console.error('Canvas 节点未找到', this.data.canvasId); + // 如果找不到canvas,200ms后重试 + setTimeout(() => { + this.initCanvas(); + }, 200); + } + }); + }, + + // 等待canvas初始化完成 + async waitForCanvasReady() { + if (this.data.isCanvasReady && this.canvas && this.ctx) { + return true; + } + + try { + await this.data.canvasInitPromise; + return true; + } catch (err) { + console.error('Canvas 初始化失败', err); + return false; + } + }, + + // 核心方法,触发五彩纸屑效果 + async fire(options = {}) { + // 等待canvas初始化完成 + const isReady = await this.waitForCanvasReady(); + + if (!isReady || !this.canvas || !this.ctx) { + console.error('Canvas 未初始化'); + return Promise.reject('Canvas 未初始化'); + } + + return this.fireConfetti(options); + }, + + // 重置,停止当前动画 + reset() { + this.cancelAnimation(); + if (this.ctx && this.canvas) { + this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); + } + this.setData({ + animatingFettis: [] + }); + }, + + // 取消动画帧 + cancelAnimation() { + if (this.data.animationFrameId) { + if (wx.canIUse('cancelAnimationFrame')) { + cancelAnimationFrame(this.data.animationFrameId); + } else { + clearTimeout(this.data.animationFrameId); + } + this.setData({ + animationFrameId: null + }); + } + }, + + // 请求动画帧 + requestFrame(callback) { + if (wx.canIUse('requestAnimationFrame')) { + return requestAnimationFrame(callback); + } else { + return setTimeout(callback, 1000 / 60); + } + }, + + // 转换属性 + convert(val, transform) { + return transform ? transform(val) : val; + }, + + // 检查值是否有效 + isOk(val) { + return !(val === null || val === undefined); + }, + + // 获取配置属性 + prop(options, name, transform) { + return this.convert( + options && this.isOk(options[name]) ? options[name] : this.data.defaults[name], + transform + ); + }, + + // 将十六进制颜色转为RGB + hexToRgb(str) { + const val = String(str).replace(/[^0-9a-f]/gi, ''); + const hex = val.length < 6 + ? val[0] + val[0] + val[1] + val[1] + val[2] + val[2] + : val; + + return { + r: parseInt(hex.substring(0, 2), 16), + g: parseInt(hex.substring(2, 4), 16), + b: parseInt(hex.substring(4, 6), 16) + }; + }, + + // 颜色数组转RGB + colorsToRgb(colors) { + return colors.map(color => this.hexToRgb(color)); + }, + + // 只接受正整数 + onlyPositiveInt(number) { + return number < 0 ? 0 : Math.floor(number); + }, + + // 生成随机整数 + randomInt(min, max) { + return Math.floor(Math.random() * (max - min)) + min; + }, + + // 获取原点配置 + getOrigin(options) { + const origin = this.prop(options, 'origin', Object) || {}; + origin.x = this.isOk(origin.x) ? origin.x : this.data.defaults.x; + origin.y = this.isOk(origin.y) ? origin.y : this.data.defaults.y; + return origin; + }, + + // 创建随机物理属性 + randomPhysics(opts) { + const radAngle = opts.angle * (Math.PI / 180); + const radSpread = opts.spread * (Math.PI / 180); + + return { + x: opts.x, + y: opts.y, + wobble: Math.random() * 10, + wobbleSpeed: Math.min(0.11, Math.random() * 0.1 + 0.05), + velocity: (opts.startVelocity * 0.5) + (Math.random() * opts.startVelocity), + angle2D: -radAngle + ((0.5 * radSpread) - (Math.random() * radSpread)), + tiltAngle: (Math.random() * (0.75 - 0.25) + 0.25) * Math.PI, + color: opts.color, + shape: opts.shape, + tick: 0, + totalTicks: opts.ticks, + decay: opts.decay, + drift: opts.drift, + random: Math.random() + 2, + tiltSin: 0, + tiltCos: 0, + wobbleX: 0, + wobbleY: 0, + gravity: opts.gravity * 3, + ovalScalar: 0.6, + scalar: opts.scalar, + flat: opts.flat + }; + }, + + // 微信小程序不支持Path2D和标准的ellipse,创建一个ellipse方法 + ellipse(context, x, y, radiusX, radiusY, rotation, startAngle, endAngle) { + context.save(); + context.translate(x, y); + context.rotate(rotation); + context.scale(radiusX, radiusY); + context.arc(0, 0, 1, startAngle, endAngle, false); + context.restore(); + }, + + // 更新单个五彩纸屑 + updateFetti(context, fetti) { + fetti.x += Math.cos(fetti.angle2D) * fetti.velocity + fetti.drift; + fetti.y += Math.sin(fetti.angle2D) * fetti.velocity + fetti.gravity; + fetti.velocity *= fetti.decay; + + if (fetti.flat) { + fetti.wobble = 0; + fetti.wobbleX = fetti.x + (10 * fetti.scalar); + fetti.wobbleY = fetti.y + (10 * fetti.scalar); + + fetti.tiltSin = 0; + fetti.tiltCos = 0; + fetti.random = 1; + } else { + fetti.wobble += fetti.wobbleSpeed; + fetti.wobbleX = fetti.x + ((10 * fetti.scalar) * Math.cos(fetti.wobble)); + fetti.wobbleY = fetti.y + ((10 * fetti.scalar) * Math.sin(fetti.wobble)); + + fetti.tiltAngle += 0.1; + fetti.tiltSin = Math.sin(fetti.tiltAngle); + fetti.tiltCos = Math.cos(fetti.tiltAngle); + fetti.random = Math.random() + 2; + } + + const progress = (fetti.tick++) / fetti.totalTicks; + + const x1 = fetti.x + (fetti.random * fetti.tiltCos); + const y1 = fetti.y + (fetti.random * fetti.tiltSin); + const x2 = fetti.wobbleX + (fetti.random * fetti.tiltCos); + const y2 = fetti.wobbleY + (fetti.random * fetti.tiltSin); + + context.fillStyle = `rgba(${fetti.color.r}, ${fetti.color.g}, ${fetti.color.b}, ${1 - progress})`; + context.beginPath(); + + if (fetti.shape === 'circle') { + this.ellipse( + context, + fetti.x, + fetti.y, + Math.abs(x2 - x1) * fetti.ovalScalar, + Math.abs(y2 - y1) * fetti.ovalScalar, + Math.PI / 10 * fetti.wobble, + 0, + 2 * Math.PI + ); + } else if (fetti.shape === 'star') { + let rot = Math.PI / 2 * 3; + const innerRadius = 4 * fetti.scalar; + const outerRadius = 8 * fetti.scalar; + const x = fetti.x; + const y = fetti.y; + let spikes = 5; + const step = Math.PI / spikes; + + while (spikes--) { + let xTemp = x + Math.cos(rot) * outerRadius; + let yTemp = y + Math.sin(rot) * outerRadius; + context.lineTo(xTemp, yTemp); + rot += step; + + xTemp = x + Math.cos(rot) * innerRadius; + yTemp = y + Math.sin(rot) * innerRadius; + context.lineTo(xTemp, yTemp); + rot += step; + } + } else { + // square (default) + context.moveTo(Math.floor(fetti.x), Math.floor(fetti.y)); + context.lineTo(Math.floor(fetti.wobbleX), Math.floor(y1)); + context.lineTo(Math.floor(x2), Math.floor(y2)); + context.lineTo(Math.floor(x1), Math.floor(fetti.wobbleY)); + } + + context.closePath(); + context.fill(); + + return fetti.tick < fetti.totalTicks; + }, + + // 动画函数 + animate(fettis) { + const animatingFettis = [...fettis]; + const context = this.ctx; + const canvas = this.canvas; + + const update = () => { + context.clearRect(0, 0, canvas.width, canvas.height); + + const stillAlive = []; + for (let i = 0; i < animatingFettis.length; i++) { + if (this.updateFetti(context, animatingFettis[i])) { + stillAlive.push(animatingFettis[i]); + } + } + + if (stillAlive.length) { + this.setData({ + animatingFettis: stillAlive, + animationFrameId: this.requestFrame(() => update()) + }); + } else { + this.setData({ + animatingFettis: [], + animationFrameId: null + }); + } + }; + + this.setData({ + animationFrameId: this.requestFrame(() => update()) + }); + }, + + // 发射五彩纸屑 + fireConfetti(options) { + return new Promise((resolve) => { + const particleCount = this.prop(options, 'particleCount', this.onlyPositiveInt.bind(this)); + const angle = this.prop(options, 'angle', Number); + const spread = this.prop(options, 'spread', Number); + const startVelocity = this.prop(options, 'startVelocity', Number); + const decay = this.prop(options, 'decay', Number); + const gravity = this.prop(options, 'gravity', Number); + const drift = this.prop(options, 'drift', Number); + const colors = this.prop(options, 'colors', this.colorsToRgb.bind(this)); + const ticks = this.prop(options, 'ticks', Number); + const shapes = this.prop(options, 'shapes'); + const scalar = this.prop(options, 'scalar'); + const flat = !!this.prop(options, 'flat'); + const origin = this.getOrigin(options); + + let temp = particleCount; + const fettis = []; + + const startX = this.canvas.width * origin.x; + const startY = this.canvas.height * origin.y; + + while (temp--) { + fettis.push( + this.randomPhysics({ + x: startX, + y: startY, + angle: angle, + spread: spread, + startVelocity: startVelocity, + color: colors[temp % colors.length], + shape: shapes[this.randomInt(0, shapes.length)], + ticks: ticks, + decay: decay, + gravity: gravity, + drift: drift, + scalar: scalar, + flat: flat + }) + ); + } + + // 合并已有和新的五彩纸屑 + const allFettis = [...this.data.animatingFettis, ...fettis]; + + this.setData({ + animatingFettis: allFettis + }, () => { + // 如果已经有动画在运行,不需要再次启动 + if (!this.data.animationFrameId) { + this.animate(allFettis); + } + resolve(); + }); + }); + } + } +}); \ No newline at end of file diff --git a/miniprogram/components/vx-confetti/vx-confetti.json b/miniprogram/components/vx-confetti/vx-confetti.json new file mode 100644 index 0000000..ba96044 --- /dev/null +++ b/miniprogram/components/vx-confetti/vx-confetti.json @@ -0,0 +1,4 @@ +{ + "component": true, + "usingComponents": {} +} \ No newline at end of file diff --git a/miniprogram/components/vx-confetti/vx-confetti.wxml b/miniprogram/components/vx-confetti/vx-confetti.wxml new file mode 100644 index 0000000..b7696a1 --- /dev/null +++ b/miniprogram/components/vx-confetti/vx-confetti.wxml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/miniprogram/pages/assessment/assessment.ts b/miniprogram/pages/assessment/assessment.ts index 5a0563c..facac76 100644 --- a/miniprogram/pages/assessment/assessment.ts +++ b/miniprogram/pages/assessment/assessment.ts @@ -178,7 +178,7 @@ type IPageMethods = { ensureRecordPermission: () => void onMoreTap: () => void onSceneSentenceTap: () => void - onImageQaExerciseTap: () => void + onImageQaExerciseTap: (e: any) => void onSentenceTouchStart: (e: any) => void onSentenceTouchMove: (e: any) => void onSentenceTouchEnd: () => void @@ -1412,15 +1412,16 @@ Page({ }) }, - onImageQaExerciseTap() { + onImageQaExerciseTap(e: any) { const imageId = this.data.imageId || '' + const type = e?.currentTarget?.dataset?.type; if (!imageId) { wx.showToast({ title: '缺少图片ID', icon: 'none' }) return } this.setData({ isMoreMenuOpen: false, isMoreMenuClosing: false }) wx.navigateTo({ - url: `/pages/qa_exercise/qa_exercise?image_id=${encodeURIComponent(imageId)}` + url: `/pages/qa_exercise/qa_exercise?image_id=${encodeURIComponent(imageId)}${type ? ('&type=' + type) : ''}` }) }, diff --git a/miniprogram/pages/assessment/assessment.wxml b/miniprogram/pages/assessment/assessment.wxml index 5d99986..8cc6c6a 100644 --- a/miniprogram/pages/assessment/assessment.wxml +++ b/miniprogram/pages/assessment/assessment.wxml @@ -71,7 +71,8 @@ 场景句型 - 场景练习 + 问答练习 + 完形填空 diff --git a/miniprogram/pages/qa_exercise/qa_exercise.json b/miniprogram/pages/qa_exercise/qa_exercise.json index 5c83777..69b4ec3 100644 --- a/miniprogram/pages/qa_exercise/qa_exercise.json +++ b/miniprogram/pages/qa_exercise/qa_exercise.json @@ -8,6 +8,7 @@ "usingComponents": { "t-icon": "tdesign-miniprogram/icon/icon", "t-skeleton": "tdesign-miniprogram/skeleton/skeleton", - "word-dictionary": "../../components/word-dictionary/word-dictionary" + "word-dictionary": "../../components/word-dictionary/word-dictionary", + "vx-confetti": "/components/vx-confetti/vx-confetti" } } diff --git a/miniprogram/pages/qa_exercise/qa_exercise.ts b/miniprogram/pages/qa_exercise/qa_exercise.ts index c8a26fd..3c42d10 100644 --- a/miniprogram/pages/qa_exercise/qa_exercise.ts +++ b/miniprogram/pages/qa_exercise/qa_exercise.ts @@ -79,6 +79,12 @@ interface IData { processDotClasses?: string[] showCompletionPopup?: boolean isTrialMode?: boolean + fixedMode?: QuestionMode, + canvasWidth?: number, + canvasHeight?: number, + confetti?: any + nextButtonIcon?: string + isAutoSwitching?: boolean } interface IPageInstance { @@ -125,6 +131,8 @@ interface IPageInstance { checkAllQuestionsAttempted: () => void handleCompletionPopupClose: () => void handleShareAchievement: () => void + fireConfetti: () => void + resetConfetti: () => void } Page({ @@ -182,7 +190,13 @@ Page({ retryDisabled: true, audioUrlMap: {}, audioLocalMap: {}, - isPlaying: false + isPlaying: false, + fixedMode: undefined, + canvasWidth: 0, + canvasHeight: 0, + confetti: null, + nextButtonIcon: 'chevron-right', + isAutoSwitching: false }, updateProcessDots() { const list = this.data.qaList || [] @@ -321,6 +335,7 @@ Page({ this.switchQuestion(-1) }, onNextTap() { + this.setData({ nextButtonIcon: 'chevron-right', isAutoSwitching: false }) this.switchQuestion(1) }, toggleMode() { @@ -330,10 +345,37 @@ Page({ const next = order[(i + 1) % order.length] this.switchMode(next) }, + onReady() { + // 获取组件实例 + (this as any).confetti = this.selectComponent('#confetti'); + }, + fireConfetti() { + // 触发五彩纸屑效果 - 注意这是异步方法,返回Promise + (this as any).confetti.fire({ + particleCount: 100, + spread: 70, + origin: { x: 0.5, y: 0.5 } + }).then(() => { + logger.log('五彩纸屑效果已启动'); + }).catch((err: any) => { + logger.error('启动失败', err); + }); + }, + + resetConfetti() { + // 重置画布,清除五彩纸屑 + (this as any).confetti?.reset?.(); + }, + async onLoad(options: Record) { try { const app = getApp() + const type = options.type as QuestionMode + if (type && ENABLED_QUESTION_MODES.includes(type)) { + this.setData({ fixedMode: type }) + } + // 处理推荐人ID const referrerId = options.referrer || options.referrerId || options.referrer_id if (referrerId) { @@ -351,6 +393,19 @@ Page({ // duration: 3000 // }) // } + try { + const windowInfo = (wx as any).getWindowInfo ? (wx as any).getWindowInfo() : wx.getSystemInfoSync() + this.setData({ + canvasWidth: windowInfo.windowWidth, + canvasHeight: windowInfo.windowHeight + }); + } catch (e) { + this.setData({ + canvasWidth: 375, + canvasHeight: 667 + }); + logger.error('获取窗口信息失败:', e) + } const imageId = options?.id || options?.image_id || '' const thumbnailId = options?.thumbnail_id || '' // 兼容旧逻辑,如果是分享进来的可能需要通过 API 获取图片链接 @@ -400,8 +455,9 @@ Page({ // 检查是否是从成就弹窗分享 const isAchievement = options?.from === 'button' && options?.target?.dataset?.type === 'achievement' + const type = this.data.fixedMode || this.data.questionMode // 构建分享路径 - const path = `/pages/qa_exercise/qa_exercise?id=${imageId}&referrer=${myUserId}&mode=trial` + const path = `/pages/qa_exercise/qa_exercise?id=${imageId}&referrer=${myUserId}&mode=trial&type=${type}` let shareTitle = `一起解锁拍照学英语的快乐!👇` let imageUrl = this.data.imageLocalUrl || undefined @@ -525,7 +581,12 @@ Page({ const hasOptions = (Array.isArray(q?.correct_options) && q.correct_options.length > 0) || (Array.isArray(q?.incorrect_options) && q.incorrect_options.length > 0) const hasCloze = !!q?.cloze && !!q.cloze.sentence_with_blank const preferredMode: QuestionMode = hasOptions ? QUESTION_MODES.CHOICE : (hasCloze ? QUESTION_MODES.CLOZE : QUESTION_MODES.FREE_TEXT) - const mode: QuestionMode = ENABLED_QUESTION_MODES.includes(preferredMode) ? preferredMode : ENABLED_QUESTION_MODES[0] + let mode: QuestionMode = ENABLED_QUESTION_MODES.includes(preferredMode) ? preferredMode : ENABLED_QUESTION_MODES[0] + + if (this.data.fixedMode) { + mode = this.data.fixedMode + } + let choiceOptions: Array<{ content: string; correct: boolean; type?: string }> = [] if (hasOptions) { const correct = (q.correct_options || []).map((o: any) => ({ content: o?.content || '', correct: true, type: o?.type })) @@ -639,6 +700,8 @@ Page({ this.updateProcessDots() }, switchMode(mode: QuestionMode) { + if (this.data.fixedMode) return + let target: QuestionMode = mode if (!ENABLED_QUESTION_MODES.includes(target)) { target = ENABLED_QUESTION_MODES[0] @@ -931,6 +994,7 @@ Page({ } catch (e) {} }, selectOption(e: any) { + if (this.data.resultDisplayed) return const i = Number(e?.currentTarget?.dataset?.index) || 0 const flags = (this.data.selectedFlags || []).slice() const required = Number(this.data.choiceRequiredCount || 0) @@ -1002,6 +1066,7 @@ Page({ this.handleWordClick(event as any) }, selectClozeOption(e: any) { + if (this.data.resultDisplayed) return const i = Number(e?.currentTarget?.dataset?.index) || 0 this.setData({ selectedClozeIndex: i }) }, @@ -1020,14 +1085,44 @@ Page({ } }, triggerAutoNextIfCorrect(evaluation: any, qid: string) { - const resStr = String(evaluation?.result || '').toLowerCase() - if (['correct', 'exact', 'exact match', '完全正确', '完全匹配'].includes(resStr)) { + const resStr = String(evaluation?.detail || '').toLowerCase() + if (['correct'].includes(resStr)) { + this.fireConfetti() + + // Check if there is a next question + const currentIndex = this.data.currentIndex + const total = (this.data.qaList || []).length + if (currentIndex >= total - 1) { + return + } + + this.setData({ isAutoSwitching: true, nextButtonIcon: 'numbers-3' }) + + setTimeout(() => { + const currentQ = this.data.qaList[this.data.currentIndex] || {} + if (String(currentQ.id) !== qid) { + this.setData({ isAutoSwitching: false, nextButtonIcon: 'chevron-right' }) + return + } + this.setData({ nextButtonIcon: 'numbers-2' }) + }, 1000) + + setTimeout(() => { + const currentQ = this.data.qaList[this.data.currentIndex] || {} + if (String(currentQ.id) !== qid) { + this.setData({ isAutoSwitching: false, nextButtonIcon: 'chevron-right' }) + return + } + this.setData({ nextButtonIcon: 'numbers-1' }) + }, 2000) + setTimeout(() => { const currentQ = this.data.qaList[this.data.currentIndex] || {} if (String(currentQ.id) === qid) { this.onNextTap() } - }, 800) + this.setData({ isAutoSwitching: false, nextButtonIcon: 'chevron-right' }) + }, 3000) } }, async submitAttempt() { diff --git a/miniprogram/pages/qa_exercise/qa_exercise.wxml b/miniprogram/pages/qa_exercise/qa_exercise.wxml index 751da0b..3825a6b 100644 --- a/miniprogram/pages/qa_exercise/qa_exercise.wxml +++ b/miniprogram/pages/qa_exercise/qa_exercise.wxml @@ -31,7 +31,7 @@ Select the correct answer ({{selectedCount}}/{{choiceRequiredCount}}) - + @@ -47,7 +47,7 @@ {{clozeParts[1]}} - + @@ -80,9 +80,9 @@ - + - + + diff --git a/miniprogram/pages/qa_exercise/qa_exercise.wxss b/miniprogram/pages/qa_exercise/qa_exercise.wxss index db701b0..a1a57c6 100644 --- a/miniprogram/pages/qa_exercise/qa_exercise.wxss +++ b/miniprogram/pages/qa_exercise/qa_exercise.wxss @@ -60,11 +60,11 @@ .option-item.opt-incorrect { border-color: #e74c3c; background: #fdecea; } .option-item.opt-missing { border-color: #21cc80; background: #eafaf2; } .option-item.disabled { opacity: 0.6; } -.option-radio { width: 36rpx; height: 36rpx; border-radius: 50%; border: 2rpx solid #cfd8e3; display: flex; align-items: center; justify-content: center; } +.option-radio { width: 36rpx; height: 36rpx; min-width: 36rpx; min-height: 36rpx; max-width: 36rpx; max-height: 36rpx; flex: 0 0 36rpx; flex-shrink: 0; align-self: center; box-sizing: border-box; border-radius: 50%; border: 2rpx solid #cfd8e3; display: flex; align-items: center; justify-content: center; } .radio-dot { width: 16rpx; height: 16rpx; border-radius: 50%; background: transparent; } .radio-dot.on { background: #21cc80; } .radio-dot.red { background: #e74c3c; } -.option-text { font-size: 30rpx; color: #001858; line-height: 44rpx; } +.option-text { flex: 1; min-width: 0; font-size: 30rpx; color: #001858; line-height: 44rpx; } .cloze-sentence { display: flex; flex-wrap: wrap; gap: 8rpx; align-items: baseline; } .cloze-text { font-size: 40rpx; font-weight: 700; color: #001858; line-height: 56rpx; } .cloze-fill { font-size: 40rpx; font-weight: 700; color: #001858; line-height: 56rpx; } @@ -397,3 +397,13 @@ from { opacity: 0; transform: scale(0.9); } to { opacity: 1; transform: scale(1); } } + +.confetti-canvas { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100vh; + z-index: 9999; + pointer-events: none; /* 确保canvas不会阻挡点击事件 */ +} diff --git a/miniprogram/pages/upload/upload.ts b/miniprogram/pages/upload/upload.ts index 0760f1f..b7ea93a 100755 --- a/miniprogram/pages/upload/upload.ts +++ b/miniprogram/pages/upload/upload.ts @@ -1194,6 +1194,7 @@ Page({ try { const imageId = e?.currentTarget?.dataset?.imageId; const thumbnailId = e?.currentTarget?.dataset?.thumbnailId; + const type = e?.currentTarget?.dataset?.type; if (!imageId) return; const list = (this.data.selectedDateImages || []).map((img: any) => ({ ...img, more_open: false, menu_closing: false })); this.setData({ selectedDateImages: list }, () => { @@ -1202,7 +1203,7 @@ Page({ } }); wx.navigateTo({ - url: `/pages/qa_exercise/qa_exercise?image_id=${encodeURIComponent(imageId)}${thumbnailId ? ('&thumbnail_id=' + encodeURIComponent(thumbnailId)) : ''}` + url: `/pages/qa_exercise/qa_exercise?image_id=${encodeURIComponent(imageId)}${thumbnailId ? ('&thumbnail_id=' + encodeURIComponent(thumbnailId)) : ''}${type ? ('&type=' + type) : ''}` }); } catch (err) {} }, diff --git a/miniprogram/pages/upload/upload.wxml b/miniprogram/pages/upload/upload.wxml index c2c4a19..8b2f103 100755 --- a/miniprogram/pages/upload/upload.wxml +++ b/miniprogram/pages/upload/upload.wxml @@ -92,7 +92,8 @@ 场景句型 - 场景练习 + 问答练习 + 完形填空 @@ -114,7 +115,8 @@ 场景句型 - 场景练习 + 问答练习 + 完形填空 @@ -133,7 +135,8 @@ 场景句型 - 场景练习 + 问答练习 + 完形填空