mirror of
https://gh-proxy.org/https://github.com/klinsmaya/SQLBot.git
synced 2026-11-08 17:26:55 +08:00
perf: User Language
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
"""010_upgrade_user_language
|
||||
|
||||
Revision ID: 8dc3b1bdbfef
|
||||
Revises: 804b08ac329d
|
||||
Create Date: 2025-06-10 11:21:35.257604
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
import sqlmodel.sql.sqltypes
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '8dc3b1bdbfef'
|
||||
down_revision = '804b08ac329d'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
|
||||
op.add_column('sys_user', sa.Column('language', sa.VARCHAR(length=255), server_default=sa.text("'zh-CN'::character varying"), nullable=False))
|
||||
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
|
||||
op.drop_column('sys_user', 'language')
|
||||
|
||||
# ### end Alembic commands ###
|
||||
@@ -1,5 +1,7 @@
|
||||
from fastapi import APIRouter, Depends, Request
|
||||
from apps.system.crud.user import get_user_info
|
||||
from apps.system.models.user import user_grid
|
||||
from apps.system.schemas.system_schema import UserLanguage
|
||||
from common.core.deps import CurrentUser, SessionDep
|
||||
from common.core.pagination import Paginator
|
||||
from common.core.schemas import PaginatedResponse, PaginationParams
|
||||
@@ -8,8 +10,12 @@ router = APIRouter(tags=["user"], prefix="/user")
|
||||
|
||||
|
||||
@router.get("/info")
|
||||
async def user_info(current_user: CurrentUser):
|
||||
return current_user.to_dict()
|
||||
async def user_info(session: SessionDep, current_user: CurrentUser):
|
||||
db_user = get_user_info(session=session, user_id=current_user.id)
|
||||
if not db_user:
|
||||
return {"message": "User not found"}
|
||||
db_user.password = None
|
||||
return db_user
|
||||
|
||||
|
||||
@router.get("/pager/{pageNum}/{pageSize}", response_model=PaginatedResponse[user_grid])
|
||||
@@ -24,4 +30,17 @@ async def pager(
|
||||
return await paginator.get_paginated_response(
|
||||
model=user_grid,
|
||||
pagination=pagination,
|
||||
**filters)
|
||||
**filters)
|
||||
|
||||
@router.put("/language")
|
||||
async def langChange(session: SessionDep, current_user: CurrentUser, language: UserLanguage):
|
||||
lang = language.language
|
||||
if lang not in ["zh-CN", "en"]:
|
||||
return {"message": "Language not supported"}
|
||||
db_user = get_user_info(session=session, user_id=current_user.id)
|
||||
if not db_user:
|
||||
return {"message": "User not found"}
|
||||
db_user.language = lang
|
||||
session.add(db_user)
|
||||
session.commit()
|
||||
return {"message": "Language changed successfully", "language": lang}
|
||||
@@ -10,6 +10,12 @@ def get_user_by_account(*, session: Session, account: str) -> sys_user | None:
|
||||
result_user = sys_user.model_validate(session_user)
|
||||
return result_user
|
||||
|
||||
def get_user_info(*, session: Session, user_id: int) -> sys_user | None:
|
||||
db_user = session.get(user_grid, user_id)
|
||||
if not db_user:
|
||||
return None
|
||||
return db_user
|
||||
|
||||
def authenticate(*, session: Session, account: str, password: str) -> sys_user | None:
|
||||
db_user = get_user_by_account(session=session, account=account)
|
||||
if not db_user:
|
||||
|
||||
@@ -21,4 +21,5 @@ class user_grid(sys_user, table=True):
|
||||
email: str
|
||||
status: int
|
||||
create_time: int
|
||||
language: str = Field(max_length=255, default="zh-CN")
|
||||
|
||||
|
||||
@@ -2,4 +2,8 @@ from pydantic import BaseModel
|
||||
|
||||
class model_status(BaseModel):
|
||||
status: bool
|
||||
ids: list[int]
|
||||
ids: list[int]
|
||||
|
||||
|
||||
class UserLanguage(BaseModel):
|
||||
language: str
|
||||
@@ -5,5 +5,6 @@ export const userApi = {
|
||||
add: (data: any) => request.post('/settings/terminology', data),
|
||||
edit: (data: any) => request.put('/settings/terminology', data),
|
||||
delete: (id: number) => request.delete(`/settings/terminology/${id}`),
|
||||
query: (id: number) => request.get(`/settings/terminology/${id}`)
|
||||
query: (id: number) => request.get(`/settings/terminology/${id}`),
|
||||
language: (data: any) => request.put('/user/language', data),
|
||||
}
|
||||
@@ -8,5 +8,6 @@ export const AuthApi = {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
}
|
||||
}),
|
||||
logout: () => request.post('/auth/logout')
|
||||
logout: () => request.post('/auth/logout'),
|
||||
info: () => request.get('/user/info')
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<el-dropdown @command="changeLanguage" trigger="hover">
|
||||
<div class="lang-switch">
|
||||
<span>{{ selectedLanguage === 'zh-CN' ? '中文' : 'English' }}</span>
|
||||
<el-icon class="el-icon--right">
|
||||
<ArrowDown />
|
||||
</el-icon>
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="en" :class="{ 'selected-lang': selectedLanguage === 'en' }">
|
||||
English
|
||||
</el-dropdown-item>
|
||||
<el-dropdown-item command="zh-CN" :class="{ 'selected-lang': selectedLanguage === 'zh-CN' }">
|
||||
中文
|
||||
</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { ArrowDown } from '@element-plus/icons-vue'
|
||||
import { userApi } from '@/api/auth'
|
||||
|
||||
const { locale } = useI18n()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const selectedLanguage = computed(() => {
|
||||
return userStore.language
|
||||
})
|
||||
|
||||
const changeLanguage = (lang: string) => {
|
||||
locale.value = lang
|
||||
userStore.setLanguage(lang)
|
||||
|
||||
const param = {
|
||||
language: lang
|
||||
}
|
||||
userApi.language(param)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.lang-switch {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
color: var(--el-text-color-primary);
|
||||
|
||||
.el-icon--right {
|
||||
margin-left: 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.selected-lang {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
</style>
|
||||
@@ -77,21 +77,7 @@
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="switchLayout">Switch Layout</el-dropdown-item>
|
||||
<el-dropdown-item @click="logout">Logout</el-dropdown-item>
|
||||
|
||||
<el-dropdown @command="changeLanguage">
|
||||
<div class="lang-switch">
|
||||
Language
|
||||
<el-icon>
|
||||
<i class="el-icon-arrow-down"/>
|
||||
</el-icon>
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="en">English</el-dropdown-item>
|
||||
<el-dropdown-item command="zh-CN">中文</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<el-dropdown-item><language-selector /></el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
@@ -121,6 +107,8 @@
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="switchLayout">Switch Layout</el-dropdown-item>
|
||||
<el-dropdown-item @click="logout">Logout</el-dropdown-item>
|
||||
<el-dropdown-item><language-selector /></el-dropdown-item>
|
||||
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
@@ -170,8 +158,9 @@ import icon_ai from '@/assets/svg/icon_ai.svg'
|
||||
import {ArrowLeftBold} from '@element-plus/icons-vue'
|
||||
import {useCache} from '@/utils/useCache'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import LanguageSelector from '@/components/Language-selector/index.vue'
|
||||
|
||||
const { locale, t } = useI18n()
|
||||
const { t } = useI18n()
|
||||
const {wsCache} = useCache()
|
||||
const topLayout = ref(false)
|
||||
const router = useRouter()
|
||||
@@ -234,9 +223,6 @@ const switchLayout = () => {
|
||||
wsCache.set('sqlbot-topbar-layout', topLayout.value)
|
||||
}
|
||||
|
||||
const changeLanguage = (lang: string) => {
|
||||
locale.value = lang
|
||||
}
|
||||
onMounted(() => {
|
||||
topLayout.value = wsCache.get('sqlbot-topbar-layout') || true
|
||||
})
|
||||
@@ -558,12 +544,4 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
}
|
||||
.lang-switch {
|
||||
cursor: pointer;
|
||||
padding: 0 12px;
|
||||
|
||||
&:hover {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -3,11 +3,12 @@ import en from './en.json'
|
||||
import zhCN from './zh-CN.json'
|
||||
import elementEnLocale from 'element-plus-secondary/es/locale/lang/en'
|
||||
import elementZhLocale from 'element-plus-secondary/es/locale/lang/zh-cn'
|
||||
import { useCache } from '@/utils/useCache'
|
||||
const { wsCache } = useCache()
|
||||
|
||||
const getDefaultLocale = () => {
|
||||
/* const savedLang = localStorage.getItem('lang')
|
||||
return savedLang || 'zh-CN' */
|
||||
return 'zh-CN'
|
||||
const language = wsCache.get('user.language')
|
||||
return language || 'zh-CN'
|
||||
}
|
||||
|
||||
const messages = {
|
||||
|
||||
@@ -1,19 +1,25 @@
|
||||
import { ElMessage } from 'element-plus-secondary'
|
||||
import { useCache } from '@/utils/useCache'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
|
||||
const userStore = useUserStore()
|
||||
const { wsCache } = useCache()
|
||||
const whiteList = ['/login']
|
||||
export const watchRouter = (router: any) => {
|
||||
router.beforeEach(async (to: any, from: any, next: any) => {
|
||||
const token = wsCache.get('user.token')
|
||||
if (!token) {
|
||||
if (whiteList.includes(to.path)) {
|
||||
next()
|
||||
} else {
|
||||
ElMessage.error('Please login first')
|
||||
next('/login')
|
||||
}
|
||||
if (whiteList.includes(to.path)) {
|
||||
next()
|
||||
return
|
||||
}
|
||||
if (!token) {
|
||||
ElMessage.error('Please login first')
|
||||
next('/login')
|
||||
return
|
||||
}
|
||||
if (!wsCache.get('user.uid')) {
|
||||
await userStore.info()
|
||||
}
|
||||
if (to.path === '/') {
|
||||
next('/chat')
|
||||
return
|
||||
|
||||
+116
-21
@@ -2,29 +2,124 @@ import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import { AuthApi } from '@/api/login'
|
||||
import { useCache } from '@/utils/useCache'
|
||||
import { i18n } from '@/i18n'
|
||||
import { store } from './index'
|
||||
|
||||
const { wsCache } = useCache()
|
||||
export const useUserStore = defineStore('user', () => {
|
||||
const token = ref('')
|
||||
const username = ref('')
|
||||
|
||||
const login = async (formData: { username: string; password: string }) => {
|
||||
username.value = formData.username
|
||||
const res: any = await AuthApi.login(formData)
|
||||
token.value = res.access_token
|
||||
wsCache.set('user.token', token.value)
|
||||
username.value = formData.username
|
||||
}
|
||||
|
||||
const logout = () => {
|
||||
token.value = ''
|
||||
username.value = ''
|
||||
wsCache.delete('user.token')
|
||||
}
|
||||
|
||||
return {
|
||||
token,
|
||||
username,
|
||||
login,
|
||||
logout
|
||||
interface UserState {
|
||||
token: string
|
||||
uid: string
|
||||
name: string
|
||||
oid: string
|
||||
language: string
|
||||
exp: number
|
||||
time: number
|
||||
}
|
||||
|
||||
export const UserStore = defineStore('user', {
|
||||
state: (): UserState => {
|
||||
return {
|
||||
token: null,
|
||||
uid: null,
|
||||
name: null,
|
||||
oid: null,
|
||||
language: 'zh-CN',
|
||||
exp: null,
|
||||
time: null
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
getToken(): string {
|
||||
return this.token
|
||||
},
|
||||
getUid(): string {
|
||||
return this.uid
|
||||
},
|
||||
getName(): string {
|
||||
return this.name
|
||||
},
|
||||
getOid(): string {
|
||||
return this.oid
|
||||
},
|
||||
getLanguage(): string {
|
||||
return this.language
|
||||
},
|
||||
getExp(): number {
|
||||
return this.exp
|
||||
},
|
||||
getTime(): number {
|
||||
return this.time
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
|
||||
async login(formData: { username: string; password: string }) {
|
||||
const res: any = await AuthApi.login(formData)
|
||||
this.setToken(res.access_token)
|
||||
},
|
||||
|
||||
logout() {
|
||||
this.clear()
|
||||
},
|
||||
|
||||
async info () {
|
||||
const res: any = await AuthApi.info()
|
||||
const res_data = res || {}
|
||||
|
||||
const keys: string[] = ['uid', 'name', 'oid', 'language', 'exp', 'time']
|
||||
|
||||
keys.forEach(key => {
|
||||
const dkey = key === 'uid' ? 'id' : key
|
||||
this[key] = res_data[dkey]
|
||||
wsCache.set('user.' + key, this[key])
|
||||
})
|
||||
this.setLanguage(this.language)
|
||||
},
|
||||
setToken(token: string) {
|
||||
wsCache.set('user.token', token)
|
||||
this.token = token
|
||||
},
|
||||
setExp(exp: number) {
|
||||
wsCache.set('user.exp', exp)
|
||||
this.exp = exp
|
||||
},
|
||||
setTime(time: number) {
|
||||
wsCache.set('user.time', time)
|
||||
this.time = time
|
||||
},
|
||||
setUid(uid: string) {
|
||||
wsCache.set('user.uid', uid)
|
||||
this.uid = uid
|
||||
},
|
||||
setName(name: string) {
|
||||
wsCache.set('user.name', name)
|
||||
this.name = name
|
||||
},
|
||||
setOid(oid: string) {
|
||||
wsCache.set('user.oid', oid)
|
||||
this.oid = oid
|
||||
},
|
||||
setLanguage(language: string) {
|
||||
if (!language || language === 'zh_CN') {
|
||||
language = 'zh-CN'
|
||||
}
|
||||
wsCache.set('user.language', language)
|
||||
this.language = language
|
||||
i18n.global.locale.value = language
|
||||
/* const { locale } = useI18n()
|
||||
locale.value = language */
|
||||
// locale.setLang(language)
|
||||
},
|
||||
clear() {
|
||||
const keys: string[] = ['token', 'uid', 'name', 'oid', 'language', 'exp', 'time']
|
||||
keys.forEach(key => wsCache.delete('user.' + key))
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
export const useUserStore = () => {
|
||||
return UserStore(store)
|
||||
}
|
||||
Reference in New Issue
Block a user