添加i18n
This commit is contained in:
parent
91f99c41d5
commit
72d4e40da3
13
src/App.vue
13
src/App.vue
@ -1,10 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
// import {useRouter} from "vue-router";
|
||||
import HomeView from "./components/views/HomeView.vue";
|
||||
//
|
||||
// const token = localStorage.getItem("token")
|
||||
// const router = useRouter()
|
||||
// if (token == null) router.push("/login")
|
||||
import {checkLogin} from "./script/functions.ts";
|
||||
import {useUserinfoStore} from "./stores/userinfo-store.ts";
|
||||
|
||||
const userinfoStore = useUserinfoStore()
|
||||
!async function () {
|
||||
const isLogin = await checkLogin()
|
||||
if(!isLogin) userinfoStore.clearToken()
|
||||
}()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -1,8 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
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 username = ref('')
|
||||
const password = ref('')
|
||||
@ -10,9 +14,11 @@ const passwordVisibleState = ref(false)
|
||||
const loading = ref(false)
|
||||
const loginDisabled = ref(false)
|
||||
const loginIcon = ref<any>(null)
|
||||
const isLoginFaild = ref(false)
|
||||
|
||||
async function tryLogin() {
|
||||
loading.value = true
|
||||
isLoginFaild.value = false
|
||||
const isSuccess = await login(username.value, password.value)
|
||||
if (isSuccess) {
|
||||
loginIcon.value = "mdi-check"
|
||||
@ -20,12 +26,40 @@ async function tryLogin() {
|
||||
loginDisabled.value = true
|
||||
setTimeout(() => {
|
||||
showLoginDialog.value = false
|
||||
}, 1000)
|
||||
loginDisabled.value = false
|
||||
loginIcon.value = null
|
||||
}, 1500)
|
||||
} else {
|
||||
alert("login failed")
|
||||
isLoginFaild.value = true
|
||||
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>
|
||||
|
||||
<template>
|
||||
@ -33,19 +67,22 @@ async function tryLogin() {
|
||||
<v-app>
|
||||
<v-app-bar>
|
||||
<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 text="SERVICES" to="/services" :active="false"></v-btn>
|
||||
<v-btn class="home-menu-button" :text="t('$vuetify.menu.home')" to="/" :active="false" prepend-icon="mdi-home"></v-btn>
|
||||
<v-btn class="home-menu-button" :text="t('$vuetify.menu.services')" to="/services" :active="false"></v-btn>
|
||||
<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>
|
||||
<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-list>
|
||||
<v-list-item value="1" @click="showLoginDialog = true">
|
||||
<v-list-item-title>LOGIN</v-list-item-title>
|
||||
<v-list-item value="1" @click="showLoginDialog = true; isLoginFaild = false" v-if="!userinfoStore.isLogin" append-icon="mdi-login">
|
||||
<v-list-item-title>{{ t('$vuetify.user.login') }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
<v-list-item value="2" to="/input" :active="false">
|
||||
<v-list-item-title>REGISTER</v-list-item-title>
|
||||
<v-list-item value="2" :active="false" @click="logout" v-if="userinfoStore.isLogin" append-icon="mdi-logout">
|
||||
<v-list-item-title>{{ t('$vuetify.user.logout') }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
@ -57,27 +94,29 @@ async function tryLogin() {
|
||||
</v-app>
|
||||
</v-responsive>
|
||||
<v-dialog v-model="showLoginDialog" persistent width="400">
|
||||
<v-card prepend-icon="mdi-login" title="LOGIN" class="px-10">
|
||||
<v-text-field class="login-dialong-input" flat prepend-inner-icon="mdi-account-outline" label="USERNAME"
|
||||
density="compact"
|
||||
<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="t('$vuetify.login.username')"
|
||||
density="compact" @input="resetLoginError"
|
||||
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"
|
||||
:type="passwordVisibleState ? 'text' :'password'" title="password"
|
||||
:type="passwordVisibleState ? 'text' :'password'" title="password" @input="resetLoginError"
|
||||
:append-inner-icon="passwordVisibleState ? 'mdi-eye-off' : 'mdi-eye'"
|
||||
@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">
|
||||
PLEASE CONFIRM YOU HAVE READ AND AGREE TO THE
|
||||
<v-label class="text-caption"><a href="javascript:void(0);">TERMS OF SERVICE</a></v-label>
|
||||
{{ t('$vuetify.login.agreement') }}
|
||||
<v-label class="text-caption"><a href="/input" target="_blank">{{ t('$vuetify.login.termsOfService') }}</a></v-label>
|
||||
</v-card-text>
|
||||
</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-spacer></v-spacer>
|
||||
<v-btn text="CANCEL" @click="showLoginDialog = false"></v-btn>
|
||||
<v-btn :loading="loading" color="primary" text="LOGIN" @click="tryLogin()" :icon="loginIcon"
|
||||
<v-btn :text="t('$vuetify.login.button.cancel')" @click="showLoginDialog = false"></v-btn>
|
||||
<v-btn :loading="loading" color="primary" :text="t('$vuetify.login.button.login')" @click="tryLogin" :icon="loginIcon"
|
||||
:disabled="loginDisabled">
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
@ -93,4 +132,8 @@ async function tryLogin() {
|
||||
.login-dialong-input {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.home-menu-button {
|
||||
width: 100px;
|
||||
}
|
||||
</style>
|
@ -1,7 +1,17 @@
|
||||
import {createVuetify} from "vuetify";
|
||||
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({
|
||||
locale: {
|
||||
locale: localStorage.getItem('locale') ?? 'en',
|
||||
fallback: 'en',
|
||||
messages
|
||||
},
|
||||
icons: {
|
||||
defaultSet: 'mdi',
|
||||
aliases,
|
||||
@ -13,5 +23,8 @@ export const vuetify = createVuetify({
|
||||
global: {
|
||||
ripple: true
|
||||
}
|
||||
},
|
||||
theme: {
|
||||
defaultTheme: localStorage.getItem('theme') ?? 'light'
|
||||
}
|
||||
})
|
@ -10,6 +10,12 @@ interface LoginInfo {
|
||||
}
|
||||
}
|
||||
|
||||
interface Msg {
|
||||
code: number,
|
||||
msg?: string,
|
||||
value?: any
|
||||
}
|
||||
|
||||
async function request(path: string, init: RequestInit): Promise<Response> {
|
||||
const useSiteConfig = useSiteConfigStore()
|
||||
const siteConfig = useSiteConfig.siteConfig
|
||||
@ -31,7 +37,7 @@ async function login(username: string, password: string): Promise<boolean> {
|
||||
try {
|
||||
const response = await responsePromise
|
||||
const body: LoginInfo = await response.json()
|
||||
if(body.code == 0) {
|
||||
if (body.code == 0) {
|
||||
userinfoStore.setToken(body.value?.token ?? '')
|
||||
return true
|
||||
} else {
|
||||
@ -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
28
src/script/i18n/en.ts
Normal 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
|
127
src/script/i18n/locale-interface.ts
Normal file
127
src/script/i18n/locale-interface.ts
Normal 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
28
src/script/i18n/zhHans.ts
Normal 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
|
@ -2,18 +2,22 @@ import {defineStore} from "pinia";
|
||||
import {computed, ref} from "vue";
|
||||
|
||||
export const useUserinfoStore = defineStore('userinfo', () => {
|
||||
const token = ref('')
|
||||
const localToken = localStorage.getItem('token') ?? ''
|
||||
const token = ref(localToken)
|
||||
const username = ref('')
|
||||
const isLogin = computed(() => token.value.length == 0)
|
||||
const isLogin = computed(() => token.value.length != 0)
|
||||
const setToken = (t: string): void => {
|
||||
token.value = t
|
||||
localStorage.setItem('token', t)
|
||||
}
|
||||
const clearToken = () => {
|
||||
setToken('')
|
||||
}
|
||||
const getToken = (): string => {
|
||||
if (token.value.length == 0) {
|
||||
token.value = localStorage.getItem('token') ?? ''
|
||||
token.value = localToken
|
||||
}
|
||||
return token.value
|
||||
}
|
||||
return {token, username, isLogin, setToken, getToken}
|
||||
return {token, username, isLogin, setToken, getToken, clearToken}
|
||||
})
|
Loading…
Reference in New Issue
Block a user