[lisa-feat]feat: 页面优化以及更新
This commit is contained in:
@@ -2,6 +2,8 @@
|
||||
"pages": [
|
||||
"pages/upload/upload",
|
||||
"pages/result/result",
|
||||
"pages/result_show/result_show",
|
||||
"pages/assessment/assessment",
|
||||
"pages/profile/profile",
|
||||
"pages/index/index",
|
||||
"pages/logs/logs",
|
||||
|
||||
12
miniprogram/pages/assessment/assessment.json
Normal file
12
miniprogram/pages/assessment/assessment.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"navigationBarTitleText": "口语评估",
|
||||
"navigationBarTextStyle": "black",
|
||||
"navigationBarBackgroundColor": "#ffffff",
|
||||
"backgroundColor": "#f8f9fa",
|
||||
"backgroundTextStyle": "light",
|
||||
"enablePullDownRefresh": false,
|
||||
"onReachBottomDistance": 50,
|
||||
"usingComponents": {
|
||||
"t-icon": "tdesign-miniprogram/icon/icon"
|
||||
}
|
||||
}
|
||||
297
miniprogram/pages/assessment/assessment.ts
Normal file
297
miniprogram/pages/assessment/assessment.ts
Normal file
@@ -0,0 +1,297 @@
|
||||
// assessment.ts
|
||||
import { FILE_BASE_URL } from '../../utils/config'
|
||||
import apiManager from '../../utils/api'
|
||||
const recorderManager = wx.getRecorderManager()
|
||||
|
||||
interface IPageData {
|
||||
imagePath: string
|
||||
currentSentence: any
|
||||
sentences: any[]
|
||||
currentIndex: number
|
||||
totalScore: number
|
||||
accuracyScore: number
|
||||
completenessScore: number
|
||||
fluencyScore: number
|
||||
circleProgressStyle: string
|
||||
isRecording: boolean
|
||||
recordStartTime: number
|
||||
recordDuration: number
|
||||
remainingTime: number // 剩余录音时间
|
||||
hasScoreInfo: boolean // 是否有评分信息
|
||||
}
|
||||
|
||||
type IPageMethods = {
|
||||
updateCircleProgress: () => void
|
||||
startRecording: () => void
|
||||
stopRecording: () => void
|
||||
prevSentence: () => void
|
||||
nextSentence: () => void
|
||||
handleRecordStart: () => void
|
||||
handleRecordEnd: () => void
|
||||
}
|
||||
|
||||
interface IPageInstance extends IPageMethods {
|
||||
recordTimer?: number
|
||||
data: IPageData
|
||||
}
|
||||
|
||||
Page<IPageData, IPageInstance>({
|
||||
data: {
|
||||
imagePath: '', // 图片路径
|
||||
currentSentence: '', // 当前显示的例句
|
||||
sentences: [], // 例句列表
|
||||
currentIndex: 0, // 当前例句索引
|
||||
totalScore: 0, // 总分
|
||||
accuracyScore: 0, // 准确性评分
|
||||
completenessScore: 0, // 完整性评分
|
||||
fluencyScore: 0, // 流利度评分
|
||||
circleProgressStyle: '', // 圆形进度条样式
|
||||
isRecording: false, // 是否正在录音
|
||||
recordStartTime: 0, // 录音开始时间
|
||||
recordDuration: 0, // 录音持续时间
|
||||
remainingTime: 30, // 剩余录音时间,默认30秒
|
||||
hasScoreInfo: false, // 是否有评分信息
|
||||
},
|
||||
|
||||
// 更新圆形进度条样式
|
||||
updateCircleProgress() {
|
||||
const { totalScore, hasScoreInfo } = this.data
|
||||
if (!hasScoreInfo || totalScore < 0) {
|
||||
this.setData({ circleProgressStyle: 'background: #f0f0f0' })
|
||||
return
|
||||
}
|
||||
const style = `background: conic-gradient(#007AFF ${totalScore * 3.6}deg, #f0f0f0 0deg)`
|
||||
this.setData({ circleProgressStyle: style })
|
||||
},
|
||||
|
||||
// 切换到上一个例句
|
||||
prevSentence() {
|
||||
const { currentIndex, sentences } = this.data
|
||||
if (currentIndex <= 0) return;
|
||||
if (currentIndex > 0) {
|
||||
const currentSentence = sentences[currentIndex - 1]
|
||||
const assessmentResult = currentSentence?.details?.assessment?.result
|
||||
const suggestedScore = assessmentResult?.SuggestedScore
|
||||
const pronAccuracy = assessmentResult?.PronAccuracy
|
||||
const pronCompletion = assessmentResult?.PronCompletion
|
||||
const pronFluency = assessmentResult?.PronFluency
|
||||
|
||||
// 检查是否所有评分都为负数
|
||||
const allNegative = [suggestedScore, pronAccuracy, pronCompletion, pronFluency].every(score => score < 0)
|
||||
|
||||
this.setData({
|
||||
currentIndex: currentIndex - 1,
|
||||
currentSentence: currentSentence,
|
||||
hasScoreInfo: !allNegative && !!currentSentence.details,
|
||||
totalScore: suggestedScore >= 0 ? Number(suggestedScore.toFixed(2)) : 0,
|
||||
accuracyScore: pronAccuracy >= 0 ? Number(pronAccuracy.toFixed(2)) : 0,
|
||||
completenessScore: pronCompletion >= 0 ? Number((pronCompletion * 100).toFixed(2)) : 0,
|
||||
fluencyScore: pronFluency >= 0 ? Number((pronFluency * 100).toFixed(2)) : 0
|
||||
})
|
||||
this.updateCircleProgress()
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
// 切换到下一个例句
|
||||
nextSentence() {
|
||||
const { currentIndex, sentences } = this.data
|
||||
if (currentIndex >= sentences.length - 1) return;
|
||||
if (currentIndex < sentences.length - 1) {
|
||||
const index = currentIndex + 1
|
||||
const currentSentence = sentences[index]
|
||||
const assessmentResult = currentSentence?.details?.assessment?.result
|
||||
const suggestedScore = assessmentResult?.SuggestedScore
|
||||
const pronAccuracy = assessmentResult?.PronAccuracy
|
||||
const pronCompletion = assessmentResult?.PronCompletion
|
||||
const pronFluency = assessmentResult?.PronFluency
|
||||
|
||||
// 检查是否所有评分都为负数
|
||||
const allNegative = [suggestedScore, pronAccuracy, pronCompletion, pronFluency].every(score => score < 0)
|
||||
|
||||
this.setData({
|
||||
currentIndex: index,
|
||||
currentSentence: currentSentence,
|
||||
hasScoreInfo: !allNegative && !!currentSentence.details,
|
||||
totalScore: suggestedScore >= 0 ? Number(suggestedScore.toFixed(2)) : 0,
|
||||
accuracyScore: pronAccuracy >= 0 ? Number(pronAccuracy.toFixed(2)) : 0,
|
||||
completenessScore: pronCompletion >= 0 ? Number((pronCompletion * 100).toFixed(2)) : 0,
|
||||
fluencyScore: pronFluency >= 0 ? Number((pronFluency * 100).toFixed(2)) : 0
|
||||
})
|
||||
this.updateCircleProgress()
|
||||
}
|
||||
},
|
||||
|
||||
// 开始录音
|
||||
startRecording() {
|
||||
const options: WechatMiniprogram.RecorderManagerStartOption = {
|
||||
duration: 30000, // 最长录音时长,单位 ms
|
||||
sampleRate: 16000, // 采样率
|
||||
numberOfChannels: 1, // 录音通道数
|
||||
encodeBitRate: 48000, // 编码码率
|
||||
format: 'mp3' as 'mp3', // 音频格式
|
||||
}
|
||||
|
||||
recorderManager.start(options)
|
||||
this.setData({
|
||||
isRecording: true,
|
||||
recordStartTime: Date.now(),
|
||||
recordDuration: 0
|
||||
})
|
||||
|
||||
// 设置定时器更新录音时长和倒计时
|
||||
this.recordTimer = setInterval(() => {
|
||||
const duration = Date.now() - this.data.recordStartTime
|
||||
const remaining = Math.max(0, 30 - Math.floor(duration / 1000))
|
||||
this.setData({
|
||||
recordDuration: duration,
|
||||
remainingTime: remaining
|
||||
})
|
||||
|
||||
// 时间到自动停止录音
|
||||
if (remaining === 0) {
|
||||
this.stopRecording()
|
||||
}
|
||||
}, 100)
|
||||
},
|
||||
|
||||
// 停止录音
|
||||
stopRecording() {
|
||||
const duration = Date.now() - this.data.recordStartTime
|
||||
if (this.recordTimer) {
|
||||
clearInterval(this.recordTimer)
|
||||
}
|
||||
|
||||
if (duration < 3000) { // 小于3秒
|
||||
wx.showToast({
|
||||
title: '说话时间太短',
|
||||
icon: 'none'
|
||||
})
|
||||
recorderManager.stop()
|
||||
this.setData({
|
||||
isRecording: false,
|
||||
remainingTime: 30 // 重置倒计时
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
recorderManager.stop()
|
||||
this.setData({ isRecording: false })
|
||||
},
|
||||
|
||||
// 长按开始录音
|
||||
handleRecordStart() {
|
||||
this.startRecording()
|
||||
},
|
||||
|
||||
// 松开结束录音
|
||||
handleRecordEnd() {
|
||||
this.stopRecording()
|
||||
},
|
||||
|
||||
onLoad(options: Record<string, string>) {
|
||||
//如果有图片ID,调用接口获取文本和评分信息
|
||||
if (options.index) {
|
||||
this.setData({ currentIndex: Number(options.index) })
|
||||
}
|
||||
if (options.imageId) {
|
||||
wx.showLoading({ title: '加载中...' })
|
||||
apiManager.getImageTextInit(options.imageId)
|
||||
.then(res => {
|
||||
// 更新图片路径(如果有)
|
||||
if (res.image_file_id) {
|
||||
this.setData({ imagePath: `${FILE_BASE_URL}/${res.image_file_id}` })
|
||||
}
|
||||
// 更新例句和评分信息
|
||||
if (res.assessments && res.assessments.length > 0) {
|
||||
const sentences = res.assessments
|
||||
const index = this.data.currentIndex || 0
|
||||
const currentSentence = sentences[index]
|
||||
const assessmentResult = currentSentence?.details?.assessment?.result
|
||||
const suggestedScore = assessmentResult?.SuggestedScore ?? -1
|
||||
const pronAccuracy = assessmentResult?.PronAccuracy ?? -1
|
||||
const pronCompletion = assessmentResult?.PronCompletion ?? -1
|
||||
const pronFluency = assessmentResult?.PronFluency ?? -1
|
||||
|
||||
// 检查是否所有评分都为负数
|
||||
const allNegative = [suggestedScore, pronAccuracy, pronCompletion, pronFluency].every((score: number) => score < 0)
|
||||
|
||||
this.setData({
|
||||
sentences,
|
||||
currentSentence,
|
||||
currentIndex: index,
|
||||
hasScoreInfo: !allNegative && !!currentSentence.details,
|
||||
totalScore: suggestedScore >= 0 ? Number(suggestedScore.toFixed(2)) : 0,
|
||||
accuracyScore: pronAccuracy >= 0 ? Number(pronAccuracy.toFixed(2)) : 0,
|
||||
completenessScore: pronCompletion >= 0 ? Number((pronCompletion * 100).toFixed(2)) : 0,
|
||||
fluencyScore: pronFluency >= 0 ? Number((pronFluency * 100).toFixed(2)) : 0
|
||||
})
|
||||
}
|
||||
// 初始化圆形进度条
|
||||
this.updateCircleProgress()
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('获取图片文本和评分信息失败:', err)
|
||||
wx.showToast({
|
||||
title: '加载失败',
|
||||
icon: 'none'
|
||||
})
|
||||
})
|
||||
.finally(() => {
|
||||
wx.hideLoading()
|
||||
})
|
||||
} else {
|
||||
// 初始化圆形进度条
|
||||
this.updateCircleProgress()
|
||||
}
|
||||
},
|
||||
|
||||
onReady() {
|
||||
// 监听录音结束事件
|
||||
recorderManager.onStop((res) => {
|
||||
if (this.data.recordDuration >= 3000) { // 只有录音时长大于3秒才提示确认
|
||||
wx.showModal({
|
||||
title: '提示',
|
||||
content: '录音完成,是否确认提交?',
|
||||
success: (result) => {
|
||||
if (result.confirm) {
|
||||
console.log('录音文件路径:', res.tempFilePath)
|
||||
apiManager.uploadFile(res.tempFilePath).then((fileId) => {
|
||||
apiManager.getAssessmentResult(fileId, this.data.currentSentence.id).then((result) => {
|
||||
console.log('口语评估结果:', result)
|
||||
const assessmentResult = result.assessment_result.assessment.result
|
||||
const suggestedScore = assessmentResult.SuggestedScore
|
||||
const pronAccuracy = assessmentResult.PronAccuracy
|
||||
const pronCompletion = assessmentResult.PronCompletion
|
||||
const pronFluency = assessmentResult.PronFluency
|
||||
|
||||
// 检查是否所有评分都为负数
|
||||
const allNegative = [suggestedScore, pronAccuracy, pronCompletion, pronFluency].every(score => score < 0)
|
||||
|
||||
// 更新评分信息
|
||||
this.setData({
|
||||
hasScoreInfo: !allNegative && !!assessmentResult,
|
||||
totalScore: suggestedScore >= 0 ? Number(suggestedScore.toFixed(2)) : 0,
|
||||
accuracyScore: pronAccuracy >= 0 ? Number(pronAccuracy.toFixed(2)) : 0,
|
||||
completenessScore: pronCompletion >= 0 ? Number(pronCompletion.toFixed(2)) : 0,
|
||||
fluencyScore: pronFluency >= 0 ? Number(pronFluency.toFixed(2)) : 0
|
||||
})
|
||||
// 更新圆形进度条
|
||||
this.updateCircleProgress()
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// 监听录音错误事件
|
||||
recorderManager.onError((res) => {
|
||||
wx.showToast({
|
||||
title: '录音失败',
|
||||
icon: 'none'
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
86
miniprogram/pages/assessment/assessment.wxml
Normal file
86
miniprogram/pages/assessment/assessment.wxml
Normal file
@@ -0,0 +1,86 @@
|
||||
<!-- assessment.wxml - 评估页面 -->
|
||||
<view class="assessment-container">
|
||||
<!-- 顶部图片区域 -->
|
||||
<view class="image-section">
|
||||
<image class="assessment-image" src="{{imagePath}}" mode="widthFix" />
|
||||
</view>
|
||||
<!-- 中间例句区域 -->
|
||||
<view class="sentence-section">
|
||||
<view class="sentence-container">
|
||||
<!-- 左箭头 -->
|
||||
<view class="arrow-btn left {{currentIndex <= 0 ? 'disabled' : ''}}" bindtap="prevSentence">
|
||||
<t-icon name="chevron-left" size="48rpx" />
|
||||
</view>
|
||||
<!-- 例句内容 -->
|
||||
<view class="sentence-content">
|
||||
<text class="sentence-text">{{currentSentence.content}}</text>
|
||||
<text class="page-indicator">{{currentIndex + 1}}/{{sentences.length}}</text>
|
||||
</view>
|
||||
<!-- 右箭头 -->
|
||||
<view class="arrow-btn right {{currentIndex >= sentences.length - 1 ? 'disabled' : ''}}" bindtap="nextSentence">
|
||||
<t-icon name="chevron-right" size="48rpx" />
|
||||
</view>
|
||||
</view>
|
||||
<!-- 话筒图标和倒计时 -->
|
||||
<view class="microphone-container">
|
||||
<t-icon name="microphone-1" class="microphone {{isRecording ? 'recording' : ''}}" size="100rpx" bind:longpress="handleRecordStart" bind:touchend="handleRecordEnd" bind:touchcancel="handleRecordEnd" />
|
||||
<text wx:if="{{isRecording}}" class="countdown">{{remainingTime}}s</text>
|
||||
<!-- 评分详解按钮 -->
|
||||
<t-button wx:if="{{hasScoreInfo}}" class="score-detail-btn" theme="primary" size="small" variant="outline" bind:tap="handleScoreDetailClick">评分详解</t-button>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 底部评分结果区域 -->
|
||||
<view class="score-section">
|
||||
<block wx:if="{{hasScoreInfo}}">
|
||||
<view class="score-container">
|
||||
<view class="total-score">
|
||||
<view class="circle-progress" style="{{circleProgressStyle}}">
|
||||
<text class="total-score-value">{{totalScore}}</text>
|
||||
<text class="total-score-label">总分</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="score-details">
|
||||
<view class="score-item">
|
||||
<text class="score-label">准确性</text>
|
||||
<view class="score-content">
|
||||
<block wx:if="{{accuracyScore >= 0}}">
|
||||
<view class="progress-bar">
|
||||
<view class="progress-fill" style="width: {{accuracyScore}}%"></view>
|
||||
</view>
|
||||
<text class="score-value">{{accuracyScore}}</text>
|
||||
</block>
|
||||
<text wx:else class="no-score-text">暂无评分</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="score-item">
|
||||
<text class="score-label">完整性</text>
|
||||
<view class="score-content">
|
||||
<block wx:if="{{completenessScore >= 0}}">
|
||||
<view class="progress-bar">
|
||||
<view class="progress-fill" style="width: {{completenessScore}}%"></view>
|
||||
</view>
|
||||
<text class="score-value">{{completenessScore}}</text>
|
||||
</block>
|
||||
<text wx:else class="no-score-text">暂无评分</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="score-item">
|
||||
<text class="score-label">流利度</text>
|
||||
<view class="score-content">
|
||||
<block wx:if="{{fluencyScore >= 0}}">
|
||||
<view class="progress-bar">
|
||||
<view class="progress-fill" style="width: {{fluencyScore}}%"></view>
|
||||
</view>
|
||||
<text class="score-value">{{fluencyScore}}</text>
|
||||
</block>
|
||||
<text wx:else class="no-score-text">暂无评分</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
<view wx:else class="no-score">
|
||||
<text class="no-score-text">暂无评分</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
263
miniprogram/pages/assessment/assessment.wxss
Normal file
263
miniprogram/pages/assessment/assessment.wxss
Normal file
@@ -0,0 +1,263 @@
|
||||
.assessment-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 顶部图片区域 */
|
||||
.image-section {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.assessment-image {
|
||||
display: block;
|
||||
width: 50%;
|
||||
margin: 40rpx auto;
|
||||
border-radius: 30rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
/* 中间例句区域 */
|
||||
.sentence-section {
|
||||
flex: 1;
|
||||
padding: 0 32rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sentence-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin: 40rpx 0;
|
||||
}
|
||||
|
||||
.arrow-btn {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #ffffff;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.arrow-btn:active {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
.arrow-btn.disabled {
|
||||
background: #e0e0e0;
|
||||
box-shadow: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.arrow-btn.disabled t-icon {
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.sentence-content {
|
||||
flex: 1;
|
||||
padding: 0 12rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.sentence-text {
|
||||
font-size: 32rpx;
|
||||
color: #333333;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 16rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.page-indicator {
|
||||
font-size: 24rpx;
|
||||
color: #666666;
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.microphone-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-top: 40rpx;
|
||||
position: relative;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.score-detail-btn {
|
||||
margin-top: 8rpx;
|
||||
min-width: 160rpx;
|
||||
}
|
||||
|
||||
.countdown {
|
||||
font-size: 32rpx;
|
||||
color: #ff3b30;
|
||||
font-weight: bold;
|
||||
margin-top: 16rpx;
|
||||
animation: fadeInOut 1s infinite;
|
||||
}
|
||||
|
||||
@keyframes fadeInOut {
|
||||
0% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
|
||||
.microphone {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
padding: 20rpx;
|
||||
background: #007AFF;
|
||||
border-radius: 50%;
|
||||
color: #ffffff;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 122, 255, 0.3);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.microphone.recording {
|
||||
transform: scale(1.1);
|
||||
background: #ff3b30;
|
||||
box-shadow: 0 4rpx 16rpx rgba(255, 59, 48, 0.3);
|
||||
animation: pulse 1.5s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
100% {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
/* 底部评分结果区域 */
|
||||
.score-section {
|
||||
padding: 32rpx 24rpx;
|
||||
background: #ffffff;
|
||||
border-top-left-radius: 24rpx;
|
||||
border-top-right-radius: 24rpx;
|
||||
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.score-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 32rpx;
|
||||
}
|
||||
|
||||
.total-score {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.circle-progress {
|
||||
width: 180rpx;
|
||||
height: 180rpx;
|
||||
border-radius: 50%;
|
||||
background: #f0f0f0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.circle-progress::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 150rpx;
|
||||
height: 150rpx;
|
||||
border-radius: 50%;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.total-score-value {
|
||||
font-size: 48rpx;
|
||||
color: #007AFF;
|
||||
font-weight: bold;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.total-score-label {
|
||||
font-size: 24rpx;
|
||||
color: #666666;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
.score-details {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.score-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.score-label {
|
||||
font-size: 22rpx;
|
||||
color: #666666;
|
||||
width: 80rpx;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.score-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.score-value {
|
||||
font-size: 24rpx;
|
||||
color: #007AFF;
|
||||
font-weight: bold;
|
||||
min-width: 48rpx;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.score-item .no-score-text {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
flex: 1;
|
||||
height: 6rpx;
|
||||
background: #f0f0f0;
|
||||
border-radius: 3rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: #007AFF;
|
||||
border-radius: 3rpx;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.no-score {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 300rpx;
|
||||
}
|
||||
|
||||
.no-score-text {
|
||||
font-size: 32rpx;
|
||||
color: #999;
|
||||
}
|
||||
12
miniprogram/pages/result_show/result_show.json
Normal file
12
miniprogram/pages/result_show/result_show.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"navigationBarTitleText": "识别结果",
|
||||
"navigationBarTextStyle": "black",
|
||||
"navigationBarBackgroundColor": "#ffffff",
|
||||
"backgroundColor": "#f8f9fa",
|
||||
"backgroundTextStyle": "light",
|
||||
"enablePullDownRefresh": false,
|
||||
"onReachBottomDistance": 50,
|
||||
"usingComponents": {
|
||||
"t-icon": "tdesign-miniprogram/icon/icon"
|
||||
}
|
||||
}
|
||||
332
miniprogram/pages/result_show/result_show.ts
Normal file
332
miniprogram/pages/result_show/result_show.ts
Normal file
@@ -0,0 +1,332 @@
|
||||
// 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 result = await apiManager.getRecognizeImage(imageId)
|
||||
|
||||
// 设置识别结果
|
||||
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: ''
|
||||
})
|
||||
console.log('--lisa-displayContent', this.data.displayContent)
|
||||
} 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;
|
||||
}
|
||||
})
|
||||
133
miniprogram/pages/result_show/result_show.wxml
Normal file
133
miniprogram/pages/result_show/result_show.wxml
Normal file
@@ -0,0 +1,133 @@
|
||||
<!-- 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>
|
||||
838
miniprogram/pages/result_show/result_show.wxss
Normal file
838
miniprogram/pages/result_show/result_show.wxss
Normal file
@@ -0,0 +1,838 @@
|
||||
/* 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;
|
||||
}
|
||||
@@ -7,8 +7,19 @@ import { FILE_BASE_URL } from '../../utils/config'
|
||||
|
||||
const app = getApp<IAppOption>()
|
||||
|
||||
type IDayType = 'morning' | 'afternoon' | 'night'
|
||||
const DayTypeMap: Record<IDayType, string> = {
|
||||
morning: '早上好',
|
||||
afternoon: '下午好',
|
||||
night: '晚上好'
|
||||
}
|
||||
|
||||
Page({
|
||||
data: {
|
||||
move_camera_pos_to: 'top' as 'top' | 'bottom',
|
||||
DayTypeMap,
|
||||
current_date: '',
|
||||
day_type: 'morning' as IDayType,
|
||||
isLoggedIn: false,
|
||||
showLoginView: false,
|
||||
userInfo: null as IUserInfo | null,
|
||||
@@ -32,6 +43,7 @@ Page({
|
||||
// 只有登录成功后才加载历史数据
|
||||
if (this.data.isLoggedIn) {
|
||||
this.loadDailySummary()
|
||||
this.checkDayType()
|
||||
}
|
||||
}).catch((error) => {
|
||||
console.error('登录检查失败:', error)
|
||||
@@ -44,6 +56,18 @@ Page({
|
||||
this.setData({ isProcessing: false }) // 清理处理状态
|
||||
},
|
||||
|
||||
|
||||
checkDayType() {
|
||||
const currentHour = new Date().getHours();
|
||||
const currentDate = new Date().toLocaleDateString('zh-CN');
|
||||
const dayType = currentHour >= 6 && currentHour < 12 ? 'morning' :
|
||||
currentHour >= 12 && currentHour < 18 ? 'afternoon' : 'night';
|
||||
this.setData({
|
||||
current_date: currentDate,
|
||||
day_type: dayType as IDayType
|
||||
});
|
||||
},
|
||||
|
||||
// 加载每日摘要数据
|
||||
async loadDailySummary(page: number = 1) {
|
||||
try {
|
||||
@@ -53,9 +77,10 @@ Page({
|
||||
}
|
||||
|
||||
const result = await apiManager.getDailySummary(page, this.data.size);
|
||||
const todaySummary = await apiManager.getTodaySummary(page, this.data.size);
|
||||
|
||||
// 处理数据,按年份分组
|
||||
const processedItems = result.items.map(item => ({
|
||||
const processedItems = [...result.items, ...todaySummary.items].map(item => ({
|
||||
...item,
|
||||
images: item.image_ids && item.thumbnail_ids ?
|
||||
item.image_ids.map((imageId: string, index: number) => ({
|
||||
@@ -105,6 +130,7 @@ Page({
|
||||
hasMore: newData.length < result.total,
|
||||
isLoading: false,
|
||||
});
|
||||
console.log('---lisa-groupedHistory', this.data.groupedHistory)
|
||||
} catch (error) {
|
||||
console.error('加载每日摘要失败:', error);
|
||||
this.setData({ isLoading: false });
|
||||
@@ -123,6 +149,19 @@ Page({
|
||||
}
|
||||
},
|
||||
|
||||
onPageScroll(e: any) {
|
||||
const scrollTop = e.scrollTop;
|
||||
if (scrollTop >= 30) {
|
||||
this.setData({
|
||||
move_camera_pos_to: 'top',
|
||||
})
|
||||
} else {
|
||||
this.setData({
|
||||
move_camera_pos_to: 'bottom',
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// 下拉刷新
|
||||
onPullDownRefresh() {
|
||||
this.setData({
|
||||
@@ -137,11 +176,29 @@ Page({
|
||||
onImageTap(e: any) {
|
||||
const { imageId } = e.currentTarget.dataset;
|
||||
if (imageId) {
|
||||
console.log('图片点击,image_id:', imageId);
|
||||
// 未来实现具体功能
|
||||
wx.navigateTo({
|
||||
url: `/pages/result_show/result_show?imageId=${imageId}`
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
onImageCardTap(e: any) {
|
||||
const { imageItems } = e.currentTarget.dataset;
|
||||
if (!imageItems?.images?.length) return;
|
||||
|
||||
const items = imageItems.images.map((item: any) => ({
|
||||
image_id: item.image_id,
|
||||
image: item.thumbnail_url,
|
||||
}));
|
||||
ActionSheet.show({
|
||||
theme: ActionSheetTheme.Grid,
|
||||
selector: '#t-images-sheet',
|
||||
context: this,
|
||||
items,
|
||||
cancelText: '取消'
|
||||
});
|
||||
},
|
||||
|
||||
// 检查登录状态
|
||||
async checkLoginStatus() {
|
||||
try {
|
||||
@@ -215,6 +272,18 @@ Page({
|
||||
}
|
||||
},
|
||||
|
||||
handleImageSelected(e: any) {
|
||||
console.log('用户选择:', e)
|
||||
if (e.detail.selected === 'cancel') return;
|
||||
|
||||
const { image_id } = e.detail.selected
|
||||
if (image_id) {
|
||||
wx.navigateTo({
|
||||
url: `/pages/result_show/result_show?imageId=${image_id}`
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// 拍照
|
||||
async handleTakePhoto() {
|
||||
try {
|
||||
|
||||
@@ -1,19 +1,13 @@
|
||||
<!--upload.wxml - 主功能页面-->
|
||||
<view class="upload-container">
|
||||
<!-- upload.wxml - 主功能页面 -->
|
||||
<view class="{{['upload-container', day_type]}}">
|
||||
<!-- 登录检查界面 -->
|
||||
<view wx:if="{{showLoginView}}" class="login-check-view">
|
||||
<view wx:if="{{showLoginView}}">
|
||||
<view class="login-prompt card">
|
||||
<text class="prompt-title">请先登录</text>
|
||||
<text class="prompt-desc">使用图片识别功能需要先登录</text>
|
||||
<button
|
||||
class="goto-login-btn"
|
||||
bindtap="checkLoginStatus"
|
||||
>
|
||||
去登录
|
||||
</button>
|
||||
<button class="goto-login-btn" bindtap="checkLoginStatus">去登录</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 主功能界面 -->
|
||||
<view wx:else class="main-content">
|
||||
<!-- 欢迎区域 -->
|
||||
@@ -25,43 +19,66 @@
|
||||
<text class="welcome-text">选择一张图片开始识别</text>
|
||||
</view>
|
||||
</view> -->
|
||||
|
||||
<!-- 功能按钮区域 -->
|
||||
<view class="action-section">
|
||||
<view class="action-buttons">
|
||||
<t-icon name="image-add" size="140rpx" bind:click="handleImageSelect" />
|
||||
<t-action-sheet id="t-action-sheet" bind:selected="handleSelected" />
|
||||
<!-- 欢迎区域 -->
|
||||
<view class="sunny-wrap" wx:if="{{ day_type !== 'night' }}">
|
||||
<t-icon name="sunny-filled" class="sunny-icon" size="220rpx" />
|
||||
<view class="face">•ᴗ•</view>
|
||||
</view>
|
||||
<view wx:else>
|
||||
<view class="moon-wrap">
|
||||
<t-icon name="moon-filled" class="moon-icon" size="160rpx" />
|
||||
</view>
|
||||
<div class="star-icon">★</div>
|
||||
<div class="star-icon-2">★</div>
|
||||
</view>
|
||||
<t-icon name="cloud-filled" class="cloud-icon" size="220rpx" />
|
||||
<view class="feature-section">
|
||||
<view class="date">{{ current_date }}</view>
|
||||
<view class="hello">{{DayTypeMap[day_type]}}</view>
|
||||
<view class="begin-text">用一个新单词, 开启美好的一天</view>
|
||||
<!-- 功能按钮区域 -->
|
||||
<view class="{{['action-section welcome-card', move_camera_pos_to]}}">
|
||||
<view class="action-buttons">
|
||||
<t-icon name="image-add" size="140rpx" bind:click="handleImageSelect" />
|
||||
<t-action-sheet id="t-action-sheet" bind:selected="handleSelected" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="history-wrap">
|
||||
<view class="history-card" wx:for="{{groupedHistory}}" wx:key="year">
|
||||
<p class="history-card-title">{{item.year}}年</p>
|
||||
<view class="history-card-list">
|
||||
<view class="history-card-item" catch:tap="onImageCardTap" data-image-items="{{historyItem}}" wx:for="{{item.items}}" wx:for-item="historyItem" wx:for-index="index" wx:key="index">
|
||||
<p class="month-day">{{historyItem.monthDay}}</p>
|
||||
<view class="images-list">
|
||||
<view wx:for="{{historyItem.images}}" wx:for-item="image" wx:key="image_id" catch:tap="onImageTap" data-image-id="{{image.image_id}}">
|
||||
<image class="image-item" src="{{image.thumbnail_url}}" mode="aspectFill" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<t-action-sheet id="t-images-sheet" bind:selected="handleImageSelected" />
|
||||
</view>
|
||||
<!-- 历史记录区域 -->
|
||||
<view>
|
||||
<view wx:for="{{groupedHistory}}" wx:key="year" class="history-section">
|
||||
<!-- 只有不是当前年份才显示年份标题 -->
|
||||
<view wx:if="{{!item.isCurrentYear}}" class="history-year">{{item.year}}</view>
|
||||
<!-- <view>
|
||||
<view wx:for="{{groupedHistory}}" wx:key="year" class="history-section"> -->
|
||||
<!-- 只有不是当前年份才显示年份标题 -->
|
||||
<!-- <view wx:if="{{!item.isCurrentYear}}" class="history-year">{{item.year}}</view>
|
||||
<t-steps layout="vertical" readonly theme="dot" current="{{groupedHistory.length-1}}">
|
||||
<t-step-item wx:for="{{item.items}}" wx:for-item="historyItem" wx:for-index="index" wx:key="index">
|
||||
<view slot="title">{{historyItem.monthDay}}</view>
|
||||
<view slot="content">
|
||||
<t-grid class="block" column="{{5}}">
|
||||
<t-grid-item
|
||||
wx:for="{{historyItem.images}}"
|
||||
wx:for-item="image"
|
||||
wx:key="image_id"
|
||||
t-class-image="image"
|
||||
image="{{image.thumbnail_url}}"
|
||||
data-image-id="{{image.image_id}}"
|
||||
bindtap="onImageTap"
|
||||
/>
|
||||
<t-grid-item wx:for="{{historyItem.images}}" wx:for-item="image" wx:key="image_id" t-class-image="image" image="{{image.thumbnail_url}}" data-image-id="{{image.image_id}}" bindtap="onImageTap" />
|
||||
</t-grid>
|
||||
</view>
|
||||
</t-step-item>
|
||||
</t-steps>
|
||||
</view>
|
||||
</view>
|
||||
</view> -->
|
||||
<!-- 仅在加载更多时显示骨架屏 -->
|
||||
<t-skeleton wx:if="{{isLoading && page >= 1}}" theme="paragraph" animation="gradient" loading="{{true}}"></t-skeleton>
|
||||
|
||||
<!-- 加载状态显示 -->
|
||||
<!-- <view wx:if="{{isProcessing}}" class="processing-section">
|
||||
<view class="processing-card">
|
||||
@@ -69,7 +86,6 @@
|
||||
<text class="processing-text">正在跳转到识别页面...</text>
|
||||
</view>
|
||||
</view> -->
|
||||
|
||||
<!-- 使用提示 -->
|
||||
<!-- <view class="tips-section">
|
||||
<view class="tips-card">
|
||||
@@ -81,5 +97,4 @@
|
||||
</view>
|
||||
</view> -->
|
||||
</view>
|
||||
|
||||
</view>
|
||||
@@ -1,15 +1,24 @@
|
||||
/* upload.wxss - 主功能页面样式(扁平化设计) */
|
||||
|
||||
.upload-container.morning,
|
||||
.upload-container.afternoon {
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
.morning .history-wrap,
|
||||
.afternoon .history-wrap {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
|
||||
.upload-container {
|
||||
min-height: 100vh;
|
||||
background-color: #f5f5f5;
|
||||
background-color: #010321e0;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 主要内容区域 */
|
||||
.main-content {
|
||||
padding: 32rpx;
|
||||
/* min-height: calc(100vh - 64rpx); */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -66,7 +75,172 @@
|
||||
line-height: 88rpx;
|
||||
}
|
||||
|
||||
/* 历史记录区域 */
|
||||
.history-wrap {
|
||||
padding: 40rpx 40rpx;
|
||||
color: #ffffff;
|
||||
}
|
||||
.history-card-list {
|
||||
width: 100%;
|
||||
}
|
||||
.history-card-item {
|
||||
padding: 40rpx;
|
||||
background-color: #ffffffb5;
|
||||
border-radius: 30rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.1);
|
||||
margin: 40rpx 0;
|
||||
color: #333333;
|
||||
}
|
||||
.images-list {
|
||||
margin-top: 20rpx;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
overflow: hidden;
|
||||
gap: 12rpx;
|
||||
}
|
||||
.image-item {
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
border-radius: 30rpx;
|
||||
flex-shrink: 0;
|
||||
box-shadow: 0 8rpx 16rpx 0 rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
/* 欢迎区域 */
|
||||
.moon-wrap {
|
||||
opacity: 0;
|
||||
color: #ffef22c7;
|
||||
animation: slideInFromTopRight 3s forwards ease-in-out, fadeIn 3s forwards linear;
|
||||
transform: translate(50%, -50%);
|
||||
}
|
||||
.moon-icon {
|
||||
top: 100rpx;
|
||||
opacity: 0.9;
|
||||
}
|
||||
.star-icon {
|
||||
position: fixed;
|
||||
top: 140px;
|
||||
right: 40px;
|
||||
font-size: 50rpx;
|
||||
color: #FFD700;
|
||||
filter: drop-shadow(0 0 10px rgba(255, 215, 0, 0.7));
|
||||
animation: scaleStar1 3s infinite ease-in-out;
|
||||
}
|
||||
.star-icon-2 {
|
||||
position: fixed;
|
||||
top: 100px;
|
||||
left: 40px;
|
||||
font-size: 40rpx;
|
||||
color: #FFD700;
|
||||
filter: drop-shadow(0 0 10px rgba(255, 215, 0, 0.7));
|
||||
animation: scaleStar2 3s infinite ease-in-out;
|
||||
}
|
||||
@keyframes scaleStar1 {
|
||||
0%,
|
||||
100% {
|
||||
transform: scale(0.8);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
}
|
||||
@keyframes scaleStar2 {
|
||||
0%,
|
||||
100% {
|
||||
transform: scale(1.2);
|
||||
}
|
||||
50% {
|
||||
transform: scale(0.8);
|
||||
}
|
||||
}
|
||||
.sunny-wrap {
|
||||
/* position: absolute;
|
||||
top: -100px;
|
||||
right: -100px;
|
||||
color: #ffef22c7;
|
||||
animation: appear 3s ease-out forwards; */
|
||||
|
||||
opacity: 0;
|
||||
color: #f6c02f;
|
||||
animation: slideInFromTopRight 3s forwards ease-in-out, fadeIn 3s forwards linear;
|
||||
transform: translate(50%, -50%);
|
||||
}
|
||||
.sunny-icon {
|
||||
opacity: 0.9;
|
||||
animation: rotate 50s linear infinite 3s; /* 3秒后开始旋转 */
|
||||
transform-origin: center center;
|
||||
}
|
||||
.face {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%); /* 垂直居中 */
|
||||
font-size: 50rpx;
|
||||
font-weight: 500;
|
||||
color:black;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
@keyframes slideInFromTopRight {
|
||||
to {
|
||||
transform: translate(30%, 40%);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes appear {
|
||||
0% {
|
||||
top: -100px;
|
||||
right: -100px;
|
||||
opacity: 0;
|
||||
transform: scale(0.2);
|
||||
}
|
||||
70% {
|
||||
opacity: 0.6;
|
||||
transform: scale(0.8);
|
||||
}
|
||||
100% {
|
||||
top: 20px;
|
||||
right: 60px;
|
||||
opacity: 0.8;
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
0% {
|
||||
transform: translate(-50%, -50%) rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: translate(-50%, -50%) rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.cloud-icon {
|
||||
position: fixed;
|
||||
bottom: 440rpx;
|
||||
left: -100rpx;
|
||||
color: #fefefe;
|
||||
/* opacity: 0.8; */
|
||||
opacity: 0.7;
|
||||
animation: moveLeftRight 4s infinite alternate linear;
|
||||
}
|
||||
|
||||
@keyframes moveLeftRight {
|
||||
0% {
|
||||
transform: translateX(-20rpx);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(20rpx);
|
||||
}
|
||||
}
|
||||
|
||||
.welcome-section {
|
||||
margin-bottom: 40rpx;
|
||||
}
|
||||
@@ -74,9 +248,10 @@
|
||||
.welcome-card {
|
||||
background: #ffffff;
|
||||
padding: 32rpx;
|
||||
border-radius: 16rpx;
|
||||
border-radius: 50%;
|
||||
text-align: center;
|
||||
box-shadow: 0 2rpx 16rpx rgba(0, 0, 0, 0.05);
|
||||
border: 4rpx dashed #e91e637a;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
@@ -106,13 +281,40 @@
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
/* 功能描述区域 */
|
||||
.feature-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
font-size: 28rpx;
|
||||
color: #666666;
|
||||
margin-top: 100rpx;
|
||||
}
|
||||
.hello {
|
||||
margin-top: 80rpx;
|
||||
font-size: 50rpx;
|
||||
/* color: black; */
|
||||
}
|
||||
.begin-text {
|
||||
margin-top: 10rpx;
|
||||
font-size: 36rpx;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
/* 功能按钮区域 */
|
||||
.action-section {
|
||||
flex: 1;
|
||||
width: 300rpx;
|
||||
height: 300rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 40rpx;
|
||||
margin-top: 100rpx;
|
||||
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.action-section .top {
|
||||
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
@@ -123,6 +325,7 @@
|
||||
gap: 32rpx;
|
||||
width: 100%;
|
||||
max-width: 400rpx;
|
||||
color: #9c27b04f;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
|
||||
105
miniprogram/types/app.ts
Normal file
105
miniprogram/types/app.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
// app.ts 的类型定义
|
||||
import { ApiManager } from '../utils/api'
|
||||
|
||||
// 用户信息接口
|
||||
export interface IUserInfo {
|
||||
id: string
|
||||
nickname?: string
|
||||
avatar_url?: string
|
||||
gender?: number
|
||||
country?: string
|
||||
province?: string
|
||||
city?: string
|
||||
language?: string
|
||||
}
|
||||
|
||||
// 登录响应接口
|
||||
export interface ILoginResponse {
|
||||
access_token: string
|
||||
access_token_expire_time: string
|
||||
session_uuid: string
|
||||
dict_level?: string
|
||||
}
|
||||
|
||||
// API响应接口
|
||||
export interface IApiResponse<T> {
|
||||
code: number
|
||||
message?: string
|
||||
msg?: string
|
||||
data: T
|
||||
}
|
||||
|
||||
// 识别结果接口
|
||||
export interface IRecognitionResult {
|
||||
text: string
|
||||
words: Array<{
|
||||
word: string
|
||||
start: number
|
||||
end: number
|
||||
}>
|
||||
}
|
||||
|
||||
// 单词详情接口
|
||||
export interface ExtendedWordDetail {
|
||||
word: string
|
||||
phonetic: string
|
||||
definition: string
|
||||
translation: string
|
||||
pos: string
|
||||
collins: number
|
||||
oxford: number
|
||||
tag: string
|
||||
bnc: number
|
||||
frq: number
|
||||
exchange: Record<string, string[]>
|
||||
detail: string
|
||||
audio: string
|
||||
}
|
||||
|
||||
// 审核历史记录接口
|
||||
export interface IAuditHistoryResponse {
|
||||
total: number
|
||||
items: Array<{
|
||||
id: string
|
||||
image_url: string
|
||||
text: string
|
||||
created_at: string
|
||||
}>
|
||||
}
|
||||
|
||||
// 每日总结接口
|
||||
export interface IDailySummaryResponse {
|
||||
total: number
|
||||
items: Array<{
|
||||
date: string
|
||||
count: number
|
||||
images: Array<{
|
||||
id: string
|
||||
url: string
|
||||
}>
|
||||
}>
|
||||
}
|
||||
|
||||
// 全局数据接口
|
||||
export interface IGlobalData {
|
||||
isLoggedIn: boolean
|
||||
userInfo?: IUserInfo
|
||||
token?: string
|
||||
dictLevel?: string
|
||||
apiManager: ApiManager
|
||||
}
|
||||
|
||||
// 小程序选项接口
|
||||
export interface IAppOption {
|
||||
globalData: IGlobalData
|
||||
userInfoReadyCallback?: WechatMiniprogram.GetUserInfoSuccessCallback
|
||||
initLoginStatus: () => void
|
||||
updateLoginStatus: (loginData: {
|
||||
access_token: string
|
||||
access_token_expire_time: string
|
||||
session_uuid: string
|
||||
userInfo?: IUserInfo
|
||||
dict_level?: string
|
||||
}) => void
|
||||
clearLoginData: () => void
|
||||
}
|
||||
@@ -571,7 +571,7 @@ class ApiManager {
|
||||
}
|
||||
|
||||
// 上传文件(第一步:上传文件获取ID)
|
||||
private async uploadFile(filePath: string, retryCount: number = 0): Promise<string> {
|
||||
async uploadFile(filePath: string, retryCount: number = 0): Promise<string> {
|
||||
const maxRetries = 1 // 最多重试1次
|
||||
|
||||
return new Promise(async (resolve, reject) => {
|
||||
@@ -722,6 +722,20 @@ class ApiManager {
|
||||
return response.data
|
||||
}
|
||||
|
||||
// 获取图片识别结果
|
||||
async getRecognizeImage(fileId: string): Promise<IRecognitionResult & { file_id: string }> {
|
||||
|
||||
const app = getApp<IAppOption>()
|
||||
const dictLevel = app.globalData.dictLevel || wx.getStorageSync('dictLevel') || 'PRIMARY'
|
||||
|
||||
const response = await this.request<IRecognitionResult>(`/api/v1/image/${fileId}`, 'GET', {
|
||||
dict_level: dictLevel
|
||||
})
|
||||
|
||||
console.log('图片识别结果获取成功:', response.data)
|
||||
return response.data
|
||||
}
|
||||
|
||||
// 上传图片并识别(对外接口,整合两个步骤)
|
||||
async uploadImage(filePath: string, type: string = 'word'): Promise<IRecognitionResult> {
|
||||
try {
|
||||
@@ -1210,39 +1224,144 @@ class ApiManager {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async getTodaySummary(page: number = 1, size: number = 15): Promise<IDailySummaryResponse> {
|
||||
try {
|
||||
console.log('开始获取今日总结数据', { page, size });
|
||||
const response = await this.request<IDailySummaryResponse>(`/api/v1/audit/today_summary?page=${page}&size=${size}`);
|
||||
console.log('获取今日总结数据成功:', response.data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('获取今日总结数据失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取口语评估结果
|
||||
async getAssessmentResult(fileId: string, imageTextId: string): Promise<{
|
||||
file_id: string
|
||||
image_text_id: string
|
||||
assessment_result: {
|
||||
assessment: {
|
||||
code: number
|
||||
final: number
|
||||
message: string
|
||||
voice_id: string
|
||||
result: {
|
||||
RefTextId: number,
|
||||
SentenceId: number,
|
||||
PronAccuracy: number,
|
||||
PronFluency: number,
|
||||
PronCompletion: number,
|
||||
SuggestedScore: number
|
||||
Words: any
|
||||
}
|
||||
}
|
||||
}
|
||||
}> {
|
||||
try {
|
||||
console.log('开始获取口语评估结果', { fileId });
|
||||
const response = await this.request<{
|
||||
file_id: string
|
||||
image_text_id: string
|
||||
assessment_result: {
|
||||
assessment: {
|
||||
code: number
|
||||
final: number
|
||||
message: string
|
||||
voice_id: string
|
||||
result: {
|
||||
RefTextId: number,
|
||||
SentenceId: number,
|
||||
PronAccuracy: number,
|
||||
PronFluency: number,
|
||||
PronCompletion: number,
|
||||
SuggestedScore: number
|
||||
Words: any
|
||||
}
|
||||
}
|
||||
}
|
||||
}>(`/api/v1/recording/assessment`, 'POST', {
|
||||
file_id: fileId,
|
||||
image_text_id: imageTextId
|
||||
})
|
||||
|
||||
console.log('获取口语评估结果成功:', response.data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('获取口语评估结果失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取图片文本和评分信息
|
||||
async getImageTextInit(imageId: string): Promise<{
|
||||
image_file_id: string,
|
||||
assessments: Array<{
|
||||
id: string
|
||||
content: string,
|
||||
details: {
|
||||
assessment: {
|
||||
code: number
|
||||
final: number
|
||||
message: string
|
||||
voice_id: string
|
||||
result: {
|
||||
RefTextId: number,
|
||||
SentenceId: number,
|
||||
PronAccuracy: number,
|
||||
PronFluency: number,
|
||||
PronCompletion: number,
|
||||
SuggestedScore: number,
|
||||
Words: any
|
||||
}
|
||||
}
|
||||
} | null
|
||||
}>
|
||||
}> {
|
||||
try {
|
||||
console.log('开始获取图片文本和评分信息', { imageId });
|
||||
const app = getApp<IAppOption>()
|
||||
const dictLevel = app.globalData.dictLevel || wx.getStorageSync('dictLevel') || 'PRIMARY'
|
||||
const response = await this.request<{
|
||||
image_file_id: string,
|
||||
assessments: Array<{
|
||||
id: string
|
||||
content: string,
|
||||
details: {
|
||||
assessment: {
|
||||
code: number
|
||||
final: number
|
||||
message: string
|
||||
voice_id: string
|
||||
result: {
|
||||
RefTextId: number,
|
||||
SentenceId: number,
|
||||
PronAccuracy: number,
|
||||
PronFluency: number,
|
||||
PronCompletion: number,
|
||||
SuggestedScore: number,
|
||||
Words: any
|
||||
}
|
||||
}
|
||||
} | null
|
||||
}>
|
||||
}>(`/api/v1/image_text/init`, 'POST', {
|
||||
dict_level: dictLevel,
|
||||
image_id: imageId
|
||||
})
|
||||
|
||||
console.log('获取图片文本和评分信息成功:', response.data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('获取图片文本和评分信息失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 导出单例
|
||||
const apiManager = new ApiManager()
|
||||
export default apiManager
|
||||
export { ApiManager }
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export { ApiManager }
|
||||
@@ -6,7 +6,6 @@
|
||||
"urlCheck": false,
|
||||
"coverView": false,
|
||||
"lazyloadPlaceholderEnable": false,
|
||||
"skylineRenderEnable": false,
|
||||
"preloadBackgroundData": false,
|
||||
"autoAudits": false,
|
||||
"useApiHook": false,
|
||||
@@ -20,5 +19,38 @@
|
||||
"ignoreDevUnusedFiles": true
|
||||
},
|
||||
"libVersion": "3.9.3",
|
||||
"condition": {}
|
||||
"condition": {
|
||||
"miniprogram": {
|
||||
"list": [
|
||||
{
|
||||
"name": "",
|
||||
"pathName": "pages/result/result",
|
||||
"query": "imagePath=http://tmp/Jg3M9pJYPJdd9ab131e406bc48acb9ed4069809fd559.png",
|
||||
"launchMode": "default",
|
||||
"scene": null
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"pathName": "pages/result_show/result_show",
|
||||
"query": "imageId=2086560419278880768",
|
||||
"launchMode": "default",
|
||||
"scene": null
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"pathName": "pages/assessment/assessment",
|
||||
"query": "imageId=2086560419278880768&index=2",
|
||||
"launchMode": "default",
|
||||
"scene": null
|
||||
},
|
||||
{
|
||||
"name": "",
|
||||
"pathName": "pages/assessment/assessment",
|
||||
"query": "imageId=2086529549784449024&index=1",
|
||||
"launchMode": "default",
|
||||
"scene": null
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user