From 60e7f272d8395a0205e3059b14bbc235cc1ea99d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B0=E5=B2=A9?= Date: Tue, 3 Mar 2026 17:58:22 +0800 Subject: [PATCH] =?UTF-8?q?JLBA202505130001=5F=E5=85=B3=E4=BA=8E=E5=90=89?= =?UTF-8?q?=E6=9E=97=E9=93=B6=E8=A1=8C=E6=96=B0=E5=BB=BAAI=E6=99=BA?= =?UTF-8?q?=E8=83=BD=E5=9F=B9=E8=AE=AD=E7=B3=BB=E7=BB=9F=E7=9A=84=E9=9C=80?= =?UTF-8?q?=E6=B1=82=5F=E7=BB=A7=E7=BB=AD=E5=BC=80=E5=8F=91=E8=AF=AD?= =?UTF-8?q?=E9=9F=B3=E6=8C=89=E9=92=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + src/common/ProtocolCodec.ts | 11 +- src/common/common.js | 2 - .../touch-btn/assets/close-active.png | Bin 0 -> 4916 bytes src/components/touch-btn/assets/close.png | Bin 0 -> 4635 bytes src/components/touch-btn/style/index.scss | 2 +- src/components/touch-btn/style/popup.scss | 185 +++++++++++------- src/components/touch-btn/touch-btn.images.ts | 4 + src/components/touch-btn/touch-btn.ts | 145 ++++++++------ src/components/touch-btn/touch-btn.types.ts | 38 +++- src/components/touch-btn/touch-btn.vue | 167 ++++++++++------ src/pages/index/dialog.vue | 14 +- src/pages/practice/index.vue | 16 +- .../ty-camera/utssdk/app-android/config.json | 2 +- .../ty-recording/utssdk/app-android/index.uts | 13 +- .../ty-recording/utssdk/app-ios/index.uts | 2 +- .../ty-recording/utssdk/interface.uts | 2 +- tsconfig.json | 4 +- 18 files changed, 385 insertions(+), 223 deletions(-) create mode 100644 src/components/touch-btn/assets/close-active.png create mode 100644 src/components/touch-btn/assets/close.png diff --git a/package.json b/package.json index 217b2a1a..040ad8a2 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "@dcloudio/vite-plugin-uni": "3.0.0-4060620250520001", "@vue/runtime-core": "^3.4.21", "sass": "1.77.0", + "typescript": "^5.9.3", "vite": "5.2.8" } } diff --git a/src/common/ProtocolCodec.ts b/src/common/ProtocolCodec.ts index 2129c88e..dd6e501d 100644 --- a/src/common/ProtocolCodec.ts +++ b/src/common/ProtocolCodec.ts @@ -47,8 +47,8 @@ export enum ControlCode { AI_ANSWER_OVER = 0b0100, // 本轮AI回答的文本内容已经全部返回 AI_CLUE = 0b0101, // 需要AI提示命令(发后端) AI_CLUE_OVER = 0b0110, // AI提示命令已经全部返回 - FRNOT_TO_SERVER_OVER = 0b0111, // 语音按钮已经抬起 - SERVER_TO_FRNOT_OVER = 0b1000, // 后端已经返回本次全部内容 + FRONT_TO_SERVER_OVER = 0b0111, // 语音按钮已经抬起 + SERVER_TO_FRONT_OVER = 0b1000, // 后端已经返回本次全部内容 } @@ -97,6 +97,11 @@ export enum ControlCommand { FINISH_PLAY = 0b0010, // 一句话播放结束 } +export interface ControlBody { + type: ControlCode; // type 是必选属性,关联你的 ControlCode 枚举 + [key: string]: any; // 索引签名:兼容后端返回的任意动态属性(txt/ossKey 等) +} + /** * 解包返回结果接口 */ @@ -108,7 +113,7 @@ export interface UnpackedResult { compression : CompressionType; compressionName : keyof typeof CompressionType; sequence : number; // 消息顺序号(0~65535,默认0) - body : Uint8Array | string | object | unknown[] | null; // PING 消息可能返回 null + body : Uint8Array | string | object | unknown[] | null | ControlBody; // PING 消息可能返回 null } /** diff --git a/src/common/common.js b/src/common/common.js index 45127627..73107d3d 100644 --- a/src/common/common.js +++ b/src/common/common.js @@ -83,12 +83,10 @@ const hideLoading = () => uni.hideLoading() // 根据姓名的长度生成对应的带星号格式 export const formatName = (name) => { - // 处理空值或非字符串情况 if (!name || typeof name !== 'string') { return ''; } - const familyName = name.charAt(0); // 取姓氏(第一个字符) const givenNameLength = name.length - 1; // 名字部分的长度 diff --git a/src/components/touch-btn/assets/close-active.png b/src/components/touch-btn/assets/close-active.png new file mode 100644 index 0000000000000000000000000000000000000000..f139f0960ec4ed3944170905b34d2c1c03c0c9de GIT binary patch literal 4916 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 .send-btn > .send-btn-loading { + display: block !important; + pointer-events: none; + cursor: not-allowed; + } + &.success > .send-btn > .send-btn-text { + display: block !important; + } + &.failed .send-btn { + pointer-events: none; + cursor: not-allowed; + background-color: rgb(80, 80, 80); + color: rgb(106, 106, 106); + .send-btn-text { + display: block !important; + } } } -.talk-dialog-box{ - position: absolute; - bottom: 520rpx ; +.talk-dialog-box { + position: relative; + // bottom: 560rpx; width: 100%; display: flex; justify-content: center; @@ -173,7 +223,7 @@ // 滑动到离开变红 &.is-out { background-color: #ff3e49; - + &::after { background-color: #ff3e49; } @@ -199,9 +249,11 @@ transition: left 0.3s ease-out; // 时长0.3秒,ease-out缓动(结束时变慢,更自然) } // 控制下边箭头指向发送 - &.init::after { + &.init::after, + &.success::after { left: 78% !important; } + &.failed { width: 506rpx; height: 114rpx; @@ -211,11 +263,15 @@ background-color: #ff3e49; } // 激活错误提示框 - .error-tip-div{ + .error-tip-div { display: flex !important; visibility: visible; // 激活后可见 // animation: fadeIn 0.4s ease-out 0.1s forwards; } + .textarea { + // display: none; + visibility: hidden; // 初始不可见且不占交互位置 + } } } .talk-dialog { @@ -239,29 +295,22 @@ top: 50%; transform: translate(-50%, -50%); } - .error-tip-div{ + .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{ + .img { margin-right: 10rpx; width: 26rpx; height: 26rpx; } } - - - - - - } diff --git a/src/components/touch-btn/touch-btn.images.ts b/src/components/touch-btn/touch-btn.images.ts index 83c8e11e..c6f901a8 100644 --- a/src/components/touch-btn/touch-btn.images.ts +++ b/src/components/touch-btn/touch-btn.images.ts @@ -4,7 +4,11 @@ export { default as cancelBtn } from './assets/cancel.png'; export { default as cancelActiveBtn } from './assets/cancel-active.png'; export { default as translateBtn } from './assets/translate.png'; export { default as translateActiveBtn } from './assets/translate-active.png'; +export { default as closeBtn } from './assets/close.png'; +export { default as closeActiveBtn } from './assets/close-active.png'; + export { default as voiceWhiteBtn } from './assets/voice-white.gif'; + export const icKeyboardSvg = () : string => { const svgXml = ` diff --git a/src/components/touch-btn/touch-btn.ts b/src/components/touch-btn/touch-btn.ts index 94387d3c..f778640e 100644 --- a/src/components/touch-btn/touch-btn.ts +++ b/src/components/touch-btn/touch-btn.ts @@ -1,11 +1,13 @@ import { ErrorCode, ShowErrorTipOptions } from './touch-btn.types'; import { get_base_url } from '@/api/request.js'; -import { getToken } from '@/common/common.js'; +import { getToken, getPhoneEnvBool } from '@/common/common.js'; +const IsAndEnv = getPhoneEnvBool('AND') + // const url = '/trapractice/voiceCallSocket/voiceCall' // const url = '/traapp/voiceTransSocket/open'; -const url = '/traapp/voiceAiModelSocket/open'; +// const url = '/traapp/asrAiModelSocket/open'; +const url = '/traapp/asrAiModelSocket/open'; const _baseUrl = get_base_url(url).split('http').join('ws'); -// const _baseUrl = ''; export const wsUrl = _baseUrl + url + '?summary=' + getToken(); const windowInfo = uni.getWindowInfo(); // 安全区域信息 @@ -17,58 +19,48 @@ export const bottomAreaTopBoundary = safeArea.bottom - btnHeight; // 屏幕横向分割线 export const horizontalSplitX = safeArea.right / 2; - -// 假设你在 Vue3 环境中,ws、wsStatus 是 ref 响应式变量 -import { ref } from 'vue'; - -const ws = ref(null); -const wsStatus = ref(false); - /** * 封装WebSocket连接为Promise * @param {string} wsUrl - WebSocket连接地址 * @returns {Promise} 成功时返回WS实例,失败时reject错误信息 */ -export const initWebSocket = (wsUrl: string): Promise => { - return new Promise((resolve, reject) => { - // 1. 创建WS连接 - const ws = uni.connectSocket({ - url: wsUrl, - fail: (err) => { - console.error('WS连接创建失败:', err); - // 连接创建失败直接reject - reject(new Error(`WS连接创建失败:${err.errMsg || err.message}`)); - }, - success: () => { - console.log('WS连接创建请求已发送'); - } - }); +export const initWebSocket = (wsUrl : string) : Promise => { + return new Promise((resolve, reject) => { + // 1. 创建WS连接 + const ws = uni.connectSocket({ + url: wsUrl, + fail: (err) => { + console.error('WS连接创建失败:', err); + // 连接创建失败直接reject + reject(new Error(`WS连接创建失败:${err.errMsg || err.message}`)); + }, + success: () => { + } + }); + + // 2. 监听连接成功(核心:连接成功时resolve,返回WS实例) + const onOpenHandler = (res) => { + resolve(ws); + }; - // 2. 监听连接成功(核心:连接成功时resolve,返回WS实例) - const onOpenHandler = (res) => { - console.log('ws已连接', res); - // resolve返回WS实例 - resolve(ws); - }; + // 4. 监听连接错误(出错时reject) + const onErrorHandler = (err) => { + console.log('WS连接错误', err); + // reject返回错误信息 + reject(new Error(`WS连接错误:${err.errMsg || err.message}`)); + }; - // 4. 监听连接错误(出错时reject) - const onErrorHandler = (err) => { - console.log('WS连接错误', err); - // reject返回错误信息 - reject(new Error(`WS连接错误:${err.errMsg || err.message}`)); - }; - - // 5. 监听连接关闭(如果在open前关闭,也reject) - const onCloseHandler = (err) => { - console.log('WS连接已关闭', err); - // 连接未成功就关闭,执行reject - reject(new Error(`WS连接已关闭:${err.errMsg || err.message}`)); - }; - // 绑定监听事件 - ws.onOpen(onOpenHandler); - ws.onError(onErrorHandler); - ws.onClose(onCloseHandler); - }); + // 5. 监听连接关闭(如果在open前关闭,也reject) + const onCloseHandler = (err) => { + console.log('WS连接已关闭', err); + // 连接未成功就关闭,执行reject + reject(new Error(`WS连接已关闭:${err.errMsg || err.message}`)); + }; + // 绑定监听事件 + ws.onOpen(onOpenHandler); + ws.onError(onErrorHandler); + ws.onClose(onCloseHandler); + }); }; /** @@ -78,19 +70,48 @@ export const initWebSocket = (wsUrl: string): Promise => { * @returns 固定返回false(保持原有返回值类型) */ export function showErrorTip( - errorCode: ErrorCode, - options: ShowErrorTipOptions = {} -): boolean { - // 解构可选参数,设置默认延迟时间为200ms - const { delayTime = 200 } = options; + errorCode : ErrorCode, + options : ShowErrorTipOptions = {} +) : boolean { + // 解构可选参数,设置默认延迟时间为200ms + const { delayTime = 200 } = options; - // 延迟显示对应的错误提示 - setTimeout(() => { - uni.showToast({ - title: errorCode, // 直接使用枚举值作为提示文本 - icon: 'none' - }); - }, delayTime); // 使用传入的延迟时间(或默认200ms) + // 延迟显示对应的错误提示 + setTimeout(() => { + uni.showToast({ + title: errorCode, // 直接使用枚举值作为提示文本 + icon: 'none' + }); + }, delayTime); // 使用传入的延迟时间(或默认200ms) + + return false; +} + +/** + * 跨平台震动反馈封装(适配安卓/iOS)轻震 + * @returns {Promise} 是否成功触发震动 + */ +export const vibrateLight = () : void => { + // #ifdef APP-PLUS + try { + if (IsAndEnv) { + uni.vibrateShort({ + success: function () { + console.log('success'); + } + }); + } else { + let UIImpactFeedbackGenerator = plus.ios.importClass( + 'UIImpactFeedbackGenerator' + ) + let impact = new UIImpactFeedbackGenerator() + impact.prepare() + impact.init(1) + impact.impactOccurred() + } + } catch (error) { + console.error('震动功能执行异常:', error); + } + // #endif +} - return false; -} \ No newline at end of file diff --git a/src/components/touch-btn/touch-btn.types.ts b/src/components/touch-btn/touch-btn.types.ts index a32303e4..7db8a762 100644 --- a/src/components/touch-btn/touch-btn.types.ts +++ b/src/components/touch-btn/touch-btn.types.ts @@ -6,7 +6,7 @@ * - 'text_input' : 文字输入中状态(文字输入面板展开,用户正在输入) * - 'voice_to_text' : 语音转文字状态(独立的语音转文字页面/面板展开) */ -export type StatusType = 'default' | 'voice_recording' | 'text_input' | 'voice_to_text'; +export type StatusType = 'default' | 'voice_recording' | 'text_input' | 'voice_to_text' | 'voice_send_ing'; /** * 语音转文字的子状态(仅当主状态为 voice_to_text 时生效) @@ -14,7 +14,7 @@ export type StatusType = 'default' | 'voice_recording' | 'text_input' | 'voice_t export type VoiceToTextSubStatus = | '' // 纯空 | 'init' // 初始化(刚进入语音转文字页面) - | 'loading' // 转换中(正在处理语音→文字) + // | 'loading' // 转换中(正在处理语音→文字) | 'success' // 转换完成(成功得到文字结果) | 'failed'; // 转换失败(网络/识别错误等) @@ -31,4 +31,36 @@ export enum ErrorCode { export interface ShowErrorTipOptions { // 提示框延迟显示时间(毫秒),默认200ms delayTime?: number; -} \ No newline at end of file +} + + +/** + * 触摸按钮组件的自定义事件类型定义 + * 包含录音、提交、加载相关的所有事件及参数规范 + */ +export type TouchBtnEmits = { + /** + * 提交语音内容(语音转文字完成后触发) + * @param voiceData - 语音相关数据(包含ossKey、识别文本等核心字段) + * @param duration - 录音时长(单位:毫秒),用于校验最短录音时长 + */ + submitVoice: [params: { ossKey: string; text: string }]; + /** + * 提交文字答案(用户确认识别结果/手动编辑后触发) + * @param answerText - 最终提交的文字答案内容 + * @param isManualEdit - 是否手动编辑过识别结果(true=手动修改,false=纯语音识别) + */ + submitAnswer: [answerText: string, isManualEdit: boolean]; + /** + * 开始录音(用户点击录音按钮时触发) + * @param recordParams - 录音初始化参数(可选,默认使用组件内置配置) + */ + startRecord: [recordParams?: { sampleRate: number; channels: number }]; + /** + * 发送加载状态(提交语音/文字时触发,控制父组件加载动画) + * @param isLoading - 是否处于加载中(true=显示加载,false=隐藏加载) + * @param loadingText - 加载提示文本(可选,默认:"处理中...") + */ + sendLoading: [params: { recordId: string; loadingText?: string }]; +}; + diff --git a/src/components/touch-btn/touch-btn.vue b/src/components/touch-btn/touch-btn.vue index 316c6797..938eae05 100644 --- a/src/components/touch-btn/touch-btn.vue +++ b/src/components/touch-btn/touch-btn.vue @@ -1,29 +1,28 @@