使用il8n
This commit is contained in:
parent
9207ca69bb
commit
07a1214127
|
|
@ -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
|
||||
|
|
@ -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'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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: '用户'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,12 +5,14 @@ import 'element-plus/dist/index.css'
|
|||
//语言配置
|
||||
import ElementPlus from 'element-plus'
|
||||
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
|
||||
import en from 'element-plus/dist/locale/en.mjs'
|
||||
|
||||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
|
||||
import App from './App.vue'
|
||||
import router from './router'
|
||||
import i18n from './i18n'
|
||||
import {setupAuthDirective} from './directives/auth'
|
||||
import { setStorage } from '@/utils/storage'
|
||||
import '@imengyu/vue3-context-menu/lib/vue3-context-menu.css'
|
||||
|
|
@ -22,9 +24,13 @@ const app = createApp(App)
|
|||
setupAuthDirective(app)
|
||||
app.use(createPinia())
|
||||
app.use(router)
|
||||
app.use(i18n)
|
||||
app.use(ContextMenu)
|
||||
app.use(ElementPlus, {
|
||||
locale: zhCn,
|
||||
})
|
||||
|
||||
app.mount('#app')
|
||||
|
||||
// 导出 app 实例以便在其他地方使用
|
||||
export default app
|
||||
|
|
|
|||
|
|
@ -14,16 +14,11 @@ export const useLanguageStore = defineStore('language', () => {
|
|||
if (!getStorage('lang')) {
|
||||
setStorage('lang', initLang)
|
||||
}
|
||||
// const setHtmlLang = (currentLang: LanguageType) => {
|
||||
// document.documentElement.lang = langMap[currentLang]
|
||||
// }
|
||||
|
||||
// setHtmlLang(lang.value)
|
||||
|
||||
const changeLang = (newLang: LanguageType) => {
|
||||
lang.value = newLang
|
||||
setStorage('lang', newLang)
|
||||
// setHtmlLang(newLang)
|
||||
// i18n 的 locale 会通过 watch 自动同步
|
||||
}
|
||||
|
||||
return { lang, changeLang }
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
<el-icon>
|
||||
<Phone />
|
||||
</el-icon>
|
||||
<span>手机号</span>
|
||||
<span>{{ t('agentUser.phone') }}</span>
|
||||
</div>
|
||||
<div class="item-value">{{ userInfo.phone || '-' }}</div>
|
||||
</div>
|
||||
|
|
@ -32,7 +32,7 @@
|
|||
<el-icon>
|
||||
<CreditCard />
|
||||
</el-icon>
|
||||
<span>账户余额</span>
|
||||
<span>{{ t('agentUser.accountBalance') }}</span>
|
||||
</div>
|
||||
<div class="item-value text-highlight">
|
||||
<AnimatedNumber :value="Number(userInfo.balance) || 0" />
|
||||
|
|
@ -43,7 +43,7 @@
|
|||
<el-icon>
|
||||
<Money />
|
||||
</el-icon>
|
||||
<span>存款金额</span>
|
||||
<span>{{ t('agentUser.depositAmount') }}</span>
|
||||
</div>
|
||||
<div class="item-value text-highlight">
|
||||
<AnimatedNumber :value="Number(userInfo.deposit) || 0" />
|
||||
|
|
@ -54,7 +54,7 @@
|
|||
<el-icon>
|
||||
<Message />
|
||||
</el-icon>
|
||||
<span>邮箱</span>
|
||||
<span>{{ t('agentUser.email') }}</span>
|
||||
</div>
|
||||
<div class="item-value">{{ userInfo.email || '-' }}</div>
|
||||
</div>
|
||||
|
|
@ -63,7 +63,7 @@
|
|||
<el-icon>
|
||||
<Clock />
|
||||
</el-icon>
|
||||
<span>注册时间</span>
|
||||
<span>{{ t('agentUser.registerTime') }}</span>
|
||||
</div>
|
||||
<div class="item-value">{{ userInfo.registerTime || '-' }}</div>
|
||||
</div>
|
||||
|
|
@ -72,7 +72,7 @@
|
|||
<el-icon>
|
||||
<Wallet />
|
||||
</el-icon>
|
||||
<span>历史交易金额</span>
|
||||
<span>{{ t('agentUser.historyTradeAmount') }}</span>
|
||||
</div>
|
||||
<div class="item-value">
|
||||
<AnimatedNumber :value="Number(userInfo.hisMeny) || 0" />
|
||||
|
|
@ -98,9 +98,11 @@ defineOptions({
|
|||
})
|
||||
import { ref, watch, computed } from 'vue'
|
||||
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 CustomerListTable from '@/components/common/Table.vue'
|
||||
import { getCustomerDetailApi } from '@/api/modules/agent/customerDetail'
|
||||
import { useLanguageStore } from '@/stores/modules/language'
|
||||
import dayjs from 'dayjs'
|
||||
import AnimatedNumber from './AnimatedNumber.vue'
|
||||
|
||||
|
|
@ -115,14 +117,18 @@ import type {
|
|||
TableColumn
|
||||
} from '@/api/types/agent'
|
||||
|
||||
const roleMap: Record<RoleType, string> = {
|
||||
1: '平台',
|
||||
2: '承包商',
|
||||
3: '特殊做市商',
|
||||
4: '普通做市商',
|
||||
5: '代理',
|
||||
6: '用户',
|
||||
}
|
||||
const { t, locale } = useI18n()
|
||||
const languageStore = useLanguageStore()
|
||||
|
||||
// 使用 computed 动态生成角色映射,支持语言切换
|
||||
const roleMap = computed<Record<RoleType, string>>(() => ({
|
||||
1: t('agentUser.role.1'),
|
||||
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<{
|
||||
|
|
@ -165,13 +171,15 @@ const userInfo = ref<UserDisplayInfo>({
|
|||
|
||||
// 表格
|
||||
const tableData = ref<TableDataRow[]>([])
|
||||
const tableColumnsOptions = ref<TableColumn[]>([
|
||||
{ label: '交易账号', prop: 'tradeAccount' },
|
||||
{ label: '账户名称', prop: 'accountName' },
|
||||
{ label: '余额', prop: 'balance', sortable: true },
|
||||
{ label: '交易杠杆', prop: 'leverage' },
|
||||
{ label: '历史交易量', prop: 'tradeVolume' },
|
||||
{ label: '添加时间', prop: 'addTime', width: 180 }
|
||||
|
||||
// 动态生成表格列配置,支持语言切换
|
||||
const tableColumnsOptions = computed<TableColumn[]>(() => [
|
||||
{ label: t('agentUser.tradeAccount'), prop: 'tradeAccount' },
|
||||
{ label: t('agentUser.accountName'), prop: 'accountName' },
|
||||
{ label: t('agentUser.balance'), prop: 'balance', sortable: true },
|
||||
{ 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 = {
|
||||
name: user.email || user.mobile || '-',
|
||||
id: user.id,
|
||||
role: roleMap[user.role_id] || '-',
|
||||
role: roleMap.value[user.role_id] || '-',
|
||||
phone: user.mobile || '-',
|
||||
email: user.email || '-',
|
||||
balance: Number(user.capital_amount) || 0,
|
||||
|
|
@ -207,7 +215,7 @@ const loadDetailData = async (id: string | number) => {
|
|||
}))
|
||||
} catch (err) {
|
||||
console.error('加载详情失败:', err);
|
||||
ElMessage.error('加载详情失败')
|
||||
ElMessage.error(t('agentUser.loadDetailFailed'))
|
||||
} finally {
|
||||
loading.value = false
|
||||
tableLoading.value = false
|
||||
|
|
@ -224,9 +232,18 @@ watch(
|
|||
{ immediate: true }
|
||||
)
|
||||
|
||||
// 监听语言切换,同步更新 i18n 的 locale
|
||||
watch(
|
||||
() => languageStore.lang,
|
||||
(newLang) => {
|
||||
locale.value = newLang
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
// 账户操作
|
||||
const handleAccount = (row: TableDataRow) => {
|
||||
ElMessage.success('操作账户:' + row.tradeAccount)
|
||||
ElMessage.success(t('agentUser.operateAccount') + ':' + row.tradeAccount)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<div class="personal-center">
|
||||
<div class="page-header">
|
||||
<h2>账户信息</h2>
|
||||
<h2>{{ t('personalCenter.pageTitle') }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="content-wrapper">
|
||||
|
|
@ -17,9 +17,9 @@
|
|||
<div class="info-text">
|
||||
<div class="username-row">
|
||||
<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 class="login-time">最后登录:{{ userInfo.last_login }}</div>
|
||||
<div class="login-time">{{ t('personalCenter.lastLogin') }}:{{ userInfo.last_login }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
|
@ -27,35 +27,35 @@
|
|||
<el-card class="card info-card">
|
||||
<div class="info-list">
|
||||
<div class="info-item">
|
||||
<span class="label">邮箱</span>
|
||||
<span class="value">{{ userInfo.email || '未设置' }}</span>
|
||||
<span class="label">{{ t('personalCenter.email') }}</span>
|
||||
<span class="value">{{ userInfo.email || t('personalCenter.notSet') }}</span>
|
||||
<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 v-else>
|
||||
<el-tag type="success" size="small">已绑定</el-tag>
|
||||
<el-tag type="success" size="small">{{ t('personalCenter.bound') }}</el-tag>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="info-item">
|
||||
<span class="label">手机号</span>
|
||||
<span class="value">{{ userInfo.mobile || '未绑定' }}</span>
|
||||
<span class="label">{{ t('personalCenter.mobile') }}</span>
|
||||
<span class="value">{{ userInfo.mobile || t('personalCenter.notBound') }}</span>
|
||||
<el-button v-if="!userInfo.mobile" link type="primary" @click="showEditDialog('mobile')">
|
||||
绑定
|
||||
{{ t('personalCenter.bind') }}
|
||||
</el-button>
|
||||
<template v-else>
|
||||
<el-tag type="success" size="small">已绑定</el-tag>
|
||||
<el-tag type="success" size="small">{{ t('personalCenter.bound') }}</el-tag>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="info-item">
|
||||
<span class="label">密码</span>
|
||||
<el-button link type="primary" @click="showPasswordDialog">修改</el-button>
|
||||
<span class="label">{{ t('personalCenter.password') }}</span>
|
||||
<el-button link type="primary" @click="showPasswordDialog">{{ t('personalCenter.modify') }}</el-button>
|
||||
</div>
|
||||
|
||||
<div class="info-item">
|
||||
<span class="label">语言</span>
|
||||
<span class="value">选择您偏好的通讯语言</span>
|
||||
<span class="label">{{ t('personalCenter.language') }}</span>
|
||||
<span class="value">{{ t('personalCenter.languageDesc') }}</span>
|
||||
<el-select v-model="userInfo.lang_type" @change="handleLanguageChange" style="width: 130px" size="small">
|
||||
<el-option label="简体中文" :value="1" />
|
||||
<el-option label="English" :value="2" />
|
||||
|
|
@ -70,7 +70,7 @@
|
|||
@cancel="handleEditDialogReset" @close="handleEditDialogReset">
|
||||
<el-form ref="editFormRef" :model="editForm" :rules="getEditRules()" label-width="80px" label-position="top">
|
||||
<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" />
|
||||
</el-form-item>
|
||||
|
||||
|
|
@ -80,20 +80,20 @@
|
|||
</Dialog>
|
||||
|
||||
<!-- 密码修改弹窗 → 验证码模式 -->
|
||||
<Dialog :visible="passwordDialogVisible" title="修改密码" width="500px" @confirm="handlePasswordConfirm"
|
||||
<Dialog :visible="passwordDialogVisible" :title="t('personalCenter.modifyPassword')" width="500px" @confirm="handlePasswordConfirm"
|
||||
@cancel="handlePasswordDialogReset" @close="handlePasswordDialogReset">
|
||||
<el-form ref="passwordFormRef" :model="passwordForm" :rules="passwordFormRules" label-width="100px"
|
||||
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 label="mobile" :disabled="!userInfo.mobile">手机验证</el-radio>
|
||||
<el-radio label="email" :disabled="!userInfo.email">邮箱验证</el-radio>
|
||||
<el-radio label="mobile" :disabled="!userInfo.mobile">{{ t('personalCenter.mobileVerify') }}</el-radio>
|
||||
<el-radio label="email" :disabled="!userInfo.email">{{ t('personalCenter.emailVerify') }}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 验证账号 -->
|
||||
<el-form-item label="验证账号">
|
||||
<el-form-item :label="t('personalCenter.verifyAccount')">
|
||||
<span style="font-weight:500;color:#333">
|
||||
{{ passwordForm.verifyType === 'mobile' ? userInfo.mobile : userInfo.email }}
|
||||
</span>
|
||||
|
|
@ -104,14 +104,14 @@
|
|||
:show-send-button="showPwdSendButton" :send-state="pwdSendState" />
|
||||
|
||||
<!-- 新密码1 -->
|
||||
<el-form-item label="新密码" prop="login_password1">
|
||||
<el-input v-model="passwordForm.login_password1" type="password" show-password placeholder="6-12位,含大小写+特殊字符"
|
||||
<el-form-item :label="t('personalCenter.newPassword')" prop="login_password1">
|
||||
<el-input v-model="passwordForm.login_password1" type="password" show-password :placeholder="t('personalCenter.passwordPlaceholder')"
|
||||
size="large" />
|
||||
</el-form-item>
|
||||
|
||||
<!-- 新密码2 -->
|
||||
<el-form-item label="确认密码" prop="login_password2">
|
||||
<el-input v-model="passwordForm.login_password2" type="password" show-password placeholder="请再次输入新密码"
|
||||
<el-form-item :label="t('personalCenter.confirmPassword')" prop="login_password2">
|
||||
<el-input v-model="passwordForm.login_password2" type="password" show-password :placeholder="t('personalCenter.confirmPasswordPlaceholder')"
|
||||
size="large" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
|
@ -124,9 +124,10 @@
|
|||
defineOptions({
|
||||
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 { UserFilled } from '@element-plus/icons-vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import Dialog from '@/components/common/Dialog.vue';
|
||||
import UserCaptcha from '@/views/user/components/Captcha.vue';
|
||||
import { useUserStore } from '@/stores/modules/user';
|
||||
|
|
@ -134,6 +135,7 @@ import { useLanguageStore } from '@/stores/modules/language';
|
|||
import { sendCaptcha, initCaptchaState, clearCaptchaTimer } from '@/utils/sendCaptcha';
|
||||
import { codeRules } from '@/views/user/config/rules';
|
||||
|
||||
const { t, locale } = useI18n()
|
||||
// 类型定义
|
||||
import type {
|
||||
UserInfo,
|
||||
|
|
@ -206,20 +208,20 @@ const getEditRules = (): FormRules => {
|
|||
switch (editField.value) {
|
||||
case 'email':
|
||||
rules.value = [
|
||||
{ required: true, message: '请输入邮箱', trigger: 'blur' },
|
||||
{ type: 'email', message: '邮箱格式不正确', trigger: 'blur' }
|
||||
{ required: true, message: t('personalCenter.pleaseInputEmail'), trigger: 'blur' },
|
||||
{ type: 'email', message: t('personalCenter.emailFormatError'), trigger: 'blur' }
|
||||
];
|
||||
return rules;
|
||||
case 'mobile':
|
||||
rules.value = [
|
||||
{ required: true, message: '请输入手机号', trigger: 'blur' },
|
||||
{ pattern: /^\+?\d{5,20}$/, message: '手机号格式不正确', trigger: 'blur' }
|
||||
{ required: true, message: t('personalCenter.pleaseInputMobile'), trigger: 'blur' },
|
||||
{ pattern: /^\+?\d{5,20}$/, message: t('personalCenter.mobileFormatError'), trigger: 'blur' }
|
||||
];
|
||||
return rules;
|
||||
case 'username':
|
||||
rules.value = [
|
||||
{ required: true, message: '请输入用户名', trigger: 'blur' },
|
||||
{ min: 2, max: 20, message: '用户名长度 2-20 位', trigger: 'blur' }
|
||||
{ required: true, message: t('personalCenter.pleaseInputUsername'), trigger: 'blur' },
|
||||
{ min: 2, max: 20, message: t('personalCenter.usernameLengthError'), trigger: 'blur' }
|
||||
];
|
||||
return rules;
|
||||
default:
|
||||
|
|
@ -229,22 +231,22 @@ const getEditRules = (): FormRules => {
|
|||
|
||||
// 密码强度校验
|
||||
const passwordFormRules = ref<FormRules>({
|
||||
verifyType: [{ required: true, message: '请选择验证方式', trigger: 'change' }],
|
||||
verifyType: [{ required: true, message: t('personalCenter.pleaseSelectVerifyMethod'), trigger: 'change' }],
|
||||
code: codeRules().code,
|
||||
login_password1: [
|
||||
{ required: true, message: '请输入新密码', trigger: 'blur' },
|
||||
{ min: 6, max: 12, message: '长度 6-12 位', trigger: 'blur' },
|
||||
{ required: true, message: t('personalCenter.pleaseInputNewPassword'), trigger: 'blur' },
|
||||
{ min: 6, max: 12, message: t('personalCenter.passwordLengthError'), trigger: 'blur' },
|
||||
{
|
||||
pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*[^a-zA-Z0-9]).{6,12}$/,
|
||||
message: '必须包含大小写+特殊字符', trigger: 'blur'
|
||||
message: t('personalCenter.passwordComplexityError'), trigger: 'blur'
|
||||
}
|
||||
],
|
||||
login_password2: [
|
||||
{ required: true, message: '请确认新密码', trigger: 'blur' },
|
||||
{ required: true, message: t('personalCenter.pleaseConfirmNewPassword'), trigger: 'blur' },
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (value !== passwordForm.login_password1) {
|
||||
callback(new Error('两次密码不一致'));
|
||||
callback(new Error(t('personalCenter.passwordMismatchError')));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
|
|
@ -261,7 +263,7 @@ const fetchUserProfile = async () => {
|
|||
Object.assign(userInfo, res);
|
||||
languageStore.changeLang(res.lang_type === 1 ? 'zh-CN' : 'en-US');
|
||||
} catch (error) {
|
||||
ElMessage.error('获取用户信息失败');
|
||||
ElMessage.error(t('personalCenter.getUserInfoFailed'));
|
||||
console.error('获取用户信息失败:', error);
|
||||
}
|
||||
};
|
||||
|
|
@ -272,19 +274,19 @@ const showEditDialog = (field: 'email' | 'mobile' | 'username') => {
|
|||
editForm.code = '';
|
||||
switch (field) {
|
||||
case 'email':
|
||||
editForm.label = '邮箱';
|
||||
editForm.label = t('personalCenter.email');
|
||||
editForm.value = userInfo.email;
|
||||
dialogTitle.value = '绑定邮箱';
|
||||
dialogTitle.value = t('personalCenter.bindEmail');
|
||||
break;
|
||||
case 'mobile':
|
||||
editForm.label = '手机号';
|
||||
editForm.label = t('personalCenter.mobile');
|
||||
editForm.value = userInfo.mobile;
|
||||
dialogTitle.value = userInfo.mobile ? '重新绑定手机号' : '绑定手机号';
|
||||
dialogTitle.value = userInfo.mobile ? t('personalCenter.rebindMobile') : t('personalCenter.bindMobile');
|
||||
break;
|
||||
case 'username':
|
||||
editForm.label = '用户名';
|
||||
editForm.label = t('personalCenter.username');
|
||||
editForm.value = userInfo.username;
|
||||
dialogTitle.value = '修改用户名';
|
||||
dialogTitle.value = t('personalCenter.modifyUsername');
|
||||
break;
|
||||
}
|
||||
dialogVisible.value = true;
|
||||
|
|
@ -296,13 +298,13 @@ const handleSendCaptcha = async () => {
|
|||
const type = editField.value === 'email' ? 'email' : 'phone';
|
||||
const receiver = editForm.value?.trim();
|
||||
if (!receiver) {
|
||||
ElMessage.warning('请输入接收验证码的账号');
|
||||
ElMessage.warning(t('personalCenter.pleaseInputReceiverAccount'));
|
||||
return;
|
||||
}
|
||||
const currentState = type === 'email' ? emailCaptchaState : phoneCaptchaState;
|
||||
await sendCaptcha(type, receiver, currentState, () => {
|
||||
return userStore.sendCode({
|
||||
reg_type: type === 'email' ? 1 : 2,
|
||||
reg_type: userStore.userInfo!.reg_type,
|
||||
[type === 'email' ? 'email' : 'mobile']: receiver,
|
||||
type: type === 'email' ? 'transfer_code' : 'bindMobile_code'
|
||||
});
|
||||
|
|
@ -358,7 +360,7 @@ const handleEditConfirm = async () => {
|
|||
// ==================== 密码修改逻辑 ====================
|
||||
const showPasswordDialog = () => {
|
||||
if (!userInfo.mobile && !userInfo.email) {
|
||||
ElMessage.warning('请先绑定手机或邮箱');
|
||||
ElMessage.warning(t('personalCenter.pleaseBindMobileOrEmail'));
|
||||
return;
|
||||
}
|
||||
passwordForm.verifyType = userInfo.mobile ? 'mobile' : 'email';
|
||||
|
|
@ -389,14 +391,14 @@ const handleSendPwdCaptcha = async () => {
|
|||
const type = passwordForm.verifyType === 'email' ? 'email' : 'phone';
|
||||
const receiver = passwordForm.verifyType === 'email' ? userInfo.email : userInfo.mobile;
|
||||
if (!receiver) {
|
||||
ElMessage.warning('请先绑定验证账号');
|
||||
ElMessage.warning(t('personalCenter.pleaseInputVerifyAccount'));
|
||||
return;
|
||||
}
|
||||
const currentState = passwordForm.verifyType === 'email' ? pwdEmailCaptchaState : pwdMobileCaptchaState;
|
||||
|
||||
await sendCaptcha(type, receiver, currentState, () => {
|
||||
return userStore.sendCode({
|
||||
reg_type: passwordForm.verifyType === 'email' ? 1 : 2,
|
||||
reg_type: userStore.userInfo!.reg_type,
|
||||
[passwordForm.verifyType]: receiver,
|
||||
type: 'resetPass_code'
|
||||
});
|
||||
|
|
@ -416,7 +418,7 @@ const handlePasswordConfirm = async () => {
|
|||
await passwordFormRef.value.validate();
|
||||
|
||||
const params: UpdatePwdByCaptchaParams = {
|
||||
reg_type: passwordForm.verifyType === 'email' ? 1 : 2,
|
||||
reg_type: userStore.userInfo!.reg_type,
|
||||
email: passwordForm.verifyType === 'email' ? userInfo.email : undefined,
|
||||
mobile: passwordForm.verifyType === 'mobile' ? userInfo.mobile : undefined,
|
||||
code: passwordForm.code,
|
||||
|
|
@ -443,6 +445,15 @@ const handlePasswordDialogReset = () => {
|
|||
passwordFormRef.value?.resetFields();
|
||||
};
|
||||
|
||||
// 监听语言切换,同步更新 i18n 的 locale
|
||||
watch(
|
||||
() => languageStore.lang,
|
||||
(newLang) => {
|
||||
locale.value = newLang
|
||||
},
|
||||
{ immediate: true }
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
fetchUserProfile();
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue