Merge pull request 'lisa-feat' (#3) from lisa-feat into main
Reviewed-on: http://gitea.xhzone.cn/felix/miniprogram-1/pulls/3
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
"enablePullDownRefresh": false,
|
||||
"onReachBottomDistance": 50,
|
||||
"usingComponents": {
|
||||
"t-slider": "tdesign-miniprogram/slider/slider",
|
||||
"t-icon": "tdesign-miniprogram/icon/icon"
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,17 @@ interface IPageData {
|
||||
hasScoreInfo: boolean // 是否有评分信息
|
||||
words: string[] // 句子内容的单词数组
|
||||
ipas: string[] // 音标的单词数组
|
||||
isScoreExpanded: boolean // 评分区域是否展开
|
||||
wordScores: Array<{ // 单词评分数组
|
||||
word: string
|
||||
pronAccuracy: number
|
||||
pronFluency: number
|
||||
}>
|
||||
standardAudioMap: { [key: string]: string } // 标准语音文件ID映射
|
||||
isPlaying: boolean // 是否正在播放音频
|
||||
audioDuration: number // 音频总时长
|
||||
currentTime: number // 当前播放时间
|
||||
sliderValue: number // 进度条值(0-100)
|
||||
}
|
||||
|
||||
type IPageMethods = {
|
||||
@@ -31,11 +42,19 @@ type IPageMethods = {
|
||||
handleRecordStart: () => void
|
||||
handleRecordEnd: () => void
|
||||
updateWordsAndIpas: (content: string, ipa: string) => void
|
||||
toggleScoreSection: () => void
|
||||
handleImagePreview: () => void
|
||||
getStandardVoice: (sentenceId: string) => Promise<void>
|
||||
playStandardVoice: () => void
|
||||
resetAudioState: () => void
|
||||
handleSliderChange: (e: any) => void
|
||||
formatTime: (seconds: number) => string
|
||||
}
|
||||
|
||||
interface IPageInstance extends IPageMethods {
|
||||
recordTimer?: number
|
||||
data: IPageData
|
||||
audioContext?: WechatMiniprogram.InnerAudioContext
|
||||
}
|
||||
|
||||
Page<IPageData, IPageInstance>({
|
||||
@@ -56,6 +75,20 @@ Page<IPageData, IPageInstance>({
|
||||
hasScoreInfo: false, // 是否有评分信息
|
||||
words: [], // 句子内容的单词数组
|
||||
ipas: [], // 音标的单词数组
|
||||
isScoreExpanded: false, // 评分区域是否展开
|
||||
wordScores: [], // 单词评分数组
|
||||
standardAudioMap: {}, // 标准语音文件ID映射
|
||||
isPlaying: false, // 是否正在播放音频
|
||||
audioDuration: 0, // 音频总时长
|
||||
currentTime: 0, // 当前播放时间
|
||||
sliderValue: 0 // 进度条值(0-100)
|
||||
},
|
||||
|
||||
// 切换评分区域展开状态
|
||||
toggleScoreSection() {
|
||||
this.setData({
|
||||
isScoreExpanded: !this.data.isScoreExpanded
|
||||
})
|
||||
},
|
||||
|
||||
// 更新单词和音标数组
|
||||
@@ -76,10 +109,26 @@ Page<IPageData, IPageInstance>({
|
||||
this.setData({ circleProgressStyle: style })
|
||||
},
|
||||
|
||||
// 重置音频播放状态
|
||||
resetAudioState() {
|
||||
if (this.audioContext) {
|
||||
this.audioContext.stop()
|
||||
}
|
||||
this.setData({
|
||||
isPlaying: false,
|
||||
currentTime: 0,
|
||||
sliderValue: 0,
|
||||
audioDuration: 0
|
||||
})
|
||||
},
|
||||
|
||||
// 切换到上一个例句
|
||||
prevSentence() {
|
||||
const { currentIndex, sentences } = this.data
|
||||
if (currentIndex <= 0) return;
|
||||
|
||||
// 重置音频播放状态
|
||||
this.resetAudioState()
|
||||
if (currentIndex > 0) {
|
||||
const currentSentence = sentences[currentIndex - 1]
|
||||
const assessmentResult = currentSentence?.details?.assessment?.result
|
||||
@@ -91,6 +140,13 @@ Page<IPageData, IPageInstance>({
|
||||
// 检查是否所有评分都为负数
|
||||
const allNegative = [suggestedScore, pronAccuracy, pronCompletion, pronFluency].every(score => score < 0)
|
||||
|
||||
// 处理单词评分数据
|
||||
const wordScores = assessmentResult?.Words?.map((word: any) => ({
|
||||
word: word.Word,
|
||||
pronAccuracy: Number(word.PronAccuracy.toFixed(2)),
|
||||
pronFluency: Number(word.PronFluency.toFixed(2))
|
||||
})) || []
|
||||
|
||||
this.setData({
|
||||
currentIndex: currentIndex - 1,
|
||||
currentSentence: currentSentence,
|
||||
@@ -98,9 +154,14 @@ Page<IPageData, IPageInstance>({
|
||||
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
|
||||
fluencyScore: pronFluency >= 0 ? Number((pronFluency * 100).toFixed(2)) : 0,
|
||||
wordScores
|
||||
})
|
||||
this.updateWordsAndIpas(currentSentence.content, currentSentence.ipa)
|
||||
// 获取当前例句的标准语音
|
||||
if (currentSentence.id) {
|
||||
this.getStandardVoice(currentSentence.id)
|
||||
}
|
||||
this.updateCircleProgress()
|
||||
|
||||
}
|
||||
@@ -110,6 +171,9 @@ Page<IPageData, IPageInstance>({
|
||||
nextSentence() {
|
||||
const { currentIndex, sentences } = this.data
|
||||
if (currentIndex >= sentences.length - 1) return;
|
||||
|
||||
// 重置音频播放状态
|
||||
this.resetAudioState()
|
||||
if (currentIndex < sentences.length - 1) {
|
||||
const index = currentIndex + 1
|
||||
const currentSentence = sentences[index]
|
||||
@@ -122,6 +186,13 @@ Page<IPageData, IPageInstance>({
|
||||
// 检查是否所有评分都为负数
|
||||
const allNegative = [suggestedScore, pronAccuracy, pronCompletion, pronFluency].every(score => score < 0)
|
||||
|
||||
// 处理单词评分数据
|
||||
const wordScores = assessmentResult?.Words?.map((word: any) => ({
|
||||
word: word.Word,
|
||||
pronAccuracy: Number(word.PronAccuracy.toFixed(2)),
|
||||
pronFluency: Number(word.PronFluency.toFixed(2))
|
||||
})) || []
|
||||
|
||||
this.setData({
|
||||
currentIndex: index,
|
||||
currentSentence: currentSentence,
|
||||
@@ -129,9 +200,14 @@ Page<IPageData, IPageInstance>({
|
||||
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
|
||||
fluencyScore: pronFluency >= 0 ? Number((pronFluency * 100).toFixed(2)) : 0,
|
||||
wordScores
|
||||
})
|
||||
this.updateWordsAndIpas(currentSentence.content, currentSentence.ipa)
|
||||
// 获取当前例句的标准语音
|
||||
if (currentSentence.id) {
|
||||
this.getStandardVoice(currentSentence.id)
|
||||
}
|
||||
this.updateCircleProgress()
|
||||
}
|
||||
},
|
||||
@@ -211,6 +287,116 @@ Page<IPageData, IPageInstance>({
|
||||
})
|
||||
},
|
||||
|
||||
// 获取标准语音
|
||||
async getStandardVoice(sentenceId: string) {
|
||||
try {
|
||||
// 如果已经获取过该例句的标准语音,直接返回
|
||||
if (this.data.standardAudioMap[sentenceId]) return
|
||||
|
||||
const id = await apiManager.getStandardVoice(sentenceId)
|
||||
if (id) {
|
||||
const fileUrl = `${FILE_BASE_URL}/${id}`
|
||||
this.setData({
|
||||
standardAudioMap: {
|
||||
...this.data.standardAudioMap,
|
||||
[sentenceId]: fileUrl
|
||||
}
|
||||
})
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('获取标准语音失败:', err)
|
||||
wx.showToast({
|
||||
title: '获取语音失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// 播放标准语音
|
||||
playStandardVoice() {
|
||||
const { currentSentence, standardAudioMap, isPlaying } = this.data
|
||||
const audioUrl = standardAudioMap[currentSentence.id]
|
||||
|
||||
if (!audioUrl) {
|
||||
wx.showToast({
|
||||
title: '暂无标准语音',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (isPlaying) {
|
||||
this.audioContext?.pause()
|
||||
this.setData({ isPlaying: false })
|
||||
return
|
||||
}
|
||||
|
||||
if (!this.audioContext) {
|
||||
this.audioContext = wx.createInnerAudioContext()
|
||||
this.audioContext.onEnded(() => {
|
||||
this.setData({
|
||||
isPlaying: false,
|
||||
currentTime: 0,
|
||||
sliderValue: 0
|
||||
})
|
||||
})
|
||||
this.audioContext.onError(() => {
|
||||
this.setData({
|
||||
isPlaying: false,
|
||||
currentTime: 0,
|
||||
sliderValue: 0
|
||||
})
|
||||
wx.showToast({
|
||||
title: '播放失败',
|
||||
icon: 'none'
|
||||
})
|
||||
})
|
||||
this.audioContext.onTimeUpdate(() => {
|
||||
const currentTime = this.audioContext!.currentTime
|
||||
const duration = this.audioContext!.duration
|
||||
const sliderValue = (currentTime / duration) * 100
|
||||
this.setData({
|
||||
currentTime,
|
||||
audioDuration: duration,
|
||||
sliderValue
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
if (this.audioContext.src !== audioUrl) {
|
||||
this.audioContext.src = audioUrl
|
||||
this.setData({
|
||||
currentTime: 0,
|
||||
sliderValue: 0
|
||||
})
|
||||
}
|
||||
|
||||
this.audioContext.play()
|
||||
this.setData({ isPlaying: true })
|
||||
},
|
||||
|
||||
// 处理进度条拖动
|
||||
handleSliderChange(e: any) {
|
||||
const value = e.detail.value
|
||||
const duration = this.audioContext?.duration || 0
|
||||
const currentTime = (value / 100) * duration
|
||||
|
||||
if (this.audioContext) {
|
||||
this.audioContext.seek(currentTime)
|
||||
this.setData({
|
||||
currentTime,
|
||||
sliderValue: value
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// 格式化时间
|
||||
formatTime(seconds: number) {
|
||||
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>) {
|
||||
//如果有图片ID,调用接口获取文本和评分信息
|
||||
if (options.index) {
|
||||
@@ -238,6 +424,13 @@ Page<IPageData, IPageInstance>({
|
||||
// 检查是否所有评分都为负数
|
||||
const allNegative = [suggestedScore, pronAccuracy, pronCompletion, pronFluency].every((score: number) => score < 0)
|
||||
|
||||
// 处理单词评分数据
|
||||
const wordScores = assessmentResult?.Words?.map((word: any) => ({
|
||||
word: word.Word,
|
||||
pronAccuracy: Number(word.PronAccuracy.toFixed(2)),
|
||||
pronFluency: Number(word.PronFluency.toFixed(2))
|
||||
})) || []
|
||||
|
||||
this.setData({
|
||||
sentences,
|
||||
currentSentence,
|
||||
@@ -246,9 +439,14 @@ Page<IPageData, IPageInstance>({
|
||||
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
|
||||
fluencyScore: pronFluency >= 0 ? Number((pronFluency * 100).toFixed(2)) : 0,
|
||||
wordScores
|
||||
})
|
||||
this.updateWordsAndIpas(currentSentence.content, currentSentence.ipa)
|
||||
// 获取当前例句的标准语音
|
||||
if (currentSentence.id) {
|
||||
this.getStandardVoice(currentSentence.id)
|
||||
}
|
||||
}
|
||||
// 初始化圆形进度条
|
||||
this.updateCircleProgress()
|
||||
@@ -269,6 +467,13 @@ Page<IPageData, IPageInstance>({
|
||||
}
|
||||
},
|
||||
|
||||
onUnload() {
|
||||
// 销毁音频实例
|
||||
if (this.audioContext) {
|
||||
this.audioContext.destroy()
|
||||
}
|
||||
},
|
||||
|
||||
onReady() {
|
||||
// 监听录音结束事件
|
||||
recorderManager.onStop((res) => {
|
||||
@@ -278,6 +483,7 @@ Page<IPageData, IPageInstance>({
|
||||
content: '录音完成,是否确认提交?',
|
||||
success: (result) => {
|
||||
if (result.confirm) {
|
||||
wx.showLoading({ title: '正在解析...' })
|
||||
console.log('录音文件路径:', res.tempFilePath)
|
||||
apiManager.uploadFile(res.tempFilePath).then((fileId) => {
|
||||
apiManager.getAssessmentResult(fileId, this.data.currentSentence.id).then((result) => {
|
||||
@@ -291,17 +497,51 @@ Page<IPageData, IPageInstance>({
|
||||
// 检查是否所有评分都为负数
|
||||
const allNegative = [suggestedScore, pronAccuracy, pronCompletion, pronFluency].every(score => score < 0)
|
||||
|
||||
// 更新当前例句的评分信息
|
||||
const { sentences, currentIndex } = this.data
|
||||
sentences[currentIndex].details = {
|
||||
assessment: {
|
||||
result: assessmentResult
|
||||
}
|
||||
}
|
||||
|
||||
// 处理单词评分数据
|
||||
const wordScores = assessmentResult.Words?.map((word: any) => ({
|
||||
word: word.Word,
|
||||
pronAccuracy: Number(word.PronAccuracy.toFixed(2)),
|
||||
pronFluency: Number(word.PronFluency.toFixed(2))
|
||||
})) || []
|
||||
|
||||
// 更新评分信息
|
||||
this.setData({
|
||||
sentences, // 保存更新后的例句数组
|
||||
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
|
||||
fluencyScore: pronFluency >= 0 ? Number(pronFluency.toFixed(2)) : 0,
|
||||
wordScores
|
||||
})
|
||||
// 更新圆形进度条
|
||||
this.updateCircleProgress()
|
||||
wx.hideLoading()
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('获取评估结果失败:', err)
|
||||
wx.showToast({
|
||||
title: '评估失败',
|
||||
icon: 'error'
|
||||
})
|
||||
wx.hideLoading()
|
||||
})
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('上传录音失败:', err)
|
||||
wx.showToast({
|
||||
title: '上传失败',
|
||||
icon: 'error'
|
||||
})
|
||||
wx.hideLoading()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,20 @@
|
||||
<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>
|
||||
<!-- 音频播放控制区域 -->
|
||||
<view class="audio-control">
|
||||
<view class="audio-title">标准音频</view>
|
||||
<view class="audio-player">
|
||||
<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">{{formatTime(currentTime)}}</text>
|
||||
<t-slider class="progress-slider" value="{{sliderValue}}" bind:change="handleSliderChange" showValue="{{false}}" />
|
||||
<text class="time-text">{{formatTime(audioDuration)}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 评分详解按钮 -->
|
||||
<t-button wx:if="{{hasScoreInfo}}" class="score-detail-btn" theme="primary" size="small" variant="outline" bind:tap="handleScoreDetailClick">
|
||||
评分详解
|
||||
@@ -39,50 +53,68 @@
|
||||
</view>
|
||||
</view>
|
||||
<!-- 底部评分结果区域 -->
|
||||
<view class="score-section">
|
||||
<view class="score-section {{isScoreExpanded ? 'expanded' : ''}}" bindtap="toggleScoreSection">
|
||||
<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 class="score-overview">
|
||||
<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>
|
||||
<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 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>
|
||||
<view class="word-score-row">
|
||||
<text class="word-score-label">流利度</text>
|
||||
<text class="word-score-value">{{item.pronFluency}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -92,4 +124,6 @@
|
||||
<text class="no-score-text">暂无评分</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 遮罩层 -->
|
||||
<view class="mask" wx:if="{{isScoreExpanded}}" catchtap="toggleScoreSection"></view>
|
||||
</view>
|
||||
@@ -100,6 +100,7 @@
|
||||
}
|
||||
|
||||
.page-indicator {
|
||||
margin-top: 30rpx;
|
||||
font-size: 24rpx;
|
||||
color: #666666;
|
||||
display: block;
|
||||
@@ -110,11 +111,63 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-top: 40rpx;
|
||||
position: relative;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.audio-control {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: start;
|
||||
width: 100%;
|
||||
padding: 24rpx 32rpx;
|
||||
background: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.audio-title {
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.audio-player {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24rpx;
|
||||
}
|
||||
|
||||
.play-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 96rpx;
|
||||
height: 96rpx;
|
||||
padding: 0;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.audio-progress {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.time-text {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
min-width: 70rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.progress-slider {
|
||||
flex: 1;
|
||||
margin: 0 8rpx;
|
||||
}
|
||||
|
||||
.score-detail-btn {
|
||||
margin-top: 8rpx;
|
||||
min-width: 160rpx;
|
||||
@@ -171,12 +224,90 @@
|
||||
border-top-left-radius: 24rpx;
|
||||
border-top-right-radius: 24rpx;
|
||||
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.1);
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
transition: all 0.3s ease-in-out;
|
||||
max-height: 25vh;
|
||||
}
|
||||
|
||||
.score-section.expanded {
|
||||
transform: translateY(0);
|
||||
height: 80vh;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.score-section::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 16rpx;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 60rpx;
|
||||
height: 6rpx;
|
||||
background: #e0e0e0;
|
||||
border-radius: 3rpx;
|
||||
}
|
||||
|
||||
.expanded .score-section::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
z-index: 99;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.expanded + .mask {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.score-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.score-overview {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 32rpx;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.expanded .score-overview {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
|
||||
.expanded .total-score {
|
||||
margin-bottom: 48rpx;
|
||||
transform: scale(1.2);
|
||||
}
|
||||
|
||||
.expanded .score-details {
|
||||
width: 100%;
|
||||
padding: 0 32rpx;
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.score-details {
|
||||
transition: all 0.3s ease-in-out;
|
||||
opacity: 0.8;
|
||||
transform: translateY(20rpx);
|
||||
}
|
||||
|
||||
.total-score {
|
||||
@@ -285,4 +416,57 @@
|
||||
.no-score-text {
|
||||
font-size: 32rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 单词评分列表样式 */
|
||||
.word-scores-list {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24rpx;
|
||||
max-height: calc(80vh - 400rpx);
|
||||
overflow-y: auto;
|
||||
border-top: 1rpx solid #f0f0f0;
|
||||
margin-top: 32rpx;
|
||||
}
|
||||
|
||||
.word-score-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 24rpx;
|
||||
background: #f8f8f8;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.word-text {
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.word-score-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.word-score-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.word-score-label {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.word-score-value {
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
min-width: 80rpx;
|
||||
text-align: right;
|
||||
}
|
||||
@@ -234,7 +234,6 @@ Page({
|
||||
displayContent: validDescriptions,
|
||||
errorTip: ''
|
||||
})
|
||||
console.log('--lisa-displayContent', this.data.displayContent)
|
||||
} else {
|
||||
this.setData({
|
||||
errorTip: '暂无描述信息'
|
||||
|
||||
@@ -130,7 +130,6 @@ Page({
|
||||
hasMore: newData.length < result.total,
|
||||
isLoading: false,
|
||||
});
|
||||
console.log('---lisa-groupedHistory', this.data.groupedHistory)
|
||||
} catch (error) {
|
||||
console.error('加载每日摘要失败:', error);
|
||||
this.setData({ isLoading: false });
|
||||
@@ -177,7 +176,7 @@ Page({
|
||||
const { imageId } = e.currentTarget.dataset;
|
||||
if (imageId) {
|
||||
wx.navigateTo({
|
||||
url: `/pages/result_show/result_show?imageId=${imageId}`
|
||||
url: `/pages/assessment/assessment?imageId=${imageId}`
|
||||
})
|
||||
}
|
||||
},
|
||||
@@ -279,7 +278,7 @@ Page({
|
||||
const { image_id } = e.detail.selected
|
||||
if (image_id) {
|
||||
wx.navigateTo({
|
||||
url: `/pages/result_show/result_show?imageId=${image_id}`
|
||||
url: `/pages/assessment/assessment?imageId=${image_id}`
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
@@ -50,11 +50,11 @@
|
||||
<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}}">
|
||||
<scroll-view class="images-list" scroll-x enable-flex>
|
||||
<view class="image-item-wrap" 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>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -38,6 +38,10 @@
|
||||
}
|
||||
|
||||
.login-prompt {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
text-align: center;
|
||||
background: #ffffff;
|
||||
padding: 60rpx 40rpx;
|
||||
@@ -94,10 +98,16 @@
|
||||
.images-list {
|
||||
margin-top: 20rpx;
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
overflow: hidden;
|
||||
gap: 12rpx;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.images-list::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.image-item-wrap {
|
||||
margin-right: 16rpx;
|
||||
}
|
||||
.image-item {
|
||||
width: 100rpx;
|
||||
|
||||
@@ -1254,7 +1254,11 @@ class ApiManager {
|
||||
PronFluency: number,
|
||||
PronCompletion: number,
|
||||
SuggestedScore: number
|
||||
Words: any
|
||||
Words: {
|
||||
Word: string
|
||||
PronAccuracy: number,
|
||||
PronFluency: number,
|
||||
}[]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1277,7 +1281,11 @@ class ApiManager {
|
||||
PronFluency: number,
|
||||
PronCompletion: number,
|
||||
SuggestedScore: number
|
||||
Words: any
|
||||
Words: {
|
||||
Word: string
|
||||
PronAccuracy: number,
|
||||
PronFluency: number,
|
||||
}[]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1314,7 +1322,11 @@ class ApiManager {
|
||||
PronFluency: number,
|
||||
PronCompletion: number,
|
||||
SuggestedScore: number,
|
||||
Words: any
|
||||
Words: {
|
||||
Word: string
|
||||
PronAccuracy: number,
|
||||
PronFluency: number,
|
||||
}[]
|
||||
}
|
||||
}
|
||||
} | null
|
||||
@@ -1343,7 +1355,11 @@ class ApiManager {
|
||||
PronFluency: number,
|
||||
PronCompletion: number,
|
||||
SuggestedScore: number,
|
||||
Words: any
|
||||
Words: {
|
||||
Word: string
|
||||
PronAccuracy: number,
|
||||
PronFluency: number,
|
||||
}[]
|
||||
}
|
||||
}
|
||||
} | null
|
||||
@@ -1361,6 +1377,18 @@ class ApiManager {
|
||||
}
|
||||
}
|
||||
|
||||
async getStandardVoice(text_id: string | number): Promise<number | string> {
|
||||
try {
|
||||
console.log('开始获取标准音频');
|
||||
const response = await this.request<number | string>(`/api/v1/image_text/standard/${text_id}`);
|
||||
console.log('获取标准音频成功:', response.data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('获取标准音频失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 导出单例
|
||||
|
||||
Reference in New Issue
Block a user