mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-21 22:53:56 +08:00
fix: resolve mobile type and lint errors
This commit is contained in:
@@ -1,9 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onHide, onLaunch, onShow } from '@dcloudio/uni-app'
|
import { onHide, onLaunch, onShow } from '@dcloudio/uni-app'
|
||||||
import { watch, onMounted } from 'vue'
|
import { onMounted, watch } from 'vue'
|
||||||
import { usePageAuth } from '@/hooks/usePageAuth'
|
import { usePageAuth } from '@/hooks/usePageAuth'
|
||||||
import { useConfigStore } from '@/store'
|
|
||||||
import { t } from '@/i18n'
|
import { t } from '@/i18n'
|
||||||
|
import { useConfigStore } from '@/store'
|
||||||
import { useLangStore } from '@/store/lang'
|
import { useLangStore } from '@/store/lang'
|
||||||
import 'abortcontroller-polyfill/dist/abortcontroller-polyfill-only'
|
import 'abortcontroller-polyfill/dist/abortcontroller-polyfill-only'
|
||||||
|
|
||||||
@@ -37,9 +37,9 @@ function updateTabBarText() {
|
|||||||
success: () => {},
|
success: () => {},
|
||||||
fail: (err) => {
|
fail: (err) => {
|
||||||
console.log('设置首页tabBar文本失败:', err)
|
console.log('设置首页tabBar文本失败:', err)
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// 设置配网tabBar文本
|
// 设置配网tabBar文本
|
||||||
uni.setTabBarItem({
|
uni.setTabBarItem({
|
||||||
index: 1,
|
index: 1,
|
||||||
@@ -47,9 +47,9 @@ function updateTabBarText() {
|
|||||||
success: () => {},
|
success: () => {},
|
||||||
fail: (err) => {
|
fail: (err) => {
|
||||||
console.log('设置配网tabBar文本失败:', err)
|
console.log('设置配网tabBar文本失败:', err)
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
// 设置系统tabBar文本
|
// 设置系统tabBar文本
|
||||||
uni.setTabBarItem({
|
uni.setTabBarItem({
|
||||||
index: 2,
|
index: 2,
|
||||||
@@ -57,9 +57,10 @@ function updateTabBarText() {
|
|||||||
success: () => {},
|
success: () => {},
|
||||||
fail: (err) => {
|
fail: (err) => {
|
||||||
console.log('设置系统tabBar文本失败:', err)
|
console.log('设置系统tabBar文本失败:', err)
|
||||||
}
|
},
|
||||||
})
|
})
|
||||||
} catch (error) {
|
}
|
||||||
|
catch (error) {
|
||||||
console.log('更新tabBar文本时出错:', error)
|
console.log('更新tabBar文本时出错:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+547
-547
File diff suppressed because it is too large
Load Diff
@@ -1,23 +1,23 @@
|
|||||||
|
import type { Language } from '@/store/lang'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { useLangStore } from '@/store/lang'
|
import { useLangStore } from '@/store/lang'
|
||||||
import type { Language } from '@/store/lang'
|
|
||||||
|
|
||||||
|
import de from './de'
|
||||||
|
import en from './en'
|
||||||
|
import pt_BR from './pt_BR'
|
||||||
|
import vi from './vi'
|
||||||
// 导入各个语言的翻译文件
|
// 导入各个语言的翻译文件
|
||||||
import zh_CN from './zh_CN'
|
import zh_CN from './zh_CN'
|
||||||
import en from './en'
|
|
||||||
import zh_TW from './zh_TW'
|
import zh_TW from './zh_TW'
|
||||||
import de from './de'
|
|
||||||
import vi from './vi'
|
|
||||||
import pt_BR from './pt_BR'
|
|
||||||
|
|
||||||
// 语言包映射
|
// 语言包映射
|
||||||
const messages = {
|
const messages = {
|
||||||
zh_CN: zh_CN,
|
zh_CN,
|
||||||
en,
|
en,
|
||||||
zh_TW: zh_TW,
|
zh_TW,
|
||||||
de,
|
de,
|
||||||
vi,
|
vi,
|
||||||
pt_BR: pt_BR,
|
pt_BR,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 当前使用的语言
|
// 当前使用的语言
|
||||||
@@ -42,7 +42,7 @@ export function t(key: string, params?: Record<string, string | number>): string
|
|||||||
|
|
||||||
// 直接查找扁平键名
|
// 直接查找扁平键名
|
||||||
if (langMessages && typeof langMessages === 'object' && key in langMessages) {
|
if (langMessages && typeof langMessages === 'object' && key in langMessages) {
|
||||||
let value = langMessages[key]
|
const value = langMessages[key]
|
||||||
if (typeof value === 'string') {
|
if (typeof value === 'string') {
|
||||||
// 处理参数替换
|
// 处理参数替换
|
||||||
if (params) {
|
if (params) {
|
||||||
@@ -76,4 +76,4 @@ export function getSupportedLanguages(): { code: Language, name: string }[] {
|
|||||||
{ code: 'vi', name: 'Tiếng Việt' },
|
{ code: 'vi', name: 'Tiếng Việt' },
|
||||||
{ code: 'pt_BR', name: 'Português (Brasil)' },
|
{ code: 'pt_BR', name: 'Português (Brasil)' },
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,9 +29,9 @@ export default {
|
|||||||
'login.requiredCaptcha': 'O código de verificação não pode estar vazio',
|
'login.requiredCaptcha': 'O código de verificação não pode estar vazio',
|
||||||
'login.requiredMobile': 'Por favor, insira um número de telefone válido',
|
'login.requiredMobile': 'Por favor, insira um número de telefone válido',
|
||||||
'login.captchaError': 'Erro no código de verificação gráfico',
|
'login.captchaError': 'Erro no código de verificação gráfico',
|
||||||
'login.forgotPassword': 'Esqueceu a Senha',
|
'login.forgotPassword': 'Esqueceu a Senha',
|
||||||
'login.userAgreement': 'Termos de Uso',
|
'login.userAgreement': 'Termos de Uso',
|
||||||
'login.privacyPolicy': 'Política de Privacidade',
|
'login.privacyPolicy': 'Política de Privacidade',
|
||||||
|
|
||||||
// Register page
|
// Register page
|
||||||
'register.pageTitle': 'Cadastro',
|
'register.pageTitle': 'Cadastro',
|
||||||
|
|||||||
+547
-547
File diff suppressed because it is too large
Load Diff
@@ -29,9 +29,9 @@ export default {
|
|||||||
'login.requiredCaptcha': '驗證碼不能為空',
|
'login.requiredCaptcha': '驗證碼不能為空',
|
||||||
'login.requiredMobile': '請輸入正確的手機號碼',
|
'login.requiredMobile': '請輸入正確的手機號碼',
|
||||||
'login.captchaError': '圖形驗證碼錯誤',
|
'login.captchaError': '圖形驗證碼錯誤',
|
||||||
'login.forgotPassword': '忘記密碼',
|
'login.forgotPassword': '忘記密碼',
|
||||||
'login.userAgreement': '用戶協議',
|
'login.userAgreement': '用戶協議',
|
||||||
'login.privacyPolicy': '隱私政策',
|
'login.privacyPolicy': '隱私政策',
|
||||||
|
|
||||||
// 忘記密碼頁面
|
// 忘記密碼頁面
|
||||||
'retrievePassword.title': '重置密碼',
|
'retrievePassword.title': '重置密碼',
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { ConfigProviderThemeVars } from 'wot-design-uni'
|
import type { ConfigProviderThemeVars } from 'wot-design-uni/components/wd-config-provider/types'
|
||||||
|
|
||||||
const themeVars: ConfigProviderThemeVars = {
|
const themeVars: ConfigProviderThemeVars = {
|
||||||
// colorTheme: 'red',
|
// colorTheme: 'red',
|
||||||
|
|||||||
@@ -38,10 +38,8 @@ onLoad(() => {
|
|||||||
<wd-tabbar
|
<wd-tabbar
|
||||||
v-if="customTabbarEnable"
|
v-if="customTabbarEnable"
|
||||||
v-model="tabbarStore.curIdx"
|
v-model="tabbarStore.curIdx"
|
||||||
bordered
|
|
||||||
safe-area-inset-bottom
|
safe-area-inset-bottom bordered placeholder fixed
|
||||||
placeholder
|
|
||||||
fixed
|
|
||||||
@change="selectTabBar"
|
@change="selectTabBar"
|
||||||
>
|
>
|
||||||
<block v-for="(item, idx) in tabbarList" :key="item.path">
|
<block v-for="(item, idx) in tabbarList" :key="item.path">
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { ConfigProviderThemeVars } from 'wot-design-uni'
|
import type { ConfigProviderThemeVars } from 'wot-design-uni/components/wd-config-provider/types'
|
||||||
import FgTabbar from './fg-tabbar/fg-tabbar.vue'
|
import FgTabbar from './fg-tabbar/fg-tabbar.vue'
|
||||||
|
|
||||||
const themeVars: ConfigProviderThemeVars = {
|
const themeVars: ConfigProviderThemeVars = {
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
import { VueQueryPlugin } from '@tanstack/vue-query'
|
import { VueQueryPlugin } from '@tanstack/vue-query'
|
||||||
import { createSSRApp } from 'vue'
|
import { createSSRApp } from 'vue'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import { routeInterceptor } from './router/interceptor'
|
|
||||||
|
|
||||||
import store from './store'
|
|
||||||
import '@/style/index.scss'
|
|
||||||
import 'virtual:uno.css'
|
|
||||||
|
|
||||||
// 导入国际化相关功能
|
// 导入国际化相关功能
|
||||||
import { initI18n } from './i18n'
|
import { initI18n } from './i18n'
|
||||||
import { useLangStore } from './store/lang'
|
|
||||||
|
import { routeInterceptor } from './router/interceptor'
|
||||||
|
import store from './store'
|
||||||
|
|
||||||
|
import '@/style/index.scss'
|
||||||
|
import 'virtual:uno.css'
|
||||||
|
|
||||||
export function createApp() {
|
export function createApp() {
|
||||||
const app = createSSRApp(App)
|
const app = createSSRApp(App)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { AgentSnapshot, AgentSnapshotData } from '@/api/agent/types'
|
import type { AgentSnapshot, AgentSnapshotData } from '@/api/agent/types'
|
||||||
import { computed, ref, watch } from 'vue'
|
import { computed, ref, watch } from 'vue'
|
||||||
import { useMessage } from 'wot-design-uni'
|
import { useMessage } from 'wot-design-uni/components/wd-message-box'
|
||||||
import {
|
import {
|
||||||
deleteAgentSnapshot,
|
deleteAgentSnapshot,
|
||||||
getAgentDetail,
|
getAgentDetail,
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onLoad } from '@dcloudio/uni-app'
|
import { onLoad } from '@dcloudio/uni-app'
|
||||||
import { computed, onMounted, ref } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
import CustomTabs from '@/components/custom-tabs/index.vue'
|
import CustomTabs from '@/components/custom-tabs/index.vue'
|
||||||
import { t } from '@/i18n'
|
import { t } from '@/i18n'
|
||||||
import ChatHistory from '@/pages/chat-history/index.vue'
|
import ChatHistory from '@/pages/chat-history/index.vue'
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
</route>
|
</route>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useMessage } from 'wot-design-uni'
|
import { useMessage } from 'wot-design-uni/components/wd-message-box'
|
||||||
import { getMcpAddress, getMcpTools } from '@/api/agent/agent'
|
import { getMcpAddress, getMcpTools } from '@/api/agent/agent'
|
||||||
import { t } from '@/i18n'
|
import { t } from '@/i18n'
|
||||||
import { usePluginStore } from '@/store'
|
import { usePluginStore } from '@/store'
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { useToast } from 'wot-design-uni'
|
import { useToast } from 'wot-design-uni/components/wd-toast'
|
||||||
import { t } from '@/i18n'
|
import { t } from '@/i18n'
|
||||||
|
|
||||||
// 类型定义
|
// 类型定义
|
||||||
@@ -228,15 +228,15 @@ async function generateAndPlay() {
|
|||||||
generating.value = true
|
generating.value = true
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log(t('deviceConfig.generatingUltrasonicConfigAudio') + '...')
|
console.log(`${t('deviceConfig.generatingUltrasonicConfigAudio')}...`)
|
||||||
|
|
||||||
// 准备配网数据 - 参考HTML文件格式
|
// 准备配网数据 - 参考HTML文件格式
|
||||||
const dataStr = `${props.selectedNetwork.ssid}\n${props.password}`
|
const dataStr = `${props.selectedNetwork.ssid}\n${props.password}`
|
||||||
const textBytes = stringToBytes(dataStr)
|
const textBytes = stringToBytes(dataStr)
|
||||||
const fullBytes = [...START_BYTES, ...textBytes, checksum(textBytes), ...END_BYTES]
|
const fullBytes = [...START_BYTES, ...textBytes, checksum(textBytes), ...END_BYTES]
|
||||||
|
|
||||||
console.log(t('deviceConfig.configData') + ':', { ssid: props.selectedNetwork.ssid, password: props.password })
|
console.log(`${t('deviceConfig.configData')}:`, { ssid: props.selectedNetwork.ssid, password: props.password })
|
||||||
console.log(t('deviceConfig.dataBytesLength') + ':', textBytes.length)
|
console.log(`${t('deviceConfig.dataBytesLength')}:`, textBytes.length)
|
||||||
|
|
||||||
// 转换为比特流
|
// 转换为比特流
|
||||||
let bits: number[] = []
|
let bits: number[] = []
|
||||||
@@ -244,7 +244,7 @@ async function generateAndPlay() {
|
|||||||
bits = bits.concat(toBits(b))
|
bits = bits.concat(toBits(b))
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log(t('deviceConfig.bitStreamLength') + ':', bits.length)
|
console.log(`${t('deviceConfig.bitStreamLength')}:`, bits.length)
|
||||||
|
|
||||||
// AFSK调制 - 减少采样率降低文件大小
|
// AFSK调制 - 减少采样率降低文件大小
|
||||||
const reducedSampleRate = 22050 // 降低采样率
|
const reducedSampleRate = 22050 // 降低采样率
|
||||||
@@ -267,7 +267,7 @@ async function generateAndPlay() {
|
|||||||
const base64 = arrayBufferToBase64(wavBuffer)
|
const base64 = arrayBufferToBase64(wavBuffer)
|
||||||
const dataUri = `data:audio/wav;base64,${base64}`
|
const dataUri = `data:audio/wav;base64,${base64}`
|
||||||
|
|
||||||
console.log(t('deviceConfig.base64Length') + ':', base64.length, t('deviceConfig.about'), Math.round(base64.length / 1024), 'KB')
|
console.log(`${t('deviceConfig.base64Length')}:`, base64.length, t('deviceConfig.about'), Math.round(base64.length / 1024), 'KB')
|
||||||
|
|
||||||
// 检查数据大小
|
// 检查数据大小
|
||||||
if (base64.length > 1024 * 1024) { // 超过1MB
|
if (base64.length > 1024 * 1024) { // 超过1MB
|
||||||
@@ -277,7 +277,7 @@ async function generateAndPlay() {
|
|||||||
audioFilePath.value = dataUri
|
audioFilePath.value = dataUri
|
||||||
audioGenerated.value = true
|
audioGenerated.value = true
|
||||||
|
|
||||||
console.log(t('deviceConfig.audioGenerationSuccess') + ',比特流长度:', bits.length, t('deviceConfig.samplePoints') + ':', floatBuf.length)
|
console.log(`${t('deviceConfig.audioGenerationSuccess')},比特流长度:`, bits.length, `${t('deviceConfig.samplePoints')}:`, floatBuf.length)
|
||||||
|
|
||||||
toast.success(t('deviceConfig.soundWaveGenerationSuccess'))
|
toast.success(t('deviceConfig.soundWaveGenerationSuccess'))
|
||||||
|
|
||||||
@@ -287,8 +287,8 @@ async function generateAndPlay() {
|
|||||||
}, 800) // 增加延迟时间
|
}, 800) // 增加延迟时间
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
console.error(t('deviceConfig.audioGenerationFailed') + ':', error)
|
console.error(`${t('deviceConfig.audioGenerationFailed')}:`, error)
|
||||||
toast.error(`${t('deviceConfig.soundWaveGenerationFailed')}: ${error.message || error}`)
|
toast.error(`${t('deviceConfig.soundWaveGenerationFailed')}: ${error.message || error}`)
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
generating.value = false
|
generating.value = false
|
||||||
@@ -384,7 +384,7 @@ async function playAudio() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
innerAudioContext.onError((error) => {
|
innerAudioContext.onError((error) => {
|
||||||
console.error(t('deviceConfig.audioPlaybackFailed') + ':', error)
|
console.error(`${t('deviceConfig.audioPlaybackFailed')}:`, error)
|
||||||
playing.value = false
|
playing.value = false
|
||||||
|
|
||||||
let errorMsg = t('deviceConfig.audioPlaybackFailed')
|
let errorMsg = t('deviceConfig.audioPlaybackFailed')
|
||||||
@@ -417,10 +417,10 @@ async function playAudio() {
|
|||||||
}, 300)
|
}, 300)
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
console.error(t('deviceConfig.audioPlaybackError') + ':', error)
|
console.error(`${t('deviceConfig.audioPlaybackError')}:`, error)
|
||||||
playing.value = false
|
playing.value = false
|
||||||
await cleanupAudio()
|
await cleanupAudio()
|
||||||
toast.error(`${t('deviceConfig.playbackFailed')}: ${error.message}`)
|
toast.error(`${t('deviceConfig.playbackFailed')}: ${error.message}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -433,7 +433,7 @@ async function cleanupAudio() {
|
|||||||
console.log(t('deviceConfig.cleaningUpAudioContext'))
|
console.log(t('deviceConfig.cleaningUpAudioContext'))
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
console.log(t('deviceConfig.cleaningUpAudioContextFailed') + ':', e)
|
console.log(`${t('deviceConfig.cleaningUpAudioContextFailed')}:`, e)
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
audioContext.value = null
|
audioContext.value = null
|
||||||
@@ -483,38 +483,38 @@ async function stopAudio() {
|
|||||||
:disabled="!canGenerate"
|
:disabled="!canGenerate"
|
||||||
@click="generateAndPlay"
|
@click="generateAndPlay"
|
||||||
>
|
>
|
||||||
{{ generating ? t('deviceConfig.generating') : '🎵 ' + t('deviceConfig.generateAndPlaySoundWave') }}
|
{{ generating ? t('deviceConfig.generating') : `🎵 ${t('deviceConfig.generateAndPlaySoundWave')}` }}
|
||||||
</wd-button>
|
</wd-button>
|
||||||
|
|
||||||
<wd-button
|
<wd-button
|
||||||
v-if="audioGenerated"
|
v-if="audioGenerated"
|
||||||
type="success"
|
type="success"
|
||||||
size="large"
|
size="large"
|
||||||
block
|
block
|
||||||
:loading="playing"
|
:loading="playing"
|
||||||
@click="playAudio"
|
@click="playAudio"
|
||||||
>
|
>
|
||||||
{{ playing ? t('deviceConfig.playing') : '🔊 ' + t('deviceConfig.playSoundWave') }}
|
{{ playing ? t('deviceConfig.playing') : `🔊 ${t('deviceConfig.playSoundWave')}` }}
|
||||||
</wd-button>
|
</wd-button>
|
||||||
|
|
||||||
<wd-button
|
<wd-button
|
||||||
v-if="playing"
|
v-if="playing"
|
||||||
type="warning"
|
type="warning"
|
||||||
size="large"
|
size="large"
|
||||||
block
|
block
|
||||||
@click="stopAudio"
|
@click="stopAudio"
|
||||||
>
|
>
|
||||||
⏹️ {{ t('deviceConfig.stopPlaying') }}
|
⏹️ {{ t('deviceConfig.stopPlaying') }}
|
||||||
</wd-button>
|
</wd-button>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 音频控制选项 -->
|
<!-- 音频控制选项 -->
|
||||||
<view v-if="audioGenerated" class="audio-options">
|
<view v-if="audioGenerated" class="audio-options">
|
||||||
<view class="option-item">
|
<view class="option-item">
|
||||||
<wd-checkbox v-model="autoLoop">
|
<wd-checkbox v-model="autoLoop">
|
||||||
{{ t('deviceConfig.autoLoopPlaySoundWave') }}
|
{{ t('deviceConfig.autoLoopPlaySoundWave') }}
|
||||||
</wd-checkbox>
|
</wd-checkbox>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 音频播放器 -->
|
<!-- 音频播放器 -->
|
||||||
@@ -531,33 +531,33 @@ async function stopAudio() {
|
|||||||
|
|
||||||
<!-- 使用说明 -->
|
<!-- 使用说明 -->
|
||||||
<view class="help-section">
|
<view class="help-section">
|
||||||
<view class="help-title">
|
<view class="help-title">
|
||||||
{{ t('deviceConfig.ultrasonicConfigInstructions') }}
|
{{ t('deviceConfig.ultrasonicConfigInstructions') }}
|
||||||
</view>
|
|
||||||
<view class="help-content">
|
|
||||||
<text class="help-item">
|
|
||||||
1. {{ t('deviceConfig.ensureWifiNetworkSelectedAndPasswordEntered') }}
|
|
||||||
</text>
|
|
||||||
<text class="help-item">
|
|
||||||
2. {{ t('deviceConfig.clickGenerateAndPlaySoundWave') }}
|
|
||||||
</text>
|
|
||||||
<text class="help-item">
|
|
||||||
3. {{ t('deviceConfig.bringPhoneCloseToXiaozhiDevice') }}
|
|
||||||
</text>
|
|
||||||
<text class="help-item">
|
|
||||||
4. {{ t('deviceConfig.duringAudioPlaybackXiaozhiWillReceive') }}
|
|
||||||
</text>
|
|
||||||
<text class="help-item">
|
|
||||||
5. {{ t('deviceConfig.afterConfigSuccessDeviceWillConnect') }}
|
|
||||||
</text>
|
|
||||||
<text class="help-tip">
|
|
||||||
{{ t('deviceConfig.usesAfskModulation') }}
|
|
||||||
</text>
|
|
||||||
<text class="help-tip">
|
|
||||||
{{ t('deviceConfig.ensureModeratePhoneVolume') }}
|
|
||||||
</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
|
<view class="help-content">
|
||||||
|
<text class="help-item">
|
||||||
|
1. {{ t('deviceConfig.ensureWifiNetworkSelectedAndPasswordEntered') }}
|
||||||
|
</text>
|
||||||
|
<text class="help-item">
|
||||||
|
2. {{ t('deviceConfig.clickGenerateAndPlaySoundWave') }}
|
||||||
|
</text>
|
||||||
|
<text class="help-item">
|
||||||
|
3. {{ t('deviceConfig.bringPhoneCloseToXiaozhiDevice') }}
|
||||||
|
</text>
|
||||||
|
<text class="help-item">
|
||||||
|
4. {{ t('deviceConfig.duringAudioPlaybackXiaozhiWillReceive') }}
|
||||||
|
</text>
|
||||||
|
<text class="help-item">
|
||||||
|
5. {{ t('deviceConfig.afterConfigSuccessDeviceWillConnect') }}
|
||||||
|
</text>
|
||||||
|
<text class="help-tip">
|
||||||
|
{{ t('deviceConfig.usesAfskModulation') }}
|
||||||
|
</text>
|
||||||
|
<text class="help-tip">
|
||||||
|
{{ t('deviceConfig.ensureModeratePhoneVolume') }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, ref } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
import { useToast } from 'wot-design-uni'
|
import { useToast } from 'wot-design-uni/components/wd-toast'
|
||||||
import { t } from '@/i18n'
|
import { t } from '@/i18n'
|
||||||
|
|
||||||
// 类型定义
|
// 类型定义
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
import { t } from '@/i18n'
|
import { t } from '@/i18n'
|
||||||
import UltrasonicConfig from './components/ultrasonic-config.vue'
|
import UltrasonicConfig from './components/ultrasonic-config.vue'
|
||||||
import WifiConfig from './components/wifi-config.vue'
|
import WifiConfig from './components/wifi-config.vue'
|
||||||
@@ -68,12 +68,10 @@ function onNetworkSelected(network: WiFiNetwork | null, password: string) {
|
|||||||
function onConnectionStatusChange(connected: boolean) {
|
function onConnectionStatusChange(connected: boolean) {
|
||||||
console.log('ESP32连接状态:', connected)
|
console.log('ESP32连接状态:', connected)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 在组件挂载后设置导航栏标题
|
// 在组件挂载后设置导航栏标题
|
||||||
import { onMounted } from 'vue'
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
uni.setNavigationBarTitle({
|
uni.setNavigationBarTitle({
|
||||||
title: t('deviceConfig.pageTitle')
|
title: t('deviceConfig.pageTitle'),
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
@@ -86,18 +84,18 @@ onMounted(() => {
|
|||||||
<!-- 配网方式选择 -->
|
<!-- 配网方式选择 -->
|
||||||
<view class="pb-[20rpx] first:pt-[20rpx]">
|
<view class="pb-[20rpx] first:pt-[20rpx]">
|
||||||
<text class="text-[32rpx] text-[#232338] font-bold">
|
<text class="text-[32rpx] text-[#232338] font-bold">
|
||||||
{{ t('deviceConfig.configMethod') }}
|
{{ t('deviceConfig.configMethod') }}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="mb-[24rpx] border border-[#eeeeee] rounded-[20rpx] bg-[#fbfbfb] p-[24rpx]" style="box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);">
|
<view class="mb-[24rpx] border border-[#eeeeee] rounded-[20rpx] bg-[#fbfbfb] p-[24rpx]" style="box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);">
|
||||||
<view class="flex cursor-pointer items-center justify-between border border-[#eeeeee] rounded-[12rpx] bg-[#f5f7fb] p-[20rpx] transition-all duration-300 active:border-[#336cff] active:bg-[#eef3ff]" @click="showConfigTypeSelector">
|
<view class="flex cursor-pointer items-center justify-between border border-[#eeeeee] rounded-[12rpx] bg-[#f5f7fb] p-[20rpx] transition-all duration-300 active:border-[#336cff] active:bg-[#eef3ff]" @click="showConfigTypeSelector">
|
||||||
<text class="text-[28rpx] text-[#232338] font-medium">
|
<text class="text-[28rpx] text-[#232338] font-medium">
|
||||||
{{ t('deviceConfig.configMethod') }}
|
{{ t('deviceConfig.configMethod') }}
|
||||||
</text>
|
</text>
|
||||||
<text class="mx-[16rpx] flex-1 text-right text-[26rpx] text-[#65686f]">
|
<text class="mx-[16rpx] flex-1 text-right text-[26rpx] text-[#65686f]">
|
||||||
{{ configType === 'wifi' ? t('deviceConfig.wifiConfig') : t('deviceConfig.ultrasonicConfig') }}
|
{{ configType === 'wifi' ? t('deviceConfig.wifiConfig') : t('deviceConfig.ultrasonicConfig') }}
|
||||||
</text>
|
</text>
|
||||||
<wd-icon name="arrow-right" custom-class="text-[20rpx] text-[#9d9ea3]" />
|
<wd-icon name="arrow-right" custom-class="text-[20rpx] text-[#9d9ea3]" />
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@@ -105,8 +103,8 @@ onMounted(() => {
|
|||||||
<!-- WiFi网络选择 -->
|
<!-- WiFi网络选择 -->
|
||||||
<view class="pb-[20rpx]">
|
<view class="pb-[20rpx]">
|
||||||
<text class="text-[32rpx] text-[#232338] font-bold">
|
<text class="text-[32rpx] text-[#232338] font-bold">
|
||||||
{{ t('deviceConfig.networkConfig') }}
|
{{ t('deviceConfig.networkConfig') }}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="mb-[24rpx] border border-[#eeeeee] rounded-[20rpx] bg-[#fbfbfb] p-[24rpx]" style="box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);">
|
<view class="mb-[24rpx] border border-[#eeeeee] rounded-[20rpx] bg-[#fbfbfb] p-[24rpx]" style="box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);">
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { Device, FirmwareType } from '@/api/device'
|
import type { Device, FirmwareType } from '@/api/device'
|
||||||
import { computed, onMounted, ref } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
import { useMessage } from 'wot-design-uni'
|
import { useMessage } from 'wot-design-uni/components/wd-message-box'
|
||||||
import { bindDevice, bindDeviceManual, getBindDevices, getFirmwareTypes, unbindDevice, updateDeviceAutoUpdate } from '@/api/device'
|
import { bindDevice, bindDeviceManual, getBindDevices, getFirmwareTypes, unbindDevice, updateDeviceAutoUpdate } from '@/api/device'
|
||||||
import { t } from '@/i18n'
|
import { t } from '@/i18n'
|
||||||
import { toast } from '@/utils/toast'
|
import { toast } from '@/utils/toast'
|
||||||
|
|||||||
@@ -9,23 +9,23 @@
|
|||||||
</route>
|
</route>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, onMounted, ref } from "vue";
|
import { onLoad } from '@dcloudio/uni-app'
|
||||||
import { onLoad } from "@dcloudio/uni-app";
|
import { computed, onMounted, ref } from 'vue'
|
||||||
import { useConfigStore } from "@/store";
|
|
||||||
import { getEnvBaseUrl, sm2Encrypt } from "@/utils";
|
|
||||||
import { toast } from "@/utils/toast";
|
|
||||||
// 导入国际化相关功能
|
|
||||||
import { t, initI18n } from "@/i18n";
|
|
||||||
// 导入API接口
|
// 导入API接口
|
||||||
import { retrievePassword, sendSmsCode } from "@/api/auth";
|
import { retrievePassword, sendSmsCode } from '@/api/auth'
|
||||||
|
// 导入国际化相关功能
|
||||||
|
import { initI18n, t } from '@/i18n'
|
||||||
|
import { useConfigStore } from '@/store'
|
||||||
|
import { getEnvBaseUrl, sm2Encrypt } from '@/utils'
|
||||||
|
import { toast } from '@/utils/toast'
|
||||||
|
|
||||||
// 获取屏幕边界到安全区域距离
|
// 获取屏幕边界到安全区域距离
|
||||||
let safeAreaInsets;
|
let safeAreaInsets
|
||||||
let systemInfo;
|
let systemInfo
|
||||||
|
|
||||||
// #ifdef MP-WEIXIN
|
// #ifdef MP-WEIXIN
|
||||||
// 微信小程序使用新的API
|
// 微信小程序使用新的API
|
||||||
systemInfo = uni.getWindowInfo();
|
systemInfo = uni.getWindowInfo()
|
||||||
safeAreaInsets = systemInfo.safeArea
|
safeAreaInsets = systemInfo.safeArea
|
||||||
? {
|
? {
|
||||||
top: systemInfo.safeArea.top,
|
top: systemInfo.safeArea.top,
|
||||||
@@ -33,147 +33,148 @@ safeAreaInsets = systemInfo.safeArea
|
|||||||
bottom: systemInfo.windowHeight - systemInfo.safeArea.bottom,
|
bottom: systemInfo.windowHeight - systemInfo.safeArea.bottom,
|
||||||
left: systemInfo.safeArea.left,
|
left: systemInfo.safeArea.left,
|
||||||
}
|
}
|
||||||
: null;
|
: null
|
||||||
// #endif
|
// #endif
|
||||||
|
|
||||||
// #ifndef MP-WEIXIN
|
// #ifndef MP-WEIXIN
|
||||||
// 其他平台继续使用uni API
|
// 其他平台继续使用uni API
|
||||||
systemInfo = uni.getSystemInfoSync();
|
systemInfo = uni.getSystemInfoSync()
|
||||||
safeAreaInsets = systemInfo.safeAreaInsets;
|
safeAreaInsets = systemInfo.safeAreaInsets
|
||||||
// #endif
|
// #endif
|
||||||
|
|
||||||
// 表单数据
|
// 表单数据
|
||||||
interface ForgotPasswordData {
|
interface ForgotPasswordData {
|
||||||
mobile: string;
|
mobile: string
|
||||||
captcha: string;
|
captcha: string
|
||||||
captchaId: string;
|
captchaId: string
|
||||||
mobileCaptcha: string;
|
mobileCaptcha: string
|
||||||
newPassword: string;
|
newPassword: string
|
||||||
confirmPassword: string;
|
confirmPassword: string
|
||||||
areaCode: string;
|
areaCode: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const formData = ref<ForgotPasswordData>({
|
const formData = ref<ForgotPasswordData>({
|
||||||
mobile: "",
|
mobile: '',
|
||||||
captcha: "",
|
captcha: '',
|
||||||
captchaId: "",
|
captchaId: '',
|
||||||
mobileCaptcha: "",
|
mobileCaptcha: '',
|
||||||
newPassword: "",
|
newPassword: '',
|
||||||
confirmPassword: "",
|
confirmPassword: '',
|
||||||
areaCode: "+86",
|
areaCode: '+86',
|
||||||
});
|
})
|
||||||
|
|
||||||
// 验证码图片
|
// 验证码图片
|
||||||
const captchaImage = ref("");
|
const captchaImage = ref('')
|
||||||
const loading = ref(false);
|
const loading = ref(false)
|
||||||
|
|
||||||
// 获取配置store
|
// 获取配置store
|
||||||
const configStore = useConfigStore();
|
const configStore = useConfigStore()
|
||||||
|
|
||||||
// State for area code action sheet
|
// State for area code action sheet
|
||||||
const showAreaCodeSheet = ref(false);
|
const showAreaCodeSheet = ref(false)
|
||||||
const selectedAreaCode = ref("+86");
|
const selectedAreaCode = ref('+86')
|
||||||
|
// 短信验证码倒计时
|
||||||
|
const smsCountdown = ref(0)
|
||||||
|
const smsLoading = ref(false)
|
||||||
const areaCodeList = computed(() =>
|
const areaCodeList = computed(() =>
|
||||||
(configStore.config.mobileAreaList || []).map((item) => {
|
(configStore.config.mobileAreaList || []).map((item) => {
|
||||||
return {
|
return {
|
||||||
value: item.key,
|
value: item.key,
|
||||||
label: `${item.name} (${item.key})`,
|
label: `${item.name} (${item.key})`,
|
||||||
};
|
}
|
||||||
})
|
}),
|
||||||
);
|
)
|
||||||
|
|
||||||
const canSendMobileCaptcha = computed(() => {
|
const canSendMobileCaptcha = computed(() => {
|
||||||
const mobile = formData.value.mobile;
|
const mobile = formData.value.mobile
|
||||||
const phoneRegex = /^1[3-9]\d{9}$/;
|
const phoneRegex = /^1[3-9]\d{9}$/
|
||||||
return phoneRegex.test(mobile) && smsCountdown.value === 0;
|
return phoneRegex.test(mobile) && smsCountdown.value === 0
|
||||||
});
|
})
|
||||||
|
|
||||||
// SM2公钥
|
// SM2公钥
|
||||||
const sm2PublicKey = computed(() => {
|
const sm2PublicKey = computed(() => {
|
||||||
return configStore.config.sm2PublicKey;
|
return configStore.config.sm2PublicKey
|
||||||
});
|
})
|
||||||
|
|
||||||
// 短信验证码倒计时
|
|
||||||
const smsCountdown = ref(0);
|
|
||||||
const smsLoading = ref(false);
|
|
||||||
|
|
||||||
// 打开区号选择弹窗
|
// 打开区号选择弹窗
|
||||||
function openAreaCodeSheet() {
|
function openAreaCodeSheet() {
|
||||||
showAreaCodeSheet.value = true;
|
showAreaCodeSheet.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// 选择区号
|
// 选择区号
|
||||||
function selectAreaCode(item: { value: string; label: string }) {
|
function selectAreaCode(item: { value: string, label: string }) {
|
||||||
selectedAreaCode.value = item.value;
|
selectedAreaCode.value = item.value
|
||||||
formData.value.areaCode = item.value;
|
formData.value.areaCode = item.value
|
||||||
showAreaCodeSheet.value = false;
|
showAreaCodeSheet.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 关闭区号选择弹窗
|
// 关闭区号选择弹窗
|
||||||
function closeAreaCodeSheet() {
|
function closeAreaCodeSheet() {
|
||||||
showAreaCodeSheet.value = false;
|
showAreaCodeSheet.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成UUID
|
// 生成UUID
|
||||||
function generateUUID() {
|
function generateUUID() {
|
||||||
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
||||||
const r = (Math.random() * 16) | 0;
|
const r = (Math.random() * 16) | 0
|
||||||
const v = c === "x" ? r : (r & 0x3) | 0x8;
|
const v = c === 'x' ? r : (r & 0x3) | 0x8
|
||||||
return v.toString(16);
|
return v.toString(16)
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取图形验证码
|
// 获取图形验证码
|
||||||
async function refreshCaptcha() {
|
async function refreshCaptcha() {
|
||||||
const uuid = generateUUID();
|
const uuid = generateUUID()
|
||||||
formData.value.captchaId = uuid;
|
formData.value.captchaId = uuid
|
||||||
captchaImage.value = `${getEnvBaseUrl()}/user/captcha?uuid=${uuid}&t=${Date.now()}`;
|
captchaImage.value = `${getEnvBaseUrl()}/user/captcha?uuid=${uuid}&t=${Date.now()}`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 发送短信验证码
|
// 发送短信验证码
|
||||||
async function handleSendSmsCode() {
|
async function handleSendSmsCode() {
|
||||||
// 手机号格式验证
|
// 手机号格式验证
|
||||||
const phoneRegex = /^1[3-9]\d{9}$/;
|
const phoneRegex = /^1[3-9]\d{9}$/
|
||||||
if (!phoneRegex.test(formData.value.mobile)) {
|
if (!phoneRegex.test(formData.value.mobile)) {
|
||||||
toast.warning(t("retrievePassword.inputCorrectMobile"));
|
toast.warning(t('retrievePassword.inputCorrectMobile'))
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!formData.value.captcha) {
|
if (!formData.value.captcha) {
|
||||||
toast.warning(t("retrievePassword.captchaRequired"));
|
toast.warning(t('retrievePassword.captchaRequired'))
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
smsLoading.value = true;
|
smsLoading.value = true
|
||||||
// 将手机号转换为国际格式
|
// 将手机号转换为国际格式
|
||||||
const internationalPhone = formData.value.areaCode + formData.value.mobile;
|
const internationalPhone = formData.value.areaCode + formData.value.mobile
|
||||||
// 调用发送短信验证码API
|
// 调用发送短信验证码API
|
||||||
await sendSmsCode({
|
await sendSmsCode({
|
||||||
phone: internationalPhone,
|
phone: internationalPhone,
|
||||||
captcha: formData.value.captcha,
|
captcha: formData.value.captcha,
|
||||||
captchaId: formData.value.captchaId
|
captchaId: formData.value.captchaId,
|
||||||
})
|
})
|
||||||
|
|
||||||
toast.success(t("retrievePassword.captchaSendSuccess"));
|
toast.success(t('retrievePassword.captchaSendSuccess'))
|
||||||
|
|
||||||
// 开始倒计时
|
// 开始倒计时
|
||||||
smsCountdown.value = 60;
|
smsCountdown.value = 60
|
||||||
const timer = setInterval(() => {
|
const timer = setInterval(() => {
|
||||||
smsCountdown.value--;
|
smsCountdown.value--
|
||||||
if (smsCountdown.value <= 0) {
|
if (smsCountdown.value <= 0) {
|
||||||
clearInterval(timer);
|
clearInterval(timer)
|
||||||
}
|
}
|
||||||
}, 1000);
|
}, 1000)
|
||||||
} catch (error: any) {
|
}
|
||||||
|
catch (error: any) {
|
||||||
// 处理验证码错误
|
// 处理验证码错误
|
||||||
if (error.message.includes("请求错误[10067]")) {
|
if (error.message.includes('请求错误[10067]')) {
|
||||||
toast.warning(t("login.captchaError"));
|
toast.warning(t('login.captchaError'))
|
||||||
}
|
}
|
||||||
// 发送失败重新获取图形验证码
|
// 发送失败重新获取图形验证码
|
||||||
refreshCaptcha();
|
refreshCaptcha()
|
||||||
} finally {
|
}
|
||||||
smsLoading.value = false;
|
finally {
|
||||||
|
smsLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,64 +182,65 @@ async function handleSendSmsCode() {
|
|||||||
async function handleResetPassword() {
|
async function handleResetPassword() {
|
||||||
// 表单验证
|
// 表单验证
|
||||||
if (!formData.value.mobile) {
|
if (!formData.value.mobile) {
|
||||||
toast.warning(t("retrievePassword.mobileRequired"));
|
toast.warning(t('retrievePassword.mobileRequired'))
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 手机号格式验证
|
// 手机号格式验证
|
||||||
const phoneRegex = /^1[3-9]\d{9}$/;
|
const phoneRegex = /^1[3-9]\d{9}$/
|
||||||
if (!phoneRegex.test(formData.value.mobile)) {
|
if (!phoneRegex.test(formData.value.mobile)) {
|
||||||
toast.warning(t("retrievePassword.inputCorrectMobile"));
|
toast.warning(t('retrievePassword.inputCorrectMobile'))
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将手机号转换为国际格式
|
// 将手机号转换为国际格式
|
||||||
const internationalPhone = formData.value.areaCode + formData.value.mobile;
|
const internationalPhone = formData.value.areaCode + formData.value.mobile
|
||||||
|
|
||||||
if (!formData.value.captcha) {
|
if (!formData.value.captcha) {
|
||||||
toast.warning(t("retrievePassword.captchaRequired"));
|
toast.warning(t('retrievePassword.captchaRequired'))
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!formData.value.mobileCaptcha) {
|
if (!formData.value.mobileCaptcha) {
|
||||||
toast.warning(t("retrievePassword.mobileCaptchaRequired"));
|
toast.warning(t('retrievePassword.mobileCaptchaRequired'))
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!formData.value.newPassword) {
|
if (!formData.value.newPassword) {
|
||||||
toast.warning(t("retrievePassword.newPasswordRequired"));
|
toast.warning(t('retrievePassword.newPasswordRequired'))
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!formData.value.confirmPassword) {
|
if (!formData.value.confirmPassword) {
|
||||||
toast.warning(t("retrievePassword.confirmNewPasswordRequired"));
|
toast.warning(t('retrievePassword.confirmNewPasswordRequired'))
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (formData.value.newPassword !== formData.value.confirmPassword) {
|
if (formData.value.newPassword !== formData.value.confirmPassword) {
|
||||||
toast.warning(t("retrievePassword.passwordsNotMatch"));
|
toast.warning(t('retrievePassword.passwordsNotMatch'))
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
loading.value = true;
|
loading.value = true
|
||||||
|
|
||||||
// 检查SM2公钥是否配置
|
// 检查SM2公钥是否配置
|
||||||
if (!sm2PublicKey.value) {
|
if (!sm2PublicKey.value) {
|
||||||
toast.warning(t("sm2.publicKeyNotConfigured"));
|
toast.warning(t('sm2.publicKeyNotConfigured'))
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加密密码
|
// 加密密码
|
||||||
let encryptedPassword;
|
let encryptedPassword
|
||||||
try {
|
try {
|
||||||
// 拼接图形验证码和新密码进行加密
|
// 拼接图形验证码和新密码进行加密
|
||||||
const captchaAndPassword = formData.value.captcha + formData.value.newPassword;
|
const captchaAndPassword = formData.value.captcha + formData.value.newPassword
|
||||||
encryptedPassword = sm2Encrypt(sm2PublicKey.value, captchaAndPassword);
|
encryptedPassword = sm2Encrypt(sm2PublicKey.value, captchaAndPassword)
|
||||||
} catch (error) {
|
}
|
||||||
console.error("密码加密失败:", error);
|
catch (error) {
|
||||||
toast.warning(t("sm2.encryptionFailed"));
|
console.error('密码加密失败:', error)
|
||||||
return;
|
toast.warning(t('sm2.encryptionFailed'))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 调用重置密码API
|
// 调用重置密码API
|
||||||
@@ -246,53 +248,56 @@ async function handleResetPassword() {
|
|||||||
phone: internationalPhone,
|
phone: internationalPhone,
|
||||||
code: formData.value.mobileCaptcha,
|
code: formData.value.mobileCaptcha,
|
||||||
password: encryptedPassword,
|
password: encryptedPassword,
|
||||||
captchaId: formData.value.captchaId
|
captchaId: formData.value.captchaId,
|
||||||
})
|
})
|
||||||
|
|
||||||
toast.success(t("retrievePassword.passwordUpdateSuccess"));
|
toast.success(t('retrievePassword.passwordUpdateSuccess'))
|
||||||
|
|
||||||
// 跳转到登录页
|
// 跳转到登录页
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
uni.redirectTo({
|
uni.redirectTo({
|
||||||
url: "/pages/login/index",
|
url: '/pages/login/index',
|
||||||
});
|
})
|
||||||
}, 1000);
|
}, 1000)
|
||||||
} catch (error: any) {
|
}
|
||||||
|
catch (error: any) {
|
||||||
// 处理验证码错误
|
// 处理验证码错误
|
||||||
if (error.message.includes("请求错误[10067]")) {
|
if (error.message.includes('请求错误[10067]')) {
|
||||||
toast.warning(t("login.captchaError"));
|
toast.warning(t('login.captchaError'))
|
||||||
}
|
}
|
||||||
// 重置失败重新获取验证码
|
// 重置失败重新获取验证码
|
||||||
refreshCaptcha();
|
refreshCaptcha()
|
||||||
} finally {
|
}
|
||||||
loading.value = false;
|
finally {
|
||||||
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 返回登录
|
// 返回登录
|
||||||
function goBack() {
|
function goBack() {
|
||||||
uni.redirectTo({
|
uni.redirectTo({
|
||||||
url: "/pages/login/index",
|
url: '/pages/login/index',
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 页面加载时获取验证码
|
// 页面加载时获取验证码
|
||||||
onLoad(() => {
|
onLoad(() => {
|
||||||
refreshCaptcha();
|
refreshCaptcha()
|
||||||
});
|
})
|
||||||
|
|
||||||
// 组件挂载时确保配置已加载
|
// 组件挂载时确保配置已加载
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
if (!configStore.config.name) {
|
if (!configStore.config.name) {
|
||||||
try {
|
try {
|
||||||
await configStore.fetchPublicConfig();
|
await configStore.fetchPublicConfig()
|
||||||
} catch (error) {
|
}
|
||||||
console.error("获取配置失败:", error);
|
catch (error) {
|
||||||
|
console.error('获取配置失败:', error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 初始化国际化
|
// 初始化国际化
|
||||||
initI18n();
|
initI18n()
|
||||||
});
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -471,7 +476,7 @@ onMounted(async () => {
|
|||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
content: "";
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -50%;
|
top: -50%;
|
||||||
left: -50%;
|
left: -50%;
|
||||||
@@ -713,10 +718,7 @@ onMounted(async () => {
|
|||||||
margin-bottom: 30rpx;
|
margin-bottom: 30rpx;
|
||||||
box-shadow: 0 8rpx 24rpx rgba(102, 126, 234, 0.3);
|
box-shadow: 0 8rpx 24rpx rgba(102, 126, 234, 0.3);
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
background-color: var(
|
background-color: var(--wot-button-primary-bg-color, var(--wot-color-theme, #4d80f0));
|
||||||
--wot-button-primary-bg-color,
|
|
||||||
var(--wot-color-theme, #4d80f0)
|
|
||||||
);
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
line-height: 80rpx;
|
line-height: 80rpx;
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
import type { Agent } from '@/api/agent/types'
|
import type { Agent } from '@/api/agent/types'
|
||||||
import { computed, onMounted, ref } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
// 在组件挂载后设置导航栏标题
|
// 在组件挂载后设置导航栏标题
|
||||||
import { useMessage } from 'wot-design-uni'
|
import { useMessage } from 'wot-design-uni/components/wd-message-box'
|
||||||
import useZPaging from 'z-paging/components/z-paging/js/hooks/useZPaging.js'
|
import useZPaging from 'z-paging/components/z-paging/js/hooks/useZPaging.js'
|
||||||
import { createAgent, deleteAgent, getAgentList } from '@/api/agent/agent'
|
import { createAgent, deleteAgent, getAgentList } from '@/api/agent/agent'
|
||||||
import { t } from '@/i18n'
|
import { t } from '@/i18n'
|
||||||
@@ -167,7 +167,7 @@ function openCreateDialog() {
|
|||||||
msg: '',
|
msg: '',
|
||||||
inputPlaceholder: t('home.inputPlaceholder'),
|
inputPlaceholder: t('home.inputPlaceholder'),
|
||||||
inputValue: '',
|
inputValue: '',
|
||||||
inputPattern: /^.{1,64}$/i,
|
inputPattern: /^.{1,64}$/,
|
||||||
inputError: t('home.createError'),
|
inputError: t('home.createError'),
|
||||||
confirmButtonText: t('home.createNow'),
|
confirmButtonText: t('home.createNow'),
|
||||||
cancelButtonText: t('common.cancel'),
|
cancelButtonText: t('common.cancel'),
|
||||||
|
|||||||
@@ -401,7 +401,9 @@ onMounted(async () => {
|
|||||||
<text class="policy-link" @click="goToUserAgreement">
|
<text class="policy-link" @click="goToUserAgreement">
|
||||||
{{ t('login.userAgreement') }}
|
{{ t('login.userAgreement') }}
|
||||||
</text>
|
</text>
|
||||||
<text class="policy-divider">|</text>
|
<text class="policy-divider">
|
||||||
|
{{ '|' }}
|
||||||
|
</text>
|
||||||
<text class="policy-link" @click="goToPrivacyPolicy">
|
<text class="policy-link" @click="goToPrivacyPolicy">
|
||||||
{{ t('login.privacyPolicy') }}
|
{{ t('login.privacyPolicy') }}
|
||||||
</text>
|
</text>
|
||||||
|
|||||||
@@ -9,15 +9,14 @@
|
|||||||
</route>
|
</route>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, onMounted, ref } from 'vue';
|
import { computed, onMounted, ref } from 'vue'
|
||||||
import { register, sendSmsCode } from '@/api/auth';
|
import { register, sendSmsCode } from '@/api/auth'
|
||||||
import { useConfigStore } from '@/store';
|
|
||||||
import { getEnvBaseUrl } from '@/utils';
|
|
||||||
import { toast } from '@/utils/toast';
|
|
||||||
// 导入国际化相关功能
|
// 导入国际化相关功能
|
||||||
import { t, initI18n } from '@/i18n';
|
import { initI18n, t } from '@/i18n'
|
||||||
|
import { useConfigStore } from '@/store'
|
||||||
// 导入SM2加密工具
|
// 导入SM2加密工具
|
||||||
import { sm2Encrypt } from '@/utils';
|
import { getEnvBaseUrl, sm2Encrypt } from '@/utils'
|
||||||
|
import { toast } from '@/utils/toast'
|
||||||
|
|
||||||
// 获取屏幕边界到安全区域距离
|
// 获取屏幕边界到安全区域距离
|
||||||
let safeAreaInsets
|
let safeAreaInsets
|
||||||
@@ -135,9 +134,9 @@ function generateUUID() {
|
|||||||
|
|
||||||
// 获取验证码
|
// 获取验证码
|
||||||
async function refreshCaptcha() {
|
async function refreshCaptcha() {
|
||||||
const uuid = generateUUID();
|
const uuid = generateUUID()
|
||||||
formData.value.captchaId = uuid;
|
formData.value.captchaId = uuid
|
||||||
captchaImage.value = `${getEnvBaseUrl()}/user/captcha?uuid=${uuid}&t=${Date.now()}`;
|
captchaImage.value = `${getEnvBaseUrl()}/user/captcha?uuid=${uuid}&t=${Date.now()}`
|
||||||
}
|
}
|
||||||
|
|
||||||
// 发送短信验证码
|
// 发送短信验证码
|
||||||
@@ -253,7 +252,8 @@ async function handleRegister() {
|
|||||||
// 拼接验证码和密码
|
// 拼接验证码和密码
|
||||||
const captchaAndPassword = formData.value.captcha + formData.value.password
|
const captchaAndPassword = formData.value.captcha + formData.value.password
|
||||||
encryptedPassword = sm2Encrypt(sm2PublicKey.value, captchaAndPassword)
|
encryptedPassword = sm2Encrypt(sm2PublicKey.value, captchaAndPassword)
|
||||||
} catch (error) {
|
}
|
||||||
|
catch (error) {
|
||||||
console.error('密码加密失败:', error)
|
console.error('密码加密失败:', error)
|
||||||
toast.warning(t('sm2.encryptionFailed'))
|
toast.warning(t('sm2.encryptionFailed'))
|
||||||
return
|
return
|
||||||
@@ -275,7 +275,7 @@ async function handleRegister() {
|
|||||||
// 跳转到登录页
|
// 跳转到登录页
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
uni.redirectTo({
|
uni.redirectTo({
|
||||||
url: '/pages/login/index'
|
url: '/pages/login/index',
|
||||||
})
|
})
|
||||||
}, 1000)
|
}, 1000)
|
||||||
}
|
}
|
||||||
@@ -299,7 +299,7 @@ async function handleRegister() {
|
|||||||
// 返回登录
|
// 返回登录
|
||||||
function goBack() {
|
function goBack() {
|
||||||
uni.redirectTo({
|
uni.redirectTo({
|
||||||
url: '/pages/login/index'
|
url: '/pages/login/index',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -321,8 +321,6 @@ onMounted(async () => {
|
|||||||
// 初始化国际化
|
// 初始化国际化
|
||||||
initI18n()
|
initI18n()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -385,41 +383,41 @@ onMounted(async () => {
|
|||||||
<view class="input-group">
|
<view class="input-group">
|
||||||
<view class="input-wrapper">
|
<view class="input-wrapper">
|
||||||
<wd-input
|
<wd-input
|
||||||
v-model="formData.password"
|
v-model="formData.password"
|
||||||
custom-class="styled-input"
|
custom-class="styled-input"
|
||||||
no-border
|
no-border
|
||||||
:placeholder="t('register.enterPassword')"
|
:placeholder="t('register.enterPassword')"
|
||||||
show-password
|
show-password
|
||||||
:maxlength="20"
|
:maxlength="20"
|
||||||
/>
|
/>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="input-group">
|
<view class="input-group">
|
||||||
<view class="input-wrapper">
|
<view class="input-wrapper">
|
||||||
<wd-input
|
<wd-input
|
||||||
v-model="formData.confirmPassword"
|
v-model="formData.confirmPassword"
|
||||||
custom-class="styled-input"
|
custom-class="styled-input"
|
||||||
no-border
|
no-border
|
||||||
:placeholder="t('register.confirmPassword')"
|
:placeholder="t('register.confirmPassword')"
|
||||||
show-password
|
show-password
|
||||||
:maxlength="20"
|
:maxlength="20"
|
||||||
/>
|
/>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="input-group">
|
<view class="input-group">
|
||||||
<view class="input-wrapper captcha-wrapper">
|
<view class="input-wrapper captcha-wrapper">
|
||||||
<wd-input
|
<wd-input
|
||||||
v-model="formData.captcha"
|
v-model="formData.captcha"
|
||||||
custom-class="styled-input"
|
custom-class="styled-input"
|
||||||
no-border
|
no-border
|
||||||
:placeholder="t('register.enterCode')"
|
:placeholder="t('register.enterCode')"
|
||||||
:maxlength="6"
|
:maxlength="6"
|
||||||
/>
|
/>
|
||||||
<view class="captcha-image" @click="refreshCaptcha">
|
<view class="captcha-image" @click="refreshCaptcha">
|
||||||
<image :src="captchaImage" class="captcha-img" />
|
<image :src="captchaImage" class="captcha-img" />
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@@ -427,21 +425,21 @@ onMounted(async () => {
|
|||||||
<view v-if="enableMobileRegister" class="input-group">
|
<view v-if="enableMobileRegister" class="input-group">
|
||||||
<view class="input-wrapper sms-wrapper">
|
<view class="input-wrapper sms-wrapper">
|
||||||
<wd-input
|
<wd-input
|
||||||
v-model="formData.mobileCaptcha"
|
v-model="formData.mobileCaptcha"
|
||||||
custom-class="styled-input"
|
custom-class="styled-input"
|
||||||
no-border
|
no-border
|
||||||
:placeholder="t('register.enterCode')"
|
:placeholder="t('register.enterCode')"
|
||||||
type="number"
|
type="number"
|
||||||
:maxlength="6"
|
:maxlength="6"
|
||||||
/>
|
/>
|
||||||
<wd-button
|
<wd-button
|
||||||
:loading="smsLoading"
|
:loading="smsLoading"
|
||||||
:disabled="smsCountdown > 0"
|
:disabled="smsCountdown > 0"
|
||||||
custom-class="sms-btn"
|
custom-class="sms-btn"
|
||||||
@click="sendSmsVerification"
|
@click="sendSmsVerification"
|
||||||
>
|
>
|
||||||
{{ smsCountdown > 0 ? `${smsCountdown}s` : t('register.getCode') }}
|
{{ smsCountdown > 0 ? `${smsCountdown}s` : t('register.getCode') }}
|
||||||
</wd-button>
|
</wd-button>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@@ -462,7 +460,6 @@ onMounted(async () => {
|
|||||||
{{ t('register.loginNow') }}
|
{{ t('register.loginNow') }}
|
||||||
</text>
|
</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@@ -508,8 +505,6 @@ onMounted(async () => {
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</wd-action-sheet>
|
</wd-action-sheet>
|
||||||
|
|
||||||
|
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { Language } from '@/store/lang'
|
import type { Language } from '@/store/lang'
|
||||||
import { computed, onMounted, reactive, ref } from 'vue'
|
import { computed, onMounted, reactive, ref } from 'vue'
|
||||||
import { useToast } from 'wot-design-uni'
|
import { useToast } from 'wot-design-uni/components/wd-toast'
|
||||||
import { changeLanguage, getCurrentLanguage, getSupportedLanguages, t } from '@/i18n'
|
import { changeLanguage, getCurrentLanguage, getSupportedLanguages, t } from '@/i18n'
|
||||||
import { useConfigStore } from '@/store'
|
import { useConfigStore } from '@/store'
|
||||||
import { clearServerBaseUrlOverride, getEnvBaseUrl, getServerBaseUrlOverride, setServerBaseUrlOverride } from '@/utils'
|
import { clearServerBaseUrlOverride, getEnvBaseUrl, getServerBaseUrlOverride, setServerBaseUrlOverride } from '@/utils'
|
||||||
@@ -265,7 +265,7 @@ function showAbout() {
|
|||||||
title: t('settings.aboutApp', { appName: import.meta.env.VITE_APP_TITLE }),
|
title: t('settings.aboutApp', { appName: import.meta.env.VITE_APP_TITLE }),
|
||||||
content: t('settings.aboutContent', {
|
content: t('settings.aboutContent', {
|
||||||
appName: import.meta.env.VITE_APP_TITLE,
|
appName: import.meta.env.VITE_APP_TITLE,
|
||||||
version: '0.9.5'
|
version: '0.9.5',
|
||||||
}),
|
}),
|
||||||
showCancel: false,
|
showCancel: false,
|
||||||
confirmText: t('common.confirm'),
|
confirmText: t('common.confirm'),
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { ChatHistory, CreateSpeakerData, VoicePrint } from '@/api/voiceprint'
|
import type { ChatHistory, CreateSpeakerData, VoicePrint } from '@/api/voiceprint'
|
||||||
import { computed, onMounted, ref } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
import { useMessage } from 'wot-design-uni'
|
import { useMessage } from 'wot-design-uni/components/wd-message-box'
|
||||||
import { useToast } from 'wot-design-uni/components/wd-toast'
|
import { useToast } from 'wot-design-uni/components/wd-toast'
|
||||||
import { createVoicePrint, deleteVoicePrint, getAudioDownloadId, getChatHistory, getVoicePrintList, updateVoicePrint } from '@/api/voiceprint'
|
import { createVoicePrint, deleteVoicePrint, getAudioDownloadId, getChatHistory, getVoicePrintList, updateVoicePrint } from '@/api/voiceprint'
|
||||||
import { t } from '@/i18n'
|
import { t } from '@/i18n'
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { PublicConfig } from '@/api/auth'
|
import type { PublicConfig } from '@/api/auth'
|
||||||
import { getPublicConfig } from '@/api/auth'
|
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
import { getPublicConfig } from '@/api/auth'
|
||||||
|
|
||||||
// 初始化状态
|
// 初始化状态
|
||||||
const initialConfigState: PublicConfig = {
|
const initialConfigState: PublicConfig = {
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { ref } from 'vue'
|
import type { Ref } from 'vue'
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
// 支持的语言类型
|
// 支持的语言类型
|
||||||
export type Language = 'zh_CN' | 'en' | 'zh_TW' | 'de' | 'vi' | 'pt_BR'
|
export type Language = 'zh_CN' | 'en' | 'zh_TW' | 'de' | 'vi' | 'pt_BR'
|
||||||
|
|
||||||
export interface LangStore {
|
export interface LangStore {
|
||||||
currentLang: Language
|
currentLang: Ref<Language>
|
||||||
changeLang: (lang: Language) => void
|
changeLang: (lang: Language) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,4 +38,4 @@ export const useLangStore = defineStore(
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ export function getEnvBaseUploadUrl() {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成SM2密钥对(十六进制格式)
|
* 生成SM2密钥对(十六进制格式)
|
||||||
* @returns {Object} 包含公钥和私钥的对象
|
* @returns {object} 包含公钥和私钥的对象
|
||||||
*/
|
*/
|
||||||
export function generateSm2KeyPairHex() {
|
export function generateSm2KeyPairHex() {
|
||||||
// 使用sm-crypto库生成SM2密钥对
|
// 使用sm-crypto库生成SM2密钥对
|
||||||
|
|||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
import type { Component } from 'vue'
|
||||||
|
|
||||||
|
// wot-design-uni 1.9.1 exposes raw Vue source through its global declarations.
|
||||||
|
// Keep the components used by this app recognizable without pulling that source into vue-tsc.
|
||||||
|
declare module 'vue' {
|
||||||
|
export interface GlobalComponents {
|
||||||
|
WdActionSheet: Component
|
||||||
|
WdButton: Component
|
||||||
|
WdCheckbox: Component
|
||||||
|
WdConfigProvider: Component
|
||||||
|
WdFab: Component
|
||||||
|
WdIcon: Component
|
||||||
|
WdImg: Component
|
||||||
|
WdInput: Component
|
||||||
|
WdLoading: Component
|
||||||
|
WdMessageBox: Component
|
||||||
|
WdNavbar: Component
|
||||||
|
WdPicker: Component
|
||||||
|
WdPopup: Component
|
||||||
|
WdSegmented: Component
|
||||||
|
WdSlider: Component
|
||||||
|
WdStatusTip: Component
|
||||||
|
WdSwipeAction: Component
|
||||||
|
WdSwitch: Component
|
||||||
|
WdTabbar: Component
|
||||||
|
WdTabbarItem: Component
|
||||||
|
WdTag: Component
|
||||||
|
WdToast: Component
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,7 +14,6 @@
|
|||||||
"@dcloudio/types",
|
"@dcloudio/types",
|
||||||
"@uni-helper/uni-types",
|
"@uni-helper/uni-types",
|
||||||
"@types/wechat-miniprogram",
|
"@types/wechat-miniprogram",
|
||||||
"wot-design-uni/global.d.ts",
|
|
||||||
"z-paging/types",
|
"z-paging/types",
|
||||||
"./src/typings.d.ts"
|
"./src/typings.d.ts"
|
||||||
],
|
],
|
||||||
|
|||||||
Reference in New Issue
Block a user