添加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">
|
<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>
|
||||||
|
@ -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>
|
@ -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'
|
||||||
}
|
}
|
||||||
})
|
})
|
@ -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
|
||||||
@ -31,7 +37,7 @@ async function login(username: string, password: string): Promise<boolean> {
|
|||||||
try {
|
try {
|
||||||
const response = await responsePromise
|
const response = await responsePromise
|
||||||
const body: LoginInfo = await response.json()
|
const body: LoginInfo = await response.json()
|
||||||
if(body.code == 0) {
|
if (body.code == 0) {
|
||||||
userinfoStore.setToken(body.value?.token ?? '')
|
userinfoStore.setToken(body.value?.token ?? '')
|
||||||
return true
|
return true
|
||||||
} else {
|
} 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";
|
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}
|
||||||
})
|
})
|
Loading…
Reference in New Issue
Block a user