Files
miniprogram-1/miniprogram/pages/result/result.ts
2025-11-30 19:18:39 +08:00

745 lines
22 KiB
TypeScript
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// result.ts - 结果展示页面(动画效果和识别结果展示)
import { DictItem, IRecognitionResponse, SenseItem } from 'miniprogram/types/app';
import apiManager from '../../utils/api'
import imageManager from '../../utils/image'
// 从全局类型定义中导入接口
interface BubbleData {
id: string;
word?: string | null;
}
// 为DictItem添加辅助属性用于WXML中简化访问
interface ProcessedDictItem extends DictItem {
primaryWord: string; // 主要单词
ukPronunciation: string | null; // 英式发音
usPronunciation: string | null; // 美式发音
senses: SenseItem[];
}
Page({
data: {
imageId: '',
imagePath: '', // 图片路径
imageInfo: null as any,
// 动画状态
animationStage: 'loading', // loading, processing, result
showBubbles: false,
showResultArea: false,
showCardBubbles: false, // 卡片周围闪烁气泡
cardBubbles: [] as BubbleData[], // 卡片气泡数据
// 识别结果
recognitionResult: null as IRecognitionResponse | null,
bubbleList: [] as BubbleData[],
// 单词详情缓存
wordDetailCache: {} as Record<string, any>,
// 显示内容
displayContent: '',
displayType: '', // description | word
currentWord: '',
currentWordDetail: null as any,
processedWordDetail: null as ProcessedDictItem[] | null, // 处理后的单词详情用于WXML简化访问
isLoadingContent: false,
// 图片样式
imageStyle: '',
cardStyle: '',
// 系统信息
systemInfo: null as any
},
onLoad(options: any) {
console.log('结果页面加载', options)
// 获取系统信息
this.getSystemInfo()
// 获取传入的图片路径
const imagePath = decodeURIComponent(options.imagePath || '')
if (!imagePath) {
wx.showToast({
title: '图片路径错误',
icon: 'none'
})
this.goBack()
return
}
this.setData({ imagePath })
this.initImageDisplay()
},
// 获取系统信息
getSystemInfo() {
const systemInfo = wx.getSystemInfoSync()
this.setData({ systemInfo })
},
// 初始化图片显示
async initImageDisplay() {
try {
// 获取图片信息
const imageInfo = await imageManager.getImageInfo(this.data.imagePath)
this.setData({ imageInfo })
// 设置全屏图片样式
this.setFullscreenImageStyle()
// 立即开始动画
this.startProcessingAnimation()
} catch (error) {
console.error('获取图片信息失败:', error)
wx.showToast({
title: '图片加载失败',
icon: 'none'
})
this.goBack()
}
},
// 设置全屏图片样式
setFullscreenImageStyle() {
const { systemInfo, imageInfo } = this.data
if (!systemInfo || !imageInfo) return
const imageStyle = `
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border-radius: 0;
z-index: 100;
transition: all 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94);
`
const cardStyle = `
position: fixed;
width: 100%;
height: 100%;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
opacity: 1;
`
this.setData({ imageStyle, cardStyle })
},
// 开始处理动画
async startProcessingAnimation() {
this.setData({ animationStage: 'processing' })
// 立即开始识别,但内部控制显示时机
this.startRecognition()
// // 图片缩小到中央卡片
setTimeout(() => {
this.animateToCard()
}, 600)
},
// 动画到卡片状态
animateToCard() {
const imageStyle = `
border-radius: 24rpx;
`
const cardStyle = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 400rpx;
height: 400rpx;
opacity: 1;
transition: all 1s cubic-bezier(0.25, 0.46, 0.45, 0.94);
`
this.setData({
imageStyle,
cardStyle,
showBubbles: true
})
// 延迟生成卡片周围的闪烁气泡
setTimeout(() => {
this.generateCardBubbles()
}, 800) // 与卡片动画同步完成
},
// 生成卡片周围的闪烁气泡
generateCardBubbles() {
console.log('生成卡片周围的闪烁气泡')
// 使用相对定位,不需要复杂的坐标计算
// 气泡位置通过CSS类名控制
const cardBubbles: BubbleData[] = [
{ id: 'card_bubble_1' },
{ id: 'card_bubble_2' },
{ id: 'card_bubble_3' },
{ id: 'card_bubble_4' },
]
// console.log('生成的卡片气泡数据:', cardBubbles)
this.setData({
cardBubbles,
showCardBubbles: true
})
},
// 隐藏卡片气泡
hideCardBubbles() {
// 添加淡出动画效果
const cardBubbles = this.data.cardBubbles.map(bubble => ({
...bubble,
fadeOut: true
}));
this.setData({
cardBubbles
});
// 延迟隐藏气泡,让淡出动画完成
setTimeout(() => {
this.setData({
showCardBubbles: false,
cardBubbles: []
});
}, 1000); // 与CSS过渡时间保持一致
},
// 开始识别
async startRecognition() {
// 记录开始时间
const startTime = Date.now()
const minDuration = 2500 // 最少等待2500ms确保动画完整播放
try {
console.log('开始图片识别,图片路径:', this.data.imagePath)
// 在上传前压缩图片
console.log('开始压缩图片...')
// const compressedImagePath = await imageManager.compressImage(this.data.imagePath, {
// quality: 80,
// maxWidth: 1200,
// maxHeight: 1200
// })
// const result = await apiManager.uploadImage(compressedImagePath)
const result = await apiManager.uploadImage(this.data.imagePath)
// 验证识别结果的数据结构
if (!result) {
throw new Error('识别结果为空')
}
// 设置识别结果
this.setData({ recognitionResult: result })
// 计算已经耗费的时间
const elapsedTime = Date.now() - startTime
const remainingTime = Math.max(0, minDuration - elapsedTime)
console.log(`识别完成,耗时: ${elapsedTime}ms, 还需等待: ${remainingTime}ms`)
// 如果还没到最少等待时间,则等待剩余时间后再显示结果
if (remainingTime > 0) {
setTimeout(() => {
// 隐藏卡片气泡
this.hideCardBubbles()
this.showRecognitionResult()
}, remainingTime)
} else {
// 隐藏卡片气泡
this.hideCardBubbles()
// 如果已经超过最少等待时间,立即显示结果
this.showRecognitionResult()
}
} catch (error) {
console.error('图片识别失败:', error)
this.hideCardBubbles()
// 更详细的错误信息
let errorMessage = '识别失败,请重试'
if (error instanceof Error) {
errorMessage = error.message || '识别失败,请重试'
if (error.message.includes('登录')) {
errorMessage = '登录已过期,请重新登录'
} else if (error.message.includes('网络')) {
errorMessage = '网络连接失败,请检查网络'
} else if (error.message.includes('文件')) {
errorMessage = '文件上传失败,请重试'
}
}
wx.showToast({
title: errorMessage,
icon: 'none',
duration: 3000
})
// 失败后允许返回
setTimeout(() => {
this.goBack()
}, 3000)
}
},
// 显示识别结果
showRecognitionResult() {
const { recognitionResult } = this.data
if (!recognitionResult) {
console.error('识别结果为空')
return
}
console.log('开始显示识别结果:', recognitionResult)
// 图片移动到上方
setTimeout(() => {
this.animateToTop()
}, 500)
// 生成气泡数据 - 添加防护性检查
// const refWords = recognitionResult.result?.level1?.ref_word || []
// const bubbleList = this.generateBubbleData(refWords)
// console.log('生成的气泡数据:', bubbleList)
setTimeout(() => {
this.setData({
imageId: recognitionResult.image_id,
// bubbleList,
animationStage: 'result',
// showResultArea: true
})
wx.navigateTo({
url: '/pages/assessment/assessment?imageId=' + recognitionResult.image_id
})
}, 1000)
// 默认显示描述内容
setTimeout(() => {
this.showDescription()
}, 500)
},
// 动画到顶部
animateToTop() {
const cardStyle = `
position: fixed;
top: 15%;
left: 50%;
transform: translate(-50%, -50%);
width: 300rpx;
height: 300rpx;
opacity: 1;
transition: all 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94);
`
this.setData({ cardStyle })
},
// 生成气泡数据
generateBubbleData(refWords: string[]): BubbleData[] {
console.log('generateBubbleData 输入参数:', refWords)
const { systemInfo } = this.data
if (!systemInfo) {
console.warn('systemInfo 为空,无法生成气泡数据')
return []
}
// 防护性检查确保refWords是数组且不为空
if (!refWords || !Array.isArray(refWords) || refWords.length === 0) {
console.warn('refWords 为空或不是数组,返回空气泡列表')
return []
}
const bubbles: BubbleData[] = []
// 生成气泡数据
for (let i = 0; i < refWords.length; i++) {
const word = refWords[i]
if (!word || typeof word !== 'string') {
console.warn(`refWords[${i}] 数据格式错误:`, word)
continue
}
bubbles.push({
id: `bubble_${i}`,
word: word
})
}
console.log('生成的气泡数据rpx:', bubbles)
return bubbles
},
// 点击图片 - 显示描述
handleImageClick() {
this.showDescription()
},
// 点击气泡 - 显示单词详情
// async handleBubbleClick(e: any) {
// const { word } = e.currentTarget.dataset
// if (!word) return
// // 检查是否已经缓存了该单词的详情
// if (this.data.wordDetailCache[word]) {
// console.log('使用缓存的单词详情:', word)
// const wordDetail = this.data.wordDetailCache[word]
// // 处理单词详情数据简化WXML中的访问
// const processedData = this.processWordDetail(wordDetail)
// this.setData({
// currentWord: word,
// currentWordDetail: wordDetail,
// processedWordDetail: processedData,
// displayType: 'word',
// isLoadingContent: false
// })
// return
// }
// this.setData({
// currentWord: word,
// displayType: 'word',
// isLoadingContent: true,
// displayContent: ''
// })
// try {
// // 调用API获取单词详情
// const wordDetail: any = await apiManager.getWordDetail(word)
// // 处理单词详情数据简化WXML中的访问
// const processedData = this.processWordDetail(wordDetail)
// // 将单词详情缓存起来
// const wordDetailCache = {
// ...this.data.wordDetailCache,
// [word]: wordDetail
// }
// this.setData({
// currentWordDetail: wordDetail,
// processedWordDetail: processedData,
// isLoadingContent: false,
// wordDetailCache
// })
// } catch (error) {
// console.error('获取单词详情失败:', error)
// this.setData({
// displayContent: '获取单词详情失败,请稍后重试',
// isLoadingContent: false,
// currentWordDetail: null,
// processedWordDetail: null
// })
// }
// },
// 处理单词详情数据简化WXML中的访问
processWordDetail(wordDetail: any): ProcessedDictItem[] | null {
if (!wordDetail.dict_list) return null;
return wordDetail.dict_list.map((dictItem: DictItem) => {
// 提取发音信息
let ukPronunciation = null;
let usPronunciation = null;
if (dictItem.pronunciations) {
ukPronunciation = dictItem.pronunciations.uk_ipa || null;
usPronunciation = dictItem.pronunciations.us_ipa || null;
}
// 处理 examples
dictItem.senses.forEach(sense => {
sense.examples = sense.examples || [];
sense.examples = sense.examples.map(example => {
example.en = example.en || '';
example.en = example.en.replace(/[^a-zA-Z\s]/g, '').trim();
return {
...example
};
});
});
return {
...dictItem,
primaryWord: this.data.currentWord,
ukPronunciation,
usPronunciation,
senses: dictItem.senses
} as ProcessedDictItem;
});
},
// 显示描述内容
showDescription() {
const { recognitionResult } = this.data
if (!recognitionResult) {
console.error('recognitionResult 为空,无法显示描述')
return
}
// 防护性检查确保description存在且是数组
console.log('--lisa0-check', recognitionResult)
if (!recognitionResult?.result?.level1?.desc_en || !Array.isArray(recognitionResult?.result?.level1?.desc_en)) {
console.warn('description 为空或不是数组,使用默认文本')
this.setData({
displayType: 'description',
displayContent: '暂无描述信息',
currentWord: '',
isLoadingContent: false
})
return
}
// 过滤空值并格式化描述内容
const validDescriptions = recognitionResult?.result?.level1?.desc_en
.filter(desc => desc && typeof desc === 'string' && desc.trim().length > 0)
.map(desc => {
// 确保首字母大写
return desc.charAt(0).toUpperCase() + desc.slice(1)
})
const displayContent = validDescriptions.length > 0
? validDescriptions.join('\n\n')
: '暂无描述信息'
console.log('显示描述内容:', displayContent)
this.setData({
displayType: 'description',
displayContent,
currentWord: '',
isLoadingContent: false
})
},
// 返回上一页
goBack() {
if (getCurrentPages().length > 1) {
wx.navigateBack()
} else {
wx.switchTab({
url: '/pages/upload/upload'
})
}
},
// 重新识别
handleReRecognize() {
wx.switchTab({
url: '/pages/upload/upload'
})
},
// 保存图片
async handleSaveImage() {
try {
await imageManager.saveImageToPhotosAlbum(this.data.imagePath)
} catch (error) {
console.error('保存图片失败:', error)
}
},
// 页面卸载时不清除音频缓存
onUnload() {
console.log('页面卸载,保留音频缓存')
// 不再清除音频缓存,让用户可以跨页面使用
},
// 切换signpost显示
toggleSignpost(e: any) {
const { index, dictIndex } = e.currentTarget.dataset
const currentWordDetail = this.data.currentWordDetail
if (!currentWordDetail || !currentWordDetail.dict_list) return
const dictList = [...currentWordDetail.dict_list]
if (dictList[dictIndex] && dictList[dictIndex].senses[index]) {
dictList[dictIndex].senses[index].showSignpostCn = !dictList[dictIndex].senses[index].showSignpostCn
// 同时更新处理后的数据
const processedWordDetail = this.data.processedWordDetail ? [...this.data.processedWordDetail] : null
if (processedWordDetail && processedWordDetail[dictIndex] && processedWordDetail[dictIndex].senses[index]) {
processedWordDetail[dictIndex].senses[index].showSignpostCn = dictList[dictIndex].senses[index].showSignpostCn
}
this.setData({
[`currentWordDetail.dict_list[${dictIndex}].senses[${index}].showSignpostCn`]: dictList[dictIndex].senses[index].showSignpostCn,
processedWordDetail
})
}
},
// 切换definition显示
toggleDefinition(e: any) {
const { index, dictIndex } = e.currentTarget.dataset
const currentWordDetail = this.data.currentWordDetail
if (!currentWordDetail || !currentWordDetail.dict_list) return
const dictList = [...currentWordDetail.dict_list]
if (dictList[dictIndex] && dictList[dictIndex].senses[index]) {
dictList[dictIndex].senses[index].showDefinitionCn = !dictList[dictIndex].senses[index].showDefinitionCn
// 同时更新处理后的数据
const processedWordDetail = this.data.processedWordDetail ? [...this.data.processedWordDetail] : null
if (processedWordDetail && processedWordDetail[dictIndex] && processedWordDetail[dictIndex].senses[index]) {
processedWordDetail[dictIndex].senses[index].showDefinitionCn = dictList[dictIndex].senses[index].showDefinitionCn
}
this.setData({
[`currentWordDetail.dict_list[${dictIndex}].senses[${index}].showDefinitionCn`]: dictList[dictIndex].senses[index].showDefinitionCn,
processedWordDetail
})
}
},
// 切换examples显示
toggleExamples(e: any) {
const { index, dictIndex } = e.currentTarget.dataset
const currentWordDetail = this.data.currentWordDetail
if (!currentWordDetail || !currentWordDetail.dict_list) return
const dictList = [...currentWordDetail.dict_list]
if (dictList[dictIndex] && dictList[dictIndex].senses[index]) {
dictList[dictIndex].senses[index].showExamples = !dictList[dictIndex].senses[index].showExamples
// 同时更新处理后的数据
const processedWordDetail = this.data.processedWordDetail ? [...this.data.processedWordDetail] : null
if (processedWordDetail && processedWordDetail[dictIndex] && processedWordDetail[dictIndex].senses[index]) {
processedWordDetail[dictIndex].senses[index].showExamples = dictList[dictIndex].senses[index].showExamples
}
this.setData({
[`currentWordDetail.dict_list[${dictIndex}].senses[${index}].showExamples`]: dictList[dictIndex].senses[index].showExamples,
processedWordDetail
})
}
},
// 播放音频
playAudio(e: any) {
const { audio } = e.currentTarget.dataset
if (audio) {
// 调用API管理器播放音频
apiManager.playAudio(audio).catch(error => {
console.error('播放音频失败:', error)
// 检查是否是因为音频正在播放导致的拒绝
if (error.message === '音频正在播放中,请等待播放完成') {
// 不显示错误提示,因为这是正常的行为限制
console.log('音频正在播放中,已阻止重复播放')
} else {
wx.showToast({
title: '音频播放失败',
icon: 'none'
})
}
})
}
},
preventTagChange() {
return false;
},
// 切换例句翻译显示
toggleDefTranslation(e: any) {
const definition = e.currentTarget.dataset.definition
if (!definition) return false;
// 更新处理后的数据中的显示状态
const processedWordDetail = this.data.processedWordDetail ? [...this.data.processedWordDetail] : null
if (processedWordDetail) {
for (let dictIndex = 0; dictIndex < processedWordDetail.length; dictIndex++) {
const dictItem = processedWordDetail[dictIndex]
if (dictItem.senses) {
for (let senseIndex = 0; senseIndex < dictItem.senses.length; senseIndex++) {
const sense = dictItem.senses[senseIndex]
if (sense.definitions) {
const ex = sense.definitions[0]
if (ex.en === definition.en) {
// 切换显示状态
ex.showTranslation = !ex.showTranslation
// 更新数据
this.setData({
processedWordDetail
})
return false;
}
}
}
}
}
}
return false;
},
// 切换例句翻译显示
toggleExampleTranslation(e: any) {
const example = e.currentTarget.dataset.example
if (!example) return
// 更新处理后的数据中的显示状态
const processedWordDetail = this.data.processedWordDetail ? [...this.data.processedWordDetail] : null
if (processedWordDetail) {
for (let dictIndex = 0; dictIndex < processedWordDetail.length; dictIndex++) {
const dictItem = processedWordDetail[dictIndex]
if (dictItem.senses) {
for (let senseIndex = 0; senseIndex < dictItem.senses.length; senseIndex++) {
const sense = dictItem.senses[senseIndex]
if (sense.examples) {
for (let exampleIndex = 0; exampleIndex < sense.examples.length; exampleIndex++) {
const ex = sense.examples[exampleIndex]
// 检查是否是同一个例句(通过英文内容匹配)
if (ex.en === example.en) {
// 切换显示状态
ex.showTranslation = !ex.showTranslation
// 更新数据
this.setData({
processedWordDetail
})
return
}
}
}
}
}
}
}
},
// 点击描述文本跳转到 assessment 页面
handleDescriptionClick(e: WechatMiniprogram.BaseEvent) {
const content = e.currentTarget.dataset.content
if (content) {
wx.navigateTo({
url: `/pages/assessment/assessment?imageId=${this.data.imageId}&index=0`
})
}
},
})