fix ui
This commit is contained in:
@@ -125,7 +125,10 @@ interface IPageData {
|
||||
focusWordIndex: number,
|
||||
focusTransform: string,
|
||||
cachedHighlightWords: string[],
|
||||
isWordEmptyResult: boolean
|
||||
cachedSentenceIndex: number,
|
||||
isWordEmptyResult: boolean,
|
||||
recordPermissionGranted: boolean
|
||||
loadingMaskVisible: boolean
|
||||
}
|
||||
|
||||
type IPageMethods = {
|
||||
@@ -159,7 +162,9 @@ type IPageMethods = {
|
||||
processCollinsData: (collinsData: any) => any // Add this line
|
||||
computeHighlightLayout:() => void
|
||||
onMicHighlight: () => void
|
||||
ensureRecordPermission: () => void
|
||||
onMoreTap: () => void
|
||||
noop: () => void
|
||||
}
|
||||
|
||||
interface IPageInstance extends IPageMethods {
|
||||
@@ -258,47 +263,67 @@ Page<IPageData, IPageInstance>({
|
||||
focusWordIndex: 0,
|
||||
focusTransform: '',
|
||||
cachedHighlightWords: [],
|
||||
isMoreMenuClosing: false
|
||||
cachedSentenceIndex: -1,
|
||||
isMoreMenuClosing: false,
|
||||
recordPermissionGranted: false,
|
||||
loadingMaskVisible: false
|
||||
},
|
||||
|
||||
onMicHighlight() {
|
||||
const pre = this.data.cachedHighlightWords || []
|
||||
if (!pre || pre.length === 0) {
|
||||
this.computeHighlightLayout()
|
||||
}
|
||||
const words = this.data.cachedHighlightWords || []
|
||||
if (!words || words.length === 0) return
|
||||
const cachedIdx = typeof this.data.cachedSentenceIndex === 'number' ? this.data.cachedSentenceIndex : -1
|
||||
const currentIdx = typeof this.data.selectedSentenceIndex === 'number' ? this.data.selectedSentenceIndex : -1
|
||||
const needRecompute = (!pre || pre.length === 0 || cachedIdx !== currentIdx)
|
||||
|
||||
this.setData({
|
||||
overlayVisible: true,
|
||||
highlightWords: words.map((w: any) => ({ ...w, transform: 'translate(0px, 0px) scale(1)' })),
|
||||
highlightShow: false,
|
||||
highlightZoom: false
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
this.setData({ highlightShow: true })
|
||||
const startAnim = () => {
|
||||
const words = this.data.cachedHighlightWords || []
|
||||
if (!words || words.length === 0) return
|
||||
this.setData({
|
||||
overlayVisible: true,
|
||||
highlightWords: words.map((w: any) => ({ ...w, transform: 'translate(0px, 0px) scale(1)' })),
|
||||
highlightShow: false,
|
||||
highlightZoom: false
|
||||
})
|
||||
setTimeout(() => {
|
||||
this.setData({ highlightZoom: true })
|
||||
try {
|
||||
wx.nextTick(() => {
|
||||
const updated = (this.data.highlightWords || []).map((w: any) => ({
|
||||
...w,
|
||||
transform: w.targetTransform
|
||||
}))
|
||||
this.setData({ highlightWords: updated })
|
||||
})
|
||||
} catch (e) {
|
||||
setTimeout(() => {
|
||||
const updated = (this.data.highlightWords || []).map((w: any) => ({
|
||||
...w,
|
||||
transform: w.targetTransform
|
||||
}))
|
||||
this.setData({ highlightWords: updated })
|
||||
}, 0)
|
||||
}
|
||||
}, 500)
|
||||
}, 50)
|
||||
this.setData({ highlightShow: true })
|
||||
setTimeout(() => {
|
||||
this.setData({ highlightZoom: true })
|
||||
try {
|
||||
wx.nextTick(() => {
|
||||
const updated = (this.data.highlightWords || []).map((w: any) => ({
|
||||
...w,
|
||||
transform: w.targetTransform
|
||||
}))
|
||||
this.setData({ highlightWords: updated })
|
||||
})
|
||||
} catch (e) {
|
||||
setTimeout(() => {
|
||||
const updated = (this.data.highlightWords || []).map((w: any) => ({
|
||||
...w,
|
||||
transform: w.targetTransform
|
||||
}))
|
||||
this.setData({ highlightWords: updated })
|
||||
}, 0)
|
||||
}
|
||||
}, 500)
|
||||
}, 50)
|
||||
}
|
||||
|
||||
if (needRecompute) {
|
||||
try {
|
||||
wx.nextTick(() => {
|
||||
this.computeHighlightLayout()
|
||||
setTimeout(() => startAnim(), 80)
|
||||
})
|
||||
} catch (e) {
|
||||
setTimeout(() => {
|
||||
this.computeHighlightLayout()
|
||||
setTimeout(() => startAnim(), 80)
|
||||
}, 0)
|
||||
}
|
||||
} else {
|
||||
startAnim()
|
||||
}
|
||||
},
|
||||
// 切换评分区域展开状态
|
||||
toggleScoreSection() {
|
||||
@@ -494,14 +519,17 @@ Page<IPageData, IPageInstance>({
|
||||
this.setData({ recordDuration: duration })
|
||||
|
||||
if (duration < 3000) { // 小于3秒
|
||||
wx.showToast({
|
||||
title: '说话时间太短',
|
||||
icon: 'none'
|
||||
})
|
||||
recorderManager.stop()
|
||||
this.setData({
|
||||
isRecording: false,
|
||||
remainingTime: 30 // 重置倒计时
|
||||
remainingTime: 30,
|
||||
overlayVisible: false,
|
||||
highlightShow: false,
|
||||
highlightZoom: false,
|
||||
focusTransform: '',
|
||||
highlightWords: [],
|
||||
cachedHighlightWords: [],
|
||||
cachedSentenceIndex: -1
|
||||
})
|
||||
return
|
||||
}
|
||||
@@ -574,6 +602,7 @@ Page<IPageData, IPageInstance>({
|
||||
|
||||
// 播放标准语音
|
||||
playStandardVoice() {
|
||||
if (this.data.isRecording) return
|
||||
const { currentSentence, standardAudioMap, isPlaying } = this.data
|
||||
const audioUrl = standardAudioMap[currentSentence.id]
|
||||
|
||||
@@ -736,6 +765,7 @@ Page<IPageData, IPageInstance>({
|
||||
|
||||
// 新增:播放评分结果音频(使用当前句子的 file_id)
|
||||
playAssessmentVoice() {
|
||||
if (this.data.isRecording) return
|
||||
const { currentSentence, isPlaying } = this.data
|
||||
const fileId = currentSentence?.file_id
|
||||
|
||||
@@ -1056,7 +1086,14 @@ Page<IPageData, IPageInstance>({
|
||||
fluencyScore: pronFluency >= 0 ? Number((pronFluency * 100).toFixed(2)) : 0,
|
||||
wordScores,
|
||||
justSelectedByWord: false,
|
||||
prototypeWord: ''
|
||||
prototypeWord: '',
|
||||
overlayVisible: false,
|
||||
highlightShow: false,
|
||||
highlightZoom: false,
|
||||
focusTransform: '',
|
||||
highlightWords: [],
|
||||
cachedHighlightWords: [],
|
||||
cachedSentenceIndex: -1
|
||||
})
|
||||
|
||||
// 预加载并绑定新的标准语音
|
||||
@@ -1074,12 +1111,14 @@ Page<IPageData, IPageInstance>({
|
||||
},
|
||||
|
||||
onLoad(options: Record<string, string>) {
|
||||
this.ensureRecordPermission()
|
||||
// 如果图片ID,调用接口获取文本和评分信息
|
||||
if (options.index) {
|
||||
this.setData({ currentIndex: Number(options.index) })
|
||||
}
|
||||
if (options.imageId) {
|
||||
wx.showLoading({ title: '加载中...' })
|
||||
this.setData({ loadingMaskVisible: true })
|
||||
apiManager.getImageTextInit(options.imageId)
|
||||
.then(res => {
|
||||
// 更新图片路径(如果有)
|
||||
@@ -1092,6 +1131,10 @@ Page<IPageData, IPageInstance>({
|
||||
.catch(error => {
|
||||
console.error('获取图片失败:', error)
|
||||
})
|
||||
.finally(() => {
|
||||
this.setData({ loadingMaskVisible: false })
|
||||
try { wx.nextTick(() => { this.computeHighlightLayout() }) } catch (e) { setTimeout(() => this.computeHighlightLayout(), 0) }
|
||||
})
|
||||
}
|
||||
// 更新例句和评分信息
|
||||
if (res.assessments && res.assessments.length > 0) {
|
||||
@@ -1140,6 +1183,16 @@ Page<IPageData, IPageInstance>({
|
||||
if (currentSentence.id) {
|
||||
this.getStandardVoice(currentSentence.id)
|
||||
}
|
||||
try {
|
||||
wx.nextTick(() => {
|
||||
this.computeHighlightLayout()
|
||||
})
|
||||
} catch (e) {
|
||||
setTimeout(() => this.computeHighlightLayout(), 0)
|
||||
}
|
||||
if (!res.image_file_id) {
|
||||
this.setData({ loadingMaskVisible: false })
|
||||
}
|
||||
}
|
||||
// 初始化圆形进度条
|
||||
// this.updateCircleProgress()
|
||||
@@ -1150,6 +1203,8 @@ Page<IPageData, IPageInstance>({
|
||||
title: '加载失败',
|
||||
icon: 'none'
|
||||
})
|
||||
this.setData({ loadingMaskVisible: false })
|
||||
try { wx.nextTick(() => { this.computeHighlightLayout() }) } catch (e) { setTimeout(() => this.computeHighlightLayout(), 0) }
|
||||
})
|
||||
.finally(() => {
|
||||
wx.hideLoading()
|
||||
@@ -1162,9 +1217,8 @@ Page<IPageData, IPageInstance>({
|
||||
// 录音事件监听(提前绑定,避免事件丢失)
|
||||
if (!recorderHandlersBound) {
|
||||
recorderManager.onStop((res) => {
|
||||
// 使用实例属性的时长判断,避免 setData 异步导致值为 0
|
||||
const ms = this.lastRecordDurationMs || 0
|
||||
if (ms >= 3000) { // 只有录音时长大于3秒才提示确认
|
||||
const ms = Date.now() - this.data.recordStartTime
|
||||
if (ms >= 3000) {
|
||||
wx.showModal({
|
||||
title: '提示',
|
||||
content: '录音完成,是否确认提交?',
|
||||
@@ -1225,6 +1279,11 @@ Page<IPageData, IPageInstance>({
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
wx.showToast({
|
||||
title: '说话时间太短',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1242,6 +1301,8 @@ Page<IPageData, IPageInstance>({
|
||||
}
|
||||
},
|
||||
|
||||
noop() {},
|
||||
|
||||
onUnload() {
|
||||
// 销毁音频实例
|
||||
if (this.audioContext) {
|
||||
@@ -1425,6 +1486,7 @@ Page<IPageData, IPageInstance>({
|
||||
},
|
||||
|
||||
onScoreTap() {
|
||||
if (this.data.isRecording) return
|
||||
console.log('Score button tapped')
|
||||
// 当当前选中例句无评分信息时,不响应点击
|
||||
if (!this.data.hasScoreInfo) {
|
||||
@@ -1463,6 +1525,7 @@ Page<IPageData, IPageInstance>({
|
||||
})
|
||||
},
|
||||
onTransTap() {
|
||||
if (this.data.isRecording) return
|
||||
console.log('User button tapped')
|
||||
// Cycle through translation display modes
|
||||
const currentMode = this.data.transDisplayMode
|
||||
@@ -1515,6 +1578,11 @@ Page<IPageData, IPageInstance>({
|
||||
imageMode: 'aspectFit'
|
||||
})
|
||||
}
|
||||
|
||||
try { if ((this as any)._layoutDebounceTimer) clearTimeout((this as any)._layoutDebounceTimer) } catch (e) {}
|
||||
;(this as any)._layoutDebounceTimer = setTimeout(() => {
|
||||
this.computeHighlightLayout()
|
||||
}, 100)
|
||||
},
|
||||
|
||||
// 处理句子数据,分割单词和音标
|
||||
@@ -1592,6 +1660,7 @@ Page<IPageData, IPageInstance>({
|
||||
},
|
||||
|
||||
onMoreTap() {
|
||||
if (this.data.isRecording) return
|
||||
const { isMoreMenuOpen } = this.data
|
||||
if (!isMoreMenuOpen) {
|
||||
this.setData({ isMoreMenuOpen: true, isMoreMenuClosing: false })
|
||||
@@ -1683,10 +1752,48 @@ Page<IPageData, IPageInstance>({
|
||||
}
|
||||
})
|
||||
|
||||
this.setData({ cachedHighlightWords: targetWords })
|
||||
this.setData({ cachedHighlightWords: targetWords, cachedSentenceIndex: selectedSentenceIndex })
|
||||
})
|
||||
},
|
||||
|
||||
ensureRecordPermission() {
|
||||
try {
|
||||
wx.getSetting({
|
||||
success: (res) => {
|
||||
const granted = !!(res.authSetting && res.authSetting['scope.record'])
|
||||
if (granted) {
|
||||
this.setData({ recordPermissionGranted: true })
|
||||
return
|
||||
}
|
||||
wx.authorize({
|
||||
scope: 'scope.record',
|
||||
success: () => {
|
||||
this.setData({ recordPermissionGranted: true })
|
||||
},
|
||||
fail: () => {
|
||||
wx.showModal({
|
||||
title: '需要麦克风权限',
|
||||
content: '录音功能需要麦克风权限,请在设置中开启',
|
||||
confirmText: '去设置',
|
||||
cancelText: '取消',
|
||||
success: (r) => {
|
||||
if (r.confirm) {
|
||||
wx.openSetting({
|
||||
success: (s) => {
|
||||
const ok = !!(s.authSetting && s.authSetting['scope.record'])
|
||||
this.setData({ recordPermissionGranted: ok })
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
} catch (e) {}
|
||||
},
|
||||
|
||||
// 返回上一个单词的查询结果
|
||||
handleBackToPreviousWord() {
|
||||
const { previousWord } = this.data;
|
||||
@@ -1709,4 +1816,4 @@ Page<IPageData, IPageInstance>({
|
||||
// 查询上一个单词
|
||||
this.handleWordClick(event);
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -36,24 +36,26 @@
|
||||
<text class="overlay-text">{{item.text}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view wx:if="{{isRecording}}" class="recording-mask" catchtouchstart="noop" catchtouchmove="noop" catchtouchend="noop"></view>
|
||||
<view wx:if="{{loadingMaskVisible}}" class="page-loading-mask" catchtap="noop" catchtouchstart="noop" catchtouchmove="noop" catchtouchend="noop"></view>
|
||||
<!-- 底部按钮区域 -->
|
||||
<view class="bottom-button-area">
|
||||
<view wx:if="{{isMoreMenuOpen}}" class="more-menu-modal"></view>
|
||||
<view class="button-row">
|
||||
<t-icon name="{{isPlaying ? 'pause' : 'play'}}" class="bottom-button" size="48rpx" bind:tap="playStandardVoice" />
|
||||
<view class="bottom-button-img-wrap bottom-button" bind:tap="onTransTap">
|
||||
<t-icon name="{{isPlaying ? 'pause' : 'play'}}" class="bottom-button {{isRecording ? 'disabled' : ''}}" size="48rpx" bind:tap="playStandardVoice" />
|
||||
<view class="bottom-button-img-wrap bottom-button {{isRecording ? 'disabled' : ''}}" bind:tap="onTransTap">
|
||||
<t-icon name="translate" class="trans-button left-half {{transDisplayMode === 'en_ipa' ? 'trans-active' : 'trans-deactive'}}" size="48rpx" />
|
||||
<t-icon name="translate" class="trans-button right-half {{transDisplayMode === 'en_zh' ? 'trans-active' : 'trans-deactive'}}" size="48rpx" />
|
||||
</view>
|
||||
<view class="bottom-button mic-wrap" bindtap="onMicHighlight">
|
||||
<t-icon name="microphone-1" color="{{isRecording ? '#FFFFFF' : '#333333'}}" class="microphone {{isRecording ? 'recording' : 'bottom-button'}}" size="48rpx" bind:longpress="handleRecordStart" bind:touchend="handleRecordEnd" bind:touchcancel="handleRecordEnd" />
|
||||
<view class="bottom-button mic-wrap" bindtap="onMicHighlight" catchtouchstart="noop" catchtouchmove="noop" catchtouchend="noop">
|
||||
<t-icon name="microphone-1" color="{{isRecording ? '#FFFFFF' : '#333333'}}" class="microphone {{isRecording ? 'recording' : 'bottom-button'}} {{recordPermissionGranted ? '' : 'disabled'}}" size="48rpx" bind:longpress="handleRecordStart" bind:touchend="handleRecordEnd" bind:touchcancel="handleRecordEnd" />
|
||||
<view wx:if="{{isRecording}}" class="mic">
|
||||
<view class="mic-shadow"></view>
|
||||
</view>
|
||||
</view>
|
||||
<t-icon name="fact-check" class="bottom-button {{hasScoreInfo ? '' : 'disabled'}}" size="48rpx" bind:tap="onScoreTap" />
|
||||
<t-icon name="fact-check" class="bottom-button {{(hasScoreInfo && !isRecording) ? '' : 'disabled'}}" size="48rpx" bind:tap="onScoreTap" />
|
||||
<!-- <t-icon name="ellipsis" class="bottom-button {{isMoreMenuOpen ? 'more-open' : ''}}" size="48rpx" bind:tap="onMoreTap" /> -->
|
||||
<view class="bottom-button" bindtap="onMoreTap">
|
||||
<view class="bottom-button {{isRecording ? 'disabled' : ''}}" bindtap="onMoreTap">
|
||||
<view class="ul {{isMoreMenuOpen ? 'active' : ''}}">
|
||||
<view class="dot1"></view>
|
||||
<view class="dot2"></view>
|
||||
|
||||
@@ -573,6 +573,26 @@
|
||||
.overlay-word.show .overlay-text { font-weight: 600; }
|
||||
.overlay-word.zoom { transform-origin: center center; transition: transform 500ms ease; }
|
||||
|
||||
.page-loading-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: transparent;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.recording-mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: transparent;
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
.bottom-button {
|
||||
padding: 20rpx;
|
||||
border-radius: 50%;
|
||||
|
||||
Reference in New Issue
Block a user