fix structure
This commit is contained in:
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
.history
|
||||
.DS_Store
|
||||
miniprogram/static/.DS_Store
|
||||
@@ -2,7 +2,6 @@
|
||||
"pages": [
|
||||
"pages/upload/upload",
|
||||
"pages/result/result",
|
||||
"pages/result_show/result_show",
|
||||
"pages/assessment/assessment",
|
||||
"pages/profile/profile",
|
||||
"pages/index/index",
|
||||
|
||||
@@ -25,6 +25,17 @@ interface IPageData {
|
||||
word: string
|
||||
pronAccuracy: number
|
||||
pronFluency: number
|
||||
matchTag?: number // 0:匹配单词、1:新增单词、2:缺少单词、3:错读的词、4:未录入单词
|
||||
phoneInfos?: Array<{ // 音标信息数组
|
||||
phone: string // 国际音标
|
||||
pronAccuracy: number // 音标评分
|
||||
matchTag: number // 音标匹配度
|
||||
}>
|
||||
}>
|
||||
matchTagLegend: Array<{ // MatchTag 说明
|
||||
tag: number
|
||||
description: string
|
||||
color: string
|
||||
}>
|
||||
standardAudioMap: { [key: string]: string } // 标准语音文件ID映射
|
||||
isPlaying: boolean // 是否正在播放音频
|
||||
@@ -48,7 +59,6 @@ type IPageMethods = {
|
||||
playStandardVoice: () => void
|
||||
resetAudioState: () => void
|
||||
handleSliderChange: (e: any) => void
|
||||
formatTime: (seconds: number | undefined) => string
|
||||
}
|
||||
|
||||
interface IPageInstance extends IPageMethods {
|
||||
@@ -77,6 +87,13 @@ Page<IPageData, IPageInstance>({
|
||||
ipas: [], // 音标的单词数组
|
||||
isScoreExpanded: false, // 评分区域是否展开
|
||||
wordScores: [], // 单词评分数组
|
||||
matchTagLegend: [
|
||||
{ tag: 0, description: '匹配', color: '#ffffff' },
|
||||
{ tag: 1, description: '新增', color: '#ffebee' },
|
||||
{ tag: 2, description: '缺少', color: '#e3f2fd' },
|
||||
{ tag: 3, description: '错读', color: '#fff3e0' },
|
||||
{ tag: 4, description: '未录入', color: '#f5f5f5' }
|
||||
],
|
||||
standardAudioMap: {}, // 标准语音文件ID映射
|
||||
isPlaying: false, // 是否正在播放音频
|
||||
audioDuration: 0, // 音频总时长
|
||||
@@ -149,7 +166,13 @@ Page<IPageData, IPageInstance>({
|
||||
const wordScores = assessmentResult?.Words?.map((word: any) => ({
|
||||
word: word.Word,
|
||||
pronAccuracy: Number(word.PronAccuracy.toFixed(2)),
|
||||
pronFluency: Number(word.PronFluency.toFixed(2))
|
||||
pronFluency: Number(word.PronFluency.toFixed(2)),
|
||||
matchTag: word.MatchTag || 0,
|
||||
phoneInfos: word.PhoneInfos?.map((phone: any) => ({
|
||||
phone: phone.Phone,
|
||||
pronAccuracy: Number(phone.PronAccuracy.toFixed(2)),
|
||||
matchTag: phone.MatchTag || 0
|
||||
})) || []
|
||||
})) || []
|
||||
|
||||
this.setData({
|
||||
@@ -195,7 +218,13 @@ Page<IPageData, IPageInstance>({
|
||||
const wordScores = assessmentResult?.Words?.map((word: any) => ({
|
||||
word: word.Word,
|
||||
pronAccuracy: Number(word.PronAccuracy.toFixed(2)),
|
||||
pronFluency: Number(word.PronFluency.toFixed(2))
|
||||
pronFluency: Number(word.PronFluency.toFixed(2)),
|
||||
matchTag: word.MatchTag || 0,
|
||||
phoneInfos: word.PhoneInfos?.map((phone: any) => ({
|
||||
phone: phone.Phone,
|
||||
pronAccuracy: Number(phone.PronAccuracy.toFixed(2)),
|
||||
matchTag: phone.MatchTag || 0
|
||||
})) || []
|
||||
})) || []
|
||||
|
||||
this.setData({
|
||||
@@ -475,14 +504,16 @@ Page<IPageData, IPageInstance>({
|
||||
}
|
||||
},
|
||||
|
||||
// 格式化时间
|
||||
formatTime(seconds: number | undefined) {
|
||||
if (seconds === undefined || isNaN(seconds)) {
|
||||
return '00:00'
|
||||
async handleWordClick(e: any) {
|
||||
const { word } = e.currentTarget.dataset
|
||||
if (!word) return
|
||||
|
||||
try {
|
||||
// 调用API获取单词详情
|
||||
const wordDetail: any = await apiManager.getWordDetail(word)
|
||||
} catch (error) {
|
||||
console.error('获取单词详情失败:', error)
|
||||
}
|
||||
const minutes = Math.floor(seconds / 60)
|
||||
const remainingSeconds = Math.floor(seconds % 60)
|
||||
return `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`
|
||||
},
|
||||
|
||||
onLoad(options: Record<string, string>) {
|
||||
@@ -516,7 +547,13 @@ Page<IPageData, IPageInstance>({
|
||||
const wordScores = assessmentResult?.Words?.map((word: any) => ({
|
||||
word: word.Word,
|
||||
pronAccuracy: Number(word.PronAccuracy.toFixed(2)),
|
||||
pronFluency: Number(word.PronFluency.toFixed(2))
|
||||
pronFluency: Number(word.PronFluency.toFixed(2)),
|
||||
matchTag: word.MatchTag || 0,
|
||||
phoneInfos: word.PhoneInfos?.map((phone: any) => ({
|
||||
phone: phone.Phone,
|
||||
pronAccuracy: Number(phone.PronAccuracy.toFixed(2)),
|
||||
matchTag: phone.MatchTag || 0
|
||||
})) || []
|
||||
})) || []
|
||||
|
||||
this.setData({
|
||||
@@ -597,7 +634,13 @@ Page<IPageData, IPageInstance>({
|
||||
const wordScores = assessmentResult.Words?.map((word: any) => ({
|
||||
word: word.Word,
|
||||
pronAccuracy: Number(word.PronAccuracy.toFixed(2)),
|
||||
pronFluency: Number(word.PronFluency.toFixed(2))
|
||||
pronFluency: Number(word.PronFluency.toFixed(2)),
|
||||
matchTag: word.MatchTag || 0,
|
||||
phoneInfos: word.PhoneInfos?.map((phone: any) => ({
|
||||
phone: phone.Phone,
|
||||
pronAccuracy: Number(phone.PronAccuracy.toFixed(2)),
|
||||
matchTag: phone.MatchTag || 0
|
||||
}))
|
||||
})) || []
|
||||
|
||||
// 更新评分信息
|
||||
|
||||
@@ -16,8 +16,13 @@
|
||||
<view class="sentence-wrapper">
|
||||
<view class="sentence-content-wrapper">
|
||||
<view class="word-wrapper" wx:for="{{words}}" wx:key="index">
|
||||
<text class="sentence-text">{{item}}</text>
|
||||
<text wx:if="{{ipas[index]}}" class="sentence-ipa">{{ipas[index]}}</text>
|
||||
<text class="sentence-text" data-word="{{item}}" bindtap="handleWordClick">
|
||||
{{item}}
|
||||
</text>
|
||||
<text wx:if="{{ipas[index] && ipas[index] !== '*'}}" class="sentence-ipa">
|
||||
{{ipas[index]}}
|
||||
</text>
|
||||
<text wx:else class="sentence-ipa"></text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -39,11 +44,7 @@
|
||||
<t-button class="play-btn" theme="primary" size="large" variant="outline" bind:tap="playStandardVoice">
|
||||
<t-icon name="{{isPlaying ? 'pause' : 'play'}}" size="48rpx" />
|
||||
</t-button>
|
||||
<view class="audio-progress">
|
||||
<text class="time-text">{{currentTime ? formatTime(currentTime) : '00:00'}}</text>
|
||||
<t-slider class="progress-slider" value="{{sliderValue}}" bind:change="handleSliderChange" theme="primary" max="100" step="1" />
|
||||
<text class="time-text">{{audioDuration ? formatTime(audioDuration) : '00:00'}}</text>
|
||||
</view>
|
||||
<t-slider class="progress-slider" value="{{sliderValue}}" bind:change="handleSliderChange" theme="primary" max="100" step="1" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -98,18 +99,39 @@
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- MatchTag 说明 -->
|
||||
<view class="match-tag-legend">
|
||||
<view class="legend-header">
|
||||
<text class="legend-title">匹配说明:</text>
|
||||
<view class="legend-items">
|
||||
<view class="legend-item" wx:for="{{matchTagLegend}}" wx:key="tag">
|
||||
<view class="color-box" style="background-color: {{item.color}}"></view>
|
||||
<text class="legend-text">{{item.description}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 单词评分列表 -->
|
||||
<view class="word-scores-list">
|
||||
<view class="word-score-item" wx:for="{{wordScores}}" wx:key="word">
|
||||
<text class="word-text">{{item.word}}</text>
|
||||
<view class="word-score-details">
|
||||
<view class="word-score-row">
|
||||
<text class="word-score-label">准确性</text>
|
||||
<text class="word-score-value">{{item.pronAccuracy}}</text>
|
||||
<view class="word-score-item" wx:for="{{wordScores}}" wx:key="word" style="background-color: {{matchTagLegend[item.matchTag || 0].color}}">
|
||||
<view class="word-header">
|
||||
<text class="word-text">{{item.word}}</text>
|
||||
<view class="word-score-details">
|
||||
<view class="word-score-row">
|
||||
<text class="word-score-label">准确性</text>
|
||||
<text class="word-score-value">{{item.pronAccuracy}}</text>
|
||||
</view>
|
||||
<view class="word-score-row">
|
||||
<text class="word-score-label">流利度</text>
|
||||
<text class="word-score-value">{{item.pronFluency}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="word-score-row">
|
||||
<text class="word-score-label">流利度</text>
|
||||
<text class="word-score-value">{{item.pronFluency}}</text>
|
||||
</view>
|
||||
<!-- 音标信息 -->
|
||||
<view class="phone-infos" wx:if="{{item.phoneInfos && item.phoneInfos.length > 0}}">
|
||||
<view class="phone-info-item" wx:for="{{item.phoneInfos}}" wx:for-item="phoneInfo" wx:key="phone" style="background-color: {{matchTagLegend[phoneInfo.matchTag || 0].color}}">
|
||||
<text class="phone-text">[{{phoneInfo.phone}}]</text>
|
||||
<text class="phone-score">{{phoneInfo.pronAccuracy}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -152,26 +152,8 @@
|
||||
border: 6rpx solid #000;
|
||||
}
|
||||
|
||||
.audio-progress {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
/* background: #f8f8f8; */
|
||||
padding: 16rpx;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.time-text {
|
||||
font-size: 24rpx;
|
||||
color: #001858;
|
||||
min-width: 70rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.progress-slider {
|
||||
flex: 1;
|
||||
/* margin: 0 16rpx; */
|
||||
--td-slider-bar-height: 8rpx;
|
||||
--td-slider-dot-size: 32rpx;
|
||||
}
|
||||
@@ -222,7 +204,7 @@
|
||||
|
||||
/* 底部评分结果区域 */
|
||||
.score-section {
|
||||
padding: 32rpx 24rpx;
|
||||
padding: 24rpx 16rpx;
|
||||
background: #ffffff;
|
||||
border-top-left-radius: 24rpx;
|
||||
border-top-right-radius: 24rpx;
|
||||
@@ -234,6 +216,7 @@
|
||||
z-index: 100;
|
||||
transition: all 0.3s ease-in-out;
|
||||
max-height: 20vh;
|
||||
touch-action: pan-y;
|
||||
}
|
||||
|
||||
.score-section.expanded {
|
||||
@@ -285,26 +268,22 @@
|
||||
.score-overview {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 32rpx;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.expanded .score-overview {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-bottom: 32rpx;
|
||||
margin-bottom: 20rpx;
|
||||
/* align-items: center; */
|
||||
}
|
||||
|
||||
.expanded .total-score {
|
||||
margin-bottom: 48rpx;
|
||||
transform: scale(1.2);
|
||||
/* margin-bottom: 48rpx; */
|
||||
}
|
||||
|
||||
.expanded .score-details {
|
||||
width: 100%;
|
||||
padding: 0 32rpx;
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.score-details {
|
||||
@@ -422,54 +401,126 @@
|
||||
}
|
||||
|
||||
/* 单词评分列表样式 */
|
||||
.match-tag-legend {
|
||||
padding: 16rpx;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.legend-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.legend-title {
|
||||
font-size: 24rpx;
|
||||
color: #333;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.legend-items {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.color-box {
|
||||
width: 20rpx;
|
||||
height: 20rpx;
|
||||
border: 1rpx solid #e0e0e0;
|
||||
border-radius: 4rpx;
|
||||
}
|
||||
|
||||
.legend-text {
|
||||
font-size: 22rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.word-scores-list {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24rpx;
|
||||
gap: 16rpx;
|
||||
max-height: calc(80vh - 400rpx);
|
||||
overflow-y: auto;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
margin-top: 32rpx;
|
||||
padding: 16rpx;
|
||||
}
|
||||
|
||||
.word-score-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12rpx;
|
||||
padding: 16rpx;
|
||||
border: 1rpx solid #e0e0e0;
|
||||
border-radius: 8rpx;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.word-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 24rpx;
|
||||
background: #f8f8f8;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.phone-infos {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8rpx;
|
||||
margin-top: 8rpx;
|
||||
padding-top: 8rpx;
|
||||
border-top: 1rpx dashed #e0e0e0;
|
||||
}
|
||||
|
||||
.phone-info-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
padding: 4rpx 8rpx;
|
||||
border-radius: 4rpx;
|
||||
border: 1rpx solid #e0e0e0;
|
||||
}
|
||||
|
||||
.phone-text {
|
||||
font-size: 24rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.phone-score {
|
||||
font-size: 22rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.word-text {
|
||||
font-size: 32rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.word-score-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12rpx;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.word-score-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
gap: 8rpx;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.word-score-label {
|
||||
font-size: 24rpx;
|
||||
font-size: 22rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.word-score-value {
|
||||
font-size: 28rpx;
|
||||
font-size: 22rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
min-width: 80rpx;
|
||||
text-align: right;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// result.ts - 结果展示页面(动画效果和识别结果展示)
|
||||
import { DictItem, IRecognitionResult, SenseItem } from 'miniprogram/types/app';
|
||||
import { DictItem, IRecognitionResponse, SenseItem } from 'miniprogram/types/app';
|
||||
import apiManager from '../../utils/api'
|
||||
import imageManager from '../../utils/image'
|
||||
|
||||
@@ -31,7 +31,7 @@ Page({
|
||||
cardBubbles: [] as BubbleData[], // 卡片气泡数据
|
||||
|
||||
// 识别结果
|
||||
recognitionResult: null as IRecognitionResult | null,
|
||||
recognitionResult: null as IRecognitionResponse | null,
|
||||
bubbleList: [] as BubbleData[],
|
||||
|
||||
// 单词详情缓存
|
||||
@@ -267,10 +267,11 @@ Page({
|
||||
|
||||
} 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('网络')) {
|
||||
@@ -309,15 +310,15 @@ Page({
|
||||
}, 500)
|
||||
|
||||
// 生成气泡数据 - 添加防护性检查
|
||||
const refWords = recognitionResult.res.ref_word || []
|
||||
const bubbleList = this.generateBubbleData(refWords)
|
||||
// const refWords = recognitionResult.result?.level1?.ref_word || []
|
||||
// const bubbleList = this.generateBubbleData(refWords)
|
||||
|
||||
console.log('生成的气泡数据:', bubbleList)
|
||||
// console.log('生成的气泡数据:', bubbleList)
|
||||
|
||||
setTimeout(() => {
|
||||
this.setData({
|
||||
imageId: recognitionResult.image_id,
|
||||
bubbleList,
|
||||
// bubbleList,
|
||||
animationStage: 'result',
|
||||
showResultArea: true
|
||||
})
|
||||
@@ -391,65 +392,65 @@ Page({
|
||||
},
|
||||
|
||||
// 点击气泡 - 显示单词详情
|
||||
async handleBubbleClick(e: any) {
|
||||
const { word } = e.currentTarget.dataset
|
||||
if (!word) return
|
||||
// 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]
|
||||
// // 检查是否已经缓存了该单词的详情
|
||||
// if (this.data.wordDetailCache[word]) {
|
||||
// console.log('使用缓存的单词详情:', word)
|
||||
// const wordDetail = this.data.wordDetailCache[word]
|
||||
|
||||
// 处理单词详情数据,简化WXML中的访问
|
||||
const processedData = this.processWordDetail(wordDetail)
|
||||
// // 处理单词详情数据,简化WXML中的访问
|
||||
// const processedData = this.processWordDetail(wordDetail)
|
||||
|
||||
this.setData({
|
||||
currentWord: word,
|
||||
currentWordDetail: wordDetail,
|
||||
processedWordDetail: processedData,
|
||||
displayType: 'word',
|
||||
isLoadingContent: false
|
||||
})
|
||||
return
|
||||
}
|
||||
// this.setData({
|
||||
// currentWord: word,
|
||||
// currentWordDetail: wordDetail,
|
||||
// processedWordDetail: processedData,
|
||||
// displayType: 'word',
|
||||
// isLoadingContent: false
|
||||
// })
|
||||
// return
|
||||
// }
|
||||
|
||||
this.setData({
|
||||
currentWord: word,
|
||||
displayType: 'word',
|
||||
isLoadingContent: true,
|
||||
displayContent: ''
|
||||
})
|
||||
// this.setData({
|
||||
// currentWord: word,
|
||||
// displayType: 'word',
|
||||
// isLoadingContent: true,
|
||||
// displayContent: ''
|
||||
// })
|
||||
|
||||
try {
|
||||
// 调用API获取单词详情
|
||||
const wordDetail: any = await apiManager.getWordDetail(word)
|
||||
// try {
|
||||
// // 调用API获取单词详情
|
||||
// const wordDetail: any = await apiManager.getWordDetail(word)
|
||||
|
||||
// 处理单词详情数据,简化WXML中的访问
|
||||
const processedData = this.processWordDetail(wordDetail)
|
||||
// // 处理单词详情数据,简化WXML中的访问
|
||||
// const processedData = this.processWordDetail(wordDetail)
|
||||
|
||||
// 将单词详情缓存起来
|
||||
const wordDetailCache = {
|
||||
...this.data.wordDetailCache,
|
||||
[word]: wordDetail
|
||||
}
|
||||
// // 将单词详情缓存起来
|
||||
// const wordDetailCache = {
|
||||
// ...this.data.wordDetailCache,
|
||||
// [word]: wordDetail
|
||||
// }
|
||||
|
||||
this.setData({
|
||||
currentWordDetail: wordDetail,
|
||||
processedWordDetail: processedData,
|
||||
isLoadingContent: false,
|
||||
wordDetailCache
|
||||
})
|
||||
// this.setData({
|
||||
// currentWordDetail: wordDetail,
|
||||
// processedWordDetail: processedData,
|
||||
// isLoadingContent: false,
|
||||
// wordDetailCache
|
||||
// })
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取单词详情失败:', error)
|
||||
this.setData({
|
||||
displayContent: '获取单词详情失败,请稍后重试',
|
||||
isLoadingContent: false,
|
||||
currentWordDetail: null,
|
||||
processedWordDetail: null
|
||||
})
|
||||
}
|
||||
},
|
||||
// } catch (error) {
|
||||
// console.error('获取单词详情失败:', error)
|
||||
// this.setData({
|
||||
// displayContent: '获取单词详情失败,请稍后重试',
|
||||
// isLoadingContent: false,
|
||||
// currentWordDetail: null,
|
||||
// processedWordDetail: null
|
||||
// })
|
||||
// }
|
||||
// },
|
||||
|
||||
// 处理单词详情数据,简化WXML中的访问
|
||||
processWordDetail(wordDetail: any): ProcessedDictItem[] | null {
|
||||
@@ -496,7 +497,8 @@ Page({
|
||||
}
|
||||
|
||||
// 防护性检查:确保description存在且是数组
|
||||
if (!recognitionResult.res.description || !Array.isArray(recognitionResult.res.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',
|
||||
@@ -508,7 +510,7 @@ Page({
|
||||
}
|
||||
|
||||
// 过滤空值并格式化描述内容
|
||||
const validDescriptions = recognitionResult.res.description
|
||||
const validDescriptions = recognitionResult?.result?.level1?.desc_en
|
||||
.filter(desc => desc && typeof desc === 'string' && desc.trim().length > 0)
|
||||
.map(desc => {
|
||||
// 确保首字母大写
|
||||
@@ -730,32 +732,6 @@ Page({
|
||||
}
|
||||
},
|
||||
|
||||
// 临时调试方法 - 模拟识别结果(用于测试)
|
||||
debugSimulateResult() {
|
||||
console.log('模拟识别结果 - 用于调试')
|
||||
|
||||
const mockResult: IRecognitionResult = {
|
||||
id: 'mock_001',
|
||||
res: {
|
||||
description: [
|
||||
'这是一张包含英文单词的图片',
|
||||
'图片中识别到了多个关键单词'
|
||||
],
|
||||
ref_word: [
|
||||
'hello',
|
||||
'world',
|
||||
'test',
|
||||
'example',
|
||||
'sample'
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
console.log('设置模拟结果:', mockResult)
|
||||
this.setData({ recognitionResult: mockResult })
|
||||
this.showRecognitionResult()
|
||||
},
|
||||
|
||||
// 点击描述文本跳转到 assessment 页面
|
||||
handleDescriptionClick(e: WechatMiniprogram.BaseEvent) {
|
||||
const content = e.currentTarget.dataset.content
|
||||
|
||||
@@ -11,11 +11,11 @@
|
||||
<view wx:for="{{cardBubbles}}" wx:key="id" class="bubble-item card-bubble card-bubble-{{index + 1}} {{item.fadeOut ? 'fade-out' : ''}}"></view>
|
||||
</view>
|
||||
<!-- 结果气泡 -->
|
||||
<view wx:if="{{animationStage === 'result' && bubbleList.length > 0}}" class="bubbles-container result-bubbles">
|
||||
<!-- <view wx:if="{{animationStage === 'result' && bubbleList.length > 0}}" class="bubbles-container result-bubbles">
|
||||
<view wx:for="{{bubbleList}}" wx:key="id" class="bubble-item bubble-interactive result-bubble result-bubble-{{index + 1}}" data-word="{{item.word}}" bindtap="handleBubbleClick">
|
||||
<text class="bubble-text">{{item.word}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view> -->
|
||||
</view>
|
||||
<!-- 结果显示区域 -->
|
||||
<view wx:if="{{showResultArea}}" class="result-display-area display-area">
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"navigationBarTitleText": "识别结果",
|
||||
"navigationBarTextStyle": "black",
|
||||
"navigationBarBackgroundColor": "#ffffff",
|
||||
"backgroundColor": "#f8f9fa",
|
||||
"backgroundTextStyle": "light",
|
||||
"enablePullDownRefresh": false,
|
||||
"onReachBottomDistance": 50,
|
||||
"usingComponents": {
|
||||
"t-icon": "tdesign-miniprogram/icon/icon"
|
||||
}
|
||||
}
|
||||
@@ -1,336 +0,0 @@
|
||||
// result_show.ts - 结果展示页面(仅展示识别结果)
|
||||
import { DictItem, IRecognitionResult, SenseItem } from 'miniprogram/types/app';
|
||||
import apiManager from '../../utils/api'
|
||||
import { FILE_BASE_URL } from '../../utils/config'
|
||||
|
||||
// 为DictItem添加辅助属性,用于WXML中简化访问
|
||||
interface ProcessedDictItem extends DictItem {
|
||||
primaryWord: string; // 主要单词
|
||||
ukPronunciation: string | null; // 英式发音
|
||||
usPronunciation: string | null; // 美式发音
|
||||
senses: SenseItem[];
|
||||
}
|
||||
|
||||
Page({
|
||||
data: {
|
||||
imageId: '',
|
||||
imagePath: '',
|
||||
// 识别结果
|
||||
recognitionResult: null as IRecognitionResult | null,
|
||||
bubbleList: [] as Array<{id: string; word?: string | null}>,
|
||||
|
||||
// 单词详情缓存
|
||||
wordDetailCache: {} as Record<string, any>,
|
||||
|
||||
// 显示内容
|
||||
displayContent: [] as string[],
|
||||
errorTip: '',
|
||||
displayType: '', // description | word
|
||||
currentWord: '',
|
||||
currentWordDetail: null as any,
|
||||
processedWordDetail: null as ProcessedDictItem[] | null, // 处理后的单词详情,用于WXML简化访问
|
||||
isLoadingContent: false,
|
||||
},
|
||||
|
||||
onLoad(options: any) {
|
||||
console.log('结果展示页面加载', options)
|
||||
|
||||
// 获取传入的图片ID
|
||||
const imageId = options.imageId
|
||||
if (!imageId) {
|
||||
wx.showToast({
|
||||
title: '参数错误',
|
||||
icon: 'none'
|
||||
})
|
||||
this.goBack()
|
||||
return
|
||||
}
|
||||
this.setData({ imageId })
|
||||
// 获取识别结果
|
||||
this.loadRecognitionResult(imageId)
|
||||
},
|
||||
|
||||
// 加载识别结果
|
||||
async loadRecognitionResult(imageId: string) {
|
||||
try {
|
||||
const { task_id } = await apiManager.recognizeImageAsync(imageId)
|
||||
|
||||
// 轮询获取识别结果
|
||||
const result = await apiManager.recognizeGetTask(task_id)
|
||||
|
||||
|
||||
|
||||
// 设置识别结果
|
||||
this.setData({ recognitionResult: result })
|
||||
|
||||
// 设置图片路径
|
||||
this.setData({ imagePath: `${FILE_BASE_URL}/${result.file_id}` })
|
||||
|
||||
// 生成气泡数据
|
||||
const refWords = result.res.ref_word || []
|
||||
const bubbleList = this.generateBubbleData(refWords)
|
||||
|
||||
this.setData({
|
||||
bubbleList,
|
||||
displayType: 'description'
|
||||
})
|
||||
|
||||
// 显示描述内容
|
||||
this.showDescription()
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取识别结果失败:', error)
|
||||
wx.showToast({
|
||||
title: '获取结果失败',
|
||||
icon: 'none'
|
||||
})
|
||||
this.goBack()
|
||||
}
|
||||
},
|
||||
|
||||
// 生成气泡数据
|
||||
generateBubbleData(refWords: string[]) {
|
||||
if (!refWords || !Array.isArray(refWords) || refWords.length === 0) {
|
||||
return []
|
||||
}
|
||||
|
||||
return refWords.map((word, index) => ({
|
||||
id: `bubble_${index}`,
|
||||
word: word
|
||||
}))
|
||||
},
|
||||
|
||||
// 点击气泡 - 显示单词详情
|
||||
async handleBubbleClick(e: any) {
|
||||
const { word } = e.currentTarget.dataset
|
||||
if (!word) return
|
||||
|
||||
// 检查是否已经缓存了该单词的详情
|
||||
if (this.data.wordDetailCache[word]) {
|
||||
const wordDetail = this.data.wordDetailCache[word]
|
||||
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,
|
||||
errorTip: '',
|
||||
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({
|
||||
errorTip: '获取单词详情失败,请稍后重试',
|
||||
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;
|
||||
});
|
||||
},
|
||||
|
||||
// 点击图片 - 显示描述
|
||||
handleImageClick() {
|
||||
this.showDescription()
|
||||
},
|
||||
|
||||
// 点击描述文本跳转到 assessment 页面
|
||||
handleDescriptionClick(e: WechatMiniprogram.BaseEvent) {
|
||||
const index = e.currentTarget.dataset.index
|
||||
wx.navigateTo({
|
||||
url: `/pages/assessment/assessment?imageId=${this.data.imageId}&index=${index}`
|
||||
});
|
||||
},
|
||||
|
||||
// 显示描述内容
|
||||
showDescription() {
|
||||
const { recognitionResult } = this.data
|
||||
if (!recognitionResult) {
|
||||
console.error('recognitionResult 为空,无法显示描述')
|
||||
return
|
||||
}
|
||||
|
||||
// 防护性检查:确保description存在且是数组
|
||||
if (!recognitionResult.res.description || !Array.isArray(recognitionResult.res.description)) {
|
||||
this.setData({
|
||||
displayType: 'description',
|
||||
errorTip: '暂无描述信息',
|
||||
currentWord: '',
|
||||
isLoadingContent: false
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 过滤空值并格式化描述内容
|
||||
const validDescriptions = recognitionResult.res.description
|
||||
.filter(desc => desc && typeof desc === 'string' && desc.trim().length > 0)
|
||||
.map(desc => {
|
||||
// 确保首字母大写
|
||||
return desc.charAt(0).toUpperCase() + desc.slice(1)
|
||||
})
|
||||
|
||||
if (validDescriptions.length) {
|
||||
this.setData({
|
||||
displayContent: validDescriptions,
|
||||
errorTip: ''
|
||||
})
|
||||
} else {
|
||||
this.setData({
|
||||
errorTip: '暂无描述信息'
|
||||
})
|
||||
}
|
||||
|
||||
this.setData({
|
||||
displayType: 'description',
|
||||
currentWord: '',
|
||||
isLoadingContent: false
|
||||
})
|
||||
},
|
||||
|
||||
// 返回上一页
|
||||
goBack() {
|
||||
if (getCurrentPages().length > 1) {
|
||||
wx.navigateBack()
|
||||
} else {
|
||||
wx.switchTab({
|
||||
url: '/pages/upload/upload'
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// 播放音频
|
||||
playAudio(e: any) {
|
||||
const { audio } = e.currentTarget.dataset
|
||||
if (audio) {
|
||||
apiManager.playAudio(audio).catch(error => {
|
||||
console.error('播放音频失败:', error)
|
||||
if (error.message !== '音频正在播放中,请等待播放完成') {
|
||||
wx.showToast({
|
||||
title: '音频播放失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// 切换释义翻译显示
|
||||
toggleDefTranslation(e: WechatMiniprogram.BaseEvent) {
|
||||
const definition = e.currentTarget.dataset.definition;
|
||||
if (!definition) return false;
|
||||
|
||||
const processedWordDetail = this.data.processedWordDetail ? [...this.data.processedWordDetail] : null;
|
||||
if (processedWordDetail) {
|
||||
for (const dictItem of processedWordDetail) {
|
||||
if (dictItem.senses) {
|
||||
for (const sense of dictItem.senses) {
|
||||
if (sense.definitions) {
|
||||
const def = sense.definitions.find(d => d.en === definition.en);
|
||||
if (def) {
|
||||
def.showTranslation = !def.showTranslation;
|
||||
this.setData({ processedWordDetail });
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
// 切换例句翻译显示
|
||||
toggleExampleTranslation(e: WechatMiniprogram.BaseEvent) {
|
||||
const example = e.currentTarget.dataset.example;
|
||||
if (!example) return false;
|
||||
|
||||
const processedWordDetail = this.data.processedWordDetail ? [...this.data.processedWordDetail] : null;
|
||||
if (processedWordDetail) {
|
||||
for (const dictItem of processedWordDetail) {
|
||||
if (dictItem.senses) {
|
||||
for (const sense of dictItem.senses) {
|
||||
if (sense.examples) {
|
||||
const ex = sense.examples.find(e => e.en === example.en);
|
||||
if (ex) {
|
||||
ex.showTranslation = !ex.showTranslation;
|
||||
this.setData({ processedWordDetail });
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
// 阻止标签点击事件
|
||||
preventTagChange() {
|
||||
return false;
|
||||
}
|
||||
})
|
||||
@@ -1,133 +0,0 @@
|
||||
<!-- result_show.wxml - 结果展示页面 -->
|
||||
<view class="result-container">
|
||||
<!-- 主要内容区域 -->
|
||||
<view class="main-content">
|
||||
<!-- 图片卡片容器(用于光晕效果) -->
|
||||
<view class="image-card-container">
|
||||
<!-- 图片显示 -->
|
||||
<image class="result-image glow-effect" src="{{imagePath}}" mode="aspectFill" bindtap="handleImageClick" />
|
||||
<!-- 结果气泡 -->
|
||||
<view class="bubbles-container result-bubbles">
|
||||
<view wx:for="{{bubbleList}}" wx:key="id" class="bubble-item bubble-interactive result-bubble result-bubble-{{index + 1}}" data-word="{{item.word}}" bindtap="handleBubbleClick" style="background: #007AFF; color: #fff;">
|
||||
<text class="bubble-text">{{item.word}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 结果显示区域 -->
|
||||
<view class="result-display-area display-area">
|
||||
<!-- 内容区域 -->
|
||||
<scroll-view class="display-content" scroll-y="true" enable-back-to-top="true" show-scrollbar="true" scroll-with-animation="true" style="flex: 1;">
|
||||
<!-- 显示内容 -->
|
||||
<view wx:if="{{!isLoadingContent}}" class="content-area">
|
||||
<!-- 显示图片描述内容 -->
|
||||
<view wx:if="{{displayType === 'description'}}" class="content-text">
|
||||
<view wx:if="{{displayContent.length}}">
|
||||
<view class="text-primary" wx:for="{{displayContent}}" wx:key="index" bindtap="handleDescriptionClick" data-index="{{index}}">
|
||||
<text>{{item}}</text>
|
||||
<t-icon name="microphone-1" class="microphone" size="40rpx" />
|
||||
</view>
|
||||
</view>
|
||||
<text class="text-primary" wx:else>{{errorTip}}</text>
|
||||
</view>
|
||||
<!-- 显示单词详情内容 -->
|
||||
<view wx:elif="{{displayType === 'word' && processedWordDetail}}" class="word-detail-container">
|
||||
<!-- 遍历处理后的dict_list数组 -->
|
||||
<view wx:for="{{processedWordDetail}}" wx:key="index" class="dict-item">
|
||||
<!-- 标题栏 -->
|
||||
<view class="word-header">
|
||||
<view class="word-header-container">
|
||||
<text class="word-title">{{currentWord}}</text>
|
||||
<!-- 音标 -->
|
||||
<view class="pronunciations">
|
||||
<!-- 如果两种发音都存在 -->
|
||||
<text wx:if="{{item.ukPronunciation && item.usPronunciation}}" class="ipa">
|
||||
/{{item.ukPronunciation}} $ {{item.usPronunciation}}/
|
||||
</text>
|
||||
<!-- 只有 uk 发音 -->
|
||||
<text wx:elif="{{item.ukPronunciation}}" class="ipa">
|
||||
/{{item.ukPronunciation}}/
|
||||
</text>
|
||||
<!-- 只有 us 发音 -->
|
||||
<text wx:elif="{{item.usPronunciation}}" class="ipa">
|
||||
/{{item.usPronunciation}}/
|
||||
</text>
|
||||
<!-- 音频按钮 -->
|
||||
<t-icon class="ipa-audio" wx:if="{{item.ukPronunciation && item.pronunciations && item.pronunciations.uk_audio}}" bind:click="playAudio" data-audio="{{item.pronunciations.uk_audio}}" name="sound" size="40rpx" data-name="{{item}}" />
|
||||
<t-icon class="ipa-audio" wx:if="{{item.usPronunciation && item.pronunciations && item.pronunciations.us_audio}}" bind:click="playAudio" data-audio="{{item.pronunciations.us_audio}}" name="sound" size="40rpx" data-name="{{item}}" style="margin-left: 10rpx;" />
|
||||
</view>
|
||||
</view>
|
||||
<!-- 频率标签 -->
|
||||
<view class="frequency-tags">
|
||||
<t-tag wx:if="{{item.transitive && item.transitive.length > 0}}" wx:for="{{item.transitive}}" wx:key="index" variant="light" theme="success" style="margin-right: 12rpx;">
|
||||
{{item}}
|
||||
</t-tag>
|
||||
<t-tag wx:if="{{item.part_of_speech}}" variant="light" theme="success" style="margin-right: 12rpx;">
|
||||
{{item.part_of_speech}}
|
||||
</t-tag>
|
||||
<t-tag wx:if="{{item.frequency && item.frequency.level_tag}}" variant="light" theme="danger" style="margin-left: 10rpx;font-family:arial, helvetica;">
|
||||
{{item.frequency.level_tag}}
|
||||
</t-tag>
|
||||
<t-tag wx:if="{{item.frequency && item.frequency.spoken_tag}}" variant="light" theme="warning" style="margin-left: 10rpx;">
|
||||
{{item.frequency.spoken_tag}}
|
||||
</t-tag>
|
||||
<t-tag wx:if="{{item.frequency && item.frequency.written_tag}}" variant="light" theme="warning" style="margin-left: 10rpx;">
|
||||
{{item.frequency.written_tag}}
|
||||
</t-tag>
|
||||
<t-tag wx:if="{{item.frequency && item.frequency.level_tag}}" variant="light" theme="danger" style="margin-left: 10rpx;font-family:arial, helvetica;">
|
||||
{{item.frequency.level_tag}}
|
||||
</t-tag>
|
||||
<t-tag wx:if="{{item.frequency && item.frequency.spoken_tag}}" variant="light" theme="warning" style="margin-left: 10rpx;">
|
||||
{{item.frequency.spoken_tag}}
|
||||
</t-tag>
|
||||
<t-tag wx:if="{{item.frequency && item.frequency.written_tag}}" variant="light" theme="warning" style="margin-left: 10rpx;">
|
||||
{{item.frequency.written_tag}}
|
||||
</t-tag>
|
||||
</view>
|
||||
</view>
|
||||
<!-- senses内容 -->
|
||||
<view wx:if="{{item.senses}}" class="senses-container">
|
||||
<t-collapse wx:for="{{item.senses}}" wx:key="index" class="word-collapse" defaultValue="{{item.senses[0].id}}" expandIcon>
|
||||
<t-collapse-panel value="{{item.id}}" expand-icon="{{item.examples && item.examples.length > 0}}" disabled="{{item.examples && item.examples.length < 1}}">
|
||||
<view class="word-collapse-header" slot="header">
|
||||
<view wx:if="{{item.countability && item.countability.length > 0}}" style="display: inline;">
|
||||
<t-tag wx:for="{{item.countability}}" wx:key="index" variant="light" style="margin-right: 12rpx;">
|
||||
{{item}}
|
||||
</t-tag>
|
||||
</view>
|
||||
<view wx:if="{{item.signpost_en && item.signpost_cn}}" style="display: inline; margin-right: 12rpx;">
|
||||
<t-check-tag variant="light" default-checked content="{{ [item.signpost_en, item.signpost_en + ' / ' + item.signpost_cn ] }}" catch:tap="preventTagChange" />
|
||||
</view>
|
||||
<text>{{item.definitions[0].en}}</text>
|
||||
<view wx:if="{{item.definitions[0] && item.definitions[0].cn}}" style="display: inline-block; margin-left: 12rpx; vertical-align: middle;height: 46rpx;">
|
||||
<t-icon wx:if="{{!item.definitions[0].showTranslation}}" bind:click="toggleDefTranslation" catch:tap="preventTagChange" data-definition="{{item.definitions[0]}}" name="translate" size="36rpx" />
|
||||
</view>
|
||||
<text wx:if="{{item.definitions[0].cn && item.definitions[0].showTranslation}}" style="margin-left: 12rpx;color: #007AFF;">
|
||||
{{item.definitions[0].cn}}
|
||||
</text>
|
||||
</view>
|
||||
<view wx:for="{{item.examples}}" wx:key="index" style="vertical-align: middle;padding: 8rpx 0;">
|
||||
<text>{{item.en}}</text>
|
||||
<view wx:if="{{item.audio}}" class="word-collapse-content-icon">
|
||||
<t-icon bind:click="playAudio" data-audio="{{item.audio}}" name="sound" size="34rpx" data-name="{{item}}" />
|
||||
</view>
|
||||
<view wx:if="{{item.cn && !item.showTranslation}}" class="word-collapse-content-icon">
|
||||
<t-icon bind:click="toggleExampleTranslation" data-example="{{item}}" name="translate" size="34rpx" />
|
||||
</view>
|
||||
<text wx:if="{{item.cn && item.showTranslation}}" style="color: #007AFF;">
|
||||
{{item.cn}}
|
||||
</text>
|
||||
</view>
|
||||
</t-collapse-panel>
|
||||
</t-collapse>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 默认提示 -->
|
||||
<view wx:else class="content-text">
|
||||
<text class="text-primary">点击图片查看描述,点击气泡查看单词详情</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -1,838 +0,0 @@
|
||||
/* result.wxss - 结果展示页面样式(扁平化设计) */
|
||||
|
||||
.result-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
min-height: 100vh;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* 图片显示 */
|
||||
.result-image {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.glow-effect {
|
||||
box-shadow: 0 8rpx 32rpx rgba(0, 122, 255, 0.3);
|
||||
}
|
||||
|
||||
/* 图片卡片容器 */
|
||||
.image-card-container {
|
||||
z-index: 100;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
position: fixed;
|
||||
top: 15%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 300rpx;
|
||||
height: 300rpx;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* 通用气泡容器 */
|
||||
.bubbles-container {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
/* 通用气泡项 */
|
||||
.bubble-item {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.8s ease;
|
||||
}
|
||||
|
||||
.bubble-interactive {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
border-radius: 16rpx;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 24rpx;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
animation: showBubble .8s forwards ease-in-out,
|
||||
bubbleJump 2s ease-in-out infinite !important;
|
||||
pointer-events: auto;
|
||||
padding: 8rpx 16rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 122, 255, 0.3);
|
||||
}
|
||||
|
||||
.bubble-interactive:active {
|
||||
transform: scale(0.9);
|
||||
background: #0056CC;
|
||||
}
|
||||
|
||||
@keyframes bubbleJump {
|
||||
0%, 100% { transform: translateY(0) scale(1); }
|
||||
50% { transform: translateY(-10rpx) scale(1.05); }
|
||||
}
|
||||
@keyframes showBubble {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.result-bubble-1 {
|
||||
left: -80rpx;
|
||||
top: -25rpx;
|
||||
animation-delay: 0s;
|
||||
z-index: 101;
|
||||
}
|
||||
|
||||
/* 位置5:左侧中下方 */
|
||||
.result-bubble-2 {
|
||||
left: -80rpx;
|
||||
top: 170rpx;
|
||||
animation-delay: 0.4s;
|
||||
z-index: 101;
|
||||
}
|
||||
|
||||
/* 位置4:右侧中上方 */
|
||||
.result-bubble-3 {
|
||||
right: -80rpx;
|
||||
top: 80rpx;
|
||||
animation-delay: 0.2s;
|
||||
z-index: 101;
|
||||
}
|
||||
|
||||
/* 位置8:右侧最下方 */
|
||||
.result-bubble-4 {
|
||||
right: -80rpx;
|
||||
top: 268rpx;
|
||||
animation-delay: 0.6s;
|
||||
z-index: 101;
|
||||
}
|
||||
|
||||
.bubble-text {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
word-break: break-all;
|
||||
color: #fff;
|
||||
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.result-bubbles {
|
||||
z-index: 50;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.bubble-interactive {
|
||||
pointer-events: auto;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.result-bubble {
|
||||
width: auto;
|
||||
height: auto;
|
||||
min-width: 80rpx;
|
||||
min-height: 40rpx;
|
||||
padding: 8rpx 16rpx;
|
||||
background: #007AFF;
|
||||
color: #fff;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 122, 255, 0.3);
|
||||
}
|
||||
|
||||
.result-bubble:active {
|
||||
transform: scale(0.9);
|
||||
background: #0056CC;
|
||||
}
|
||||
|
||||
@keyframes bubbleJump {
|
||||
0%, 100% { transform: translateY(0) scale(1); }
|
||||
50% { transform: translateY(-10rpx) scale(1.05); }
|
||||
}
|
||||
|
||||
.image-card-container {
|
||||
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);
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.result-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 16rpx;
|
||||
object-fit: cover;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.glow-effect {
|
||||
box-shadow: 0 8rpx 32rpx rgba(0, 122, 255, 0.3);
|
||||
}
|
||||
|
||||
/* 结果显示区域 */
|
||||
.result-display-area {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: #ffffff;
|
||||
border-top-left-radius: 24rpx;
|
||||
border-top-right-radius: 24rpx;
|
||||
height: 60vh;
|
||||
max-height: 60vh;
|
||||
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.1);
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.display-header {
|
||||
padding: 32rpx 32rpx 0;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.display-tabs {
|
||||
display: flex;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.tab-btn {
|
||||
flex: 1;
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 20rpx;
|
||||
border-radius: 12rpx;
|
||||
font-size: 28rpx;
|
||||
color: #666666;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.tab-btn.active {
|
||||
background-color: #007AFF;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.tab-word {
|
||||
font-size: 24rpx;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.display-content {
|
||||
flex: 1;
|
||||
height: auto;
|
||||
box-sizing: border-box;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.content-area {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.content-text {
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
line-height: 1.6;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
/* 单词详情样式 */
|
||||
.word-detail-container {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.dict-item {
|
||||
margin-bottom: 40rpx;
|
||||
border-radius: 16rpx;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.word-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-bottom: 20rpx;
|
||||
border-bottom: 1rpx solid #e9ecef;
|
||||
}
|
||||
|
||||
.word-header-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.word-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.frequency-tags {
|
||||
display: inline-block;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
|
||||
.sense-item {
|
||||
margin-bottom: 20rpx;
|
||||
padding: 15rpx;
|
||||
background-color: white;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.signpost {
|
||||
padding: 10rpx;
|
||||
background-color: #e9ecef;
|
||||
border-radius: 8rpx;
|
||||
margin-bottom: 15rpx;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.signpost-en {
|
||||
font-weight: bold;
|
||||
color: #495057;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
|
||||
.signpost-cn {
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.definitions {
|
||||
padding: 10rpx 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.definition-en {
|
||||
font-size: 28rpx;
|
||||
color: #212529;
|
||||
margin-bottom: 8rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.definition-cn {
|
||||
font-size: 26rpx;
|
||||
color: #6c757d;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.examples-section {
|
||||
margin-top: 15rpx;
|
||||
}
|
||||
|
||||
.examples-toggle {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10rpx;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 8rpx;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.toggle-icon {
|
||||
font-size: 24rpx;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.examples-list {
|
||||
margin-top: 15rpx;
|
||||
padding: 10rpx;
|
||||
background-color: #ffffff;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.example-item {
|
||||
padding: 10rpx 0;
|
||||
border-bottom: 1rpx solid #e9ecef;
|
||||
}
|
||||
|
||||
.example-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.example-en {
|
||||
font-size: 26rpx;
|
||||
color: #212529;
|
||||
display: block;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.example-cn {
|
||||
font-size: 24rpx;
|
||||
color: #6c757d;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.toggle-tip {
|
||||
font-size: 22rpx;
|
||||
color: #007AFF;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.dict-item {
|
||||
margin-bottom: 40rpx;
|
||||
border-radius: 16rpx;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.word-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-bottom: 20rpx;
|
||||
border-bottom: 1rpx solid #e9ecef;
|
||||
}
|
||||
|
||||
.word-header-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.word-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.frequency-tags {
|
||||
display: inline-block;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
|
||||
.tag {
|
||||
display: inline-block;
|
||||
padding: 4rpx 12rpx;
|
||||
font-size: 20rpx;
|
||||
border-radius: 8rpx;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
|
||||
.level-tag {
|
||||
background-color: #007AFF;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.spoken-tag {
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.written-tag {
|
||||
background-color: #ffc107;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.level-tag {
|
||||
background-color: #007AFF;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.spoken-tag {
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.written-tag {
|
||||
background-color: #ffc107;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.pronunciations {
|
||||
margin-top: 10rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.ipa {
|
||||
font-family: arial, helvetica, sans-serif;
|
||||
font-size: 24rpx;
|
||||
color: #495057;
|
||||
margin-left: 12rpx;
|
||||
}
|
||||
|
||||
.ipa-audio {
|
||||
padding: 12rpx;
|
||||
}
|
||||
|
||||
.audio-btn {
|
||||
background-color: #007AFF;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 6rpx 16rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 20rpx;
|
||||
margin-left: 10rpx;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.audio-btn.small {
|
||||
padding: 4rpx 12rpx;
|
||||
font-size: 18rpx;
|
||||
}
|
||||
|
||||
.audio-btn:active {
|
||||
background-color: #0056CC;
|
||||
}
|
||||
|
||||
.senses-container {
|
||||
padding: 10rpx 0;
|
||||
}
|
||||
|
||||
.sense-item {
|
||||
margin-bottom: 20rpx;
|
||||
padding: 15rpx;
|
||||
background-color: white;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.signpost {
|
||||
padding: 10rpx;
|
||||
background-color: #e9ecef;
|
||||
border-radius: 8rpx;
|
||||
margin-bottom: 15rpx;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.signpost-en {
|
||||
font-weight: bold;
|
||||
color: #495057;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
|
||||
.signpost-cn {
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.definitions {
|
||||
padding: 10rpx 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.definition-en {
|
||||
font-size: 28rpx;
|
||||
color: #212529;
|
||||
margin-bottom: 8rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.definition-cn {
|
||||
font-size: 26rpx;
|
||||
color: #6c757d;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.examples-section {
|
||||
margin-top: 15rpx;
|
||||
}
|
||||
|
||||
.examples-toggle {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 10rpx;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 8rpx;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.toggle-icon {
|
||||
font-size: 24rpx;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.examples-list {
|
||||
margin-top: 15rpx;
|
||||
padding: 10rpx;
|
||||
background-color: #ffffff;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.example-item {
|
||||
padding: 10rpx 0;
|
||||
border-bottom: 1rpx solid #e9ecef;
|
||||
}
|
||||
|
||||
.example-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.example-en {
|
||||
font-size: 26rpx;
|
||||
color: #212529;
|
||||
display: block;
|
||||
margin-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.example-cn {
|
||||
font-size: 24rpx;
|
||||
color: #6c757d;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.toggle-tip {
|
||||
font-size: 22rpx;
|
||||
color: #007AFF;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.word-collapse {
|
||||
--td-cell-vertical-padding: 8rpx;
|
||||
--td-cell-horizontal-padding: 0rpx;
|
||||
--td-collapse-horizontal-padding: 0rpx;
|
||||
--td-collapse-content-padding: 8rpx;
|
||||
--td-text-color-disabled: rgba(0,0,0,.9);
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.word-collapse-content-icon {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
padding: 0 6rpx;
|
||||
}
|
||||
|
||||
/* 加载状态 */
|
||||
.loading-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 40rpx;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
border: 4rpx solid #f3f3f3;
|
||||
border-top: 4rpx solid #007AFF;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
margin-top: 20rpx;
|
||||
font-size: 28rpx;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
/* 气泡列表 */
|
||||
.bubble-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20rpx;
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.bubble-item {
|
||||
padding: 16rpx 30rpx;
|
||||
background: #ffffff;
|
||||
border-radius: 30rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.bubble-item:active {
|
||||
transform: scale(0.95);
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
/* 结果显示区域 */
|
||||
.result-display-area {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: #ffffff;
|
||||
border-top-left-radius: 24rpx;
|
||||
border-top-right-radius: 24rpx;
|
||||
height: 60vh;
|
||||
max-height: 60vh;
|
||||
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.1);
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.display-content {
|
||||
flex: 1;
|
||||
height: auto;
|
||||
max-height: none;
|
||||
box-sizing: border-box;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.content-text {
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
line-height: 1.6;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.content-area {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 单词详情样式 */
|
||||
.word-detail-container {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.dict-item {
|
||||
margin-bottom: 40rpx;
|
||||
border-radius: 16rpx;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.word-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-bottom: 20rpx;
|
||||
border-bottom: 1rpx solid #e9ecef;
|
||||
}
|
||||
|
||||
.word-header-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex_start;
|
||||
}
|
||||
|
||||
.word-title {
|
||||
font-size: 36rpx;
|
||||
font-weight: bold;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.frequency-tags {
|
||||
display: inline-block;
|
||||
margin-top: 10rpx;
|
||||
}
|
||||
|
||||
.pronunciations {
|
||||
margin-top: 10rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.ipa {
|
||||
font-family: arial, helvetica, sans-serif;
|
||||
font-size: 24rpx;
|
||||
color: #495057;
|
||||
margin-left: 12rpx;
|
||||
}
|
||||
|
||||
.ipa-audio {
|
||||
padding: 12rpx;
|
||||
}
|
||||
|
||||
.senses-container {
|
||||
padding: 10rpx 0;
|
||||
}
|
||||
|
||||
.word-collapse {
|
||||
--td-cell-vertical-padding: 8rpx;
|
||||
--td-cell-horizontal-padding: 0rpx;
|
||||
--td-collapse-horizontal-padding: 0rpx;
|
||||
--td-collapse-content-padding: 8rpx;
|
||||
--td-text-color-disabled: rgba(0,0,0,.9);
|
||||
}
|
||||
|
||||
.word-collapse-content-icon {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
padding: 0 6rpx;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.microphone {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.text-primary {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
9
miniprogram/types/app.d.ts
vendored
9
miniprogram/types/app.d.ts
vendored
@@ -31,14 +31,6 @@ interface ILoginResponse {
|
||||
dict_level?: string; // 新增字段,用户词典等级配置
|
||||
}
|
||||
|
||||
// 图片识别结果接口
|
||||
interface IRecognitionResult {
|
||||
id: string | number;
|
||||
res:{
|
||||
description: string[];
|
||||
ref_word: string[];
|
||||
}
|
||||
}
|
||||
|
||||
// 添加单词详情的数据接口定义
|
||||
interface DictItem {
|
||||
@@ -145,7 +137,6 @@ export {
|
||||
IAppOption,
|
||||
IUserInfo,
|
||||
ILoginResponse,
|
||||
IRecognitionResult,
|
||||
DictItem,
|
||||
SenseItem,
|
||||
ExampleItem,
|
||||
|
||||
@@ -29,14 +29,23 @@ export interface IApiResponse<T> {
|
||||
data: T
|
||||
}
|
||||
|
||||
// 识别结果接口
|
||||
export interface IRecognitionResult {
|
||||
text: string
|
||||
words: Array<{
|
||||
word: string
|
||||
start: number
|
||||
end: number
|
||||
}>
|
||||
export interface IRecognitionResponse {
|
||||
task_id: string
|
||||
status: string
|
||||
error_message?: string
|
||||
image_id?: string
|
||||
result?: IRecognitionResponseResult
|
||||
}
|
||||
|
||||
export interface IRecognitionResponseResultLevel {
|
||||
desc_en: string[]
|
||||
desc_zh: string[]
|
||||
}
|
||||
|
||||
export interface IRecognitionResponseResult {
|
||||
level1: IRecognitionResponseResultLevel,
|
||||
level2: IRecognitionResponseResultLevel,
|
||||
level3: IRecognitionResponseResultLevel,
|
||||
}
|
||||
|
||||
// 单词详情接口
|
||||
@@ -56,6 +65,16 @@ export interface ExtendedWordDetail {
|
||||
audio: string
|
||||
}
|
||||
|
||||
export interface YdWordDetail {
|
||||
ee: {}
|
||||
ec: {}
|
||||
discriminate: {}
|
||||
etym: {}
|
||||
expand_ec: {}
|
||||
phrs: {}
|
||||
simple: {}
|
||||
}
|
||||
|
||||
// 审核历史记录接口
|
||||
export interface IAuditHistoryResponse {
|
||||
total: number
|
||||
|
||||
@@ -3,11 +3,12 @@ import {
|
||||
IAppOption,
|
||||
ILoginResponse,
|
||||
IApiResponse,
|
||||
IRecognitionResult,
|
||||
IRecognitionResponse,
|
||||
ExtendedWordDetail,
|
||||
IAuditHistoryResponse,
|
||||
IUserInfo,
|
||||
IDailySummaryResponse
|
||||
IDailySummaryResponse,
|
||||
YdWordDetail
|
||||
} from '../types/app';
|
||||
import { BASE_URL } from './config';
|
||||
|
||||
@@ -317,6 +318,11 @@ class ApiManager {
|
||||
this.handleTokenExpired()
|
||||
reject(new Error('登录已过期'))
|
||||
}
|
||||
} else if (res.statusCode === 403) {
|
||||
const response = res.data as IApiResponse<T>
|
||||
const errorMsg = response.msg || '请求失败'
|
||||
console.error('403 错误:', errorMsg, response)
|
||||
reject(new Error(errorMsg))
|
||||
} else {
|
||||
console.error('HTTP错误:', res.statusCode, res.data)
|
||||
wx.showToast({
|
||||
@@ -704,24 +710,6 @@ class ApiManager {
|
||||
})
|
||||
}
|
||||
|
||||
// 图片识别(第二步:通过文件ID进行识别)
|
||||
private async recognizeImage(fileId: string, type: string = 'word'): Promise<IRecognitionResult> {
|
||||
console.log('开始图片识别请求:', { fileId, type })
|
||||
|
||||
// 获取当前的词典等级配置
|
||||
const app = getApp<IAppOption>()
|
||||
const dictLevel = app.globalData.dictLevel || wx.getStorageSync('dictLevel') || 'PRIMARY'
|
||||
|
||||
const response = await this.request<IRecognitionResult>('/api/v1/image/recognize', 'POST', {
|
||||
file_id: fileId,
|
||||
type: type,
|
||||
dict_level: dictLevel // 添加词典等级参数
|
||||
})
|
||||
|
||||
console.log('图片识别成功:', response.data)
|
||||
return response.data
|
||||
}
|
||||
|
||||
// 图片识别(第二步:通过文件ID进行识别)
|
||||
async recognizeImageAsync(fileId: string, type: string = 'word'): Promise<{task_id: string, status: string, message: string}> {
|
||||
console.log('开始图片识别请求:', { fileId, type })
|
||||
@@ -741,16 +729,16 @@ class ApiManager {
|
||||
}
|
||||
|
||||
// 获取识别结果
|
||||
async recognizeGetTask(taskId: string | number): Promise<{image_id: string, task_id: string, status: string, error_message: string, result?: IRecognitionResult}> {
|
||||
async recognizeGetTask(taskId: string | number): Promise<IRecognitionResponse> {
|
||||
|
||||
const response = await this.request<{image_id: string, task_id: string, status: string, error_message: string, result?: IRecognitionResult}>(`/api/v1/image/recognize/task/${taskId}`, 'GET')
|
||||
const response = await this.request<IRecognitionResponse>(`/api/v1/image/recognize/task/${taskId}`, 'GET')
|
||||
|
||||
console.log('图片识别成功:', response.data)
|
||||
return response.data
|
||||
}
|
||||
|
||||
// 上传图片并识别(对外接口,整合两个步骤)
|
||||
async uploadImage(filePath: string, type: string = 'word'): Promise<IRecognitionResult> {
|
||||
async uploadImage(filePath: string, type: string = 'word'): Promise<IRecognitionResponse> {
|
||||
try {
|
||||
// wx.showLoading({ title: '上传中...' })
|
||||
|
||||
@@ -766,19 +754,13 @@ class ApiManager {
|
||||
const { task_id } = await this.recognizeImageAsync(fileId, type)
|
||||
|
||||
// 轮询获取识别结果
|
||||
let recognitionResult: IRecognitionResult | null = null
|
||||
let recognitionResult: IRecognitionResponse | null = null
|
||||
while (true) {
|
||||
try {
|
||||
const res = await this.recognizeGetTask(task_id)
|
||||
console.log('--lisa-res', res)
|
||||
if (res.status === 'completed' && res.result) {
|
||||
recognitionResult = {
|
||||
'res': res.result,
|
||||
'task_id': task_id,
|
||||
'status': res.status,
|
||||
'error_message': res.error_message,
|
||||
'image_id': res.image_id
|
||||
}
|
||||
recognitionResult = res
|
||||
break
|
||||
}
|
||||
await new Promise(resolve => setTimeout(resolve, 3000)) // 2秒轮询一次
|
||||
@@ -820,9 +802,9 @@ class ApiManager {
|
||||
}
|
||||
|
||||
// 获取单词详情
|
||||
async getWordDetail(word: string): Promise<ExtendedWordDetail> {
|
||||
async getWordDetail(word: string): Promise<YdWordDetail> {
|
||||
console.log('获取单词详情')
|
||||
const response = await this.request<ExtendedWordDetail>(`/api/v1/dict/word/${encodeURIComponent(word)}`)
|
||||
const response = await this.request<YdWordDetail>(`/api/v1/dict/word/${encodeURIComponent(word)}`)
|
||||
console.log('获取单词详情成功:', response)
|
||||
return response.data
|
||||
}
|
||||
@@ -1294,6 +1276,12 @@ class ApiManager {
|
||||
Word: string
|
||||
PronAccuracy: number,
|
||||
PronFluency: number,
|
||||
MatchTag: number,
|
||||
PhoneInfos?: {
|
||||
Phone: string,
|
||||
PronAccuracy: number,
|
||||
MatchTag: number
|
||||
}[],
|
||||
}[]
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user