feat(i18n): 管理端与玩家端三语支持(中/英/马来语)
- 管理后台 adminT 文案库、结算与代理端页面、表单校验 - 玩家端 vue-i18n 补全首页/公告/串关与 ms 文案 - Element Plus ms 语言包与共享 locale 工具
This commit is contained in:
47
apps/admin/src/components/AdminLocaleSwitcher.vue
Normal file
47
apps/admin/src/components/AdminLocaleSwitcher.vue
Normal file
@@ -0,0 +1,47 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { useAdminLocale, type AdminLocale } from '../composables/useAdminLocale';
|
||||
|
||||
const { locale, locales, setLocale, t } = useAdminLocale();
|
||||
|
||||
const current = computed(() => locales.find((l) => l.code === locale.value) ?? locales[0]);
|
||||
|
||||
function onChange(e: Event) {
|
||||
setLocale((e.target as HTMLSelectElement).value as AdminLocale);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<label class="admin-locale" :title="t('lang')">
|
||||
<span class="admin-locale-flag" aria-hidden="true">{{ current.flag }}</span>
|
||||
<select :value="locale" class="admin-locale-select" :aria-label="t('lang')" @change="onChange">
|
||||
<option v-for="l in locales" :key="l.code" :value="l.code">
|
||||
{{ l.flag }} {{ l.label }}
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.admin-locale {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
.admin-locale-flag {
|
||||
font-size: 18px;
|
||||
line-height: 1;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.admin-locale-select {
|
||||
background: #0d0d0d;
|
||||
color: var(--green-text);
|
||||
border: 1px solid var(--green-border);
|
||||
border-radius: 6px;
|
||||
padding: 4px 8px 4px 4px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
min-width: 108px;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useAdminLocale } from '../composables/useAdminLocale';
|
||||
|
||||
const { t } = useAdminLocale();
|
||||
|
||||
const input = ref('');
|
||||
const code = ref('');
|
||||
@@ -69,9 +72,9 @@ defineExpose({ validate, refresh });
|
||||
<input v-model="honeypot" type="text" name="website" tabindex="-1"
|
||||
autocomplete="off" class="hp-field" aria-hidden="true" />
|
||||
<input v-model="input" type="text" inputmode="numeric" maxlength="4"
|
||||
class="captcha-input" placeholder="Captcha" autocomplete="off" />
|
||||
class="captcha-input" :placeholder="t('login.captcha_ph')" autocomplete="off" />
|
||||
<canvas ref="canvasRef" class="captcha-canvas"
|
||||
title="点击刷新" role="button" tabindex="0"
|
||||
:title="t('login.captcha_refresh')" role="button" tabindex="0"
|
||||
@click="refresh" @keydown.enter="refresh" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -79,10 +82,10 @@ defineExpose({ validate, refresh });
|
||||
<style scoped>
|
||||
.captcha-row {
|
||||
display: flex;
|
||||
gap: 0;
|
||||
align-items: stretch;
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
.hp-field {
|
||||
position: absolute;
|
||||
left: -9999px;
|
||||
@@ -91,25 +94,21 @@ defineExpose({ validate, refresh });
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.captcha-input {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding: 0 14px;
|
||||
border: 1px solid #333;
|
||||
border-right: none;
|
||||
border: none;
|
||||
border-radius: 8px 0 0 8px;
|
||||
background: #0d0d0d;
|
||||
color: #fff;
|
||||
background: #ffffff;
|
||||
color: #111;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
outline: none;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
.captcha-input::placeholder { color: #555; }
|
||||
.captcha-input:focus { border-color: rgba(0, 196, 65, 0.6); }
|
||||
|
||||
.captcha-input::placeholder {
|
||||
color: #9ca3af;
|
||||
}
|
||||
.captcha-canvas {
|
||||
flex-shrink: 0;
|
||||
width: 108px;
|
||||
|
||||
Reference in New Issue
Block a user