在家开发了btn按钮样式

This commit is contained in:
终究是寂寞
2026-03-03 00:27:27 +08:00
parent 1aaf78c96b
commit f6b1ef16c3
5 changed files with 293 additions and 151 deletions
+2 -2
View File
@@ -4,10 +4,10 @@ ENV = 'development'
#VITE_APP_BASE_API_Url = 'https://aits.jlbank.com.cn:7001' VITE_APP_BASE_API_Url = 'https://aits.jlbank.com.cn:7001'
VITE_APP_BASE_API_Url = 'https://aitstest.jlbank.com.cn:7001' #VITE_APP_BASE_API_Url = 'https://aitstest.jlbank.com.cn:7001'
# VITE_APP_BASE_API_Url = 'https://aitstest.jlbank.com.cn:7002' # VITE_APP_BASE_API_Url = 'https://aitstest.jlbank.com.cn:7002'
# VITE_APP_BASE_API_Url = 'http://192.168.247.200' # VITE_APP_BASE_API_Url = 'http://192.168.247.200'
+182 -11
View File
@@ -4,6 +4,7 @@
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
background-color: rgba(0, 0, 0, 0.8); background-color: rgba(0, 0, 0, 0.8);
position: relative;
} }
/* 1. 定义通用的向右移出动画(核心:添加时长,解决秒没) */ /* 1. 定义通用的向右移出动画(核心:添加时长,解决秒没) */
@keyframes moveRightOut { @keyframes moveRightOut {
@@ -24,29 +25,44 @@
left: -400rpx; /* 目标位置:向左移出400rpx */ left: -400rpx; /* 目标位置:向左移出400rpx */
} }
} }
/* 2. 定义通用的向左移出动画(适配 btn-box-1) */
@keyframes moveBottomOut {
0% {
transform: translateY(0%);
}
100% {
transform: translateY(100%);
}
}
@keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
.page-1 { .page-1 {
background-color: red; position: absolute;
&.init { width: 100%;
bottom: 0;
&.voice_to_text{
.btn-box-1 { .btn-box-1 {
position: absolute; /* 定位是动画生效的前提 */ animation: moveLeftOut 0.3s ease-out forwards;
left: 0; /* 初始位置 */
/* 核心动画配置:时长+曲线+保留最终状态 */
animation: moveLeftOut 0.5s ease-out forwards;
/* 清除冲突样式 */ /* 清除冲突样式 */
transform: none !important; transform: none !important;
transition: none !important; transition: none !important;
} }
.btn-box-2 { .btn-box-2 {
position: absolute; /* 定位是动画生效的前提 */ animation: moveRightOut 0.3s ease-out forwards;
right: 0; /* 初始位置 */
/* 核心动画配置:时长+曲线+保留最终状态 */
animation: moveRightOut 0.5s ease-out forwards;
/* 清除冲突样式 */ /* 清除冲突样式 */
transform: none !important; transform: none !important;
transition: none !important; transition: none !important;
} }
.circle-bg-box{
animation: moveBottomOut 0.3s ease-out forwards;
}
} }
// 两侧按钮 // 两侧按钮
.btns-box { .btns-box {
@@ -93,4 +109,159 @@
line-height: 46px; line-height: 46px;
} }
} }
}
.page-2{
width: 100%;
overflow: hidden;
font-size: 28rpx;
text-align: center;
display: none;
z-index: 12000;
justify-content: space-around;
margin-bottom:234rpx;;
position: absolute;
bottom: 0;
opacity: 0;
visibility: hidden; // 初始不可见且不占交互位置
&.voice_to_text {
display: flex !important;
visibility: visible; // 激活后可见
animation: fadeIn 0.4s ease-out 0.1s forwards;
}
.transVoiceIng {
width: 154rpx;
height: 154rpx;
overflow: hidden;
position: relative;
z-index: 12010;
&.cancel-btn {
background: url(@/static/images/course/cancel-btn.png) no-repeat;
background-size: cover;
}
&.trans-btn {
background: url(@/static/images/course/trans-btn.png) no-repeat;
background-size: cover;
}
}
.send-btn {
position: relative;
width: 262rpx;
height: 154rpx;
line-height: 154rpx;
overflow: hidden;
background-color: #d8d8d8;
border-radius: 77rpx;
font-size: 33rpx;
z-index: 12010;
}
}
.talk-dialog-box{
position: absolute;
bottom: 520rpx ;
width: 100%;
display: flex;
justify-content: center;
}
.talk-dialog-div {
width: 506rpx;
min-height: 234rpx;
background-color: #06f;
border-radius: 32rpx;
transition: width 0.3s ease;
// 滑动到离开变红
&.is-out {
background-color: #ff3e49;
&::after {
background-color: #ff3e49;
}
}
// 滑动到转文字变宽
&.is-translate {
width: 706rpx;
.gif-box {
display: none;
}
}
&::after {
content: '';
width: 44rpx;
height: 44rpx;
clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
background-color: #06f;
overflow: hidden;
position: absolute;
bottom: -22rpx;
left: 50%;
margin-left: -22rpx;
transition: left 0.3s ease-out; // 时长0.3秒,ease-out缓动(结束时变慢,更自然)
}
// 控制下边箭头指向发送
&.init::after {
left: 78% !important;
}
&.failed {
width: 506rpx;
height: 114rpx;
min-height: 114rpx;
background-color: #ff3e49;
&::after {
background-color: #ff3e49;
}
// 激活错误提示框
.error-tip-div{
display: flex !important;
visibility: visible; // 激活后可见
// animation: fadeIn 0.4s ease-out 0.1s forwards;
}
}
}
.talk-dialog {
width: 100%;
height: 100%;
overflow: hidden;
position: relative;
.text-div {
overflow-y: scroll;
max-height: 434rpx;
padding: 20rpx;
font-size: 32rpx;
color: #fff;
line-height: 40rpx;
}
.gif-box {
position: absolute;
width: 200rpx;
height: 80rpx;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
.error-tip-div{
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
position: absolute;
display: none;
visibility: hidden; // 初始不可见且不占交互位置
display: flex;
align-items: center;
color: #ffffff;
font-size: 24rpx;
font-weight: 400;
.img{
margin-right: 10rpx;
width: 26rpx;
height: 26rpx;
}
}
} }
+8 -2
View File
@@ -11,15 +11,21 @@ export const icKeyboardSvg = () : string => {
`; `;
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgXml)}`; return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgXml)}`;
}; };
export const icMicSvg = () : string => { export const iconMicSvg = () : string => {
const svgXml = ` const svgXml = `
<svg t="1772357429950" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1775" width="200" height="200"><path d="M513.788813 938.289925c-113.566274 0-220.223734-44.167967-300.444149-124.388381-80.220415-80.220415-124.388382-186.877876-124.388382-300.44415s44.167967-220.227983 124.388382-300.448398c165.543834-165.548083 435.348714-165.548083 600.892548 0 165.548083 165.548083 165.548083 435.348714 0 600.892548-80.220415 80.220415-186.877876 124.388382-300.44415 124.388381z m0-785.973112c-92.538158 0-185.072066 35.453344-255.379651 105.756681-68.2001 68.204349-105.75668 158.63927-105.756681 255.3839s37.556581 187.175303 105.756681 255.379652c68.204349 68.2001 158.936697 106.054108 255.379651 105.75668 96.74888 0 187.179552-37.556581 255.379652-105.75668 140.912598-140.912598 140.912598-369.850954 0-510.759303-70.303336-70.307585-162.841494-105.75668-255.379652-105.756681z" fill="#515151" p-id="1776"></path><path d="M318.672199 341.705826h46.313693c11.047303 0 19.545228 8.497925 19.545228 19.120332v46.313693c0 10.622407-8.497925 19.120332-19.120332 19.120332h-46.738589c-10.622407 0.424896-19.120332-8.073029-19.120332-18.695436v-46.738589c0-10.622407 8.497925-19.120332 19.120332-19.120332zM488.630705 341.705826h46.313693c11.047303 0 19.545228 8.497925 19.545229 19.120332v46.313693c0 10.622407-8.497925 19.120332-19.120332 19.120332h-46.73859c-10.622407 0.424896-19.120332-8.073029-19.120332-18.695436v-46.738589c0-10.622407 8.497925-19.120332 19.120332-19.120332zM658.589212 341.705826h46.313693c11.047303 0 19.545228 8.497925 19.545228 19.120332v46.313693c0 10.622407-8.497925 19.120332-19.120332 19.120332h-46.738589c-10.622407 0.424896-19.120332-8.073029-19.120332-18.695436v-46.738589c0-10.622407 8.497925-19.120332 19.120332-19.120332zM318.672199 469.174705h46.313693c10.622407 0 19.120332 8.497925 19.120332 19.120332v46.313693c0 10.622407-8.497925 19.120332-19.120332 19.120332H318.672199c-10.622407 0.424896-19.120332-8.073029-19.120332-18.695435v-46.73859c0-10.622407 8.497925-19.120332 19.120332-19.120332zM488.630705 469.174705h46.313693c10.622407 0 19.120332 8.497925 19.120332 19.120332v46.313693c0 10.622407-8.497925 19.120332-19.120332 19.120332h-46.313693c-10.622407 0.424896-19.120332-8.073029-19.120332-18.695435v-46.73859c0-10.622407 8.497925-19.120332 19.120332-19.120332zM658.589212 469.174705h46.313693c10.622407 0 19.120332 8.497925 19.120332 19.120332v46.313693c0 10.622407-8.497925 19.120332-19.120332 19.120332h-46.313693c-10.622407 0.424896-19.120332-8.073029-19.120332-18.695435v-46.73859c0-10.622407 8.497925-19.120332 19.120332-19.120332zM458.887967 660.378025h106.224066c17.420747 0 31.86722 14.446473 31.86722 31.86722s-14.446473 31.86722-31.86722 31.86722h-106.224066c-17.420747 0-31.86722-14.446473-31.86722-31.86722s14.446473-31.86722 31.86722-31.86722z" fill="#515151" p-id="1777"></path></svg> <svg t="1772357429950" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1775" width="200" height="200"><path d="M513.788813 938.289925c-113.566274 0-220.223734-44.167967-300.444149-124.388381-80.220415-80.220415-124.388382-186.877876-124.388382-300.44415s44.167967-220.227983 124.388382-300.448398c165.543834-165.548083 435.348714-165.548083 600.892548 0 165.548083 165.548083 165.548083 435.348714 0 600.892548-80.220415 80.220415-186.877876 124.388382-300.44415 124.388381z m0-785.973112c-92.538158 0-185.072066 35.453344-255.379651 105.756681-68.2001 68.204349-105.75668 158.63927-105.756681 255.3839s37.556581 187.175303 105.756681 255.379652c68.204349 68.2001 158.936697 106.054108 255.379651 105.75668 96.74888 0 187.179552-37.556581 255.379652-105.75668 140.912598-140.912598 140.912598-369.850954 0-510.759303-70.303336-70.307585-162.841494-105.75668-255.379652-105.756681z" fill="#515151" p-id="1776"></path><path d="M318.672199 341.705826h46.313693c11.047303 0 19.545228 8.497925 19.545228 19.120332v46.313693c0 10.622407-8.497925 19.120332-19.120332 19.120332h-46.738589c-10.622407 0.424896-19.120332-8.073029-19.120332-18.695436v-46.738589c0-10.622407 8.497925-19.120332 19.120332-19.120332zM488.630705 341.705826h46.313693c11.047303 0 19.545228 8.497925 19.545229 19.120332v46.313693c0 10.622407-8.497925 19.120332-19.120332 19.120332h-46.73859c-10.622407 0.424896-19.120332-8.073029-19.120332-18.695436v-46.738589c0-10.622407 8.497925-19.120332 19.120332-19.120332zM658.589212 341.705826h46.313693c11.047303 0 19.545228 8.497925 19.545228 19.120332v46.313693c0 10.622407-8.497925 19.120332-19.120332 19.120332h-46.738589c-10.622407 0.424896-19.120332-8.073029-19.120332-18.695436v-46.738589c0-10.622407 8.497925-19.120332 19.120332-19.120332zM318.672199 469.174705h46.313693c10.622407 0 19.120332 8.497925 19.120332 19.120332v46.313693c0 10.622407-8.497925 19.120332-19.120332 19.120332H318.672199c-10.622407 0.424896-19.120332-8.073029-19.120332-18.695435v-46.73859c0-10.622407 8.497925-19.120332 19.120332-19.120332zM488.630705 469.174705h46.313693c10.622407 0 19.120332 8.497925 19.120332 19.120332v46.313693c0 10.622407-8.497925 19.120332-19.120332 19.120332h-46.313693c-10.622407 0.424896-19.120332-8.073029-19.120332-18.695435v-46.73859c0-10.622407 8.497925-19.120332 19.120332-19.120332zM658.589212 469.174705h46.313693c10.622407 0 19.120332 8.497925 19.120332 19.120332v46.313693c0 10.622407-8.497925 19.120332-19.120332 19.120332h-46.313693c-10.622407 0.424896-19.120332-8.073029-19.120332-18.695435v-46.73859c0-10.622407 8.497925-19.120332 19.120332-19.120332zM458.887967 660.378025h106.224066c17.420747 0 31.86722 14.446473 31.86722 31.86722s-14.446473 31.86722-31.86722 31.86722h-106.224066c-17.420747 0-31.86722-14.446473-31.86722-31.86722s14.446473-31.86722 31.86722-31.86722z" fill="#515151" p-id="1777"></path></svg>
`; `;
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgXml)}`; return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgXml)}`;
}; };
export const icSendSvg = () : string => { export const iconSendSvg = () : string => {
const svgXml = ` const svgXml = `
<svg t="1772375437715" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="44211" width="200" height="200"><path d="M512 56.888889c251.363556 0 455.111111 203.747556 455.111111 455.111111s-203.747556 455.111111-455.111111 455.111111S56.888889 763.363556 56.888889 512 260.636444 56.888889 512 56.888889z m-28.444444 324.664889V711.111111a28.444444 28.444444 0 0 0 56.888888 0V381.553778l150.556445 150.556444a28.444444 28.444444 0 0 0 40.220444-40.220444l-199.111111-199.111111a28.444444 28.444444 0 0 0-40.220444 0l-199.111111 199.111111a28.444444 28.444444 0 0 0 40.220444 40.220444L483.555556 381.553778z" fill="#0066FF" p-id="44212"></path></svg> <svg t="1772375437715" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="44211" width="200" height="200"><path d="M512 56.888889c251.363556 0 455.111111 203.747556 455.111111 455.111111s-203.747556 455.111111-455.111111 455.111111S56.888889 763.363556 56.888889 512 260.636444 56.888889 512 56.888889z m-28.444444 324.664889V711.111111a28.444444 28.444444 0 0 0 56.888888 0V381.553778l150.556445 150.556444a28.444444 28.444444 0 0 0 40.220444-40.220444l-199.111111-199.111111a28.444444 28.444444 0 0 0-40.220444 0l-199.111111 199.111111a28.444444 28.444444 0 0 0 40.220444 40.220444L483.555556 381.553778z" fill="#0066FF" p-id="44212"></path></svg>
`; `;
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgXml)}`; return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgXml)}`;
}; };
export const iconRecognizeErrorSvg = () : string => {
const svgXml = `
<svg t="1772464917665" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5331" width="64" height="64"><path d="M512 4.12672c280.49408 0 507.87328 227.3792 507.87328 507.87328 0 280.49408-227.3792 507.87328-507.87328 507.87328C231.50592 1019.87328 4.12672 792.49408 4.12672 512 4.12672 231.50592 231.50592 4.12672 512 4.12672zM512 685.96736c-42.47552 0-76.91264 34.42688-76.91264 76.91264 0 42.47552 34.43712 76.91264 76.91264 76.91264 42.47552 0 76.91264-34.43712 76.91264-76.91264C588.91264 720.39424 554.47552 685.96736 512 685.96736zM509.78816 625.83808c36.58752 0 66.24256-29.66528 66.24256-66.24256l0-309.1456c0-36.58752-29.65504-66.24256-66.24256-66.24256-36.58752 0-66.24256 29.66528-66.24256 66.24256l0 309.1456C443.5456 596.18304 473.20064 625.83808 509.78816 625.83808z" fill="#ffffff" p-id="5332"></path></svg>
`;
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svgXml)}`;
};
@@ -12,6 +12,7 @@ export type StatusType = 'default' | 'voice_recording' | 'text_input' | 'voice_t
* 语音转文字的子状态(仅当主状态为 voice_to_text 时生效) * 语音转文字的子状态(仅当主状态为 voice_to_text 时生效)
*/ */
export type VoiceToTextSubStatus = export type VoiceToTextSubStatus =
| '' // 纯空
| 'init' // 初始化(刚进入语音转文字页面) | 'init' // 初始化(刚进入语音转文字页面)
| 'loading' // 转换中(正在处理语音→文字) | 'loading' // 转换中(正在处理语音→文字)
| 'success' // 转换完成(成功得到文字结果) | 'success' // 转换完成(成功得到文字结果)
+100 -136
View File
@@ -2,8 +2,8 @@
<view> <view>
<view class="talk-btns" :class="{action:isRecording, inputMode:!isInputVisible}"> <view class="talk-btns" :class="{action:isRecording, inputMode:!isInputVisible}">
<view class="touch-btn" @click="disabled_click"> <view class="touch-btn" @click="disabled_click">
<button v-if="!isTextOnlyMode && isInputVisible" class="voice-btn" @touchstart="startRecord" @touchend="stopRecord()" @touchmove="handleMove" <button v-if="!isTextOnlyMode && isInputVisible" class="voice-btn" @touchstart="startRecord"
@touchcancel="stopRecord()" :plain="true"> @touchend="stopRecord()" @touchmove="handleMove" @touchcancel="stopRecord()" :plain="true">
<text class="voice-text">{{ isRecording ? '松开发送' : '按住说话' }}</text> <text class="voice-text">{{ isRecording ? '松开发送' : '按住说话' }}</text>
</button> </button>
<input v-if="!isVoiceOnlyMode && !isInputVisible" class="input-box" ref="inputRef" v-model="inputText" <input v-if="!isVoiceOnlyMode && !isInputVisible" class="input-box" ref="inputRef" v-model="inputText"
@@ -15,43 +15,54 @@
<image class="talk-right-icon" :src="talkRightIcon" mode="widthFix" @click="changeBtn"></image> <image class="talk-right-icon" :src="talkRightIcon" mode="widthFix" @click="changeBtn"></image>
</view> </view>
</view> </view>
<!-- :class="{ 'is-out': isOutStatus === '02', 'is-translate': isOutStatus === '03', voice_to_text_status: status === 'voice_to_text'}" -->
<uni-popup ref="popupRef" class="popup"> <uni-popup ref="popupRef" class="popup">
<view class="overlay-box"> <view class="overlay-box">
<view class="fl1"></view> <view class="fl1"></view>
<view class="talk-dialog" <view class="talk-dialog-box">
:class="{ 'is-out': isOutStatus === '02', 'is-translate': isOutStatus === '03' }"> <view class="talk-dialog-div" :class="dialogDynamicClasses">
<image :src="voiceWhiteBtn" mode="" class="gif-box"></image> <view class="talk-dialog" >
<uv-textarea ref="inputRef" v-model="textAreaText" :maxlength="255" @click.stop <image :src="voiceWhiteBtn" mode="" class="gif-box"></image>
customStyle="background: #ffffff00; padding-bottom: 50rpx; color: #fff; caret-color: #fff" <uv-textarea ref="inputRef" v-model="textAreaText" :maxlength="255" @click.stop
text-style="color: #fff" border="none" placeholderStyle="color:#999999;font-size:26rpx;" customStyle="background: #ffffff00; padding-bottom: 50rpx; color: #fff; caret-color: #fff"
placeholder=""></uv-textarea> text-style="color: #fff" border="none" placeholderStyle="color:#999999;font-size:26rpx;"
placeholder=""></uv-textarea>
<view class="error-tip-div">
<image :src="iconRecognizeErrorSvg()" mode="aspectFill" class="img"></image>
<view class="">
未识别到文字
</view>
</view>
</view>
</view>
</view> </view>
<view class="page-1" <!-- 第一形态两侧按钮 -->
:class="[ <view class="page-1" :class="[ status]">
status === 'voice_to_text' ? `${voice_to_text_status}` : ''
]">
<!-- 第一形态两侧按钮 -->
<view class="btns-box"> <view class="btns-box">
<!-- 左侧取消按钮 --> <!-- 左侧取消按钮 -->
<image class="btn-box btn-box-1" :src="isOutStatus == '02' ? cancelActiveBtn : cancelBtn" mode="heightFix" @click="closePopup()"></image> <image class="btn-box btn-box-1" :src="isOutStatus == '02' ? cancelActiveBtn : cancelBtn"
mode="heightFix" @click="closePopup()"></image>
<!-- 右侧转文字按钮 --> <!-- 右侧转文字按钮 -->
<image class="btn-box btn-box-2" :src="isOutStatus == '03' ? translateActiveBtn : translateBtn" mode="heightFix"></image> <image class="btn-box btn-box-2" :src="isOutStatus == '03' ? translateActiveBtn : translateBtn"
mode="heightFix"></image>
</view> </view>
<!-- 下方按钮 --> <!-- 下方按钮 -->
<view class="circle-bg-box"> <view class="circle-bg-box">
<view class="tips-box">松开发送</view> <view class="tips-box">松开发送</view>
</view> </view>
</view> </view>
<!-- 第二形态 -->
<view class="page-2" :class="[ status, voice_to_text_status]">
<!-- 左侧XX -->
<!-- <view class="page-2"> <view class="transVoiceIng cancel-btn" @click="closePopup()"></view>
<view class="cancel-btn transVoiceIng" @click="closeModel()"></view> <!-- 中间发送原语音 -->
<view class="trans-btn transVoiceIng" @click="sendVoice()" v-show="props.answerMethod !== '02'"></view> <view class="transVoiceIng trans-btn" @click="testFun()"></view>
<view class="send-btn" @click="sendText()">发送</view> <!-- 右侧发送按钮椭圆 -->
</view> --> <view class="send-btn">发送</view>
</view>
</view> </view>
</uni-popup> </uni-popup>
</view> </view>
@@ -59,8 +70,8 @@
<script setup lang="ts"> <script setup lang="ts">
// 导出图片 // 导出图片
import { cancelBtn, cancelActiveBtn, translateBtn, translateActiveBtn, voiceWhiteBtn, icKeyboardSvg, icMicSvg,icSendSvg } from './touch-btn.images'; import { cancelBtn, cancelActiveBtn, translateBtn, translateActiveBtn, voiceWhiteBtn, icKeyboardSvg, iconMicSvg, iconSendSvg, iconRecognizeErrorSvg } from './touch-btn.images';
import { StatusType,VoiceToTextSubStatus } from './touch-btn.types'; import { StatusType, VoiceToTextSubStatus } from './touch-btn.types';
import { wsUrl, bottomAreaTopBoundary, horizontalSplitX, initWebSocket } from './touch-btn'; import { wsUrl, bottomAreaTopBoundary, horizontalSplitX, initWebSocket } from './touch-btn';
import { ref, computed, onMounted, nextTick, watch, reactive } from 'vue'; import { ref, computed, onMounted, nextTick, watch, reactive } from 'vue';
import common from '@/common/common'; import common from '@/common/common';
@@ -118,12 +129,20 @@
} }
}); });
const textAreaText = computed({ const textAreaText = computed({
get() { get() {
return allText.value + tempText.value; return allText.value + tempText.value;
}, },
set(newValue) { set(newValue) {
allText.value = newValue; allText.value = newValue;
} }
});
const dialogDynamicClasses = computed(() => {
const classes = [];
isOutStatus.value === '02' && classes.push('is-out')
isOutStatus.value === '03' && classes.push('is-translate')
voice_to_text_status.value !== '' && classes.push(voice_to_text_status.value)
return classes;
}); });
const allText = ref(''); const allText = ref('');
const tempText = ref(''); const tempText = ref('');
@@ -140,9 +159,9 @@
const isVoiceOnlyMode = computed(() => props.mode === 'voice-only'); const isVoiceOnlyMode = computed(() => props.mode === 'voice-only');
const isTextOnlyMode = computed(() => props.mode === 'text-only'); const isTextOnlyMode = computed(() => props.mode === 'text-only');
const talkRightIcon = computed(() => { const talkRightIcon = computed(() => {
if(isInputVisible.value) return icKeyboardSvg(); if (isInputVisible.value) return icKeyboardSvg();
if(inputText.value) return icSendSvg(); if (inputText.value) return iconSendSvg();
return icMicSvg(); return iconMicSvg();
}); });
// 计算是否超出按下盒子位置01代表下方盒子,02代表左上03代表右上 // 计算是否超出按下盒子位置01代表下方盒子,02代表左上03代表右上
const isOutStatus = computed(() => { const isOutStatus = computed(() => {
@@ -156,7 +175,9 @@
touchPos.x = pageX; touchPos.x = pageX;
touchPos.y = pageY; touchPos.y = pageY;
}; };
const testFun = () => {
voice_to_text_status.value = 'failed'
}
// 开启语音 // 开启语音
const startRecord = async (obj : { touches : any[]; }) => { const startRecord = async (obj : { touches : any[]; }) => {
try { try {
@@ -172,13 +193,14 @@
// 标记录音开始 // 标记录音开始
isRecording.value = true; isRecording.value = true;
status.value = 'voice_recording' status.value = 'voice_recording'
voice_to_text_status.value = ''
tempText.value = ''; tempText.value = '';
allText.value = ''; allText.value = '';
initWebSocket(wsUrl).then(socketTask => { initWebSocket(wsUrl).then(socketTask => {
ws.value = socketTask ws.value = socketTask
wsStatus.value = true; wsStatus.value = true;
console.log('WS连接成功'); console.log('WS连接成功');
ws.value.onMessage((res: { data: Uint8Array | ArrayBuffer; }) => { ws.value.onMessage((res : { data : Uint8Array | ArrayBuffer; }) => {
try { try {
const { msgType, body } = ProtocolCodec.unpack(res.data); const { msgType, body } = ProtocolCodec.unpack(res.data);
if (msgType === MessageType.TEXT_MESSAGE) { if (msgType === MessageType.TEXT_MESSAGE) {
@@ -208,9 +230,9 @@
recordStartTime.value = Date.now(); recordStartTime.value = Date.now();
// 弹出录音界面 // 弹出录音界面
popupRef.value.open(); popupRef.value.open();
startAudioRecord({ startAudioRecord({
onFrame: (pcmData : any) => { onFrame: (pcmData : any) => {
if(wsStatus.value) { if (wsStatus.value) {
console.log('已连接', pcmData.length); console.log('已连接', pcmData.length);
ws.value.send({ ws.value.send({
data: ProtocolCodec.pack(MessageType.AUDIO_DATA, pcmData) data: ProtocolCodec.pack(MessageType.AUDIO_DATA, pcmData)
@@ -232,32 +254,32 @@
}); });
}, 140) }, 140)
}; };
// 结束录音 // 结束录音
const closePopup = () => { const closePopup = () => {
popupRef.value.close(); popupRef.value.close();
} }
const stopRecord = async () => { const stopRecord = async () => {
clearTimeout(pressTimer.value); clearTimeout(pressTimer.value);
if (!isRecording.value) return; // 未处于录音状态 if (!isRecording.value) return; // 未处于录音状态
if(isOutStatus.value === '03') { // 转换文字状态 if (isOutStatus.value === '03') { // 转换文字状态
console.log('已发送', '03转文字'); console.log('已发送', '03转文字');
switchFromVoiceToText() switchFromVoiceToText()
} }
const recordDurationMs = Date.now() - recordStartTime.value; const recordDurationMs = Date.now() - recordStartTime.value;
if(isOutStatus.value !== '02') { // 取消的时候,不校验最低时间 if (isOutStatus.value !== '02') { // 取消的时候,不校验最低时间
// 核心:计算录音总时长(毫秒转秒,保留1位小数) // 核心:计算录音总时长(毫秒转秒,保留1位小数)
if (recordDurationMs < 600) { if (recordDurationMs < 600) {
await new Promise<void>((resolve) => { await new Promise<void>((resolve) => {
setTimeout(() => { setTimeout(() => {
resolve() resolve()
}, 600-recordDurationMs) }, 600 - recordDurationMs)
}) })
} }
} }
isRecording.value = false; isRecording.value = false;
if(isOutStatus.value === '02' && ws.value) { // 取消的时候自己主动关闭websocket,其他的时候需要等待后端的消息关闭 if (isOutStatus.value === '02' && ws.value) { // 取消的时候自己主动关闭websocket,其他的时候需要等待后端的消息关闭
ws.value.close({ ws.value.close({
code: 1000, code: 1000,
reason: '挂断', reason: '挂断',
@@ -268,32 +290,32 @@
}); });
} }
stopAudioRecord(); // 关掉录音 stopAudioRecord(); // 关掉录音
if(isOutStatus.value !== '03') { // 转文字时候不关遮罩层 if (isOutStatus.value !== '03') { // 转文字时候不关遮罩层
popupRef.value.close(); popupRef.value.close();
if(isOutStatus.value !== '02') { // 取消的时候,不校验最低时间 if (isOutStatus.value !== '02') { // 取消的时候,不校验最低时间
if(recordDurationMs <= 1200) { if (recordDurationMs <= 1200) {
setTimeout(() => { setTimeout(() => {
uni.showToast({ uni.showToast({
title: '说话时间太短,没有听清!', title: '说话时间太短,没有听清!',
icon: 'none' icon: 'none'
}); });
}, 200) }, 200)
return return
} }
} }
} }
}; };
// 从录音状态切换成转文字状态 // 从录音状态切换成转文字状态
const switchFromVoiceToText = () => { const switchFromVoiceToText = () => {
status.value = 'voice_to_text' status.value = 'voice_to_text'
ws.value && ws.value.send({data: ProtocolCodec.pack(MessageType.CONTROL_CMD, { type: ControlCode.FRNOT_TO_SERVER_OVER, ossID:'', text:'' })}); ws.value && ws.value.send({ data: ProtocolCodec.pack(MessageType.CONTROL_CMD, { type: ControlCode.FRNOT_TO_SERVER_OVER, ossID: '', text: '' }) });
voice_to_text_status.value = 'init' voice_to_text_status.value = 'init'
// 执行动画 // 执行动画
11 11
} }
// 发送文本 // 发送文本
const showKeyboard = () => { const showKeyboard = () => {
if (isInputVisible.value) { if (isInputVisible.value) {
@@ -324,7 +346,7 @@
console.log('调用结束语音回答'); console.log('调用结束语音回答');
stopRecord(); stopRecord();
}; };
// 外部调用,清理输入框数据 // 外部调用,清理输入框数据
const clearinputText = (text = '') => { const clearinputText = (text = '') => {
const old_text = inputText.value; const old_text = inputText.value;
@@ -332,7 +354,6 @@
return old_text; return old_text;
}; };
defineExpose({ clearinputText, give_up }); defineExpose({ clearinputText, give_up });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@@ -340,91 +361,34 @@
@import "./style/popup.scss"; @import "./style/popup.scss";
.talk-dialog {
width: 506rpx;
min-height: 234rpx;
margin: 0 auto 160rpx auto;
background-color: #06f;
border-radius: 32rpx;
position: relative;
overflow: hidden;
transition: width 0.3s ease;
.text-div { .white-bg-box {
overflow-y: scroll; height: 365rpx;
max-height: 434rpx; background-color: #fff;
padding: 20rpx; overflow: hidden;
font-size: 32rpx; border-radius: 32rpx 32rpx 0 0;
color: #fff; padding: 58rpx 36rpx;
line-height: 40rpx;
}
&.is-translate { .box-title {
width: 706rpx; font-size: 32rpx;
font-weight: bold;
.gif-box { line-height: 46rpx;
display: none; margin-bottom: 46rpx;
}
}
&.is-out {
background-color: #ff3e49;
&::after {
background-color: #ff3e49;
}
}
.gif-box {
position: absolute;
width: 200rpx;
height: 80rpx;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
&::after {
content: '';
width: 44rpx;
height: 44rpx;
clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
background-color: #06f;
overflow: hidden;
position: absolute;
bottom: -22rpx;
left: 50%;
margin-left: -22rpx;
}
} }
.white-bg-box { .box-btns {
height: 365rpx; height: 46rpx;
background-color: #fff; line-height: 46rpx;
overflow: hidden; display: flex;
border-radius: 32rpx 32rpx 0 0; justify-content: space-between;
padding: 58rpx 36rpx; color: #999;
.box-title { .is-active {
font-size: 32rpx; color: #06f;
font-weight: bold;
line-height: 46rpx;
margin-bottom: 46rpx;
}
.box-btns {
height: 46rpx;
line-height: 46rpx;
display: flex;
justify-content: space-between;
color: #999;
.is-active {
color: #06f;
}
} }
} }
}
::v-deep .uv-input__content__field-wrapper__field { ::v-deep .uv-input__content__field-wrapper__field {
padding-right: 60rpx; padding-right: 60rpx;