添加i18n

This commit is contained in:
05412 2024-08-01 18:36:05 +08:00
parent 91f99c41d5
commit 72d4e40da3
8 changed files with 311 additions and 34 deletions

View File

@ -1,10 +1,13 @@
<script setup lang="ts"> <script setup lang="ts">
// import {useRouter} from "vue-router";
import HomeView from "./components/views/HomeView.vue"; import HomeView from "./components/views/HomeView.vue";
// import {checkLogin} from "./script/functions.ts";
// const token = localStorage.getItem("token") import {useUserinfoStore} from "./stores/userinfo-store.ts";
// const router = useRouter()
// if (token == null) router.push("/login") const userinfoStore = useUserinfoStore()
!async function () {
const isLogin = await checkLogin()
if(!isLogin) userinfoStore.clearToken()
}()
</script> </script>
<template> <template>

View File

@ -1,8 +1,12 @@
<script setup lang="ts"> <script setup lang="ts">
import {ref} from "vue"; import {ref} from "vue";
import {login} from "../../script/functions"; import {login, logout} from "../../script/functions";
import {useUserinfoStore} from "../../stores/userinfo-store.ts";
import {useLocale, useTheme} from "vuetify";
const {t, current} = useLocale()
const theme = useTheme()
const userinfoStore = useUserinfoStore()
const showLoginDialog = ref(false) const showLoginDialog = ref(false)
const username = ref('') const username = ref('')
const password = ref('') const password = ref('')
@ -10,9 +14,11 @@ const passwordVisibleState = ref(false)
const loading = ref(false) const loading = ref(false)
const loginDisabled = ref(false) const loginDisabled = ref(false)
const loginIcon = ref<any>(null) const loginIcon = ref<any>(null)
const isLoginFaild = ref(false)
async function tryLogin() { async function tryLogin() {
loading.value = true loading.value = true
isLoginFaild.value = false
const isSuccess = await login(username.value, password.value) const isSuccess = await login(username.value, password.value)
if (isSuccess) { if (isSuccess) {
loginIcon.value = "mdi-check" loginIcon.value = "mdi-check"
@ -20,12 +26,40 @@ async function tryLogin() {
loginDisabled.value = true loginDisabled.value = true
setTimeout(() => { setTimeout(() => {
showLoginDialog.value = false showLoginDialog.value = false
}, 1000) loginDisabled.value = false
loginIcon.value = null
}, 1500)
} else { } else {
alert("login failed") isLoginFaild.value = true
loading.value = false loading.value = false
} }
} }
function resetLoginError() {
isLoginFaild.value = false
}
function toogleLocale() {
switch (current.value) {
case 'en':
current.value = 'zhHans';
break;
case 'zhHans':
current.value = 'en';
break;
default:
current.value = 'en';
break;
}
localStorage.setItem('locale', current.value)
}
function toggleTheme () {
const currentTheme = theme.global.current.value.dark ? 'light' : 'dark'
theme.global.name.value = currentTheme
localStorage.setItem('theme', currentTheme)
}
</script> </script>
<template> <template>
@ -33,19 +67,22 @@ async function tryLogin() {
<v-app> <v-app>
<v-app-bar> <v-app-bar>
<v-container class="mx-auto d-flex align-center justify-start"> <v-container class="mx-auto d-flex align-center justify-start">
<v-btn text="HOME" to="/" :active="false" prepend-icon="mdi-home"></v-btn> <v-btn class="home-menu-button" :text="t('$vuetify.menu.home')" to="/" :active="false" prepend-icon="mdi-home"></v-btn>
<v-btn text="SERVICES" to="/services" :active="false"></v-btn> <v-btn class="home-menu-button" :text="t('$vuetify.menu.services')" to="/services" :active="false"></v-btn>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-text-field class="home-search-box" density="compact" label="search" rounded="lg" variant="solo-filled" <v-text-field class="home-search-box" density="compact" :label="t('$vuetify.menu.search')" rounded="lg"
variant="solo-filled"
flat hide-details single-line append-inner-icon="mdi-magnify"></v-text-field> flat hide-details single-line append-inner-icon="mdi-magnify"></v-text-field>
<v-avatar class="ml-10" icon="mdi-account" size="32" id="avatar"></v-avatar> <v-btn icon="mdi-theme-light-dark" class="ml-10" @click="toggleTheme"></v-btn>
<v-btn icon="mdi-translate" class="ml-2" @click="toogleLocale"></v-btn>
<v-btn icon="mdi-account" class="ml-2" id="avatar"></v-btn>
<v-menu open-on-hover activator="#avatar" open-delay="0" close-delay="500" location="bottom" width="150"> <v-menu open-on-hover activator="#avatar" open-delay="0" close-delay="500" location="bottom" width="150">
<v-list> <v-list>
<v-list-item value="1" @click="showLoginDialog = true"> <v-list-item value="1" @click="showLoginDialog = true; isLoginFaild = false" v-if="!userinfoStore.isLogin" append-icon="mdi-login">
<v-list-item-title>LOGIN</v-list-item-title> <v-list-item-title>{{ t('$vuetify.user.login') }}</v-list-item-title>
</v-list-item> </v-list-item>
<v-list-item value="2" to="/input" :active="false"> <v-list-item value="2" :active="false" @click="logout" v-if="userinfoStore.isLogin" append-icon="mdi-logout">
<v-list-item-title>REGISTER</v-list-item-title> <v-list-item-title>{{ t('$vuetify.user.logout') }}</v-list-item-title>
</v-list-item> </v-list-item>
</v-list> </v-list>
</v-menu> </v-menu>
@ -57,27 +94,29 @@ async function tryLogin() {
</v-app> </v-app>
</v-responsive> </v-responsive>
<v-dialog v-model="showLoginDialog" persistent width="400"> <v-dialog v-model="showLoginDialog" persistent width="400">
<v-card prepend-icon="mdi-login" title="LOGIN" class="px-10"> <v-card prepend-icon="mdi-login" :title="t('$vuetify.login.title')" class="px-10">
<v-text-field class="login-dialong-input" flat prepend-inner-icon="mdi-account-outline" label="USERNAME" <v-text-field class="login-dialong-input" flat prepend-inner-icon="mdi-account-outline" :label="t('$vuetify.login.username')"
density="compact" density="compact" @input="resetLoginError"
hide-details single-line rounded="lg" variant="solo-filled" v-model="username"></v-text-field> hide-details single-line rounded="lg" variant="solo-filled" v-model="username"></v-text-field>
<v-text-field class="login-dialong-input" flat prepend-inner-icon="mdi-lock-outline" label="PASSWORD" <v-text-field class="login-dialong-input" flat prepend-inner-icon="mdi-lock-outline" :label="t('$vuetify.login.password')"
density="compact" hide-details single-line v-model="password" rounded="lg" variant="solo-filled" density="compact" hide-details single-line v-model="password" rounded="lg" variant="solo-filled"
:type="passwordVisibleState ? 'text' :'password'" title="password" :type="passwordVisibleState ? 'text' :'password'" title="password" @input="resetLoginError"
:append-inner-icon="passwordVisibleState ? 'mdi-eye-off' : 'mdi-eye'" :append-inner-icon="passwordVisibleState ? 'mdi-eye-off' : 'mdi-eye'"
@click:append-inner="passwordVisibleState = !passwordVisibleState"></v-text-field> @click:append-inner="passwordVisibleState = !passwordVisibleState"></v-text-field>
<v-card color="surface-variant" variant="tonal"> <v-alert class="text-medium-emphasis text-caption mb-2" type="warning" icon="mdi-alert" closable
v-model="isLoginFaild">{{ t('$vuetify.login.failedNote') }}</v-alert>
<v-card color="surface-variant mt-5" variant="tonal">
<v-card-text class="text-medium-emphasis text-caption"> <v-card-text class="text-medium-emphasis text-caption">
PLEASE CONFIRM YOU HAVE READ AND AGREE TO THE {{ t('$vuetify.login.agreement') }}
<v-label class="text-caption"><a href="javascript:void(0);">TERMS OF SERVICE</a></v-label> <v-label class="text-caption"><a href="/input" target="_blank">{{ t('$vuetify.login.termsOfService') }}</a></v-label>
</v-card-text> </v-card-text>
</v-card> </v-card>
<v-checkbox class="login-dialong-input" label="Remember me" hide-details color="primary"></v-checkbox> <v-checkbox class="login-dialong-input" :label="t('$vuetify.login.rememberMe')" hide-details color="primary"></v-checkbox>
<v-card-actions> <v-card-actions>
<v-spacer></v-spacer> <v-spacer></v-spacer>
<v-btn text="CANCEL" @click="showLoginDialog = false"></v-btn> <v-btn :text="t('$vuetify.login.button.cancel')" @click="showLoginDialog = false"></v-btn>
<v-btn :loading="loading" color="primary" text="LOGIN" @click="tryLogin()" :icon="loginIcon" <v-btn :loading="loading" color="primary" :text="t('$vuetify.login.button.login')" @click="tryLogin" :icon="loginIcon"
:disabled="loginDisabled"> :disabled="loginDisabled">
</v-btn> </v-btn>
</v-card-actions> </v-card-actions>
@ -93,4 +132,8 @@ async function tryLogin() {
.login-dialong-input { .login-dialong-input {
margin: 10px 0; margin: 10px 0;
} }
.home-menu-button {
width: 100px;
}
</style> </style>

View File

@ -1,7 +1,17 @@
import {createVuetify} from "vuetify"; import {createVuetify} from "vuetify";
import {aliases, mdi} from "vuetify/iconsets/mdi"; import {aliases, mdi} from "vuetify/iconsets/mdi";
import en from "../script/i18n/en.ts";
import zhHans from "../script/i18n/zhHans.ts";
const messages: any = {
en, zhHans
}
export const vuetify = createVuetify({ export const vuetify = createVuetify({
locale: {
locale: localStorage.getItem('locale') ?? 'en',
fallback: 'en',
messages
},
icons: { icons: {
defaultSet: 'mdi', defaultSet: 'mdi',
aliases, aliases,
@ -13,5 +23,8 @@ export const vuetify = createVuetify({
global: { global: {
ripple: true ripple: true
} }
},
theme: {
defaultTheme: localStorage.getItem('theme') ?? 'light'
} }
}) })

View File

@ -10,6 +10,12 @@ interface LoginInfo {
} }
} }
interface Msg {
code: number,
msg?: string,
value?: any
}
async function request(path: string, init: RequestInit): Promise<Response> { async function request(path: string, init: RequestInit): Promise<Response> {
const useSiteConfig = useSiteConfigStore() const useSiteConfig = useSiteConfigStore()
const siteConfig = useSiteConfig.siteConfig const siteConfig = useSiteConfig.siteConfig
@ -42,4 +48,29 @@ async function login(username: string, password: string): Promise<boolean> {
} }
} }
export { request, login } function logout() {
const userinfoStore = useUserinfoStore()
userinfoStore.setToken('')
window.location.reload()
}
async function checkLogin(): Promise<boolean> {
const userinfoStore = useUserinfoStore()
if (userinfoStore.isLogin) {
try {
const reponse = await request('loginCheck', {
headers: {
'Authorization': `Bearer ${userinfoStore.token}`
}
})
const body: Msg = await reponse.json()
return body.code == 0
} catch (_) {
return false
}
} else {
return false
}
}
export {request, login, logout, checkLogin}

28
src/script/i18n/en.ts Normal file
View File

@ -0,0 +1,28 @@
import {en as locale_en} from "vuetify/locale";
import LocaleInterface from "./locale-interface.ts";
const en: LocaleInterface = {
...locale_en,
menu: {
home: 'HOME',
services: 'SERVICES',
search: 'search'
},
user: {
login: 'Login',
logout: 'Logout'
},
login: {
title: 'Login',
username: 'USERNAME',
password: 'PASSWORD',
failedNote: 'Login failed, please check your username and password',
rememberMe: 'Remember me',
agreement: 'PLEASE CONFIRM YOU HAVE READ AND AGREE TO THE',
termsOfService: 'TERMS OF SERVICE',
button: {
login: 'LOGIN',
cancel: 'CANCEL'
}
}
}
export default en

View File

@ -0,0 +1,127 @@
export default interface LocaleInterface {
menu: {
home: string,
services: string,
search: string
},
user: {
login: string,
logout: string
},
login: {
title: string,
username: string,
password: string,
failedNote: string,
rememberMe: string,
agreement: string,
termsOfService: string,
button: {
login: string,
cancel: string
}
},
// ==================
// vuetify
// ==================
badge: string;
open: string;
close: string;
dismiss: string;
confirmEdit: {
ok: string;
cancel: string;
};
dataIterator: {
noResultsText: string;
loadingText: string;
};
dataTable: {
itemsPerPageText: string;
ariaLabel: {
sortDescending: string;
sortAscending: string;
sortNone: string;
activateNone: string;
activateDescending: string;
activateAscending: string;
};
sortBy: string;
};
dataFooter: {
itemsPerPageText: string;
itemsPerPageAll: string;
nextPage: string;
prevPage: string;
firstPage: string;
lastPage: string;
pageText: string;
};
dateRangeInput: {
divider: string;
};
datePicker: {
itemsSelected: string;
range: {
title: string;
header: string;
};
title: string;
header: string;
input: {
placeholder: string;
};
};
noDataText: string;
carousel: {
prev: string;
next: string;
ariaLabel: {
delimiter: string;
};
};
calendar: {
moreEvents: string;
today: string;
};
input: {
clear: string;
prependAction: string;
appendAction: string;
otp: string;
};
fileInput: {
counter: string;
counterSize: string;
};
timePicker: {
am: string;
pm: string;
title: string;
};
pagination: {
ariaLabel: {
root: string;
next: string;
previous: string;
page: string;
currentPage: string;
first: string;
last: string;
};
};
stepper: {
next: string;
prev: string;
};
rating: {
ariaLabel: {
item: string;
};
};
loading: string;
infiniteScroll: {
loadMore: string;
empty: string;
}
}

28
src/script/i18n/zhHans.ts Normal file
View File

@ -0,0 +1,28 @@
import {zhHans as locale_zhHans} from "vuetify/locale";
import LocaleInterface from "./locale-interface.ts";
const zhHans: LocaleInterface = {
...locale_zhHans,
menu: {
home: '首页',
services: '服务',
search: '搜索'
},
user: {
login: '登录',
logout: '退出'
},
login: {
title: '登录',
username: '用户名',
password: '密码',
failedNote: '登录失败,请检查用户名和密码',
rememberMe: '保持登录',
agreement: '我已阅读并同意',
termsOfService: '服务条款',
button: {
login: '登录',
cancel: '取消'
}
}
}
export default zhHans

View File

@ -2,18 +2,22 @@ import {defineStore} from "pinia";
import {computed, ref} from "vue"; import {computed, ref} from "vue";
export const useUserinfoStore = defineStore('userinfo', () => { export const useUserinfoStore = defineStore('userinfo', () => {
const token = ref('') const localToken = localStorage.getItem('token') ?? ''
const token = ref(localToken)
const username = ref('') const username = ref('')
const isLogin = computed(() => token.value.length == 0) const isLogin = computed(() => token.value.length != 0)
const setToken = (t: string): void => { const setToken = (t: string): void => {
token.value = t token.value = t
localStorage.setItem('token', t) localStorage.setItem('token', t)
} }
const clearToken = () => {
setToken('')
}
const getToken = (): string => { const getToken = (): string => {
if (token.value.length == 0) { if (token.value.length == 0) {
token.value = localStorage.getItem('token') ?? '' token.value = localToken
} }
return token.value return token.value
} }
return {token, username, isLogin, setToken, getToken} return {token, username, isLogin, setToken, getToken, clearToken}
}) })