使用il8n

This commit is contained in:
2026-05-09 15:30:10 +08:00
parent 9207ca69bb
commit 07a1214127
7 changed files with 330 additions and 81 deletions

18
src/i18n/index.ts Normal file
View File

@ -0,0 +1,18 @@
import { createI18n } from 'vue-i18n'
import zhCN from './locales/zh-CN/index'
import enUS from './locales/en-US/index'
export type { LanguageType } from '@/stores/modules/language'
// 创建 i18n 实例
const i18n = createI18n({
legacy: false, // 使用 Composition API 模式
locale: localStorage.getItem('lang') || 'zh-CN', // 默认语言
fallbackLocale: 'zh-CN', // 回退语言
messages: {
'zh-CN': zhCN,
'en-US': enUS
}
})
export default i18n

View File

@ -0,0 +1,101 @@
// English translation file
export default {
// Common
common: {
loading: 'Loading...',
submit: 'Submit',
cancel: 'Cancel',
confirm: 'Confirm',
edit: 'Edit',
delete: 'Delete',
save: 'Save',
close: 'Close',
search: 'Search',
reset: 'Reset',
pleaseInput: 'Please enter',
pleaseSelect: 'Please select',
operation: 'Operation',
status: 'Status',
createTime: 'Create Time',
updateTime: 'Update Time',
remark: 'Remark',
success: 'Operation successful',
error: 'Operation failed',
warning: 'Warning',
info: 'Info'
},
// Personal Center
personalCenter: {
pageTitle: 'Account Information',
username: 'Username',
email: 'Email',
mobile: 'Mobile',
password: 'Password',
language: 'Language',
languageDesc: 'Select your preferred communication language',
lastLogin: 'Last Login',
notSet: 'Not set',
notBound: 'Not bound',
bound: 'Bound',
goToBind: 'Go to bind',
bind: 'Bind',
modify: 'Modify',
bindEmail: 'Bind Email',
bindMobile: 'Bind Mobile',
rebindMobile: 'Rebind Mobile',
modifyUsername: 'Modify Username',
modifyPassword: 'Modify Password',
verifyMethod: 'Verification Method',
verifyAccount: 'Verification Account',
mobileVerify: 'Mobile Verification',
emailVerify: 'Email Verification',
newPassword: 'New Password',
confirmPassword: 'Confirm Password',
passwordPlaceholder: '6-12 characters, including uppercase, lowercase and special characters',
confirmPasswordPlaceholder: 'Please enter new password again',
// Validation messages
pleaseInputEmail: 'Please enter email',
emailFormatError: 'Email format is incorrect',
pleaseInputMobile: 'Please enter mobile number',
mobileFormatError: 'Mobile number format is incorrect',
pleaseInputUsername: 'Please enter username',
usernameLengthError: 'Username length must be 2-20 characters',
pleaseSelectVerifyMethod: 'Please select verification method',
pleaseInputNewPassword: 'Please enter new password',
passwordLengthError: 'Length must be 6-12 characters',
passwordComplexityError: 'Must include uppercase, lowercase and special characters',
pleaseConfirmNewPassword: 'Please confirm new password',
passwordMismatchError: 'Passwords do not match',
// Message prompts
pleaseBindMobileOrEmail: 'Please bind mobile or email first',
pleaseInputVerifyAccount: 'Please bind verification account first',
pleaseInputReceiverAccount: 'Please enter the account to receive verification code',
getUserInfoFailed: 'Failed to get user information',
languageChangeFailed: 'Failed to change language'
},
// Agent User Detail
agentUser: {
phone: 'Phone',
accountBalance: 'Account Balance',
depositAmount: 'Deposit Amount',
email: 'Email',
registerTime: 'Register Time',
historyTradeAmount: 'History Trade Amount',
tradeAccount: 'Trade Account',
accountName: 'Account Name',
balance: 'Balance',
tradeLeverage: 'Trade Leverage',
historyTradeVolume: 'History Trade Volume',
addTime: 'Add Time',
loadDetailFailed: 'Failed to load detail',
operateAccount: 'Operate Account',
role: {
1: 'Platform',
2: 'Contractor',
3: 'Special Market Maker',
4: 'Ordinary Market Maker',
5: 'Agent',
6: 'User'
}
}
}

View File

@ -0,0 +1,101 @@
// 中文翻译文件
export default {
// 通用
common: {
loading: '加载中...',
submit: '提交',
cancel: '取消',
confirm: '确认',
edit: '修改',
delete: '删除',
save: '保存',
close: '关闭',
search: '搜索',
reset: '重置',
pleaseInput: '请输入',
pleaseSelect: '请选择',
operation: '操作',
status: '状态',
createTime: '创建时间',
updateTime: '更新时间',
remark: '备注',
success: '操作成功',
error: '操作失败',
warning: '警告',
info: '提示'
},
// 个人中心
personalCenter: {
pageTitle: '账户信息',
username: '用户名',
email: '邮箱',
mobile: '手机号',
password: '密码',
language: '语言',
languageDesc: '选择您偏好的通讯语言',
lastLogin: '最后登录',
notSet: '未设置',
notBound: '未绑定',
bound: '已绑定',
goToBind: '去绑定',
bind: '绑定',
modify: '修改',
bindEmail: '绑定邮箱',
bindMobile: '绑定手机号',
rebindMobile: '重新绑定手机号',
modifyUsername: '修改用户名',
modifyPassword: '修改密码',
verifyMethod: '验证方式',
verifyAccount: '验证账号',
mobileVerify: '手机验证',
emailVerify: '邮箱验证',
newPassword: '新密码',
confirmPassword: '确认密码',
passwordPlaceholder: '6-12位含大小写+特殊字符',
confirmPasswordPlaceholder: '请再次输入新密码',
// 验证规则提示
pleaseInputEmail: '请输入邮箱',
emailFormatError: '邮箱格式不正确',
pleaseInputMobile: '请输入手机号',
mobileFormatError: '手机号格式不正确',
pleaseInputUsername: '请输入用户名',
usernameLengthError: '用户名长度 2-20 位',
pleaseSelectVerifyMethod: '请选择验证方式',
pleaseInputNewPassword: '请输入新密码',
passwordLengthError: '长度 6-12 位',
passwordComplexityError: '必须包含大小写+特殊字符',
pleaseConfirmNewPassword: '请确认新密码',
passwordMismatchError: '两次密码不一致',
// 消息提示
pleaseBindMobileOrEmail: '请先绑定手机或邮箱',
pleaseInputVerifyAccount: '请先绑定验证账号',
pleaseInputReceiverAccount: '请输入接收验证码的账号',
getUserInfoFailed: '获取用户信息失败',
languageChangeFailed: '语言切换失败'
},
// 代理用户详情
agentUser: {
phone: '手机号',
accountBalance: '账户余额',
depositAmount: '存款金额',
email: '邮箱',
registerTime: '注册时间',
historyTradeAmount: '历史交易金额',
tradeAccount: '交易账号',
accountName: '账户名称',
balance: '余额',
tradeLeverage: '交易杠杆',
historyTradeVolume: '历史交易量',
addTime: '添加时间',
loadDetailFailed: '加载详情失败',
operateAccount: '操作账户',
role: {
1: '平台',
2: '承包商',
3: '特殊做市商',
4: '普通做市商',
5: '代理',
6: '用户'
}
}
}

View File

@ -5,12 +5,14 @@ import 'element-plus/dist/index.css'
//语言配置 //语言配置
import ElementPlus from 'element-plus' import ElementPlus from 'element-plus'
import zhCn from 'element-plus/dist/locale/zh-cn.mjs' import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
import en from 'element-plus/dist/locale/en.mjs'
import { createApp } from 'vue' import { createApp } from 'vue'
import { createPinia } from 'pinia' import { createPinia } from 'pinia'
import App from './App.vue' import App from './App.vue'
import router from './router' import router from './router'
import i18n from './i18n'
import {setupAuthDirective} from './directives/auth' import {setupAuthDirective} from './directives/auth'
import { setStorage } from '@/utils/storage' import { setStorage } from '@/utils/storage'
import '@imengyu/vue3-context-menu/lib/vue3-context-menu.css' import '@imengyu/vue3-context-menu/lib/vue3-context-menu.css'
@ -22,9 +24,13 @@ const app = createApp(App)
setupAuthDirective(app) setupAuthDirective(app)
app.use(createPinia()) app.use(createPinia())
app.use(router) app.use(router)
app.use(i18n)
app.use(ContextMenu) app.use(ContextMenu)
app.use(ElementPlus, { app.use(ElementPlus, {
locale: zhCn, locale: zhCn,
}) })
app.mount('#app') app.mount('#app')
// 导出 app 实例以便在其他地方使用
export default app

View File

@ -14,16 +14,11 @@ export const useLanguageStore = defineStore('language', () => {
if (!getStorage('lang')) { if (!getStorage('lang')) {
setStorage('lang', initLang) setStorage('lang', initLang)
} }
// const setHtmlLang = (currentLang: LanguageType) => {
// document.documentElement.lang = langMap[currentLang]
// }
// setHtmlLang(lang.value)
const changeLang = (newLang: LanguageType) => { const changeLang = (newLang: LanguageType) => {
lang.value = newLang lang.value = newLang
setStorage('lang', newLang) setStorage('lang', newLang)
// setHtmlLang(newLang) // i18n 的 locale 会通过 watch 自动同步
} }
return { lang, changeLang } return { lang, changeLang }

View File

@ -23,7 +23,7 @@
<el-icon> <el-icon>
<Phone /> <Phone />
</el-icon> </el-icon>
<span>手机号</span> <span>{{ t('agentUser.phone') }}</span>
</div> </div>
<div class="item-value">{{ userInfo.phone || '-' }}</div> <div class="item-value">{{ userInfo.phone || '-' }}</div>
</div> </div>
@ -32,7 +32,7 @@
<el-icon> <el-icon>
<CreditCard /> <CreditCard />
</el-icon> </el-icon>
<span>账户余额</span> <span>{{ t('agentUser.accountBalance') }}</span>
</div> </div>
<div class="item-value text-highlight"> <div class="item-value text-highlight">
<AnimatedNumber :value="Number(userInfo.balance) || 0" /> <AnimatedNumber :value="Number(userInfo.balance) || 0" />
@ -43,7 +43,7 @@
<el-icon> <el-icon>
<Money /> <Money />
</el-icon> </el-icon>
<span>存款金额</span> <span>{{ t('agentUser.depositAmount') }}</span>
</div> </div>
<div class="item-value text-highlight"> <div class="item-value text-highlight">
<AnimatedNumber :value="Number(userInfo.deposit) || 0" /> <AnimatedNumber :value="Number(userInfo.deposit) || 0" />
@ -54,7 +54,7 @@
<el-icon> <el-icon>
<Message /> <Message />
</el-icon> </el-icon>
<span>邮箱</span> <span>{{ t('agentUser.email') }}</span>
</div> </div>
<div class="item-value">{{ userInfo.email || '-' }}</div> <div class="item-value">{{ userInfo.email || '-' }}</div>
</div> </div>
@ -63,7 +63,7 @@
<el-icon> <el-icon>
<Clock /> <Clock />
</el-icon> </el-icon>
<span>注册时间</span> <span>{{ t('agentUser.registerTime') }}</span>
</div> </div>
<div class="item-value">{{ userInfo.registerTime || '-' }}</div> <div class="item-value">{{ userInfo.registerTime || '-' }}</div>
</div> </div>
@ -72,7 +72,7 @@
<el-icon> <el-icon>
<Wallet /> <Wallet />
</el-icon> </el-icon>
<span>历史交易金额</span> <span>{{ t('agentUser.historyTradeAmount') }}</span>
</div> </div>
<div class="item-value"> <div class="item-value">
<AnimatedNumber :value="Number(userInfo.hisMeny) || 0" /> <AnimatedNumber :value="Number(userInfo.hisMeny) || 0" />
@ -98,9 +98,11 @@ defineOptions({
}) })
import { ref, watch, computed } from 'vue' import { ref, watch, computed } from 'vue'
import { ElAvatar, ElIcon, ElDrawer, ElButton, ElMessage } from 'element-plus' import { ElAvatar, ElIcon, ElDrawer, ElButton, ElMessage } from 'element-plus'
import { useI18n } from 'vue-i18n'
import { UserFilled, Phone, Message, Money, Wallet, Clock, CreditCard } from '@element-plus/icons-vue' import { UserFilled, Phone, Message, Money, Wallet, Clock, CreditCard } from '@element-plus/icons-vue'
import CustomerListTable from '@/components/common/Table.vue' import CustomerListTable from '@/components/common/Table.vue'
import { getCustomerDetailApi } from '@/api/modules/agent/customerDetail' import { getCustomerDetailApi } from '@/api/modules/agent/customerDetail'
import { useLanguageStore } from '@/stores/modules/language'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import AnimatedNumber from './AnimatedNumber.vue' import AnimatedNumber from './AnimatedNumber.vue'
@ -115,14 +117,18 @@ import type {
TableColumn TableColumn
} from '@/api/types/agent' } from '@/api/types/agent'
const roleMap: Record<RoleType, string> = { const { t, locale } = useI18n()
1: '平台', const languageStore = useLanguageStore()
2: '承包商',
3: '特殊做市商', // 使 computed
4: '普通做市商', const roleMap = computed<Record<RoleType, string>>(() => ({
5: '代理', 1: t('agentUser.role.1'),
6: '用户', 2: t('agentUser.role.2'),
} 3: t('agentUser.role.3'),
4: t('agentUser.role.4'),
5: t('agentUser.role.5'),
6: t('agentUser.role.6'),
}))
// //
const props = defineProps<{ const props = defineProps<{
@ -165,13 +171,15 @@ const userInfo = ref<UserDisplayInfo>({
// //
const tableData = ref<TableDataRow[]>([]) const tableData = ref<TableDataRow[]>([])
const tableColumnsOptions = ref<TableColumn[]>([
{ label: '交易账号', prop: 'tradeAccount' }, //
{ label: '账户名称', prop: 'accountName' }, const tableColumnsOptions = computed<TableColumn[]>(() => [
{ label: '余额', prop: 'balance', sortable: true }, { label: t('agentUser.tradeAccount'), prop: 'tradeAccount' },
{ label: '交易杠杆', prop: 'leverage' }, { label: t('agentUser.accountName'), prop: 'accountName' },
{ label: '历史交易量', prop: 'tradeVolume' }, { label: t('agentUser.balance'), prop: 'balance', sortable: true },
{ label: '添加时间', prop: 'addTime', width: 180 } { label: t('agentUser.tradeLeverage'), prop: 'leverage' },
{ label: t('agentUser.historyTradeVolume'), prop: 'tradeVolume' },
{ label: t('agentUser.addTime'), prop: 'addTime', width: 180 }
]) ])
@ -187,7 +195,7 @@ const loadDetailData = async (id: string | number) => {
userInfo.value = { userInfo.value = {
name: user.email || user.mobile || '-', name: user.email || user.mobile || '-',
id: user.id, id: user.id,
role: roleMap[user.role_id] || '-', role: roleMap.value[user.role_id] || '-',
phone: user.mobile || '-', phone: user.mobile || '-',
email: user.email || '-', email: user.email || '-',
balance: Number(user.capital_amount) || 0, balance: Number(user.capital_amount) || 0,
@ -207,7 +215,7 @@ const loadDetailData = async (id: string | number) => {
})) }))
} catch (err) { } catch (err) {
console.error('加载详情失败:', err); console.error('加载详情失败:', err);
ElMessage.error('加载详情失败') ElMessage.error(t('agentUser.loadDetailFailed'))
} finally { } finally {
loading.value = false loading.value = false
tableLoading.value = false tableLoading.value = false
@ -224,9 +232,18 @@ watch(
{ immediate: true } { immediate: true }
) )
// i18n locale
watch(
() => languageStore.lang,
(newLang) => {
locale.value = newLang
},
{ immediate: true }
)
// //
const handleAccount = (row: TableDataRow) => { const handleAccount = (row: TableDataRow) => {
ElMessage.success('操作账户:' + row.tradeAccount) ElMessage.success(t('agentUser.operateAccount') + '' + row.tradeAccount)
} }
</script> </script>

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="personal-center"> <div class="personal-center">
<div class="page-header"> <div class="page-header">
<h2>账户信息</h2> <h2>{{ t('personalCenter.pageTitle') }}</h2>
</div> </div>
<div class="content-wrapper"> <div class="content-wrapper">
@ -17,9 +17,9 @@
<div class="info-text"> <div class="info-text">
<div class="username-row"> <div class="username-row">
<span class="username-text">{{ userInfo.username }}</span> <span class="username-text">{{ userInfo.username }}</span>
<el-button link type="primary" size="small" @click="showEditDialog('username')">修改</el-button> <el-button link type="primary" size="small" @click="showEditDialog('username')">{{ t('personalCenter.modify') }}</el-button>
</div> </div>
<div class="login-time">最后登录{{ userInfo.last_login }}</div> <div class="login-time">{{ t('personalCenter.lastLogin') }}{{ userInfo.last_login }}</div>
</div> </div>
</div> </div>
</el-card> </el-card>
@ -27,35 +27,35 @@
<el-card class="card info-card"> <el-card class="card info-card">
<div class="info-list"> <div class="info-list">
<div class="info-item"> <div class="info-item">
<span class="label">邮箱</span> <span class="label">{{ t('personalCenter.email') }}</span>
<span class="value">{{ userInfo.email || '未设置' }}</span> <span class="value">{{ userInfo.email || t('personalCenter.notSet') }}</span>
<template v-if="!userInfo.email"> <template v-if="!userInfo.email">
<el-button link type="primary" @click="showEditDialog('email')">去绑定</el-button> <el-button link type="primary" @click="showEditDialog('email')">{{ t('personalCenter.goToBind') }}</el-button>
</template> </template>
<template v-else> <template v-else>
<el-tag type="success" size="small">已绑定</el-tag> <el-tag type="success" size="small">{{ t('personalCenter.bound') }}</el-tag>
</template> </template>
</div> </div>
<div class="info-item"> <div class="info-item">
<span class="label">手机号</span> <span class="label">{{ t('personalCenter.mobile') }}</span>
<span class="value">{{ userInfo.mobile || '未绑定' }}</span> <span class="value">{{ userInfo.mobile || t('personalCenter.notBound') }}</span>
<el-button v-if="!userInfo.mobile" link type="primary" @click="showEditDialog('mobile')"> <el-button v-if="!userInfo.mobile" link type="primary" @click="showEditDialog('mobile')">
绑定 {{ t('personalCenter.bind') }}
</el-button> </el-button>
<template v-else> <template v-else>
<el-tag type="success" size="small">已绑定</el-tag> <el-tag type="success" size="small">{{ t('personalCenter.bound') }}</el-tag>
</template> </template>
</div> </div>
<div class="info-item"> <div class="info-item">
<span class="label">密码</span> <span class="label">{{ t('personalCenter.password') }}</span>
<el-button link type="primary" @click="showPasswordDialog">修改</el-button> <el-button link type="primary" @click="showPasswordDialog">{{ t('personalCenter.modify') }}</el-button>
</div> </div>
<div class="info-item"> <div class="info-item">
<span class="label">语言</span> <span class="label">{{ t('personalCenter.language') }}</span>
<span class="value">选择您偏好的通讯语言</span> <span class="value">{{ t('personalCenter.languageDesc') }}</span>
<el-select v-model="userInfo.lang_type" @change="handleLanguageChange" style="width: 130px" size="small"> <el-select v-model="userInfo.lang_type" @change="handleLanguageChange" style="width: 130px" size="small">
<el-option label="简体中文" :value="1" /> <el-option label="简体中文" :value="1" />
<el-option label="English" :value="2" /> <el-option label="English" :value="2" />
@ -70,7 +70,7 @@
@cancel="handleEditDialogReset" @close="handleEditDialogReset"> @cancel="handleEditDialogReset" @close="handleEditDialogReset">
<el-form ref="editFormRef" :model="editForm" :rules="getEditRules()" label-width="80px" label-position="top"> <el-form ref="editFormRef" :model="editForm" :rules="getEditRules()" label-width="80px" label-position="top">
<el-form-item :label="editForm.label" prop="value"> <el-form-item :label="editForm.label" prop="value">
<el-input v-if="editField !== 'language'" v-model="editForm.value" :placeholder="`请输入${editForm.label}`" <el-input v-if="editField !== 'language'" v-model="editForm.value" :placeholder="`${t('common.pleaseInput')}${editForm.label}`"
size="large" /> size="large" />
</el-form-item> </el-form-item>
@ -80,20 +80,20 @@
</Dialog> </Dialog>
<!-- 密码修改弹窗 验证码模式 --> <!-- 密码修改弹窗 验证码模式 -->
<Dialog :visible="passwordDialogVisible" title="修改密码" width="500px" @confirm="handlePasswordConfirm" <Dialog :visible="passwordDialogVisible" :title="t('personalCenter.modifyPassword')" width="500px" @confirm="handlePasswordConfirm"
@cancel="handlePasswordDialogReset" @close="handlePasswordDialogReset"> @cancel="handlePasswordDialogReset" @close="handlePasswordDialogReset">
<el-form ref="passwordFormRef" :model="passwordForm" :rules="passwordFormRules" label-width="100px" <el-form ref="passwordFormRef" :model="passwordForm" :rules="passwordFormRules" label-width="100px"
label-position="top"> label-position="top">
<!-- 选择验证方式 --> <!-- 选择验证方式 -->
<el-form-item label="验证方式" prop="verifyType"> <el-form-item :label="t('personalCenter.verifyMethod')" prop="verifyType">
<el-radio-group v-model="passwordForm.verifyType" @change="onVerifyTypeChange"> <el-radio-group v-model="passwordForm.verifyType" @change="onVerifyTypeChange">
<el-radio label="mobile" :disabled="!userInfo.mobile">手机验证</el-radio> <el-radio label="mobile" :disabled="!userInfo.mobile">{{ t('personalCenter.mobileVerify') }}</el-radio>
<el-radio label="email" :disabled="!userInfo.email">邮箱验证</el-radio> <el-radio label="email" :disabled="!userInfo.email">{{ t('personalCenter.emailVerify') }}</el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<!-- 验证账号 --> <!-- 验证账号 -->
<el-form-item label="验证账号"> <el-form-item :label="t('personalCenter.verifyAccount')">
<span style="font-weight:500;color:#333"> <span style="font-weight:500;color:#333">
{{ passwordForm.verifyType === 'mobile' ? userInfo.mobile : userInfo.email }} {{ passwordForm.verifyType === 'mobile' ? userInfo.mobile : userInfo.email }}
</span> </span>
@ -104,14 +104,14 @@
:show-send-button="showPwdSendButton" :send-state="pwdSendState" /> :show-send-button="showPwdSendButton" :send-state="pwdSendState" />
<!-- 新密码1 --> <!-- 新密码1 -->
<el-form-item label="新密码" prop="login_password1"> <el-form-item :label="t('personalCenter.newPassword')" prop="login_password1">
<el-input v-model="passwordForm.login_password1" type="password" show-password placeholder="6-12位含大小写+特殊字符" <el-input v-model="passwordForm.login_password1" type="password" show-password :placeholder="t('personalCenter.passwordPlaceholder')"
size="large" /> size="large" />
</el-form-item> </el-form-item>
<!-- 新密码2 --> <!-- 新密码2 -->
<el-form-item label="确认密码" prop="login_password2"> <el-form-item :label="t('personalCenter.confirmPassword')" prop="login_password2">
<el-input v-model="passwordForm.login_password2" type="password" show-password placeholder="请再次输入新密码" <el-input v-model="passwordForm.login_password2" type="password" show-password :placeholder="t('personalCenter.confirmPasswordPlaceholder')"
size="large" /> size="large" />
</el-form-item> </el-form-item>
</el-form> </el-form>
@ -124,9 +124,10 @@
defineOptions({ defineOptions({
name: 'PersonalCenter' name: 'PersonalCenter'
}) })
import { ref, reactive, onMounted, onUnmounted, nextTick } from 'vue'; import { ref, reactive, onMounted, onUnmounted, nextTick, watch } from 'vue';
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'; import { ElMessage, type FormInstance, type FormRules } from 'element-plus';
import { UserFilled } from '@element-plus/icons-vue'; import { UserFilled } from '@element-plus/icons-vue';
import { useI18n } from 'vue-i18n';
import Dialog from '@/components/common/Dialog.vue'; import Dialog from '@/components/common/Dialog.vue';
import UserCaptcha from '@/views/user/components/Captcha.vue'; import UserCaptcha from '@/views/user/components/Captcha.vue';
import { useUserStore } from '@/stores/modules/user'; import { useUserStore } from '@/stores/modules/user';
@ -134,6 +135,7 @@ import { useLanguageStore } from '@/stores/modules/language';
import { sendCaptcha, initCaptchaState, clearCaptchaTimer } from '@/utils/sendCaptcha'; import { sendCaptcha, initCaptchaState, clearCaptchaTimer } from '@/utils/sendCaptcha';
import { codeRules } from '@/views/user/config/rules'; import { codeRules } from '@/views/user/config/rules';
const { t, locale } = useI18n()
// //
import type { import type {
UserInfo, UserInfo,
@ -206,20 +208,20 @@ const getEditRules = (): FormRules => {
switch (editField.value) { switch (editField.value) {
case 'email': case 'email':
rules.value = [ rules.value = [
{ required: true, message: '请输入邮箱', trigger: 'blur' }, { required: true, message: t('personalCenter.pleaseInputEmail'), trigger: 'blur' },
{ type: 'email', message: '邮箱格式不正确', trigger: 'blur' } { type: 'email', message: t('personalCenter.emailFormatError'), trigger: 'blur' }
]; ];
return rules; return rules;
case 'mobile': case 'mobile':
rules.value = [ rules.value = [
{ required: true, message: '请输入手机号', trigger: 'blur' }, { required: true, message: t('personalCenter.pleaseInputMobile'), trigger: 'blur' },
{ pattern: /^\+?\d{5,20}$/, message: '手机号格式不正确', trigger: 'blur' } { pattern: /^\+?\d{5,20}$/, message: t('personalCenter.mobileFormatError'), trigger: 'blur' }
]; ];
return rules; return rules;
case 'username': case 'username':
rules.value = [ rules.value = [
{ required: true, message: '请输入用户名', trigger: 'blur' }, { required: true, message: t('personalCenter.pleaseInputUsername'), trigger: 'blur' },
{ min: 2, max: 20, message: '用户名长度 2-20 位', trigger: 'blur' } { min: 2, max: 20, message: t('personalCenter.usernameLengthError'), trigger: 'blur' }
]; ];
return rules; return rules;
default: default:
@ -229,22 +231,22 @@ const getEditRules = (): FormRules => {
// //
const passwordFormRules = ref<FormRules>({ const passwordFormRules = ref<FormRules>({
verifyType: [{ required: true, message: '请选择验证方式', trigger: 'change' }], verifyType: [{ required: true, message: t('personalCenter.pleaseSelectVerifyMethod'), trigger: 'change' }],
code: codeRules().code, code: codeRules().code,
login_password1: [ login_password1: [
{ required: true, message: '请输入新密码', trigger: 'blur' }, { required: true, message: t('personalCenter.pleaseInputNewPassword'), trigger: 'blur' },
{ min: 6, max: 12, message: '长度 6-12 位', trigger: 'blur' }, { min: 6, max: 12, message: t('personalCenter.passwordLengthError'), trigger: 'blur' },
{ {
pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*[^a-zA-Z0-9]).{6,12}$/, pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*[^a-zA-Z0-9]).{6,12}$/,
message: '必须包含大小写+特殊字符', trigger: 'blur' message: t('personalCenter.passwordComplexityError'), trigger: 'blur'
} }
], ],
login_password2: [ login_password2: [
{ required: true, message: '请确认新密码', trigger: 'blur' }, { required: true, message: t('personalCenter.pleaseConfirmNewPassword'), trigger: 'blur' },
{ {
validator: (rule, value, callback) => { validator: (rule, value, callback) => {
if (value !== passwordForm.login_password1) { if (value !== passwordForm.login_password1) {
callback(new Error('两次密码不一致')); callback(new Error(t('personalCenter.passwordMismatchError')));
} else { } else {
callback(); callback();
} }
@ -261,7 +263,7 @@ const fetchUserProfile = async () => {
Object.assign(userInfo, res); Object.assign(userInfo, res);
languageStore.changeLang(res.lang_type === 1 ? 'zh-CN' : 'en-US'); languageStore.changeLang(res.lang_type === 1 ? 'zh-CN' : 'en-US');
} catch (error) { } catch (error) {
ElMessage.error('获取用户信息失败'); ElMessage.error(t('personalCenter.getUserInfoFailed'));
console.error('获取用户信息失败:', error); console.error('获取用户信息失败:', error);
} }
}; };
@ -272,19 +274,19 @@ const showEditDialog = (field: 'email' | 'mobile' | 'username') => {
editForm.code = ''; editForm.code = '';
switch (field) { switch (field) {
case 'email': case 'email':
editForm.label = '邮箱'; editForm.label = t('personalCenter.email');
editForm.value = userInfo.email; editForm.value = userInfo.email;
dialogTitle.value = '绑定邮箱'; dialogTitle.value = t('personalCenter.bindEmail');
break; break;
case 'mobile': case 'mobile':
editForm.label = '手机号'; editForm.label = t('personalCenter.mobile');
editForm.value = userInfo.mobile; editForm.value = userInfo.mobile;
dialogTitle.value = userInfo.mobile ? '重新绑定手机号' : '绑定手机号'; dialogTitle.value = userInfo.mobile ? t('personalCenter.rebindMobile') : t('personalCenter.bindMobile');
break; break;
case 'username': case 'username':
editForm.label = '用户名'; editForm.label = t('personalCenter.username');
editForm.value = userInfo.username; editForm.value = userInfo.username;
dialogTitle.value = '修改用户名'; dialogTitle.value = t('personalCenter.modifyUsername');
break; break;
} }
dialogVisible.value = true; dialogVisible.value = true;
@ -296,13 +298,13 @@ const handleSendCaptcha = async () => {
const type = editField.value === 'email' ? 'email' : 'phone'; const type = editField.value === 'email' ? 'email' : 'phone';
const receiver = editForm.value?.trim(); const receiver = editForm.value?.trim();
if (!receiver) { if (!receiver) {
ElMessage.warning('请输入接收验证码的账号'); ElMessage.warning(t('personalCenter.pleaseInputReceiverAccount'));
return; return;
} }
const currentState = type === 'email' ? emailCaptchaState : phoneCaptchaState; const currentState = type === 'email' ? emailCaptchaState : phoneCaptchaState;
await sendCaptcha(type, receiver, currentState, () => { await sendCaptcha(type, receiver, currentState, () => {
return userStore.sendCode({ return userStore.sendCode({
reg_type: type === 'email' ? 1 : 2, reg_type: userStore.userInfo!.reg_type,
[type === 'email' ? 'email' : 'mobile']: receiver, [type === 'email' ? 'email' : 'mobile']: receiver,
type: type === 'email' ? 'transfer_code' : 'bindMobile_code' type: type === 'email' ? 'transfer_code' : 'bindMobile_code'
}); });
@ -358,7 +360,7 @@ const handleEditConfirm = async () => {
// ==================== ==================== // ==================== ====================
const showPasswordDialog = () => { const showPasswordDialog = () => {
if (!userInfo.mobile && !userInfo.email) { if (!userInfo.mobile && !userInfo.email) {
ElMessage.warning('请先绑定手机或邮箱'); ElMessage.warning(t('personalCenter.pleaseBindMobileOrEmail'));
return; return;
} }
passwordForm.verifyType = userInfo.mobile ? 'mobile' : 'email'; passwordForm.verifyType = userInfo.mobile ? 'mobile' : 'email';
@ -389,14 +391,14 @@ const handleSendPwdCaptcha = async () => {
const type = passwordForm.verifyType === 'email' ? 'email' : 'phone'; const type = passwordForm.verifyType === 'email' ? 'email' : 'phone';
const receiver = passwordForm.verifyType === 'email' ? userInfo.email : userInfo.mobile; const receiver = passwordForm.verifyType === 'email' ? userInfo.email : userInfo.mobile;
if (!receiver) { if (!receiver) {
ElMessage.warning('请先绑定验证账号'); ElMessage.warning(t('personalCenter.pleaseInputVerifyAccount'));
return; return;
} }
const currentState = passwordForm.verifyType === 'email' ? pwdEmailCaptchaState : pwdMobileCaptchaState; const currentState = passwordForm.verifyType === 'email' ? pwdEmailCaptchaState : pwdMobileCaptchaState;
await sendCaptcha(type, receiver, currentState, () => { await sendCaptcha(type, receiver, currentState, () => {
return userStore.sendCode({ return userStore.sendCode({
reg_type: passwordForm.verifyType === 'email' ? 1 : 2, reg_type: userStore.userInfo!.reg_type,
[passwordForm.verifyType]: receiver, [passwordForm.verifyType]: receiver,
type: 'resetPass_code' type: 'resetPass_code'
}); });
@ -416,7 +418,7 @@ const handlePasswordConfirm = async () => {
await passwordFormRef.value.validate(); await passwordFormRef.value.validate();
const params: UpdatePwdByCaptchaParams = { const params: UpdatePwdByCaptchaParams = {
reg_type: passwordForm.verifyType === 'email' ? 1 : 2, reg_type: userStore.userInfo!.reg_type,
email: passwordForm.verifyType === 'email' ? userInfo.email : undefined, email: passwordForm.verifyType === 'email' ? userInfo.email : undefined,
mobile: passwordForm.verifyType === 'mobile' ? userInfo.mobile : undefined, mobile: passwordForm.verifyType === 'mobile' ? userInfo.mobile : undefined,
code: passwordForm.code, code: passwordForm.code,
@ -443,6 +445,15 @@ const handlePasswordDialogReset = () => {
passwordFormRef.value?.resetFields(); passwordFormRef.value?.resetFields();
}; };
// i18n locale
watch(
() => languageStore.lang,
(newLang) => {
locale.value = newLang
},
{ immediate: true }
)
onMounted(() => { onMounted(() => {
fetchUserProfile(); fetchUserProfile();
}); });