🐛 fix i18n init bug
This commit is contained in:
@@ -13,7 +13,7 @@ VITE_API_BASE_URL='http://127.0.0.1:8080'
|
||||
VITE_TITLE_NAME='五味子云相册'
|
||||
|
||||
# token key
|
||||
VITE_APP_TOKEN_KEY='schisandra'
|
||||
VITE_APP_TOKEN_KEY='Bearer'
|
||||
|
||||
# the websocket url
|
||||
VITE_WEB_SOCKET_URL='ws://127.0.0.1:3010/wx/socket'
|
||||
|
@@ -12,7 +12,7 @@ VITE_API_BASE_URL='http://1.95.0.111:5050'
|
||||
VITE_TITLE_NAME='五味子云相册'
|
||||
|
||||
# token key
|
||||
VITE_APP_TOKEN_KEY='schisandra'
|
||||
VITE_APP_TOKEN_KEY='Bearer'
|
||||
|
||||
# the websocket url
|
||||
VITE_WEB_SOCKET_URL='ws://127.0.0.1:3010/wx/socket'
|
||||
|
@@ -14,7 +14,9 @@ export default [
|
||||
{
|
||||
rules: {
|
||||
semi: "error",
|
||||
"@typescript-eslint/no-explicit-any": "off"
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
// "no-unused-vars": "off",
|
||||
// "@typescript-eslint/no-unused-vars": ["off"],
|
||||
}
|
||||
}
|
||||
];
|
||||
|
@@ -4,8 +4,19 @@ import {service} from "@/utils/alova/service.ts";
|
||||
export const getUserInfo = () => {
|
||||
return service.Get('/api/auth/user/List', {
|
||||
meta: {
|
||||
ignoreToken: true
|
||||
ignoreToken: false
|
||||
},
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
export const refreshToken = (refreshToken: string) => {
|
||||
return service.Get('/api/auth/token/refresh', {
|
||||
params: {
|
||||
refresh_token: refreshToken
|
||||
},
|
||||
meta: {
|
||||
authRole: 'refreshToken'
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@@ -33,6 +33,9 @@ body {
|
||||
justify-content: center;
|
||||
width: 600px;
|
||||
height: 420px;
|
||||
@media screen and (max-width: 768px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<AConfigProvider
|
||||
:locale="lang.getLang() === 'en' ? enUS : zhCN"
|
||||
:locale="lang.lang === 'en' ? enUS : zhCN"
|
||||
:theme="app.themeConfig"
|
||||
>
|
||||
<router-view v-slot="{ Component, route }">
|
||||
|
@@ -2,7 +2,7 @@
|
||||
import {createI18n} from 'vue-i18n';
|
||||
import zh from './language/zh.ts';
|
||||
import en from './language/en.ts';
|
||||
import {parse} from "zipson/lib";
|
||||
|
||||
|
||||
const messages = {
|
||||
en,
|
||||
@@ -11,16 +11,25 @@ const messages = {
|
||||
|
||||
|
||||
const language: string = (navigator.language || 'en').toLocaleLowerCase(); // 获取浏览器的语言
|
||||
const lang: string = localStorage.getItem("lang") as string;
|
||||
function getLanguage(): string | null {
|
||||
let lang: string | null = null;
|
||||
const langStr: string | null = localStorage.getItem('lang');
|
||||
if (langStr) {
|
||||
lang = JSON.parse(langStr).lang;
|
||||
}
|
||||
return lang;
|
||||
}
|
||||
|
||||
const i18n: any = createI18n({
|
||||
legacy: false,
|
||||
compositionOnly: false,
|
||||
globalInjection: true,
|
||||
silentTranslationWarn: true,
|
||||
locale: parse(lang).lang || language.split('-')[0] || 'zh', // 首先从缓存里拿,没有的话就用浏览器语言,
|
||||
silentFallbackWarn: true,
|
||||
missingWarn: true,
|
||||
fallbackWarn: false,
|
||||
messages
|
||||
});
|
||||
legacy: false,
|
||||
compositionOnly: false,
|
||||
globalInjection: true,
|
||||
silentTranslationWarn: true,
|
||||
locale: getLanguage() || language.split('-')[0] || 'zh', // 首先从缓存里拿,没有的话就用浏览器语言,
|
||||
silentFallbackWarn: true,
|
||||
missingWarn: true,
|
||||
fallbackWarn: false,
|
||||
messages
|
||||
})
|
||||
;
|
||||
export default i18n;
|
||||
|
@@ -34,6 +34,7 @@ export default {
|
||||
rotateCaptchaTitle: "Please drag the slider to complete the puzzle",
|
||||
systemError: "System error, please try again later",
|
||||
captchaExpired: "captcha expired, please try again",
|
||||
|
||||
},
|
||||
error: {
|
||||
networkError: 'Network error, please try again later',
|
||||
@@ -49,5 +50,7 @@ export default {
|
||||
504: 'gateway timeout (504)',
|
||||
505: 'http version not supported (505)',
|
||||
other: 'connect error',
|
||||
authTokenError: "auth token error, please try again later",
|
||||
authTokenExpired: "auth token expired, please login again",
|
||||
}
|
||||
};
|
||||
|
@@ -35,6 +35,7 @@ export default {
|
||||
systemError: "系统错误!请稍后再试!",
|
||||
captchaExpired: "验证码已过期,请重新获取!",
|
||||
|
||||
|
||||
},
|
||||
error: {
|
||||
networkError: '网络连接失败!',
|
||||
@@ -50,6 +51,8 @@ export default {
|
||||
504: '网关超时(504)',
|
||||
505: 'HTTP版本不受支持(505)',
|
||||
other: '连接出错',
|
||||
authTokenError: "认证失败,请重新登录!",
|
||||
authTokenExpired: "认证过期,请重新登录!",
|
||||
|
||||
}
|
||||
};
|
||||
|
14
src/main.ts
14
src/main.ts
@@ -3,14 +3,18 @@ import App from './App.vue';
|
||||
import '@/assets/styles/scroll-bar.scss';
|
||||
import '@/assets/styles/global.scss';
|
||||
import i18n from "@/locales/index.ts";
|
||||
import {setupStore} from "@/store/pinia.ts";
|
||||
import router from "@/router/router.ts";
|
||||
import "go-captcha-vue/dist/style.css"
|
||||
import GoCaptcha from "go-captcha-vue"
|
||||
import "go-captcha-vue/dist/style.css";
|
||||
import GoCaptcha from "go-captcha-vue";
|
||||
import {createPinia, Pinia} from "pinia";
|
||||
import piniaPluginPersistedstate from "pinia-plugin-persistedstate";
|
||||
|
||||
const pinia: Pinia = createPinia();
|
||||
pinia.use(piniaPluginPersistedstate);
|
||||
|
||||
const app = createApp(App);
|
||||
setupStore(app);
|
||||
app.use(pinia);
|
||||
app.use(router);
|
||||
app.use(i18n);
|
||||
app.use(GoCaptcha)
|
||||
app.use(GoCaptcha);
|
||||
app.mount('#app');
|
||||
|
@@ -1,25 +1,12 @@
|
||||
import {defineStore} from 'pinia';
|
||||
import {ref} from 'vue';
|
||||
import pinia from "@/store/pinia.ts";
|
||||
import {parse, stringify} from "zipson/lib";
|
||||
import {ref} from "vue";
|
||||
|
||||
export const langStore = defineStore(
|
||||
'lang',
|
||||
() => {
|
||||
const lang = ref<string>('zh');
|
||||
|
||||
function setLang(value: string) {
|
||||
lang.value = value;
|
||||
}
|
||||
|
||||
function getLang() {
|
||||
return lang.value;
|
||||
}
|
||||
|
||||
return {
|
||||
lang,
|
||||
setLang,
|
||||
getLang,
|
||||
};
|
||||
},
|
||||
{
|
||||
@@ -28,14 +15,6 @@ export const langStore = defineStore(
|
||||
key: 'lang',
|
||||
storage: localStorage,
|
||||
paths: ["lang"],
|
||||
serializer: {
|
||||
deserialize: parse,
|
||||
serialize: stringify,
|
||||
},
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export function useLangStoreWithOut() {
|
||||
return langStore(pinia);
|
||||
}
|
||||
|
@@ -2,7 +2,6 @@ import {defineStore} from 'pinia';
|
||||
import {computed, ref} from 'vue';
|
||||
import {theme} from 'ant-design-vue';
|
||||
import variables from '@/assets/styles/colors.module.scss';
|
||||
import {parse, stringify} from "zipson/lib";
|
||||
|
||||
/**
|
||||
* theme 配置
|
||||
@@ -45,10 +44,6 @@ export const useThemeStore = defineStore(
|
||||
key: 'theme',
|
||||
storage: localStorage,
|
||||
paths: ["themeName", "darkMode"],
|
||||
serializer: {
|
||||
deserialize: parse,
|
||||
serialize: stringify,
|
||||
},
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@@ -1,7 +1,6 @@
|
||||
import {defineStore} from 'pinia';
|
||||
import {ref} from 'vue';
|
||||
import {User} from "@/types/user";
|
||||
import {parse, stringify} from "zipson/lib";
|
||||
|
||||
|
||||
export const useAuthStore = defineStore(
|
||||
@@ -34,10 +33,6 @@ export const useAuthStore = defineStore(
|
||||
key: 'user',
|
||||
storage: localStorage,
|
||||
paths: ["user"],
|
||||
serializer: {
|
||||
deserialize: parse,
|
||||
serialize: stringify,
|
||||
},
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@@ -1,12 +0,0 @@
|
||||
import {createPinia, Pinia} from "pinia";
|
||||
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
|
||||
import {App} from "vue";
|
||||
|
||||
const pinia: Pinia = createPinia();
|
||||
pinia.use(piniaPluginPersistedstate)
|
||||
|
||||
export function setupStore(app: App<Element>) {
|
||||
app.use(pinia);
|
||||
}
|
||||
|
||||
export default pinia;
|
4
src/types/user.d.ts
vendored
4
src/types/user.d.ts
vendored
@@ -1,6 +1,8 @@
|
||||
export interface User {
|
||||
token?: string
|
||||
accessToken?: string
|
||||
userId?: string
|
||||
refreshToken?: string
|
||||
expiresAt?: number
|
||||
|
||||
}
|
||||
|
||||
|
@@ -6,7 +6,7 @@ export const localforageStorageAdapter = {
|
||||
localforage.setItem(key, value).then();
|
||||
},
|
||||
get(key: string) {
|
||||
let value;
|
||||
let value: any;
|
||||
localforage.getItem(key).then((res: any) => {
|
||||
if (res === null || res === undefined) {
|
||||
value = "";
|
||||
|
@@ -2,13 +2,46 @@ import {createAlova} from 'alova';
|
||||
|
||||
import VueHook from 'alova/vue';
|
||||
import useStore from "@/store";
|
||||
import {axiosRequestAdapter} from "@alova/adapter-axios";
|
||||
import {AxiosError, AxiosResponse} from "axios";
|
||||
import {message} from "ant-design-vue";
|
||||
import {localforageStorageAdapter} from "@/utils/alova/adapter/localforageStorageAdapter.ts";
|
||||
import {createServerTokenAuthentication} from "alova/client";
|
||||
import {AxiosError, AxiosResponse} from "axios";
|
||||
import {handleCode} from "@/utils/errorCode/errorCodeHandler.ts";
|
||||
import {message} from "ant-design-vue";
|
||||
import i18n from "@/locales";
|
||||
import {axiosRequestAdapter} from "@alova/adapter-axios";
|
||||
import {refreshToken} from "@/api/user";
|
||||
import router from "@/router/router.ts";
|
||||
|
||||
const {onAuthRequired, onResponseRefreshToken} = createServerTokenAuthentication<typeof VueHook,
|
||||
typeof axiosRequestAdapter>({
|
||||
refreshTokenOnSuccess: {
|
||||
// 在请求前触发,将接收到method参数,并返回boolean表示token是否过期
|
||||
isExpired: (response: AxiosResponse<any, any>, _method: any) => {
|
||||
return response.data.code === 401;
|
||||
},
|
||||
|
||||
// 当token过期时触发,在此函数中触发刷新token
|
||||
handler: async () => {
|
||||
try {
|
||||
const user = useStore().user;
|
||||
const res: any = await refreshToken(user.getUser()?.refreshToken || '');
|
||||
if (res.code === 0 && res.data) {
|
||||
user.setUser({
|
||||
userId: res.data.userId,
|
||||
accessToken: res.data.access_token,
|
||||
refreshToken: res.data.refresh_token,
|
||||
expiresAt: res.data.expires_at,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
// token刷新失败,跳转回登录页
|
||||
message.error(i18n.global.t('error.authTokenError')).then();
|
||||
await router.push('/login');
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
export const service = createAlova({
|
||||
timeout: 5000,
|
||||
baseURL: import.meta.env.VITE_APP_BASE_API,
|
||||
@@ -16,43 +49,38 @@ export const service = createAlova({
|
||||
// 请求适配器
|
||||
requestAdapter: axiosRequestAdapter(),
|
||||
l2Cache: localforageStorageAdapter,
|
||||
cacheFor: {
|
||||
// GET: {
|
||||
// mode: 'restore',
|
||||
// expire: 10 * 1000
|
||||
// },
|
||||
},
|
||||
cacheLogger: import.meta.env.VITE_NODE_ENV === 'development',
|
||||
cacheFor: {},
|
||||
// 设置全局的请求拦截器
|
||||
beforeRequest(method) {
|
||||
beforeRequest: onAuthRequired(async (method: any) => {
|
||||
if (!method.meta?.ignoreToken) {
|
||||
const user = useStore().user;
|
||||
method.config.headers.token = user.getUser()?.token;
|
||||
method.config.headers.Authorization = `${import.meta.env.VITE_APP_TOKEN_KEY} ${user.getUser()?.accessToken}`;
|
||||
}
|
||||
const lang = useStore().lang;
|
||||
method.config.headers['Accept-Language'] = lang.getLang();
|
||||
|
||||
},
|
||||
method.config.headers['Accept-Language'] = lang.lang|| 'zh';
|
||||
}),
|
||||
// 响应拦截器
|
||||
responded: {
|
||||
onSuccess: async (response: AxiosResponse) => {
|
||||
responded: onResponseRefreshToken({
|
||||
onSuccess: async (response: AxiosResponse, _method: any) => {
|
||||
if (response.data instanceof Blob) {
|
||||
return response;
|
||||
} else {
|
||||
return response.data;
|
||||
}
|
||||
},
|
||||
onError(error: AxiosError) {
|
||||
const {response} = error;
|
||||
if (response) {
|
||||
handleCode(response.status);
|
||||
}
|
||||
if (!window.navigator.onLine) {
|
||||
message.error(i18n.global.t('error.networkError')).then();
|
||||
return Promise.reject(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
onError:
|
||||
(error: AxiosError, _method: any) => {
|
||||
const {response} = error;
|
||||
if (response) {
|
||||
handleCode(response.status);
|
||||
}
|
||||
if (!window.navigator.onLine) {
|
||||
message.error(i18n.global.t('error.networkError')).then();
|
||||
return Promise.reject(error);
|
||||
}
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
|
@@ -14,7 +14,7 @@ class Request {
|
||||
this.instance.interceptors.request.use(
|
||||
(config) => {
|
||||
const user = useStore().user;
|
||||
const token: string | undefined = user.getUser()?.token;
|
||||
const token: string | undefined = user.getUser()?.accessToken;
|
||||
if (token) {
|
||||
config.headers.Authorization = `${import.meta.env.VITE_APP_TOKEN_KEY} ${token}`;
|
||||
}
|
||||
|
@@ -14,6 +14,7 @@
|
||||
background-attachment: fixed;
|
||||
/* 让背景图基于容器大小伸缩 */
|
||||
background-size: cover;
|
||||
z-index: -1;
|
||||
|
||||
.forget-left {
|
||||
width: 50%;
|
||||
|
@@ -260,8 +260,9 @@ async function sendCaptcha() {
|
||||
phoneLoginFormRef.value
|
||||
.validateFields("phone")
|
||||
.then(() => {
|
||||
showRotateCaptcha.value = true;
|
||||
getRotateCaptcha();
|
||||
getRotateCaptcha().then(() => {
|
||||
showRotateCaptcha.value = true;
|
||||
});
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.log('error', error);
|
||||
@@ -315,10 +316,11 @@ async function getRotateCaptcha() {
|
||||
* @param angle
|
||||
*/
|
||||
async function checkCaptcha(angle: number) {
|
||||
if (captchaErrorCount.value >= 1) {
|
||||
if (captchaErrorCount.value >= 2) {
|
||||
message.error(t('login.captchaError'));
|
||||
getRotateCaptcha().then();
|
||||
captchaErrorCount.value = 0;
|
||||
getRotateCaptcha().then(() => {
|
||||
captchaErrorCount.value = 0;
|
||||
});
|
||||
} else {
|
||||
const result: any = await checkRotatedCaptcha(angle, captchaData.key);
|
||||
if (result.code === 0 && result.success) {
|
||||
@@ -327,8 +329,10 @@ async function checkCaptcha(angle: number) {
|
||||
countDown();
|
||||
} else if (result.code === 1011) {
|
||||
message.error(t('login.captchaExpired'));
|
||||
getRotateCaptcha().then();
|
||||
captchaErrorCount.value = 0;
|
||||
getRotateCaptcha().then(() => {
|
||||
captchaErrorCount.value = 0;
|
||||
});
|
||||
|
||||
} else {
|
||||
captchaErrorCount.value++;
|
||||
message.error(t('login.captchaError'));
|
||||
|
@@ -14,6 +14,7 @@
|
||||
background-attachment: fixed;
|
||||
/* 让背景图基于容器大小伸缩 */
|
||||
background-size: cover;
|
||||
z-index: -1;
|
||||
|
||||
.login-left {
|
||||
width: 50%;
|
||||
|
@@ -14,6 +14,7 @@
|
||||
background-attachment: fixed;
|
||||
/* 让背景图基于容器大小伸缩 */
|
||||
background-size: cover;
|
||||
z-index: -1;
|
||||
|
||||
.qrlogin-left {
|
||||
width: 50%;
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="test">
|
||||
<AButton @click="handleClick"> {{ t('login.test') }}</AButton>
|
||||
<AButton @click="handleClick"> {{ t('login.title') }}</AButton>
|
||||
<AButton @click="changeLang('zh')"> 切换中文</AButton>
|
||||
<AButton @click="changeLang('en')"> 切换英文</AButton>
|
||||
|
||||
@@ -27,9 +27,8 @@ const handleClick = () => {
|
||||
const lang = useStore().lang;
|
||||
|
||||
async function changeLang(language: any) {
|
||||
lang.setLang(language);
|
||||
lang.lang = language;
|
||||
locale.value = language;
|
||||
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
|
@@ -14,6 +14,7 @@ import {fileURLToPath} from 'node:url';
|
||||
export default defineConfig(({mode}: { mode: string }): object => {
|
||||
const env: Record<string, string> = loadEnv(mode, process.cwd());
|
||||
return {
|
||||
base: '/',
|
||||
resolve: {
|
||||
//设置别名
|
||||
alias: {
|
||||
|
Reference in New Issue
Block a user