fix: resolve mobile type and lint errors

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