commit
33335909f9
@ -17,7 +17,7 @@ export const PROXY_CONFIG = {
|
|||||||
* @转发路径 http://localhost:9999/api/v1/user
|
* @转发路径 http://localhost:9999/api/v1/user
|
||||||
*/
|
*/
|
||||||
'/api/v1': {
|
'/api/v1': {
|
||||||
target: 'http://localhost:9999',
|
target: 'http://127.0.0.1:9999',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
16
web/i18n/index.js
Normal file
16
web/i18n/index.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { createI18n } from 'vue-i18n'
|
||||||
|
import { sStorage } from '@/utils'
|
||||||
|
|
||||||
|
import messages from './messages'
|
||||||
|
|
||||||
|
const currentLocale = sStorage.get('locale')
|
||||||
|
|
||||||
|
const i18n = createI18n({
|
||||||
|
legacy: false,
|
||||||
|
globalInjection: true,
|
||||||
|
locale: currentLocale || 'en',
|
||||||
|
fallbackLocale: 'en',
|
||||||
|
messages: messages
|
||||||
|
})
|
||||||
|
|
||||||
|
export default i18n
|
||||||
62
web/i18n/messages/cn.json
Normal file
62
web/i18n/messages/cn.json
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
{
|
||||||
|
"lang": "中文",
|
||||||
|
"app_name": "Vue FastAPI Admin",
|
||||||
|
"header": {
|
||||||
|
"label_profile": "个人信息",
|
||||||
|
"label_logout": "退出登录",
|
||||||
|
"label_logout_dialog_title": "提示",
|
||||||
|
"text_logout_confirm": "确认退出?",
|
||||||
|
"text_logout_success": "已退出登录"
|
||||||
|
},
|
||||||
|
"views": {
|
||||||
|
"login": {
|
||||||
|
"text_login": "登录",
|
||||||
|
"message_input_username_password": "请输入用户名和密码",
|
||||||
|
"message_verifying": "正在验证...",
|
||||||
|
"message_login_success": "登录成功"
|
||||||
|
},
|
||||||
|
"workbench": {
|
||||||
|
"label_workbench": "工作台",
|
||||||
|
"text_hello": "hello, {username}",
|
||||||
|
"text_welcome": "今天又是元气满满的一天!",
|
||||||
|
"label_number_of_items": "项目数",
|
||||||
|
"label_upcoming": "待办",
|
||||||
|
"label_information": "消息",
|
||||||
|
"label_project": "项目",
|
||||||
|
"label_more": "更多"
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"label_profile": "个人中心",
|
||||||
|
"label_modify_information": "修改信息",
|
||||||
|
"label_change_password": "修改密码",
|
||||||
|
"label_avatar": "头像",
|
||||||
|
"label_username": "用户姓名",
|
||||||
|
"label_email": "邮箱",
|
||||||
|
"label_old_password": "旧密码",
|
||||||
|
"label_new_password": "新密码",
|
||||||
|
"label_confirm_password": "确认密码",
|
||||||
|
"placeholder_username": "请填写姓名",
|
||||||
|
"placeholder_email": "请填写邮箱",
|
||||||
|
"placeholder_old_password": "请输入旧密码",
|
||||||
|
"placeholder_new_password": "请输入新密码",
|
||||||
|
"placeholder_confirm_password": "请再次输入新密码",
|
||||||
|
"message_username_required": "请输入昵称",
|
||||||
|
"message_old_password_required": "请输入旧密码",
|
||||||
|
"message_new_password_required": "请输入新密码",
|
||||||
|
"message_password_confirmation_required": "请再次输入密码",
|
||||||
|
"message_password_confirmation_diff": "两次密码输入不一致"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"label_error": "错误页",
|
||||||
|
"text_back_to_home": "返回首页"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"common": {
|
||||||
|
"text": {
|
||||||
|
"update_success": "修改成功"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"update": "修改"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
62
web/i18n/messages/en.json
Normal file
62
web/i18n/messages/en.json
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
{
|
||||||
|
"lang": "English",
|
||||||
|
"app_name": "Vue FastAPI Admin",
|
||||||
|
"header": {
|
||||||
|
"label_profile": "Profile",
|
||||||
|
"label_logout": "Logout",
|
||||||
|
"label_logout_dialog_title": "Hint",
|
||||||
|
"text_logout_confirm": "Logout confirm",
|
||||||
|
"text_logout_success": "Logout success"
|
||||||
|
},
|
||||||
|
"views": {
|
||||||
|
"login": {
|
||||||
|
"text_login": "Login",
|
||||||
|
"message_input_username_password": "Please enter username and password",
|
||||||
|
"message_verifying": "Verifying...",
|
||||||
|
"message_login_success": "Login successful"
|
||||||
|
},
|
||||||
|
"workbench": {
|
||||||
|
"label_workbench": "Workbench",
|
||||||
|
"text_hello": "hello, {username}",
|
||||||
|
"text_welcome": "Today is another day full of energy!",
|
||||||
|
"label_number_of_items": "Number of items",
|
||||||
|
"label_upcoming": "Upcoming",
|
||||||
|
"label_information": "Information",
|
||||||
|
"label_project": "Project",
|
||||||
|
"label_more": "More"
|
||||||
|
},
|
||||||
|
"profile": {
|
||||||
|
"label_profile": "Profile",
|
||||||
|
"label_modify_information": "Modify your information",
|
||||||
|
"label_change_password": "Change password",
|
||||||
|
"label_avatar": "Avatar",
|
||||||
|
"label_username": "Username",
|
||||||
|
"label_email": "Email",
|
||||||
|
"label_old_password": "Old password",
|
||||||
|
"label_new_password": "New password",
|
||||||
|
"label_confirm_password": "Password confirmation",
|
||||||
|
"placeholder_username": "Please fill in your name",
|
||||||
|
"placeholder_email": "Please fill in your email address",
|
||||||
|
"placeholder_old_password": "Please enter the old password",
|
||||||
|
"placeholder_new_password": "Please enter a new password",
|
||||||
|
"placeholder_confirm_password": "Please enter the confirm password",
|
||||||
|
"message_username_required": "Please enter username",
|
||||||
|
"message_old_password_required": "Please enter the old password",
|
||||||
|
"message_new_password_required": "Please enter a new password",
|
||||||
|
"message_password_confirmation_required": "Please enter confirm password",
|
||||||
|
"message_password_confirmation_diff": "Two password inputs are inconsistent"
|
||||||
|
},
|
||||||
|
"errors": {
|
||||||
|
"label_error": "Error",
|
||||||
|
"text_back_to_home": "Back to home"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"common": {
|
||||||
|
"text": {
|
||||||
|
"update_success": "Update success"
|
||||||
|
},
|
||||||
|
"buttons": {
|
||||||
|
"update": "Update"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
web/i18n/messages/index.js
Normal file
7
web/i18n/messages/index.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import * as en from './en.json'
|
||||||
|
import * as cn from './cn.json'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
en,
|
||||||
|
cn
|
||||||
|
}
|
||||||
@ -34,6 +34,7 @@
|
|||||||
"vite-plugin-html": "^3.2.0",
|
"vite-plugin-html": "^3.2.0",
|
||||||
"vite-plugin-svg-icons": "^2.0.1",
|
"vite-plugin-svg-icons": "^2.0.1",
|
||||||
"vue": "^3.3.4",
|
"vue": "^3.3.4",
|
||||||
|
"vue-i18n": "9",
|
||||||
"vue-router": "^4.2.4"
|
"vue-router": "^4.2.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
1377
web/pnpm-lock.yaml
generated
1377
web/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
30
web/src/layout/components/header/components/Languages.vue
Normal file
30
web/src/layout/components/header/components/Languages.vue
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<template>
|
||||||
|
<n-dropdown :options="options" @select="handleChangeLocale">
|
||||||
|
<n-icon mr-20 size="18" style="cursor: pointer">
|
||||||
|
<icon-mdi:globe/>
|
||||||
|
</n-icon>
|
||||||
|
</n-dropdown>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {useI18n} from 'vue-i18n'
|
||||||
|
import {useAppStore} from "@/store";
|
||||||
|
|
||||||
|
const store = useAppStore()
|
||||||
|
const { availableLocales, t } = useI18n()
|
||||||
|
|
||||||
|
const options = computed(() => {
|
||||||
|
let select = []
|
||||||
|
availableLocales.forEach(locale => {
|
||||||
|
select.push({
|
||||||
|
label: t('lang', 1, {'locale': locale}),
|
||||||
|
key: locale
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return select
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleChangeLocale = (value) => {
|
||||||
|
store.setLocale(value)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@ -11,6 +11,9 @@
|
|||||||
import { useUserStore } from '@/store'
|
import { useUserStore } from '@/store'
|
||||||
import { renderIcon } from '@/utils'
|
import { renderIcon } from '@/utils'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
const {t} = useI18n()
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
@ -18,12 +21,12 @@ const userStore = useUserStore()
|
|||||||
|
|
||||||
const options = [
|
const options = [
|
||||||
{
|
{
|
||||||
label: '个人信息',
|
label: t('header.label_profile'),
|
||||||
key: 'profile',
|
key: 'profile',
|
||||||
icon: renderIcon('mdi-account-arrow-right-outline', { size: '14px' }),
|
icon: renderIcon('mdi-account-arrow-right-outline', { size: '14px' }),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '退出登录',
|
label: t('header.label_logout'),
|
||||||
key: 'logout',
|
key: 'logout',
|
||||||
icon: renderIcon('mdi:exit-to-app', { size: '14px' }),
|
icon: renderIcon('mdi:exit-to-app', { size: '14px' }),
|
||||||
},
|
},
|
||||||
@ -34,12 +37,12 @@ function handleSelect(key) {
|
|||||||
router.push('/profile')
|
router.push('/profile')
|
||||||
} else if (key === 'logout') {
|
} else if (key === 'logout') {
|
||||||
$dialog.confirm({
|
$dialog.confirm({
|
||||||
title: '提示',
|
title: t('header.label_logout_dialog_title'),
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
content: '确认退出?',
|
content: t('header.text_logout_confirm'),
|
||||||
confirm() {
|
confirm() {
|
||||||
userStore.logout()
|
userStore.logout()
|
||||||
$message.success('已退出登录')
|
$message.success(t('header.text_logout_success'))
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
<BreadCrumb ml-15 hidden sm:block />
|
<BreadCrumb ml-15 hidden sm:block />
|
||||||
</div>
|
</div>
|
||||||
<div ml-auto flex items-center>
|
<div ml-auto flex items-center>
|
||||||
|
<Languages />
|
||||||
<ThemeMode />
|
<ThemeMode />
|
||||||
<GithubSite />
|
<GithubSite />
|
||||||
<FullScreen />
|
<FullScreen />
|
||||||
@ -18,4 +19,5 @@ import FullScreen from './components/FullScreen.vue'
|
|||||||
import UserAvatar from './components/UserAvatar.vue'
|
import UserAvatar from './components/UserAvatar.vue'
|
||||||
import GithubSite from './components/GithubSite.vue'
|
import GithubSite from './components/GithubSite.vue'
|
||||||
import ThemeMode from './components/ThemeMode.vue'
|
import ThemeMode from './components/ThemeMode.vue'
|
||||||
|
import Languages from './components/Languages.vue';
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import { setupStore } from '@/store'
|
|||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import { setupDirectives } from './directives'
|
import { setupDirectives } from './directives'
|
||||||
import { useResize } from '@/utils'
|
import { useResize } from '@/utils'
|
||||||
|
import i18n from '~/i18n'
|
||||||
|
|
||||||
async function setupApp() {
|
async function setupApp() {
|
||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
@ -19,6 +20,7 @@ async function setupApp() {
|
|||||||
await setupRouter(app)
|
await setupRouter(app)
|
||||||
setupDirectives(app)
|
setupDirectives(app)
|
||||||
app.use(useResize)
|
app.use(useResize)
|
||||||
|
app.use(i18n)
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,11 @@
|
|||||||
|
import i18n from '~/i18n'
|
||||||
|
const {t} = i18n.global
|
||||||
|
|
||||||
const Layout = () => import('@/layout/index.vue')
|
const Layout = () => import('@/layout/index.vue')
|
||||||
|
|
||||||
export const basicRoutes = [
|
export const basicRoutes = [
|
||||||
{
|
{
|
||||||
name: '工作台',
|
name: t('views.workbench.label_workbench'),
|
||||||
path: '/',
|
path: '/',
|
||||||
component: Layout,
|
component: Layout,
|
||||||
redirect: '/workbench', // 默认跳转到首页
|
redirect: '/workbench', // 默认跳转到首页
|
||||||
@ -10,9 +13,9 @@ export const basicRoutes = [
|
|||||||
{
|
{
|
||||||
path: 'workbench',
|
path: 'workbench',
|
||||||
component: () => import('@/views/workbench/index.vue'),
|
component: () => import('@/views/workbench/index.vue'),
|
||||||
name: '工作台',
|
name: t('views.workbench.label_workbench'),
|
||||||
meta: {
|
meta: {
|
||||||
title: '工作台',
|
title: t('views.workbench.label_workbench'),
|
||||||
icon: 'icon-park-outline:workbench',
|
icon: 'icon-park-outline:workbench',
|
||||||
affix: true,
|
affix: true,
|
||||||
},
|
},
|
||||||
@ -21,7 +24,7 @@ export const basicRoutes = [
|
|||||||
meta: { order: 0 },
|
meta: { order: 0 },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '个人中心',
|
name: t('views.profile.label_profile'),
|
||||||
path: '/',
|
path: '/',
|
||||||
component: Layout,
|
component: Layout,
|
||||||
isHidden: true,
|
isHidden: true,
|
||||||
@ -29,9 +32,9 @@ export const basicRoutes = [
|
|||||||
{
|
{
|
||||||
path: 'profile',
|
path: 'profile',
|
||||||
component: () => import('@/views/profile/index.vue'),
|
component: () => import('@/views/profile/index.vue'),
|
||||||
name: '个人中心',
|
name: t('views.profile.label_profile'),
|
||||||
meta: {
|
meta: {
|
||||||
title: '个人中心',
|
title: t('views.profile.label_profile'),
|
||||||
icon: 'user',
|
icon: 'user',
|
||||||
affix: true,
|
affix: true,
|
||||||
},
|
},
|
||||||
@ -45,7 +48,7 @@ export const basicRoutes = [
|
|||||||
component: Layout,
|
component: Layout,
|
||||||
redirect: '/error-page/404',
|
redirect: '/error-page/404',
|
||||||
meta: {
|
meta: {
|
||||||
title: '错误页',
|
title: t('views.errors.label_error'),
|
||||||
icon: 'mdi:alert-circle-outline',
|
icon: 'mdi:alert-circle-outline',
|
||||||
order: 99,
|
order: 99,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,5 +1,10 @@
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { useDark } from '@vueuse/core'
|
import { useDark } from '@vueuse/core'
|
||||||
|
import { sStorage } from '@/utils'
|
||||||
|
import i18n from '~/i18n'
|
||||||
|
|
||||||
|
const currentLocale = sStorage.get('locale')
|
||||||
|
const {locale} = i18n.global
|
||||||
|
|
||||||
const isDark = useDark()
|
const isDark = useDark()
|
||||||
export const useAppStore = defineStore('app', {
|
export const useAppStore = defineStore('app', {
|
||||||
@ -11,6 +16,7 @@ export const useAppStore = defineStore('app', {
|
|||||||
/** keepAlive路由的key,重新赋值可重置keepAlive */
|
/** keepAlive路由的key,重新赋值可重置keepAlive */
|
||||||
aliveKeys: {},
|
aliveKeys: {},
|
||||||
isDark,
|
isDark,
|
||||||
|
locale: currentLocale || 'en'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
@ -45,5 +51,10 @@ export const useAppStore = defineStore('app', {
|
|||||||
toggleDark() {
|
toggleDark() {
|
||||||
this.isDark = !this.isDark
|
this.isDark = !this.isDark
|
||||||
},
|
},
|
||||||
|
setLocale(newLocale) {
|
||||||
|
this.locale = newLocale
|
||||||
|
locale.value = newLocale
|
||||||
|
sStorage.set('locale', newLocale)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
<icon-custom-unauthorized text-400px text-primary></icon-custom-unauthorized>
|
<icon-custom-unauthorized text-400px text-primary></icon-custom-unauthorized>
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<n-button type="primary" @click="replace('/')">返回首页</n-button>
|
<n-button type="primary" @click="replace('/')">{{ $t('views.errors.text_back_to_home') }}</n-button>
|
||||||
</template>
|
</template>
|
||||||
</n-result>
|
</n-result>
|
||||||
</AppPage>
|
</AppPage>
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
<icon-custom-forbidden text-400px text-primary></icon-custom-forbidden>
|
<icon-custom-forbidden text-400px text-primary></icon-custom-forbidden>
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<n-button type="primary" @click="replace('/')">返回首页</n-button>
|
<n-button type="primary" @click="replace('/')">{{ $t('views.errors.text_back_to_home') }}</n-button>
|
||||||
</template>
|
</template>
|
||||||
</n-result>
|
</n-result>
|
||||||
</AppPage>
|
</AppPage>
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
<icon-custom-not-found text-400px text-primary></icon-custom-not-found>
|
<icon-custom-not-found text-400px text-primary></icon-custom-not-found>
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<n-button type="primary" @click="replace('/')">返回首页</n-button>
|
<n-button type="primary" @click="replace('/')">{{ $t('views.errors.text_back_to_home') }}</n-button>
|
||||||
</template>
|
</template>
|
||||||
</n-result>
|
</n-result>
|
||||||
</AppPage>
|
</AppPage>
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
<icon-custom-server-error text-400px text-primary></icon-custom-server-error>
|
<icon-custom-server-error text-400px text-primary></icon-custom-server-error>
|
||||||
</template>
|
</template>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<n-button type="primary" @click="replace('/')">返回首页</n-button>
|
<n-button type="primary" @click="replace('/')">{{ $t('views.errors.text_back_to_home') }}</n-button>
|
||||||
</template>
|
</template>
|
||||||
</n-result>
|
</n-result>
|
||||||
</AppPage>
|
</AppPage>
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
|
|
||||||
<div w-320 flex-col px-20 py-35>
|
<div w-320 flex-col px-20 py-35>
|
||||||
<h5 f-c-c text-24 font-normal color="#6a6a6a">
|
<h5 f-c-c text-24 font-normal color="#6a6a6a">
|
||||||
<icon-custom-logo mr-10 text-50 color-primary />{{ title }}
|
<icon-custom-logo mr-10 text-50 color-primary />{{ $t('app_name') }}
|
||||||
</h5>
|
</h5>
|
||||||
<div mt-30>
|
<div mt-30>
|
||||||
<n-input
|
<n-input
|
||||||
@ -44,7 +44,7 @@
|
|||||||
:loading="loading"
|
:loading="loading"
|
||||||
@click="handleLogin"
|
@click="handleLogin"
|
||||||
>
|
>
|
||||||
登录
|
{{ $t('views.login.text_login') }}
|
||||||
</n-button>
|
</n-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -57,11 +57,11 @@ import { lStorage, setToken } from '@/utils'
|
|||||||
import bgImg from '@/assets/images/login_bg.webp'
|
import bgImg from '@/assets/images/login_bg.webp'
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import { addDynamicRoutes } from '@/router'
|
import { addDynamicRoutes } from '@/router'
|
||||||
|
import {useI18n} from 'vue-i18n'
|
||||||
const title = import.meta.env.VITE_TITLE
|
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { query } = useRoute()
|
const { query } = useRoute()
|
||||||
|
const {t} = useI18n({ useScope: "global" })
|
||||||
|
|
||||||
const loginInfo = ref({
|
const loginInfo = ref({
|
||||||
username: '',
|
username: '',
|
||||||
@ -82,14 +82,14 @@ const loading = ref(false)
|
|||||||
async function handleLogin() {
|
async function handleLogin() {
|
||||||
const { username, password } = loginInfo.value
|
const { username, password } = loginInfo.value
|
||||||
if (!username || !password) {
|
if (!username || !password) {
|
||||||
$message.warning('请输入用户名和密码')
|
$message.warning(t('views.login.message_input_username_password'))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
$message.loading('正在验证...')
|
$message.loading(t('views.login.message_login_success'))
|
||||||
const res = await api.login({ username, password: password.toString() })
|
const res = await api.login({ username, password: password.toString() })
|
||||||
$message.success('登录成功')
|
$message.success(t('views.login.message_login_success'))
|
||||||
setToken(res.data.access_token)
|
setToken(res.data.access_token)
|
||||||
await addDynamicRoutes()
|
await addDynamicRoutes()
|
||||||
if (query.redirect) {
|
if (query.redirect) {
|
||||||
|
|||||||
@ -1,12 +1,13 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { NButton, NForm, NFormItem, NInput, NTabPane, NTabs, NImage } from 'naive-ui'
|
import { NButton, NForm, NFormItem, NInput, NTabPane, NTabs, NImage } from 'naive-ui'
|
||||||
|
import {useI18n} from "vue-i18n";
|
||||||
import CommonPage from '@/components/page/CommonPage.vue'
|
import CommonPage from '@/components/page/CommonPage.vue'
|
||||||
import { useUserStore } from '@/store'
|
import { useUserStore } from '@/store'
|
||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import { is } from '~/src/utils'
|
import { is } from '~/src/utils'
|
||||||
|
|
||||||
|
const {t} = useI18n()
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
const isLoading = ref(false)
|
const isLoading = ref(false)
|
||||||
|
|
||||||
@ -26,7 +27,7 @@ async function updateProfile() {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
userStore.setUserInfo(infoForm.value)
|
userStore.setUserInfo(infoForm.value)
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
$message.success('修改成功')
|
$message.success(t('common.text.update_success'))
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
@ -37,7 +38,7 @@ const infoFormRules = {
|
|||||||
username: [
|
username: [
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
message: '请输入昵称',
|
message: t('views.profile.message_username_required'),
|
||||||
trigger: ['input', 'blur', 'change'],
|
trigger: ['input', 'blur', 'change'],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -77,31 +78,31 @@ const passwordFormRules = {
|
|||||||
old_password: [
|
old_password: [
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
message: '请输入旧密码',
|
message: t('views.profile.message_old_password_required'),
|
||||||
trigger: ['input', 'blur', 'change'],
|
trigger: ['input', 'blur', 'change'],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
new_password: [
|
new_password: [
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
message: '请输入新密码',
|
message: t('views.profile.message_new_password_required'),
|
||||||
trigger: ['input', 'blur', 'change'],
|
trigger: ['input', 'blur', 'change'],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
confirm_password: [
|
confirm_password: [
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
message: '请再次输入密码',
|
message: t('views.profile.message_password_confirmation_required'),
|
||||||
trigger: ['input', 'blur'],
|
trigger: ['input', 'blur'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
validator: validatePasswordStartWith,
|
validator: validatePasswordStartWith,
|
||||||
message: '两次密码输入不一致',
|
message: t('views.profile.message_password_confirmation_diff'),
|
||||||
trigger: 'input',
|
trigger: 'input',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
validator: validatePasswordSame,
|
validator: validatePasswordSame,
|
||||||
message: '两次密码输入不一致',
|
message: t('views.profile.message_password_confirmation_diff'),
|
||||||
trigger: ['blur', 'password-input'],
|
trigger: ['blur', 'password-input'],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -121,7 +122,7 @@ function validatePasswordSame(rule, value) {
|
|||||||
<template>
|
<template>
|
||||||
<CommonPage :show-header="false">
|
<CommonPage :show-header="false">
|
||||||
<NTabs type="line" animated>
|
<NTabs type="line" animated>
|
||||||
<NTabPane name="website" tab="修改信息">
|
<NTabPane name="website" :tab="$t('views.profile.label_modify_information')">
|
||||||
<div class="m-30 flex items-center">
|
<div class="m-30 flex items-center">
|
||||||
<NForm
|
<NForm
|
||||||
ref="infoFormRef"
|
ref="infoFormRef"
|
||||||
@ -132,56 +133,56 @@ function validatePasswordSame(rule, value) {
|
|||||||
:rules="infoFormRules"
|
:rules="infoFormRules"
|
||||||
class="w-400"
|
class="w-400"
|
||||||
>
|
>
|
||||||
<NFormItem label="头像" path="avatar">
|
<NFormItem :label="$t('views.profile.label_avatar')" path="avatar">
|
||||||
<NImage width="100" :src="infoForm.avatar"></NImage>
|
<NImage width="100" :src="infoForm.avatar"></NImage>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NFormItem label="用户姓名" path="username">
|
<NFormItem :label="$t('views.profile.label_username')" path="username">
|
||||||
<NInput v-model:value="infoForm.username" type="text" placeholder="请填写姓名" />
|
<NInput v-model:value="infoForm.username" type="text" :placeholder="$t('views.profile.placeholder_username')" />
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NFormItem label="邮箱" path="email">
|
<NFormItem :label="$t('views.profile.label_email')" path="email">
|
||||||
<NInput v-model:value="infoForm.email" type="text" placeholder="请填写邮箱" />
|
<NInput v-model:value="infoForm.email" type="text" :placeholder="$t('views.profile.placeholder_email')" />
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NButton type="primary" :loading="isLoading" @click="updateProfile"> 修改 </NButton>
|
<NButton type="primary" :loading="isLoading" @click="updateProfile"> {{$t("common.buttons.update")}} </NButton>
|
||||||
</NForm>
|
</NForm>
|
||||||
</div>
|
</div>
|
||||||
</NTabPane>
|
</NTabPane>
|
||||||
<NTabPane name="contact" tab="修改密码">
|
<NTabPane name="contact" :tab="$t('views.profile.label_change_password')">
|
||||||
<NForm
|
<NForm
|
||||||
ref="passwordFormRef"
|
ref="passwordFormRef"
|
||||||
label-placement="left"
|
label-placement="left"
|
||||||
label-align="left"
|
label-align="left"
|
||||||
:model="passwordForm"
|
:model="passwordForm"
|
||||||
label-width="100"
|
label-width="200"
|
||||||
:rules="passwordFormRules"
|
:rules="passwordFormRules"
|
||||||
class="m-30 w-400"
|
class="m-30 w-500"
|
||||||
>
|
>
|
||||||
<NFormItem label="旧密码" path="old_password">
|
<NFormItem :label="$t('views.profile.label_old_password')" path="old_password">
|
||||||
<NInput
|
<NInput
|
||||||
v-model:value="passwordForm.old_password"
|
v-model:value="passwordForm.old_password"
|
||||||
type="password"
|
type="password"
|
||||||
show-password-on="mousedown"
|
show-password-on="mousedown"
|
||||||
placeholder="请输入旧密码"
|
:placeholder="$t('views.profile.placeholder_old_password')"
|
||||||
/>
|
/>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NFormItem label="新密码" path="new_password">
|
<NFormItem :label="$t('views.profile.label_new_password')" path="new_password">
|
||||||
<NInput
|
<NInput
|
||||||
v-model:value="passwordForm.new_password"
|
v-model:value="passwordForm.new_password"
|
||||||
:disabled="!passwordForm.old_password"
|
:disabled="!passwordForm.old_password"
|
||||||
type="password"
|
type="password"
|
||||||
show-password-on="mousedown"
|
show-password-on="mousedown"
|
||||||
placeholder="请输入新密码"
|
:placeholder="$t('views.profile.placeholder_new_password')"
|
||||||
/>
|
/>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NFormItem label="确认密码" path="confirm_password">
|
<NFormItem :label="$t('views.profile.label_confirm_password')" path="confirm_password">
|
||||||
<NInput
|
<NInput
|
||||||
v-model:value="passwordForm.confirm_password"
|
v-model:value="passwordForm.confirm_password"
|
||||||
:disabled="!passwordForm.new_password"
|
:disabled="!passwordForm.new_password"
|
||||||
type="password"
|
type="password"
|
||||||
show-password-on="mousedown"
|
show-password-on="mousedown"
|
||||||
placeholder="请再次输入新密码"
|
:placeholder="$t('views.profile.placeholder_confirm_password')"
|
||||||
/>
|
/>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NButton type="primary" :loading="isLoading" @click="updatePassword"> 修改 </NButton>
|
<NButton type="primary" :loading="isLoading" @click="updatePassword"> {{$t("common.buttons.update")}} </NButton>
|
||||||
</NForm>
|
</NForm>
|
||||||
</NTabPane>
|
</NTabPane>
|
||||||
</NTabs>
|
</NTabs>
|
||||||
|
|||||||
@ -6,8 +6,8 @@
|
|||||||
<div flex items-center>
|
<div flex items-center>
|
||||||
<img rounded-full width="60" :src="userStore.avatar" />
|
<img rounded-full width="60" :src="userStore.avatar" />
|
||||||
<div ml-10>
|
<div ml-10>
|
||||||
<p text-20 font-semibold>hello, {{ userStore.name }}</p>
|
<p text-20 font-semibold> {{ $t('views.workbench.text_hello', {username: userStore.name}) }}</p>
|
||||||
<p mt-5 text-14 op-60>今天又是元气满满的一天!</p>
|
<p mt-5 text-14 op-60>{{ $t('views.workbench.text_welcome') }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<n-space :size="12" :wrap="false">
|
<n-space :size="12" :wrap="false">
|
||||||
@ -16,9 +16,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</n-card>
|
</n-card>
|
||||||
|
|
||||||
<n-card title="项目" size="small" :segmented="true" mt-15 rounded-10>
|
<n-card :title="$t('views.workbench.label_project')" size="small" :segmented="true" mt-15 rounded-10>
|
||||||
<template #header-extra>
|
<template #header-extra>
|
||||||
<n-button text type="primary">更多</n-button>
|
<n-button text type="primary">{{$t('views.workbench.label_more')}}</n-button>
|
||||||
</template>
|
</template>
|
||||||
<div flex flex-wrap justify-between>
|
<div flex flex-wrap justify-between>
|
||||||
<n-card
|
<n-card
|
||||||
@ -29,7 +29,7 @@
|
|||||||
title="Vue FastAPI Admin"
|
title="Vue FastAPI Admin"
|
||||||
size="small"
|
size="small"
|
||||||
>
|
>
|
||||||
<p op-60>一个基于 Vue3.0、FastAPI、Naive UI 的轻量级后台管理模板</p>
|
<p op-60>{{dummyText}}</p>
|
||||||
</n-card>
|
</n-card>
|
||||||
</div>
|
</div>
|
||||||
</n-card>
|
</n-card>
|
||||||
@ -39,24 +39,28 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { useUserStore } from '@/store'
|
import { useUserStore } from '@/store'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
const statisticData = [
|
const dummyText = "一个基于 Vue3.0、FastAPI、Naive UI 的轻量级后台管理模板"
|
||||||
|
const {t} = useI18n({ useScope: "global" })
|
||||||
|
|
||||||
|
const statisticData = computed(() => [
|
||||||
{
|
{
|
||||||
id: 0,
|
id: 0,
|
||||||
label: '项目数',
|
label: t('views.workbench.label_number_of_items'),
|
||||||
value: '25',
|
value: '25',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
label: '待办',
|
label: t('views.workbench.label_upcoming'),
|
||||||
value: '4/16',
|
value: '4/16',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
label: '消息',
|
label: t('views.workbench.label_information'),
|
||||||
value: '12',
|
value: '12',
|
||||||
},
|
},
|
||||||
]
|
])
|
||||||
|
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user