feat: add comprehensive i18n support across all pages
- Add i18n support to admin sidebar, breadcrumbs, and nav-footer - Add i18n support to admin pages: system-settings, users, versions, risk-control, sessions, cloud-variables, cloud-constants - Add i18n support to auth pages: login, register - Add i18n support to install and profile pages - Add i18n support to agent pages: dashboard, finance, users, apps, cards - Replace all hard-coded Chinese text with i18n translation keys - Add comprehensive translation keys to zh.json and en.json - Fix date formatting to use locale-agnostic toLocaleDateString() - Pass t function as parameter for i18n in non-component files
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import { Check, ChevronRight, Database, Loader2, RefreshCw, Server, ShieldCheck, User } from 'lucide-vue-next'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { toast } from 'vue-sonner'
|
||||
|
||||
import { resetInstallCheck } from '@/router/guard/index'
|
||||
|
||||
const { t } = useI18n()
|
||||
const router = useRouter()
|
||||
|
||||
const loading = ref(false)
|
||||
@@ -32,10 +34,10 @@ const form = ref({
|
||||
redis_db: 0,
|
||||
})
|
||||
|
||||
const dbTypes = [
|
||||
{ value: 'sqlite', label: 'SQLite', desc: '轻量级,无需额外服务,适合小型部署' },
|
||||
{ value: 'mysql', label: 'MySQL', desc: '高性能,适合生产环境' },
|
||||
]
|
||||
const dbTypes = computed(() => [
|
||||
{ value: 'sqlite', label: t('install.dbTypeSqlite'), desc: t('install.dbTypeSqliteDesc') },
|
||||
{ value: 'mysql', label: t('install.dbTypeMysql'), desc: t('install.dbTypeMysqlDesc') },
|
||||
])
|
||||
|
||||
async function checkInstallStatus() {
|
||||
checkingStatus.value = true
|
||||
@@ -49,7 +51,7 @@ async function checkInstallStatus() {
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
console.error('检查安装状态失败:', error)
|
||||
console.error('Check install status failed:', error)
|
||||
}
|
||||
finally {
|
||||
checkingStatus.value = false
|
||||
@@ -76,12 +78,12 @@ async function testDatabase() {
|
||||
})
|
||||
const result = await res.json()
|
||||
if (!res.ok || result.code !== 200) {
|
||||
throw new Error(result.message || '数据库连接失败')
|
||||
throw new Error(result.message || t('install.dbConnectFailed'))
|
||||
}
|
||||
toast.success(result.data?.message || '数据库连接成功')
|
||||
toast.success(result.data?.message || t('install.dbConnectSuccess'))
|
||||
}
|
||||
catch (error: any) {
|
||||
toast.error(error.message || '数据库连接失败')
|
||||
toast.error(error.message || t('install.dbConnectFailed'))
|
||||
}
|
||||
finally {
|
||||
testing.value = false
|
||||
@@ -145,13 +147,13 @@ async function handleInstall() {
|
||||
})
|
||||
const result = await res.json()
|
||||
if (!res.ok || result.code !== 200) {
|
||||
throw new Error(result.message || '安装失败')
|
||||
throw new Error(result.message || t('install.installFailed'))
|
||||
}
|
||||
toast.success(result.data?.message || '安装成功')
|
||||
toast.success(result.data?.message || t('install.installSuccess'))
|
||||
step.value = 4
|
||||
}
|
||||
catch (error: any) {
|
||||
toast.error(error.message || '安装失败')
|
||||
toast.error(error.message || t('install.installFailed'))
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
@@ -180,7 +182,7 @@ onMounted(() => {
|
||||
<div class="min-h-screen flex items-center justify-center p-4 bg-gradient-to-br from-primary/5 via-background to-background">
|
||||
<div v-if="checkingStatus" class="flex items-center gap-2">
|
||||
<Loader2 class="size-5 animate-spin" />
|
||||
<span>检查安装状态...</span>
|
||||
<span>{{ t('install.checkingStatus') }}</span>
|
||||
</div>
|
||||
|
||||
<div v-else class="w-full max-w-2xl">
|
||||
@@ -191,10 +193,10 @@ onMounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
<h1 class="text-2xl font-bold">
|
||||
系统安装向导
|
||||
{{ t('install.wizardTitle') }}
|
||||
</h1>
|
||||
<p class="text-muted-foreground mt-2">
|
||||
欢迎使用软件授权管理平台,请完成以下配置
|
||||
{{ t('install.wizardDesc') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -224,12 +226,12 @@ onMounted(() => {
|
||||
<div v-if="step === 1">
|
||||
<div class="flex items-center gap-2 mb-6">
|
||||
<Database class="size-5 text-primary" />
|
||||
<h2 class="text-lg font-semibold">数据库配置</h2>
|
||||
<h2 class="text-lg font-semibold">{{ t('install.dbConfig') }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="space-y-3">
|
||||
<UiLabel>数据库类型</UiLabel>
|
||||
<UiLabel>{{ t('install.dbType') }}</UiLabel>
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<button
|
||||
v-for="db in dbTypes"
|
||||
@@ -248,31 +250,31 @@ onMounted(() => {
|
||||
<template v-if="form.db_type === 'mysql'">
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="space-y-2">
|
||||
<UiLabel for="db_host">主机地址</UiLabel>
|
||||
<UiLabel for="db_host">{{ t('install.dbHost') }}</UiLabel>
|
||||
<UiInput id="db_host" v-model="form.db_host" placeholder="localhost" />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<UiLabel for="db_port">端口</UiLabel>
|
||||
<UiLabel for="db_port">{{ t('install.dbPort') }}</UiLabel>
|
||||
<UiInput id="db_port" v-model="form.db_port" placeholder="3306" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<UiLabel for="db_name">数据库名</UiLabel>
|
||||
<UiLabel for="db_name">{{ t('install.dbName') }}</UiLabel>
|
||||
<UiInput id="db_name" v-model="form.db_name" placeholder="verification_platform" />
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="space-y-2">
|
||||
<UiLabel for="db_username">用户名</UiLabel>
|
||||
<UiLabel for="db_username">{{ t('install.dbUsername') }}</UiLabel>
|
||||
<UiInput id="db_username" v-model="form.db_username" placeholder="root" />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<UiLabel for="db_password">密码</UiLabel>
|
||||
<UiLabel for="db_password">{{ t('install.dbPassword') }}</UiLabel>
|
||||
<UiInput id="db_password" v-model="form.db_password" type="password" placeholder="••••••••" />
|
||||
</div>
|
||||
</div>
|
||||
<UiButton variant="outline" :disabled="testing" @click="testDatabase">
|
||||
<Loader2 v-if="testing" class="mr-2 size-4 animate-spin" />
|
||||
测试连接
|
||||
{{ t('install.testConnection') }}
|
||||
</UiButton>
|
||||
</template>
|
||||
</div>
|
||||
@@ -281,17 +283,17 @@ onMounted(() => {
|
||||
<div v-else-if="step === 2">
|
||||
<div class="flex items-center gap-2 mb-6">
|
||||
<ShieldCheck class="size-5 text-primary" />
|
||||
<h2 class="text-lg font-semibold">安全配置</h2>
|
||||
<h2 class="text-lg font-semibold">{{ t('install.securityConfig') }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="space-y-2">
|
||||
<UiLabel for="jwt_secret">JWT 密钥</UiLabel>
|
||||
<UiLabel for="jwt_secret">{{ t('install.jwtSecret') }}</UiLabel>
|
||||
<div class="flex gap-2">
|
||||
<UiInput
|
||||
id="jwt_secret"
|
||||
v-model="form.jwt_secret"
|
||||
placeholder="留空则自动生成"
|
||||
:placeholder="t('install.jwtSecretPlaceholder')"
|
||||
class="flex-1"
|
||||
/>
|
||||
<UiButton variant="outline" @click="generateJwtSecret">
|
||||
@@ -299,33 +301,33 @@ onMounted(() => {
|
||||
</UiButton>
|
||||
</div>
|
||||
<p class="text-sm text-muted-foreground">
|
||||
JWT 密钥用于签名认证令牌,请妥善保管
|
||||
{{ t('install.jwtSecretHint') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="p-4 rounded-lg bg-muted/50">
|
||||
<div class="flex items-center gap-2 mb-2">
|
||||
<Server class="size-4 text-muted-foreground" />
|
||||
<span class="font-medium">Redis 缓存(可选)</span>
|
||||
<span class="font-medium">{{ t('install.redisCache') }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<UiCheckbox id="use_redis" v-model:checked="form.use_redis" />
|
||||
<UiLabel for="use_redis" class="text-sm font-normal cursor-pointer">启用 Redis</UiLabel>
|
||||
<UiLabel for="use_redis" class="text-sm font-normal cursor-pointer">{{ t('install.enableRedis') }}</UiLabel>
|
||||
</div>
|
||||
<template v-if="form.use_redis">
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<div class="space-y-2">
|
||||
<UiLabel for="redis_host">主机</UiLabel>
|
||||
<UiLabel for="redis_host">{{ t('install.redisHost') }}</UiLabel>
|
||||
<UiInput id="redis_host" v-model="form.redis_host" placeholder="localhost" />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<UiLabel for="redis_port">端口</UiLabel>
|
||||
<UiLabel for="redis_port">{{ t('install.redisPort') }}</UiLabel>
|
||||
<UiInput id="redis_port" v-model="form.redis_port" placeholder="6379" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 space-y-2">
|
||||
<UiLabel for="redis_password">密码</UiLabel>
|
||||
<UiInput id="redis_password" v-model="form.redis_password" type="password" placeholder="可选" />
|
||||
<UiLabel for="redis_password">{{ t('install.redisPassword') }}</UiLabel>
|
||||
<UiInput id="redis_password" v-model="form.redis_password" type="password" :placeholder="t('install.redisPasswordPlaceholder')" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
@@ -335,23 +337,23 @@ onMounted(() => {
|
||||
<div v-else-if="step === 3">
|
||||
<div class="flex items-center gap-2 mb-6">
|
||||
<User class="size-5 text-primary" />
|
||||
<h2 class="text-lg font-semibold">管理员账号</h2>
|
||||
<h2 class="text-lg font-semibold">{{ t('install.adminAccount') }}</h2>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div class="space-y-2">
|
||||
<UiLabel for="admin_user">用户名</UiLabel>
|
||||
<UiLabel for="admin_user">{{ t('install.adminUser') }}</UiLabel>
|
||||
<UiInput id="admin_user" v-model="form.admin_user" placeholder="admin" />
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<UiLabel for="admin_pass">密码</UiLabel>
|
||||
<UiInput id="admin_pass" v-model="form.admin_pass" type="password" placeholder="至少6位" />
|
||||
<UiLabel for="admin_pass">{{ t('install.adminPass') }}</UiLabel>
|
||||
<UiInput id="admin_pass" v-model="form.admin_pass" type="password" :placeholder="t('install.adminPassPlaceholder')" />
|
||||
<p v-if="form.admin_pass && form.admin_pass.length < 6" class="text-sm text-destructive">
|
||||
密码长度至少6位
|
||||
{{ t('install.adminPassMinLength') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<UiLabel for="admin_email">邮箱(可选)</UiLabel>
|
||||
<UiLabel for="admin_email">{{ t('install.adminEmail') }}</UiLabel>
|
||||
<UiInput id="admin_email" v-model="form.admin_email" type="email" placeholder="admin@example.com" />
|
||||
</div>
|
||||
</div>
|
||||
@@ -362,12 +364,12 @@ onMounted(() => {
|
||||
<div class="size-16 rounded-full bg-green-100 dark:bg-green-900/30 flex items-center justify-center mx-auto mb-4">
|
||||
<Check class="size-8 text-green-600 dark:text-green-400" />
|
||||
</div>
|
||||
<h2 class="text-xl font-semibold mb-2">安装完成</h2>
|
||||
<h2 class="text-xl font-semibold mb-2">{{ t('install.installComplete') }}</h2>
|
||||
<p class="text-muted-foreground mb-6">
|
||||
系统已成功安装,您现在可以使用管理员账号登录
|
||||
{{ t('install.installCompleteDesc') }}
|
||||
</p>
|
||||
<UiButton @click="goLogin">
|
||||
前往登录
|
||||
{{ t('install.goToLogin') }}
|
||||
<ChevronRight class="ml-2 size-4" />
|
||||
</UiButton>
|
||||
</div>
|
||||
@@ -380,7 +382,7 @@ onMounted(() => {
|
||||
variant="outline"
|
||||
@click="step--"
|
||||
>
|
||||
上一步
|
||||
{{ t('install.prevStep') }}
|
||||
</UiButton>
|
||||
<div v-else />
|
||||
|
||||
@@ -389,7 +391,7 @@ onMounted(() => {
|
||||
:disabled="!canProceed"
|
||||
@click="goNext"
|
||||
>
|
||||
下一步
|
||||
{{ t('install.nextStep') }}
|
||||
<ChevronRight class="ml-2 size-4" />
|
||||
</UiButton>
|
||||
<UiButton
|
||||
@@ -398,7 +400,7 @@ onMounted(() => {
|
||||
@click="handleInstall"
|
||||
>
|
||||
<Loader2 v-if="loading" class="mr-2 size-4 animate-spin" />
|
||||
{{ loading ? '安装中...' : '开始安装' }}
|
||||
{{ loading ? t('install.installing') : t('install.startInstall') }}
|
||||
</UiButton>
|
||||
</UiCardFooter>
|
||||
</UiCard>
|
||||
|
||||
Reference in New Issue
Block a user