From dfcac5131236e6635af4359a5ec3eee322047ce7 Mon Sep 17 00:00:00 2001 From: rainv123 <2148537152@qq.com> Date: Fri, 26 Sep 2025 14:33:06 +0800 Subject: [PATCH 1/5] =?UTF-8?q?updata:=E7=A7=BB=E5=8A=A8=E7=AB=AF=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E8=AF=AD=E8=A8=80=E5=88=87=E6=8D=A2=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/manager-mobile/src/App.vue | 55 +++ main/manager-mobile/src/i18n/en.ts | 406 ++++++++++++++++++ main/manager-mobile/src/i18n/index.ts | 70 +++ main/manager-mobile/src/i18n/zh_CN.ts | 406 ++++++++++++++++++ main/manager-mobile/src/i18n/zh_TW.ts | 406 ++++++++++++++++++ main/manager-mobile/src/main.ts | 7 + main/manager-mobile/src/pages.json | 8 +- main/manager-mobile/src/pages/agent/edit.vue | 78 ++-- main/manager-mobile/src/pages/agent/index.vue | 12 +- main/manager-mobile/src/pages/agent/tools.vue | 69 +-- .../src/pages/chat-history/detail.vue | 21 +- .../src/pages/chat-history/index.vue | 59 +-- .../components/ultrasonic-config.vue | 173 ++++---- .../device-config/components/wifi-config.vue | 43 +- .../components/wifi-selector.vue | 171 ++++---- .../src/pages/device-config/index.vue | 35 +- .../manager-mobile/src/pages/device/index.vue | 67 +-- main/manager-mobile/src/pages/index/index.vue | 54 +-- main/manager-mobile/src/pages/login/index.vue | 187 ++++++-- .../src/pages/register/index.vue | 140 +++--- .../src/pages/settings/index.vue | 205 ++++++--- .../src/pages/voiceprint/index.vue | 124 +++--- main/manager-mobile/src/store/lang.ts | 41 ++ 23 files changed, 2248 insertions(+), 589 deletions(-) create mode 100644 main/manager-mobile/src/i18n/en.ts create mode 100644 main/manager-mobile/src/i18n/index.ts create mode 100644 main/manager-mobile/src/i18n/zh_CN.ts create mode 100644 main/manager-mobile/src/i18n/zh_TW.ts create mode 100644 main/manager-mobile/src/store/lang.ts diff --git a/main/manager-mobile/src/App.vue b/main/manager-mobile/src/App.vue index a9faa6e7..d31d32d7 100644 --- a/main/manager-mobile/src/App.vue +++ b/main/manager-mobile/src/App.vue @@ -1,12 +1,16 @@ @@ -456,18 +457,18 @@ async function stopAudio() { - 选中网络: {{ props.selectedNetwork.ssid }} + {{ t('deviceConfig.selectedNetwork') }}: {{ props.selectedNetwork.ssid }} - 信号: {{ props.selectedNetwork.rssi }}dBm + {{ t('deviceConfig.signal') }}: {{ props.selectedNetwork.rssi }}dBm - {{ props.selectedNetwork.authmode === 0 ? '开放网络' : '加密网络' }} + {{ props.selectedNetwork.authmode === 0 ? t('deviceConfig.openNetwork') : t('deviceConfig.encryptedNetwork') }} - 密码: {{ '*'.repeat(props.password.length) }} + {{ t('deviceConfig.password') }}: {{ '*'.repeat(props.password.length) }} @@ -482,81 +483,81 @@ async function stopAudio() { :disabled="!canGenerate" @click="generateAndPlay" > - {{ generating ? '生成中...' : '🎵 生成并播放声波' }} - + {{ generating ? t('deviceConfig.generating') : '🎵 ' + t('deviceConfig.generateAndPlaySoundWave') }} + - - {{ playing ? '播放中...' : '🔊 播放声波' }} - + + {{ playing ? t('deviceConfig.playing') : '🔊 ' + t('deviceConfig.playSoundWave') }} + - - ⏹️ 停止播放 - + + ⏹️ {{ t('deviceConfig.stopPlaying') }} + - - 自动循环播放声波 - - + + {{ t('deviceConfig.autoLoopPlaySoundWave') }} + + - 配网音频文件 + {{ t('deviceConfig.configAudioFile') }} - 时长: {{ audioLengthText }} + {{ t('deviceConfig.duration') }}: {{ audioLengthText }} - - 超声波配网说明 + + {{ t('deviceConfig.ultrasonicConfigInstructions') }} + + + + 1. {{ t('deviceConfig.ensureWifiNetworkSelectedAndPasswordEntered') }} + + + 2. {{ t('deviceConfig.clickGenerateAndPlaySoundWave') }} + + + 3. {{ t('deviceConfig.bringPhoneCloseToXiaozhiDevice') }} + + + 4. {{ t('deviceConfig.duringAudioPlaybackXiaozhiWillReceive') }} + + + 5. {{ t('deviceConfig.afterConfigSuccessDeviceWillConnect') }} + + + {{ t('deviceConfig.usesAfskModulation') }} + + + {{ t('deviceConfig.ensureModeratePhoneVolume') }} + + - - - 1. 确保已选择WiFi网络并输入密码 - - - 2. 点击生成并播放声波,系统会将配网信息编码为音频 - - - 3. 将手机靠近xiaozhi设备(距离1-2米) - - - 4. 音频播放时,xiaozhi会接收并解码配网信息 - - - 5. 配网成功后设备会自动连接WiFi网络 - - - 使用AFSK调制技术,通过1800Hz和1500Hz频率传输数据 - - - 请确保手机音量适中,避免环境噪音干扰 - - - diff --git a/main/manager-mobile/src/pages/device-config/components/wifi-config.vue b/main/manager-mobile/src/pages/device-config/components/wifi-config.vue index ac54b6b6..e8a4d6b7 100644 --- a/main/manager-mobile/src/pages/device-config/components/wifi-config.vue +++ b/main/manager-mobile/src/pages/device-config/components/wifi-config.vue @@ -1,6 +1,7 @@ @@ -285,10 +286,10 @@ defineExpose({ - 暂无设备 + {{ t('device.noDevice') }} - 点击右下角 + 号绑定您的第一个设备 + {{ t('device.clickToBindFirstDevice') }} diff --git a/main/manager-mobile/src/pages/index/index.vue b/main/manager-mobile/src/pages/index/index.vue index bb74ab1c..8678b80c 100644 --- a/main/manager-mobile/src/pages/index/index.vue +++ b/main/manager-mobile/src/pages/index/index.vue @@ -17,6 +17,7 @@ import { useMessage } from 'wot-design-uni' import useZPaging from 'z-paging/components/z-paging/js/hooks/useZPaging.js' import { createAgent, deleteAgent, getAgentList } from '@/api/agent/agent' import { toast } from '@/utils/toast' +import { t } from '@/i18n' defineOptions({ name: 'Home', @@ -78,11 +79,11 @@ async function handleCreateAgent(agentName: string) { await createAgent({ agentName: agentName.trim() }) // 创建成功后刷新列表 pagingRef.value.reload() - toast.success(`智能体"${agentName}"创建成功!`) + toast.success(`${t('home.agentName')}"${agentName}"${t('message.saveSuccess')}`) } catch (error: any) { console.error('创建智能体失败:', error) - const errorMessage = error?.message || '创建失败,请重试' + const errorMessage = error?.message || t('message.saveFail') toast.error(errorMessage) } } @@ -93,11 +94,11 @@ async function handleDeleteAgent(agent: Agent) { await deleteAgent(agent.id) // 删除成功后刷新列表 pagingRef.value.reload() - toast.success(`智能体"${agent.agentName}"已删除`) + toast.success(`${t('home.agentName')}${t('message.deleteSuccess')}`) } catch (error: any) { console.error('删除智能体失败:', error) - const errorMessage = error?.message || '删除失败,请重试' + const errorMessage = error?.message || t('message.deleteFail') toast.error(errorMessage) } } @@ -119,13 +120,13 @@ function handleCardClick(agent: Agent) { function openCreateDialog() { message .prompt({ - title: '创建智能体', + title: t('home.dialogTitle'), msg: '', - inputPlaceholder: '例如:客服助手、语音助理、知识问答', + inputPlaceholder: t('home.inputPlaceholder'), inputValue: '', inputPattern: /^[\u4E00-\u9FA5a-z0-9\s]{1,50}$/i, - confirmButtonText: '立即创建', - cancelButtonText: '取消', + confirmButtonText: t('home.createNow'), + cancelButtonText: t('common.cancel'), }) .then(async (result: any) => { if (result.value && String(result.value).trim()) { @@ -144,12 +145,12 @@ function formatTime(timeStr: string) { const diff = now.getTime() - date.getTime() if (diff < 60000) - return '刚刚' + return t('home.justNow') if (diff < 3600000) - return `${Math.floor(diff / 60000)}分钟前` + return `${Math.floor(diff / 60000)}${t('home.minutesAgo')}` if (diff < 86400000) - return `${Math.floor(diff / 3600000)}小时前` - return `${Math.floor(diff / 86400000)}天前` + return `${Math.floor(diff / 3600000)}${t('home.hoursAgo')}` + return `${Math.floor(diff / 86400000)}${t('home.daysAgo')}` } // 页面显示时刷新列表 @@ -159,12 +160,20 @@ onShow(() => { pagingRef.value.reload() } }) + +// 在组件挂载后设置导航栏标题 +import { onMounted } from 'vue' +onMounted(() => { + uni.setNavigationBarTitle({ + title: t('home.pageTitle') + }) +}) @@ -260,10 +266,10 @@ onShow(() => { - 暂无智能体 + {{ t('home.emptyState') }} - 点击右下角 + 号创建您的第一个智能体 + {{ t('home.createFirstAgent') }} diff --git a/main/manager-mobile/src/pages/login/index.vue b/main/manager-mobile/src/pages/login/index.vue index 71f53cce..00912902 100644 --- a/main/manager-mobile/src/pages/login/index.vue +++ b/main/manager-mobile/src/pages/login/index.vue @@ -3,7 +3,7 @@ "layout": "default", "style": { "navigationStyle": "custom", - "navigationBarTitleText": "登陆" + "navigationBarTitleText": "Login" } } @@ -15,6 +15,9 @@ import { login } from '@/api/auth' import { useConfigStore } from '@/store' import { getEnvBaseUrl } from '@/utils' import { toast } from '@/utils/toast' +// 导入国际化相关功能 +import { t, changeLanguage, getSupportedLanguages, initI18n } from '@/i18n' +import type { Language } from '@/store/lang' // 获取屏幕边界到安全区域距离 let safeAreaInsets @@ -136,28 +139,28 @@ async function handleLogin() { // 表单验证 if (loginType.value === 'username') { if (!formData.value.username) { - toast.warning('请输入用户名') + toast.warning(t('login.enterUsername')) return } } else { if (!formData.value.mobile) { - toast.warning('请输入手机号') + toast.warning(t('login.enterPhone')) return } // 手机号格式验证 const phoneRegex = /^1[3-9]\d{9}$/ if (!phoneRegex.test(formData.value.mobile)) { - toast.warning('请输入正确的手机号') + toast.warning(t('login.enterPhone')) return } } if (!formData.value.password) { - toast.warning('请输入密码') + toast.warning(t('login.enterPassword')) return } if (!formData.value.captcha) { - toast.warning('请输入验证码') + toast.warning(t('login.enterCaptcha')) return } @@ -177,7 +180,7 @@ async function handleLogin() { uni.setStorageSync('token', response.token) uni.setStorageSync('expire', response.expire) - toast.success('登录成功') + toast.success(t('message.loginSuccess')) // 跳转到主页 setTimeout(() => { @@ -189,6 +192,10 @@ async function handleLogin() { catch (error: any) { // 登录失败重新获取验证码 refreshCaptcha() + // 处理验证码错误 - 从error.message中解析错误码 + if (error.message.includes('请求错误[10067]')) { + toast.warning(t('login.captchaError')) + } } finally { loading.value = false @@ -200,14 +207,26 @@ onLoad(() => { refreshCaptcha() }) +// 语言切换相关 +const showLanguageSheet = ref(false) +const supportedLanguages = getSupportedLanguages() + +// 初始化国际化 +initI18n() + +// 切换语言 +function handleLanguageChange(lang: Language) { + changeLanguage(lang) + showLanguageSheet.value = false +} + // 组件挂载时确保配置已加载 onMounted(async () => { if (!configStore.config.name) { try { await configStore.fetchPublicConfig() - } - catch (error) { - console.error('获取配置失败:', error) + } catch (error) { + console.error(t('login.fetchConfigError'), error) } } }) @@ -219,21 +238,25 @@ onMounted(async () => { - - - + + + + + 🌐 + + + + + + @@ -253,7 +276,7 @@ onMounted(async () => { v-model="formData.mobile" custom-class="styled-input" no-border - placeholder="请输入手机号码" + :placeholder="t('login.enterPhone')" type="number" :maxlength="11" /> @@ -270,7 +293,7 @@ onMounted(async () => { v-model="formData.username" custom-class="styled-input" no-border - placeholder="请输入用户名" + :placeholder="t('login.enterUsername')" /> @@ -282,7 +305,7 @@ onMounted(async () => { v-model="formData.password" custom-class="styled-input" no-border - placeholder="请输入密码" + :placeholder="t('login.enterPassword')" clearable show-password :maxlength="20" @@ -296,7 +319,7 @@ onMounted(async () => { v-model="formData.captcha" custom-class="styled-input" no-border - placeholder="请输入验证码" + :placeholder="t('login.enterCaptcha')" :maxlength="6" /> @@ -307,7 +330,7 @@ onMounted(async () => { - 忘记密码? + {{ t('login.forgotPassword') }} @@ -315,15 +338,15 @@ onMounted(async () => { class="login-btn" @click="handleLogin" > - {{ loading ? '登录中...' : '登录' }} + {{ loading ? t('login.loggingIn') : t('login.loginButton') }} - 还没有账户? + {{ t('login.noAccount') }} - 立即注册 + {{ t('login.registerNow') }} @@ -352,7 +375,7 @@ onMounted(async () => { @@ -386,11 +409,33 @@ onMounted(async () => { custom-class="confirm-btn" @click="closeAreaCodeSheet" > - 确认 + {{ t('login.confirm') }} + + + + + + + + {{ lang.name }} + + + + + @@ -793,20 +838,52 @@ onMounted(async () => { } } } -.server-btn { +// 右上角按钮组 +.top-right-buttons { position: absolute; - right: 20rpx; // 距离右边距 - top: 40rpx; // 顶部稍微下移,不贴状态栏 + right: 20rpx; + display: flex; + gap: 20rpx; + z-index: 999; +} + +// 语言切换按钮 +.lang-btn { width: 48rpx; height: 48rpx; display: flex; align-items: center; justify-content: center; - z-index: 999; cursor: pointer; - background: rgba(255, 255, 255, 0.15); // 半透明背景,更好看 - border-radius: 24rpx; // 圆形按钮 - box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.2); // 阴影 + background: rgba(255, 255, 255, 0.15); + border-radius: 24rpx; + box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.2); + + &:active { + transform: scale(0.95); + } + + .lang-text-icon { + font-size: 28rpx; + color: #FFFFFF; + } + + &:hover { + background: rgba(255, 255, 255, 0.25); + } +} + +// 服务端设置按钮 +.server-btn { + width: 48rpx; + height: 48rpx; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + background: rgba(255, 255, 255, 0.15); + border-radius: 24rpx; + box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.2); &:active { transform: scale(0.95); @@ -814,11 +891,45 @@ onMounted(async () => { .server-icon { font-size: 28rpx; - color: #FFFFFF; // 白色图标 + color: #FFFFFF; } &:hover { - background: rgba(255, 255, 255, 0.25); // 悬停效果 + background: rgba(255, 255, 255, 0.25); + } +} + +// 语言选择弹窗样式 +.language-sheet { + background: #ffffff; + border-radius: 24rpx 24rpx 0 0; + overflow: hidden; + + .language-list { + max-height: 60vh; + padding: 0 40rpx; + + .language-item { + display: flex; + align-items: center; + padding: 32rpx 0; + border-bottom: 1rpx solid #f8f9fa; + cursor: pointer; + transition: background-color 0.3s ease; + + &:hover { + background-color: #f8f9fa; + } + + &:last-child { + border-bottom: none; + } + + .language-name { + font-size: 32rpx; + color: #333333; + } + } } } diff --git a/main/manager-mobile/src/pages/register/index.vue b/main/manager-mobile/src/pages/register/index.vue index 91463e1d..b78d61a6 100644 --- a/main/manager-mobile/src/pages/register/index.vue +++ b/main/manager-mobile/src/pages/register/index.vue @@ -14,6 +14,8 @@ import { register, sendSmsCode } from '@/api/auth' import { useConfigStore } from '@/store' import { getEnvBaseUrl } from '@/utils' import { toast } from '@/utils/toast' +// 导入国际化相关功能 +import { t, initI18n } from '@/i18n' // 获取屏幕边界到安全区域距离 let safeAreaInsets @@ -134,18 +136,18 @@ async function refreshCaptcha() { // 发送短信验证码 async function sendSmsVerification() { if (!formData.value.mobile) { - toast.warning('请输入手机号') + toast.warning(t('register.enterPhone')) return } if (!formData.value.captcha) { - toast.warning('请输入图形验证码') + toast.warning(t('register.enterCode')) return } // 手机号格式验证 const phoneRegex = /^1[3-9]\d{9}$/ if (!phoneRegex.test(formData.value.mobile)) { - toast.warning('请输入正确的手机号') + toast.warning(t('register.enterPhone')) return } @@ -157,7 +159,7 @@ async function sendSmsVerification() { captchaId: formData.value.captchaId, }) - toast.success('验证码发送成功') + toast.success(t('message.registerSuccess')) // 开始倒计时 smsCountdown.value = 60 @@ -169,6 +171,10 @@ async function sendSmsVerification() { }, 1000) } catch (error: any) { + // 处理验证码错误 - 从error.message中解析错误码 + if (error.message.includes('请求错误[10067]')) { + toast.warning(t('login.captchaError')) + } // 发送失败重新获取图形验证码 refreshCaptcha() } @@ -182,44 +188,44 @@ async function handleRegister() { // 表单验证 if (registerType.value === 'username') { if (!formData.value.username) { - toast.warning('请输入用户名') + toast.warning(t('register.enterUsername')) return } } else { if (!formData.value.mobile) { - toast.warning('请输入手机号') + toast.warning(t('register.enterPhone')) return } // 手机号格式验证 const phoneRegex = /^1[3-9]\d{9}$/ if (!phoneRegex.test(formData.value.mobile)) { - toast.warning('请输入正确的手机号') + toast.warning(t('register.enterPhone')) return } if (!formData.value.mobileCaptcha) { - toast.warning('请输入短信验证码') + toast.warning(t('register.enterCode')) return } } if (!formData.value.password) { - toast.warning('请输入密码') + toast.warning(t('register.enterPassword')) return } if (!formData.value.confirmPassword) { - toast.warning('请确认密码') + toast.warning(t('register.confirmPassword')) return } if (formData.value.password !== formData.value.confirmPassword) { - toast.warning('两次输入的密码不一致') + toast.warning(t('register.confirmPassword')) return } if (!formData.value.captcha) { - toast.warning('请输入验证码') + toast.warning(t('register.enterCode')) return } @@ -239,7 +245,7 @@ async function handleRegister() { } await register(registerData) - toast.success('注册成功') + toast.success(t('message.registerSuccess')) // 跳转到登录页 setTimeout(() => { @@ -247,6 +253,10 @@ async function handleRegister() { }, 1000) } catch (error: any) { + // 处理验证码错误 - 从error.message中解析错误码 + if (error.message.includes('请求错误[10067]')) { + toast.warning(t('login.captchaError')) + } // 注册失败重新获取验证码 refreshCaptcha() } @@ -275,7 +285,11 @@ onMounted(async () => { console.error('获取配置失败:', error) } } + // 初始化国际化 + initI18n() }) + + diff --git a/main/manager-mobile/src/pages/settings/index.vue b/main/manager-mobile/src/pages/settings/index.vue index 220d2338..5a099bf9 100644 --- a/main/manager-mobile/src/pages/settings/index.vue +++ b/main/manager-mobile/src/pages/settings/index.vue @@ -11,6 +11,8 @@ import { clearServerBaseUrlOverride, getEnvBaseUrl, getServerBaseUrlOverride, se import { isMp } from '@/utils/platform' import { computed, onMounted, reactive, ref } from 'vue' import { useToast } from 'wot-design-uni' +import { t, changeLanguage, getSupportedLanguages, getCurrentLanguage } from '@/i18n' +import type { Language } from '@/store/lang' defineOptions({ name: 'SettingsPage', @@ -62,7 +64,7 @@ function validateUrl() { } if (!/^https?:\/\/.+\/xiaozhi$/.test(baseUrlInput.value)) { - urlError.value = '请输入有效的服务端地址(以 http 或 https 开头,并以 /xiaozhi 结尾)' + urlError.value = t('settings.validServerUrl') } } @@ -85,12 +87,12 @@ async function testServerBaseUrl() { if (response.statusCode === 200) { return true } else { - toast.error('无效地址,请检查服务端是否启动或网络连接是否正常') + toast.error(t('message.invalidAddress')) return false } } catch (error) { console.error('测试服务端地址失败:', error) - toast.error('无效地址,请检查服务端是否启动或网络连接是否正常') + toast.error(t('message.invalidAddress')) return false } } @@ -98,7 +100,7 @@ async function testServerBaseUrl() { // 保存服务端地址 async function saveServerBaseUrl() { if (!baseUrlInput.value || !/^https?:\/\/.+\/xiaozhi$/.test(baseUrlInput.value)) { - toast.warning('请输入有效的服务端地址(以 http 或 https 开头,并以 /xiaozhi 结尾)') + toast.warning(t('settings.validServerUrl')) return } @@ -113,21 +115,32 @@ async function saveServerBaseUrl() { clearAllCacheAfterUrlChange() uni.showModal({ - title: '重启应用', - content: '服务端地址已保存并清空缓存,是否立即重启生效?', - confirmText: '立即重启', - cancelText: '稍后', + title: t('settings.restartApp'), + content: t('settings.serverUrlSavedAndCacheCleared'), + confirmText: t('settings.restartNow'), + cancelText: t('settings.restartLater'), success: (res) => { if (res.confirm) { restartApp() } else { - toast.success('已保存,可稍后手动重启应用') + toast.success(t('settings.restartSuccess')) } }, }) } +// 语言切换 +const supportedLanguages = getSupportedLanguages() +const currentLanguage = ref(getCurrentLanguage()) +const showLanguageSheet = ref(false) + +function handleLanguageChange(lang: Language) { + changeLanguage(lang) + showLanguageSheet.value = false + toast.success(t('settings.languageChanged')) +} + // 重置为 env 默认 function resetServerBaseUrl() { clearServerBaseUrlOverride() @@ -137,16 +150,16 @@ function resetServerBaseUrl() { clearAllCacheAfterUrlChange() uni.showModal({ - title: '重启应用', - content: '已重置为默认地址并清空缓存,是否立即重启生效?', - confirmText: '立即重启', - cancelText: '稍后', + title: t('settings.restartApp'), + content: t('settings.resetToDefaultAndCacheCleared'), + confirmText: t('settings.restartNow'), + cancelText: t('settings.restartLater'), success: (res) => { if (res.confirm) { restartApp() } else { - toast.success('已重置,可稍后手动重启应用') + toast.success(t('settings.resetSuccess')) } }, }) @@ -185,8 +198,7 @@ function clearAllCacheAfterUrlChange() { // 重新获取缓存信息 getCacheInfo() - } - catch (error) { + } catch (error) { console.error('清除缓存失败:', error) } } @@ -195,12 +207,14 @@ function clearAllCacheAfterUrlChange() { async function clearCache() { try { uni.showModal({ - title: '确认清除', - content: '确定要清除所有缓存吗?这将删除所有数据包括登录状态,需要重新登录。', + title: t('settings.confirmClear'), + content: t('settings.confirmClearMessage'), + confirmText: t('common.confirm'), + cancelText: t('common.cancel'), success: (res) => { if (res.confirm) { clearAllCacheAfterUrlChange() - toast.success('缓存清除成功,即将跳转到登录页') + toast.success(t('settings.cacheCleared')) // 延迟跳转到登录页 setTimeout(() => { @@ -209,22 +223,22 @@ async function clearCache() { } }, }) - } - catch (error) { + } catch (error) { console.error('清除缓存失败:', error) - toast.error('清除缓存失败') + toast.error(t('settings.clearCacheFailed')) } } // 关于我们 function showAbout() { uni.showModal({ - title: `关于${import.meta.env.VITE_APP_TITLE}`, - content: `${import.meta.env.VITE_APP_TITLE}\n\n基于 Vue.js 3 + uni-app 构建的跨平台移动端管理应用,为小智ESP32智能硬件提供设备管理、智能体配置等功能。\n\n© 2025 xiaozhi-esp32-server`, - title: `关于小智智控台`, - content: `小智智控台\n\n基于 Vue.js 3 + uni-app 构建的跨平台移动端管理应用,为小智ESP32智能硬件提供设备管理、智能体配置等功能。\n\n© 2025 xiaozhi-esp32-server 0.8.3`, + title: t('settings.aboutApp', { appName: import.meta.env.VITE_APP_TITLE }), + content: t('settings.aboutContent', { + appName: import.meta.env.VITE_APP_TITLE, + version: '0.8.3' + }), showCancel: false, - confirmText: '确定', + confirmText: t('common.confirm'), }) } @@ -234,19 +248,24 @@ onMounted(async () => { loadServerBaseUrl() } getCacheInfo() + + // 动态设置导航栏标题为国际化文本 + uni.setNavigationBarTitle({ + title: t('settings.title') + }) }) +// 保持与 edit.vue 一致的风格,样式主要通过类名控制 + +// 语言选择弹窗样式 +.language-sheet { + .language-list { + max-height: 50vh; + .language-item { + padding: 30rpx 0; + text-align: center; + border-bottom: 1rpx solid #f0f0f0; + .language-name { + font-size: 28rpx; + color: #333; + } + &:last-child { + border-bottom: none; + } + &:active { + background-color: #f5f7fb; + } + } + } +} + diff --git a/main/manager-mobile/src/pages/voiceprint/index.vue b/main/manager-mobile/src/pages/voiceprint/index.vue index 5fe8bbad..3cca4442 100644 --- a/main/manager-mobile/src/pages/voiceprint/index.vue +++ b/main/manager-mobile/src/pages/voiceprint/index.vue @@ -4,6 +4,7 @@ import { computed, onMounted, ref } from 'vue' import { useMessage } from 'wot-design-uni' import { useToast } from 'wot-design-uni/components/wd-toast' import { createVoicePrint, deleteVoicePrint, getChatHistory, getVoicePrintList, updateVoicePrint } from '@/api/voiceprint' +import { t } from '@/i18n' defineOptions({ name: 'VoicePrintManage', @@ -81,7 +82,7 @@ async function loadVoicePrintList() { // 检查是否有当前选中的智能体 if (!currentAgentId.value) { - console.warn('没有选中的智能体') + console.warn(t('voiceprint.noSelectedAgent')) voicePrintList.value = [] return } @@ -117,7 +118,7 @@ async function refresh() { async function loadChatHistory() { try { if (!currentAgentId.value) { - toast.error('请先选择智能体') + toast.error(t('voiceprint.pleaseSelectAgent')) return } @@ -133,24 +134,47 @@ async function loadChatHistory() { } catch (error) { console.error('获取对话记录失败:', error) - toast.error('获取对话记录失败') + toast.error(t('voiceprint.fetchHistoryFailed')) } } // 打开添加弹窗 function openAddDialog() { if (!currentAgentId.value) { - toast.error('请先选择智能体') + toast.error(t('voiceprint.pleaseSelectAgent')) return } - addForm.value = { - agentId: currentAgentId.value, - audioId: '', - sourceName: '', - introduce: '', + // 检查声纹接口是否配置(通过尝试获取声纹列表来检测) + const checkVoicePrintConfig = async () => { + try { + await getVoicePrintList(currentAgentId.value) + // 接口正常,继续打开添加弹窗 + addForm.value = { + agentId: currentAgentId.value, + audioId: '', + sourceName: '', + introduce: '', + } + showAddDialog.value = true + } catch (error: any) { + // 捕捉声纹接口未配置错误 + if (error.message && error.message.includes('请求错误[10054]')) { + toast.error(t('voiceprint.voiceprintInterfaceNotConfigured')) + } else { + // 其他错误,继续打开弹窗 + addForm.value = { + agentId: currentAgentId.value, + audioId: '', + sourceName: '', + introduce: '', + } + showAddDialog.value = true + } + } } - showAddDialog.value = true + + checkVoicePrintConfig() } // 打开编辑弹窗 @@ -162,7 +186,7 @@ function openEditDialog(item: VoicePrint) { // 获取选中音频的显示内容 function getSelectedAudioContent(audioId: string) { if (!audioId) - return '点击选择声纹向量' + return t('voiceprint.clickToSelectVector') const chatItem = chatHistoryList.value.find(item => item.audioId === audioId) return chatItem ? chatItem.content : `已选择: ${audioId.substring(0, 8)}...` } @@ -181,34 +205,34 @@ function selectAudioId({ item }: { item: any }) { // 提交添加说话人 async function submitAdd() { if (!addForm.value.sourceName.trim()) { - toast.error('请输入姓名') + toast.error(t('voiceprint.pleaseInputName')) return } if (!addForm.value.audioId) { - toast.error('请选择声纹向量') + toast.error(t('voiceprint.pleaseSelectVector')) return } try { await createVoicePrint(addForm.value) - toast.success('添加成功') + toast.success(t('voiceprint.addSuccess')) showAddDialog.value = false await loadVoicePrintList() } catch (error) { console.error('添加说话人失败:', error) - toast.error('添加说话人失败') + toast.error(t('voiceprint.addFailed')) } } // 提交编辑说话人 async function submitEdit() { if (!editForm.value.sourceName.trim()) { - toast.error('请输入姓名') + toast.error(t('voiceprint.pleaseInputName')) return } if (!editForm.value.audioId) { - toast.error('请选择声纹向量') + toast.error(t('voiceprint.pleaseSelectVector')) return } @@ -220,13 +244,13 @@ async function submitEdit() { introduce: editForm.value.introduce, createDate: editForm.value.createDate, }) - toast.success('编辑成功') + toast.success(t('voiceprint.editSuccess')) showEditDialog.value = false await loadVoicePrintList() } catch (error) { console.error('编辑说话人失败:', error) - toast.error('编辑说话人失败') + toast.error(t('voiceprint.editFailed')) } } @@ -239,11 +263,11 @@ function handleEdit(item: VoicePrint) { // 删除声纹 async function handleDelete(id: string) { message.confirm({ - msg: '确定要删除这个说话人吗?', - title: '确认删除', + msg: t('voiceprint.deleteConfirmMsg'), + title: t('voiceprint.deleteConfirmTitle'), }).then(async () => { await deleteVoicePrint(id) - toast.success('删除成功') + toast.success(t('voiceprint.deleteSuccess')) await loadVoicePrintList() }).catch(() => { console.log('点击了取消按钮') @@ -268,7 +292,7 @@ defineExpose({ - 加载中... + {{ t('voiceprint.loading') }} @@ -302,7 +326,7 @@ defineExpose({ @click="handleDelete(item.id)" > - 删除 + {{ t('voiceprint.delete') }} @@ -315,11 +339,11 @@ defineExpose({ - - 暂无声纹数据 + + {{ t('voiceprint.emptyTitle') }} - 点击右下角 + 号添加您的第一个说话人 + {{ t('voiceprint.emptyDesc') }} @@ -341,7 +365,7 @@ defineExpose({ - 添加说话人 + {{ t('voiceprint.addSpeaker') }} @@ -349,7 +373,7 @@ defineExpose({ - * 声纹向量 + * {{ t('voiceprint.voiceVector') }} - * 姓名 + * {{ t('voiceprint.name') }} - * 描述 + * {{ t('voiceprint.description') }}