diff --git a/backend/alembic/versions/8dc3b1bdbfef_010_upgrade_user_language.py b/backend/alembic/versions/8dc3b1bdbfef_010_upgrade_user_language.py
new file mode 100644
index 00000000..cca99de1
--- /dev/null
+++ b/backend/alembic/versions/8dc3b1bdbfef_010_upgrade_user_language.py
@@ -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 ###
diff --git a/backend/apps/system/api/user.py b/backend/apps/system/api/user.py
index 6992c883..754850e1 100644
--- a/backend/apps/system/api/user.py
+++ b/backend/apps/system/api/user.py
@@ -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)
\ No newline at end of file
+ **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}
\ No newline at end of file
diff --git a/backend/apps/system/crud/user.py b/backend/apps/system/crud/user.py
index a522fe24..89125544 100644
--- a/backend/apps/system/crud/user.py
+++ b/backend/apps/system/crud/user.py
@@ -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:
diff --git a/backend/apps/system/models/user.py b/backend/apps/system/models/user.py
index 7fc12dd7..9d2eddf1 100644
--- a/backend/apps/system/models/user.py
+++ b/backend/apps/system/models/user.py
@@ -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")
diff --git a/backend/apps/system/schemas/system_schema.py b/backend/apps/system/schemas/system_schema.py
index 237612cd..f7eab2f7 100644
--- a/backend/apps/system/schemas/system_schema.py
+++ b/backend/apps/system/schemas/system_schema.py
@@ -2,4 +2,8 @@ from pydantic import BaseModel
class model_status(BaseModel):
status: bool
- ids: list[int]
\ No newline at end of file
+ ids: list[int]
+
+
+class UserLanguage(BaseModel):
+ language: str
\ No newline at end of file
diff --git a/frontend/src/api/auth.ts b/frontend/src/api/auth.ts
index d3ebe6e8..3850463c 100644
--- a/frontend/src/api/auth.ts
+++ b/frontend/src/api/auth.ts
@@ -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),
}
\ No newline at end of file
diff --git a/frontend/src/api/login.ts b/frontend/src/api/login.ts
index ccd6284a..64c80cef 100644
--- a/frontend/src/api/login.ts
+++ b/frontend/src/api/login.ts
@@ -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')
}
\ No newline at end of file
diff --git a/frontend/src/components/Language-selector/index.vue b/frontend/src/components/Language-selector/index.vue
new file mode 100644
index 00000000..d9aaefda
--- /dev/null
+++ b/frontend/src/components/Language-selector/index.vue
@@ -0,0 +1,63 @@
+
+
+
+
{{ selectedLanguage === 'zh-CN' ? '中文' : 'English' }}
+
+
+
+
+
+
+
+ English
+
+
+ 中文
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/components/layout/index.vue b/frontend/src/components/layout/index.vue
index e91f5931..68cbf836 100644
--- a/frontend/src/components/layout/index.vue
+++ b/frontend/src/components/layout/index.vue
@@ -77,21 +77,7 @@
Switch Layout
Logout
-
-
-
- Language
-
-
-
-
-
-
- English
- 中文
-
-
-
+
@@ -121,6 +107,8 @@
Switch Layout
Logout
+
+
@@ -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);
- }
-}
\ No newline at end of file
diff --git a/frontend/src/i18n/index.ts b/frontend/src/i18n/index.ts
index 10cd6420..2166eede 100644
--- a/frontend/src/i18n/index.ts
+++ b/frontend/src/i18n/index.ts
@@ -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 = {
diff --git a/frontend/src/router/watch.ts b/frontend/src/router/watch.ts
index b13adfef..710d3c1d 100644
--- a/frontend/src/router/watch.ts
+++ b/frontend/src/router/watch.ts
@@ -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
diff --git a/frontend/src/stores/user.ts b/frontend/src/stores/user.ts
index f4990698..a22a6237 100644
--- a/frontend/src/stores/user.ts
+++ b/frontend/src/stores/user.ts
@@ -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))
+ }
}
-})
\ No newline at end of file
+})
+
+export const useUserStore = () => {
+ return UserStore(store)
+}
\ No newline at end of file