mirror of
https://github.com/Wei-Shaw/claude-relay-service.git
synced 2026-01-23 00:53:33 +00:00
feat: 实现AppHeader和LoginView完整国际化支持
- 完成AppHeader.vue全面国际化 * 版本检查和更新通知系统多语言支持 * 用户菜单和账户信息修改模态框国际化 * 退出登录确认流程多语言化 * 总计替换30+个硬编码中文字符串 - 实现LoginView.vue完整国际化 * 登录表单所有文本支持多语言 * 添加语言切换组件到登录页面 * 确保用户可在登录前选择语言 - 扩展三语言翻译文件 * zh-cn.js: 简体中文标准翻译 * zh-tw.js: 繁体中文专业化翻译 * en.js: 英文技术术语标准翻译 * 新增header和login完整翻译组 - 提升用户体验 * 登录页面右上角工具栏(语言+主题切换) * 响应式布局适配多设备 * 完整的首次访问多语言体验
This commit is contained in:
@@ -11,7 +11,7 @@
|
|||||||
<LogoTitle
|
<LogoTitle
|
||||||
:loading="oemLoading"
|
:loading="oemLoading"
|
||||||
:logo-src="oemSettings.siteIconData || oemSettings.siteIcon"
|
:logo-src="oemSettings.siteIconData || oemSettings.siteIcon"
|
||||||
subtitle="管理后台"
|
:subtitle="t('header.adminPanel')"
|
||||||
:title="oemSettings.siteName"
|
:title="oemSettings.siteName"
|
||||||
title-class="text-white dark:text-gray-100"
|
title-class="text-white dark:text-gray-100"
|
||||||
>
|
>
|
||||||
@@ -27,10 +27,10 @@
|
|||||||
class="inline-flex animate-pulse items-center gap-1 rounded-full border border-green-600 bg-green-500 px-2 py-0.5 text-xs text-white transition-colors hover:bg-green-600"
|
class="inline-flex animate-pulse items-center gap-1 rounded-full border border-green-600 bg-green-500 px-2 py-0.5 text-xs text-white transition-colors hover:bg-green-600"
|
||||||
:href="versionInfo.releaseInfo?.htmlUrl || '#'"
|
:href="versionInfo.releaseInfo?.htmlUrl || '#'"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
title="有新版本可用"
|
:title="t('header.newVersionAvailable')"
|
||||||
>
|
>
|
||||||
<i class="fas fa-arrow-up text-[10px]" />
|
<i class="fas fa-arrow-up text-[10px]" />
|
||||||
<span>新版本</span>
|
<span>{{ t('header.newVersion') }}</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -77,7 +77,7 @@
|
|||||||
<!-- 版本信息 -->
|
<!-- 版本信息 -->
|
||||||
<div class="border-b border-gray-100 px-4 py-3 dark:border-gray-700">
|
<div class="border-b border-gray-100 px-4 py-3 dark:border-gray-700">
|
||||||
<div class="flex items-center justify-between text-sm">
|
<div class="flex items-center justify-between text-sm">
|
||||||
<span class="text-gray-500 dark:text-gray-400">当前版本</span>
|
<span class="text-gray-500 dark:text-gray-400">{{ t('header.currentVersion') }}</span>
|
||||||
<span class="font-mono text-gray-700 dark:text-gray-300"
|
<span class="font-mono text-gray-700 dark:text-gray-300"
|
||||||
>v{{ versionInfo.current || '...' }}</span
|
>v{{ versionInfo.current || '...' }}</span
|
||||||
>
|
>
|
||||||
@@ -85,7 +85,7 @@
|
|||||||
<div v-if="versionInfo.hasUpdate" class="mt-2">
|
<div v-if="versionInfo.hasUpdate" class="mt-2">
|
||||||
<div class="mb-2 flex items-center justify-between text-sm">
|
<div class="mb-2 flex items-center justify-between text-sm">
|
||||||
<span class="font-medium text-green-600 dark:text-green-400">
|
<span class="font-medium text-green-600 dark:text-green-400">
|
||||||
<i class="fas fa-arrow-up mr-1" />有新版本
|
<i class="fas fa-arrow-up mr-1" />{{ t('header.hasUpdate') }}
|
||||||
</span>
|
</span>
|
||||||
<span class="font-mono text-green-600 dark:text-green-400"
|
<span class="font-mono text-green-600 dark:text-green-400"
|
||||||
>v{{ versionInfo.latest }}</span
|
>v{{ versionInfo.latest }}</span
|
||||||
@@ -96,14 +96,14 @@
|
|||||||
:href="versionInfo.releaseInfo?.htmlUrl || '#'"
|
:href="versionInfo.releaseInfo?.htmlUrl || '#'"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
<i class="fas fa-external-link-alt mr-1" />查看更新
|
<i class="fas fa-external-link-alt mr-1" />{{ t('header.viewUpdate') }}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else-if="versionInfo.checkingUpdate"
|
v-else-if="versionInfo.checkingUpdate"
|
||||||
class="mt-2 text-center text-xs text-gray-500 dark:text-gray-400"
|
class="mt-2 text-center text-xs text-gray-500 dark:text-gray-400"
|
||||||
>
|
>
|
||||||
<i class="fas fa-spinner fa-spin mr-1" />检查更新中...
|
<i class="fas fa-spinner fa-spin mr-1" />{{ t('header.checkingUpdate') }}
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="mt-2 text-center">
|
<div v-else class="mt-2 text-center">
|
||||||
<!-- 已是最新版提醒 -->
|
<!-- 已是最新版提醒 -->
|
||||||
@@ -114,7 +114,7 @@
|
|||||||
class="inline-block rounded-lg border border-green-200 bg-green-100 px-3 py-1.5 dark:border-green-800 dark:bg-green-900/30"
|
class="inline-block rounded-lg border border-green-200 bg-green-100 px-3 py-1.5 dark:border-green-800 dark:bg-green-900/30"
|
||||||
>
|
>
|
||||||
<p class="text-xs font-medium text-green-700 dark:text-green-400">
|
<p class="text-xs font-medium text-green-700 dark:text-green-400">
|
||||||
<i class="fas fa-check-circle mr-1" />当前已是最新版本
|
<i class="fas fa-check-circle mr-1" />{{ t('header.alreadyLatest') }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
@@ -123,7 +123,7 @@
|
|||||||
class="text-xs text-blue-500 transition-colors hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
|
class="text-xs text-blue-500 transition-colors hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300"
|
||||||
@click="checkForUpdates()"
|
@click="checkForUpdates()"
|
||||||
>
|
>
|
||||||
<i class="fas fa-sync-alt mr-1" />检查更新
|
<i class="fas fa-sync-alt mr-1" />{{ t('header.checkUpdate') }}
|
||||||
</button>
|
</button>
|
||||||
</transition>
|
</transition>
|
||||||
</div>
|
</div>
|
||||||
@@ -134,7 +134,7 @@
|
|||||||
@click="openChangePasswordModal"
|
@click="openChangePasswordModal"
|
||||||
>
|
>
|
||||||
<i class="fas fa-key text-blue-500" />
|
<i class="fas fa-key text-blue-500" />
|
||||||
<span>修改账户信息</span>
|
<span>{{ t('header.changeAccountInfo') }}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<hr class="my-2 border-gray-200 dark:border-gray-700" />
|
<hr class="my-2 border-gray-200 dark:border-gray-700" />
|
||||||
@@ -144,7 +144,7 @@
|
|||||||
@click="logout"
|
@click="logout"
|
||||||
>
|
>
|
||||||
<i class="fas fa-sign-out-alt text-red-500" />
|
<i class="fas fa-sign-out-alt text-red-500" />
|
||||||
<span>退出登录</span>
|
<span>{{ t('header.logout') }}</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -165,7 +165,7 @@
|
|||||||
>
|
>
|
||||||
<i class="fas fa-key text-white" />
|
<i class="fas fa-key text-white" />
|
||||||
</div>
|
</div>
|
||||||
<h3 class="text-xl font-bold text-gray-900 dark:text-gray-100">修改账户信息</h3>
|
<h3 class="text-xl font-bold text-gray-900 dark:text-gray-100">{{ t('header.changePasswordModal.title') }}</h3>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
class="text-gray-400 transition-colors hover:text-gray-600 dark:hover:text-gray-300"
|
class="text-gray-400 transition-colors hover:text-gray-600 dark:hover:text-gray-300"
|
||||||
@@ -181,7 +181,7 @@
|
|||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300"
|
<label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300"
|
||||||
>当前用户名</label
|
>{{ t('header.changePasswordModal.currentUsername') }}</label
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
class="form-input w-full cursor-not-allowed bg-gray-100 dark:bg-gray-700 dark:text-gray-300"
|
class="form-input w-full cursor-not-allowed bg-gray-100 dark:bg-gray-700 dark:text-gray-300"
|
||||||
@@ -190,31 +190,31 @@
|
|||||||
:value="currentUser.username || 'Admin'"
|
:value="currentUser.username || 'Admin'"
|
||||||
/>
|
/>
|
||||||
<p class="mt-2 text-xs text-gray-500 dark:text-gray-400">
|
<p class="mt-2 text-xs text-gray-500 dark:text-gray-400">
|
||||||
当前用户名,输入新用户名以修改
|
{{ t('header.changePasswordModal.currentUsernameHint') }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300"
|
<label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300"
|
||||||
>新用户名</label
|
>{{ t('header.changePasswordModal.newUsername') }}</label
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
v-model="changePasswordForm.newUsername"
|
v-model="changePasswordForm.newUsername"
|
||||||
class="form-input w-full"
|
class="form-input w-full"
|
||||||
placeholder="输入新用户名(留空保持不变)"
|
:placeholder="t('header.changePasswordModal.newUsernamePlaceholder')"
|
||||||
type="text"
|
type="text"
|
||||||
/>
|
/>
|
||||||
<p class="mt-2 text-xs text-gray-500 dark:text-gray-400">留空表示不修改用户名</p>
|
<p class="mt-2 text-xs text-gray-500 dark:text-gray-400">{{ t('header.changePasswordModal.newUsernameHint') }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300"
|
<label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300"
|
||||||
>当前密码</label
|
>{{ t('header.changePasswordModal.currentPassword') }}</label
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
v-model="changePasswordForm.currentPassword"
|
v-model="changePasswordForm.currentPassword"
|
||||||
class="form-input w-full"
|
class="form-input w-full"
|
||||||
placeholder="请输入当前密码"
|
:placeholder="t('header.changePasswordModal.currentPasswordPlaceholder')"
|
||||||
required
|
required
|
||||||
type="password"
|
type="password"
|
||||||
/>
|
/>
|
||||||
@@ -222,26 +222,26 @@
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300"
|
<label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300"
|
||||||
>新密码</label
|
>{{ t('header.changePasswordModal.newPassword') }}</label
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
v-model="changePasswordForm.newPassword"
|
v-model="changePasswordForm.newPassword"
|
||||||
class="form-input w-full"
|
class="form-input w-full"
|
||||||
placeholder="请输入新密码"
|
:placeholder="t('header.changePasswordModal.newPasswordPlaceholder')"
|
||||||
required
|
required
|
||||||
type="password"
|
type="password"
|
||||||
/>
|
/>
|
||||||
<p class="mt-2 text-xs text-gray-500 dark:text-gray-400">密码长度至少8位</p>
|
<p class="mt-2 text-xs text-gray-500 dark:text-gray-400">{{ t('header.changePasswordModal.newPasswordHint') }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300"
|
<label class="mb-3 block text-sm font-semibold text-gray-700 dark:text-gray-300"
|
||||||
>确认新密码</label
|
>{{ t('header.changePasswordModal.confirmPassword') }}</label
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
v-model="changePasswordForm.confirmPassword"
|
v-model="changePasswordForm.confirmPassword"
|
||||||
class="form-input w-full"
|
class="form-input w-full"
|
||||||
placeholder="请再次输入新密码"
|
:placeholder="t('header.changePasswordModal.confirmPasswordPlaceholder')"
|
||||||
required
|
required
|
||||||
type="password"
|
type="password"
|
||||||
/>
|
/>
|
||||||
@@ -253,7 +253,7 @@
|
|||||||
type="button"
|
type="button"
|
||||||
@click="closeChangePasswordModal"
|
@click="closeChangePasswordModal"
|
||||||
>
|
>
|
||||||
取消
|
{{ t('common.cancel') }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="btn btn-primary flex-1 px-6 py-3 font-semibold"
|
class="btn btn-primary flex-1 px-6 py-3 font-semibold"
|
||||||
@@ -262,7 +262,7 @@
|
|||||||
>
|
>
|
||||||
<div v-if="changePasswordLoading" class="loading-spinner mr-2" />
|
<div v-if="changePasswordLoading" class="loading-spinner mr-2" />
|
||||||
<i v-else class="fas fa-save mr-2" />
|
<i v-else class="fas fa-save mr-2" />
|
||||||
{{ changePasswordLoading ? '保存中...' : '保存修改' }}
|
{{ changePasswordLoading ? t('header.changePasswordModal.saving') : t('header.changePasswordModal.save') }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@@ -273,6 +273,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, reactive, computed, onMounted, onUnmounted } from 'vue'
|
import { ref, reactive, computed, onMounted, onUnmounted } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/auth'
|
||||||
import { showToast } from '@/utils/toast'
|
import { showToast } from '@/utils/toast'
|
||||||
import { apiClient } from '@/config/api'
|
import { apiClient } from '@/config/api'
|
||||||
@@ -282,6 +283,7 @@ import LanguageSwitch from '@/components/common/LanguageSwitch.vue'
|
|||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
// 当前用户信息
|
// 当前用户信息
|
||||||
const currentUser = computed(() => authStore.user || { username: 'Admin' })
|
const currentUser = computed(() => authStore.user || { username: 'Admin' })
|
||||||
@@ -391,12 +393,12 @@ const closeChangePasswordModal = () => {
|
|||||||
// 修改密码
|
// 修改密码
|
||||||
const changePassword = async () => {
|
const changePassword = async () => {
|
||||||
if (changePasswordForm.newPassword !== changePasswordForm.confirmPassword) {
|
if (changePasswordForm.newPassword !== changePasswordForm.confirmPassword) {
|
||||||
showToast('两次输入的密码不一致', 'error')
|
showToast(t('header.changePasswordModal.passwordMismatch'), 'error')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changePasswordForm.newPassword.length < 8) {
|
if (changePasswordForm.newPassword.length < 8) {
|
||||||
showToast('新密码长度至少8位', 'error')
|
showToast(t('header.changePasswordModal.passwordTooShort'), 'error')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -411,8 +413,8 @@ const changePassword = async () => {
|
|||||||
|
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
const message = changePasswordForm.newUsername
|
const message = changePasswordForm.newUsername
|
||||||
? '账户信息修改成功,请重新登录'
|
? t('header.changePasswordModal.accountInfoChangeSuccess')
|
||||||
: '密码修改成功,请重新登录'
|
: t('header.changePasswordModal.passwordChangeSuccess')
|
||||||
showToast(message, 'success')
|
showToast(message, 'success')
|
||||||
closeChangePasswordModal()
|
closeChangePasswordModal()
|
||||||
|
|
||||||
@@ -422,10 +424,10 @@ const changePassword = async () => {
|
|||||||
router.push('/login')
|
router.push('/login')
|
||||||
}, 1500)
|
}, 1500)
|
||||||
} else {
|
} else {
|
||||||
showToast(data.message || '修改失败', 'error')
|
showToast(data.message || t('header.changePasswordModal.changeFailed'), 'error')
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showToast('修改密码失败', 'error')
|
showToast(t('header.changePasswordModal.changePasswordFailed'), 'error')
|
||||||
} finally {
|
} finally {
|
||||||
changePasswordLoading.value = false
|
changePasswordLoading.value = false
|
||||||
}
|
}
|
||||||
@@ -433,10 +435,10 @@ const changePassword = async () => {
|
|||||||
|
|
||||||
// 退出登录
|
// 退出登录
|
||||||
const logout = () => {
|
const logout = () => {
|
||||||
if (confirm('确定要退出登录吗?')) {
|
if (confirm(t('header.logoutConfirm'))) {
|
||||||
authStore.logout()
|
authStore.logout()
|
||||||
router.push('/login')
|
router.push('/login')
|
||||||
showToast('已安全退出', 'success')
|
showToast(t('header.logoutSuccess'), 'success')
|
||||||
}
|
}
|
||||||
userMenuOpen.value = false
|
userMenuOpen.value = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,51 @@ export default {
|
|||||||
adminPanel: 'Admin Panel',
|
adminPanel: 'Admin Panel',
|
||||||
userMenu: 'User Menu',
|
userMenu: 'User Menu',
|
||||||
logout: 'Logout',
|
logout: 'Logout',
|
||||||
settings: 'Settings'
|
settings: 'Settings',
|
||||||
|
|
||||||
|
// Version related
|
||||||
|
currentVersion: 'Current Version',
|
||||||
|
newVersionAvailable: 'New version available',
|
||||||
|
newVersion: 'New Version',
|
||||||
|
hasUpdate: 'New Version Available',
|
||||||
|
viewUpdate: 'View Update',
|
||||||
|
checkingUpdate: 'Checking for updates...',
|
||||||
|
alreadyLatest: 'Already the latest version',
|
||||||
|
checkUpdate: 'Check Update',
|
||||||
|
|
||||||
|
// User menu items
|
||||||
|
changeAccountInfo: 'Change Account Info',
|
||||||
|
|
||||||
|
// Change password modal
|
||||||
|
changePasswordModal: {
|
||||||
|
title: 'Change Account Information',
|
||||||
|
currentUsername: 'Current Username',
|
||||||
|
currentUsernameHint: 'Current username, enter new username to modify',
|
||||||
|
newUsername: 'New Username',
|
||||||
|
newUsernamePlaceholder: 'Enter new username (leave empty to keep unchanged)',
|
||||||
|
newUsernameHint: 'Leave empty to keep username unchanged',
|
||||||
|
currentPassword: 'Current Password',
|
||||||
|
currentPasswordPlaceholder: 'Please enter current password',
|
||||||
|
newPassword: 'New Password',
|
||||||
|
newPasswordPlaceholder: 'Please enter new password',
|
||||||
|
newPasswordHint: 'Password must be at least 8 characters',
|
||||||
|
confirmPassword: 'Confirm New Password',
|
||||||
|
confirmPasswordPlaceholder: 'Please enter new password again',
|
||||||
|
saving: 'Saving...',
|
||||||
|
save: 'Save Changes',
|
||||||
|
|
||||||
|
// Messages
|
||||||
|
passwordMismatch: 'Passwords do not match',
|
||||||
|
passwordTooShort: 'New password must be at least 8 characters',
|
||||||
|
accountInfoChangeSuccess: 'Account information changed successfully, please log in again',
|
||||||
|
passwordChangeSuccess: 'Password changed successfully, please log in again',
|
||||||
|
changeFailed: 'Change failed',
|
||||||
|
changePasswordFailed: 'Failed to change password'
|
||||||
|
},
|
||||||
|
|
||||||
|
// Logout
|
||||||
|
logoutConfirm: 'Are you sure you want to logout?',
|
||||||
|
logoutSuccess: 'Logged out safely'
|
||||||
},
|
},
|
||||||
apiStats: {
|
apiStats: {
|
||||||
title: 'API Key Usage Statistics',
|
title: 'API Key Usage Statistics',
|
||||||
@@ -160,5 +204,16 @@ export default {
|
|||||||
securityNoticeSingle: 'Your API Key is only used to query your own statistical data and will not be stored or used for other purposes',
|
securityNoticeSingle: 'Your API Key is only used to query your own statistical data and will not be stored or used for other purposes',
|
||||||
securityNoticeMulti: 'Your API Keys are only used to query statistical data and will not be stored. Some individual information will not be displayed in aggregate mode.',
|
securityNoticeMulti: 'Your API Keys are only used to query statistical data and will not be stored. Some individual information will not be displayed in aggregate mode.',
|
||||||
multiKeyTip: 'Tip: Supports querying up to 30 API Keys simultaneously. Use Ctrl+Enter for quick query.'
|
multiKeyTip: 'Tip: Supports querying up to 30 API Keys simultaneously. Use Ctrl+Enter for quick query.'
|
||||||
|
},
|
||||||
|
|
||||||
|
// Login page
|
||||||
|
login: {
|
||||||
|
title: 'Admin Panel',
|
||||||
|
username: 'Username',
|
||||||
|
usernamePlaceholder: 'Please enter username',
|
||||||
|
password: 'Password',
|
||||||
|
passwordPlaceholder: 'Please enter password',
|
||||||
|
loginButton: 'Login',
|
||||||
|
loggingIn: 'Logging in...'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,51 @@ export default {
|
|||||||
adminPanel: '管理后台',
|
adminPanel: '管理后台',
|
||||||
userMenu: '用户菜单',
|
userMenu: '用户菜单',
|
||||||
logout: '退出登录',
|
logout: '退出登录',
|
||||||
settings: '系统设置'
|
settings: '系统设置',
|
||||||
|
|
||||||
|
// Version related
|
||||||
|
currentVersion: '当前版本',
|
||||||
|
newVersionAvailable: '有新版本可用',
|
||||||
|
newVersion: '新版本',
|
||||||
|
hasUpdate: '有新版本',
|
||||||
|
viewUpdate: '查看更新',
|
||||||
|
checkingUpdate: '检查更新中...',
|
||||||
|
alreadyLatest: '当前已是最新版本',
|
||||||
|
checkUpdate: '检查更新',
|
||||||
|
|
||||||
|
// User menu items
|
||||||
|
changeAccountInfo: '修改账户信息',
|
||||||
|
|
||||||
|
// Change password modal
|
||||||
|
changePasswordModal: {
|
||||||
|
title: '修改账户信息',
|
||||||
|
currentUsername: '当前用户名',
|
||||||
|
currentUsernameHint: '当前用户名,输入新用户名以修改',
|
||||||
|
newUsername: '新用户名',
|
||||||
|
newUsernamePlaceholder: '输入新用户名(留空保持不变)',
|
||||||
|
newUsernameHint: '留空表示不修改用户名',
|
||||||
|
currentPassword: '当前密码',
|
||||||
|
currentPasswordPlaceholder: '请输入当前密码',
|
||||||
|
newPassword: '新密码',
|
||||||
|
newPasswordPlaceholder: '请输入新密码',
|
||||||
|
newPasswordHint: '密码长度至少8位',
|
||||||
|
confirmPassword: '确认新密码',
|
||||||
|
confirmPasswordPlaceholder: '请再次输入新密码',
|
||||||
|
saving: '保存中...',
|
||||||
|
save: '保存修改',
|
||||||
|
|
||||||
|
// Messages
|
||||||
|
passwordMismatch: '两次输入的密码不一致',
|
||||||
|
passwordTooShort: '新密码长度至少8位',
|
||||||
|
accountInfoChangeSuccess: '账户信息修改成功,请重新登录',
|
||||||
|
passwordChangeSuccess: '密码修改成功,请重新登录',
|
||||||
|
changeFailed: '修改失败',
|
||||||
|
changePasswordFailed: '修改密码失败'
|
||||||
|
},
|
||||||
|
|
||||||
|
// Logout
|
||||||
|
logoutConfirm: '确定要退出登录吗?',
|
||||||
|
logoutSuccess: '已安全退出'
|
||||||
},
|
},
|
||||||
apiStats: {
|
apiStats: {
|
||||||
title: 'API Key 使用统计',
|
title: 'API Key 使用统计',
|
||||||
@@ -160,5 +204,16 @@ export default {
|
|||||||
securityNoticeSingle: '您的 API Key 仅用于查询自己的统计数据,不会被存储或用于其他用途',
|
securityNoticeSingle: '您的 API Key 仅用于查询自己的统计数据,不会被存储或用于其他用途',
|
||||||
securityNoticeMulti: '您的 API Keys 仅用于查询统计数据,不会被存储。聚合模式下部分个体化信息将不显示。',
|
securityNoticeMulti: '您的 API Keys 仅用于查询统计数据,不会被存储。聚合模式下部分个体化信息将不显示。',
|
||||||
multiKeyTip: '提示:最多支持同时查询 30 个 API Keys。使用 Ctrl+Enter 快速查询。'
|
multiKeyTip: '提示:最多支持同时查询 30 个 API Keys。使用 Ctrl+Enter 快速查询。'
|
||||||
|
},
|
||||||
|
|
||||||
|
// Login page
|
||||||
|
login: {
|
||||||
|
title: '管理后台',
|
||||||
|
username: '用户名',
|
||||||
|
usernamePlaceholder: '请输入用户名',
|
||||||
|
password: '密码',
|
||||||
|
passwordPlaceholder: '请输入密码',
|
||||||
|
loginButton: '登录',
|
||||||
|
loggingIn: '登录中...'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,51 @@ export default {
|
|||||||
adminPanel: '管理後台',
|
adminPanel: '管理後台',
|
||||||
userMenu: '用戶選單',
|
userMenu: '用戶選單',
|
||||||
logout: '退出登錄',
|
logout: '退出登錄',
|
||||||
settings: '系統設置'
|
settings: '系統設置',
|
||||||
|
|
||||||
|
// Version related
|
||||||
|
currentVersion: '當前版本',
|
||||||
|
newVersionAvailable: '有新版本可用',
|
||||||
|
newVersion: '新版本',
|
||||||
|
hasUpdate: '有新版本',
|
||||||
|
viewUpdate: '查看更新',
|
||||||
|
checkingUpdate: '檢查更新中...',
|
||||||
|
alreadyLatest: '當前已是最新版本',
|
||||||
|
checkUpdate: '檢查更新',
|
||||||
|
|
||||||
|
// User menu items
|
||||||
|
changeAccountInfo: '修改帳戶資訊',
|
||||||
|
|
||||||
|
// Change password modal
|
||||||
|
changePasswordModal: {
|
||||||
|
title: '修改帳戶資訊',
|
||||||
|
currentUsername: '當前用戶名',
|
||||||
|
currentUsernameHint: '當前用戶名,輸入新用戶名以修改',
|
||||||
|
newUsername: '新用戶名',
|
||||||
|
newUsernamePlaceholder: '輸入新用戶名(留空保持不變)',
|
||||||
|
newUsernameHint: '留空表示不修改用戶名',
|
||||||
|
currentPassword: '當前密碼',
|
||||||
|
currentPasswordPlaceholder: '請輸入當前密碼',
|
||||||
|
newPassword: '新密碼',
|
||||||
|
newPasswordPlaceholder: '請輸入新密碼',
|
||||||
|
newPasswordHint: '密碼長度至少8位',
|
||||||
|
confirmPassword: '確認新密碼',
|
||||||
|
confirmPasswordPlaceholder: '請再次輸入新密碼',
|
||||||
|
saving: '保存中...',
|
||||||
|
save: '保存修改',
|
||||||
|
|
||||||
|
// Messages
|
||||||
|
passwordMismatch: '兩次輸入的密碼不一致',
|
||||||
|
passwordTooShort: '新密碼長度至少8位',
|
||||||
|
accountInfoChangeSuccess: '帳戶資訊修改成功,請重新登錄',
|
||||||
|
passwordChangeSuccess: '密碼修改成功,請重新登錄',
|
||||||
|
changeFailed: '修改失敗',
|
||||||
|
changePasswordFailed: '修改密碼失敗'
|
||||||
|
},
|
||||||
|
|
||||||
|
// Logout
|
||||||
|
logoutConfirm: '確定要退出登錄嗎?',
|
||||||
|
logoutSuccess: '已安全退出'
|
||||||
},
|
},
|
||||||
apiStats: {
|
apiStats: {
|
||||||
title: 'API Key 使用統計',
|
title: 'API Key 使用統計',
|
||||||
@@ -160,5 +204,16 @@ export default {
|
|||||||
securityNoticeSingle: '您的 API Key 僅用於查詢自己的統計資料,不會被儲存或用於其他用途',
|
securityNoticeSingle: '您的 API Key 僅用於查詢自己的統計資料,不會被儲存或用於其他用途',
|
||||||
securityNoticeMulti: '您的 API Keys 僅用於查詢統計資料,不會被儲存。彙整模式下部分個體化資訊將不顯示。',
|
securityNoticeMulti: '您的 API Keys 僅用於查詢統計資料,不會被儲存。彙整模式下部分個體化資訊將不顯示。',
|
||||||
multiKeyTip: '提示:最多支援同時查詢 30 個 API Keys。使用 Ctrl+Enter 快速查詢。'
|
multiKeyTip: '提示:最多支援同時查詢 30 個 API Keys。使用 Ctrl+Enter 快速查詢。'
|
||||||
|
},
|
||||||
|
|
||||||
|
// Login page
|
||||||
|
login: {
|
||||||
|
title: '管理後台',
|
||||||
|
username: '用戶名',
|
||||||
|
usernamePlaceholder: '請輸入用戶名',
|
||||||
|
password: '密碼',
|
||||||
|
passwordPlaceholder: '請輸入密碼',
|
||||||
|
loginButton: '登錄',
|
||||||
|
loggingIn: '登錄中...'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex min-h-screen items-center justify-center p-4 sm:p-6">
|
<div class="flex min-h-screen items-center justify-center p-4 sm:p-6">
|
||||||
<!-- 主题切换按钮 - 固定在右上角 -->
|
<!-- 右上角工具栏 -->
|
||||||
<div class="fixed right-4 top-4 z-50">
|
<div class="fixed right-4 top-4 z-50 flex items-center gap-2 sm:gap-3">
|
||||||
|
<!-- 语言切换按钮 -->
|
||||||
|
<LanguageSwitch mode="dropdown" size="medium" />
|
||||||
|
<!-- 主题切换按钮 -->
|
||||||
<ThemeToggle mode="dropdown" />
|
<ThemeToggle mode="dropdown" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -34,18 +37,18 @@
|
|||||||
v-else-if="oemLoading"
|
v-else-if="oemLoading"
|
||||||
class="mx-auto mb-2 h-8 w-48 animate-pulse rounded bg-gray-300/50 sm:h-9 sm:w-64"
|
class="mx-auto mb-2 h-8 w-48 animate-pulse rounded bg-gray-300/50 sm:h-9 sm:w-64"
|
||||||
/>
|
/>
|
||||||
<p class="text-base text-gray-600 dark:text-gray-400 sm:text-lg">管理后台</p>
|
<p class="text-base text-gray-600 dark:text-gray-400 sm:text-lg">{{ t('login.title') }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form class="space-y-4 sm:space-y-6" @submit.prevent="handleLogin">
|
<form class="space-y-4 sm:space-y-6" @submit.prevent="handleLogin">
|
||||||
<div>
|
<div>
|
||||||
<label class="mb-2 block text-sm font-semibold text-gray-900 dark:text-gray-100 sm:mb-3"
|
<label class="mb-2 block text-sm font-semibold text-gray-900 dark:text-gray-100 sm:mb-3"
|
||||||
>用户名</label
|
>{{ t('login.username') }}</label
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
v-model="loginForm.username"
|
v-model="loginForm.username"
|
||||||
class="form-input w-full"
|
class="form-input w-full"
|
||||||
placeholder="请输入用户名"
|
:placeholder="t('login.usernamePlaceholder')"
|
||||||
required
|
required
|
||||||
type="text"
|
type="text"
|
||||||
/>
|
/>
|
||||||
@@ -53,12 +56,12 @@
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label class="mb-2 block text-sm font-semibold text-gray-900 dark:text-gray-100 sm:mb-3"
|
<label class="mb-2 block text-sm font-semibold text-gray-900 dark:text-gray-100 sm:mb-3"
|
||||||
>密码</label
|
>{{ t('login.password') }}</label
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
v-model="loginForm.password"
|
v-model="loginForm.password"
|
||||||
class="form-input w-full"
|
class="form-input w-full"
|
||||||
placeholder="请输入密码"
|
:placeholder="t('login.passwordPlaceholder')"
|
||||||
required
|
required
|
||||||
type="password"
|
type="password"
|
||||||
/>
|
/>
|
||||||
@@ -71,7 +74,7 @@
|
|||||||
>
|
>
|
||||||
<i v-if="!authStore.loginLoading" class="fas fa-sign-in-alt mr-2" />
|
<i v-if="!authStore.loginLoading" class="fas fa-sign-in-alt mr-2" />
|
||||||
<div v-if="authStore.loginLoading" class="loading-spinner mr-2" />
|
<div v-if="authStore.loginLoading" class="loading-spinner mr-2" />
|
||||||
{{ authStore.loginLoading ? '登录中...' : '登录' }}
|
{{ authStore.loginLoading ? t('login.loggingIn') : t('login.loginButton') }}
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
@@ -87,12 +90,15 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, computed } from 'vue'
|
import { ref, onMounted, computed } from 'vue'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/auth'
|
||||||
import { useThemeStore } from '@/stores/theme'
|
import { useThemeStore } from '@/stores/theme'
|
||||||
import ThemeToggle from '@/components/common/ThemeToggle.vue'
|
import ThemeToggle from '@/components/common/ThemeToggle.vue'
|
||||||
|
import LanguageSwitch from '@/components/common/LanguageSwitch.vue'
|
||||||
|
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
const themeStore = useThemeStore()
|
const themeStore = useThemeStore()
|
||||||
|
const { t } = useI18n()
|
||||||
const oemLoading = computed(() => authStore.oemLoading)
|
const oemLoading = computed(() => authStore.oemLoading)
|
||||||
|
|
||||||
const loginForm = ref({
|
const loginForm = ref({
|
||||||
|
|||||||
Reference in New Issue
Block a user