mirror of
https://gh-proxy.org/https://github.com/klinsmaya/SQLBot.git
synced 2026-11-08 01:06:59 +08:00
perf: User and workspace api adapt new ui
This commit is contained in:
@@ -18,7 +18,7 @@ async def pager(
|
||||
paginator = Paginator(session)
|
||||
filters = {}
|
||||
return await paginator.get_paginated_response(
|
||||
model=term_model,
|
||||
stmt=term_model,
|
||||
pagination=pagination,
|
||||
**filters)
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
from fastapi import APIRouter
|
||||
from apps.system.crud.user import get_db_user
|
||||
from apps.system.crud.user import get_db_user, user_ws_options
|
||||
from apps.system.models.user import UserModel
|
||||
from apps.system.schemas.auth import CacheName, CacheNamespace
|
||||
from apps.system.schemas.system_schema import PwdEditor, UserCreator, UserEditor, UserGrid, UserLanguage
|
||||
from common.core.deps import CurrentUser, SessionDep
|
||||
from apps.system.schemas.system_schema import PwdEditor, UserCreator, UserEditor, UserGrid, UserLanguage, UserWs
|
||||
from common.core.deps import CurrentUser, SessionDep, Trans
|
||||
from common.core.pagination import Paginator
|
||||
from common.core.schemas import PaginatedResponse, PaginationParams
|
||||
from common.core.security import md5pwd, verify_md5pwd
|
||||
@@ -14,7 +14,6 @@ router = APIRouter(tags=["user"], prefix="/user")
|
||||
async def user_info(current_user: CurrentUser):
|
||||
return current_user
|
||||
|
||||
|
||||
@router.get("/pager/{pageNum}/{pageSize}", response_model=PaginatedResponse[UserGrid])
|
||||
async def pager(
|
||||
session: SessionDep,
|
||||
@@ -25,10 +24,25 @@ async def pager(
|
||||
paginator = Paginator(session)
|
||||
filters = {}
|
||||
return await paginator.get_paginated_response(
|
||||
model=UserModel,
|
||||
stmt=UserModel,
|
||||
pagination=pagination,
|
||||
**filters)
|
||||
|
||||
@router.get("/ws")
|
||||
async def ws_options(session: SessionDep, current_user: CurrentUser, trans: Trans) -> list[UserWs]:
|
||||
return await user_ws_options(session, current_user.id, trans)
|
||||
|
||||
@router.put("/ws/{oid}")
|
||||
@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="current_user.id")
|
||||
async def ws_change(session: SessionDep, current_user: CurrentUser, oid: int):
|
||||
ws_list: list[UserWs] = await user_ws_options(session, current_user.id)
|
||||
if not any(x.id == oid for x in ws_list):
|
||||
raise RuntimeError(f"oid [{oid}] is invalid!")
|
||||
user_model: UserModel = get_db_user(session = session, user_id = current_user.id)
|
||||
user_model.oid = oid
|
||||
session.add(user_model)
|
||||
session.commit()
|
||||
|
||||
@router.get("/{id}", response_model=UserEditor)
|
||||
async def query(session: SessionDep, id: int) -> UserEditor:
|
||||
db_user: UserModel = get_db_user(session = session, user_id = id)
|
||||
@@ -58,6 +72,11 @@ async def delete(session: SessionDep, id: int):
|
||||
user_model: UserModel = get_db_user(session = session, user_id = id)
|
||||
session.delete(user_model)
|
||||
session.commit()
|
||||
|
||||
@router.delete("")
|
||||
async def batch_del(session: SessionDep, id_list: list[int]):
|
||||
for id in id_list:
|
||||
delete(session, id)
|
||||
|
||||
@router.put("/language")
|
||||
@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="current_user.id")
|
||||
@@ -69,7 +88,6 @@ async def langChange(session: SessionDep, current_user: CurrentUser, language: U
|
||||
db_user.language = lang
|
||||
session.add(db_user)
|
||||
session.commit()
|
||||
return {"message": "Language changed successfully", "language": lang}
|
||||
|
||||
@router.put("/pwd")
|
||||
@clear_cache(namespace=CacheNamespace.AUTH_INFO, cacheName=CacheName.USER_INFO, keyExpression="current_user.id")
|
||||
|
||||
@@ -1,12 +1,78 @@
|
||||
from fastapi import APIRouter
|
||||
from sqlmodel import select
|
||||
from typing import Optional
|
||||
from fastapi import APIRouter, Query
|
||||
from sqlmodel import or_, select
|
||||
from apps.system.models.system_model import UserWsModel, WorkspaceBase, WorkspaceEditor, WorkspaceModel
|
||||
from apps.system.schemas.system_schema import UserWsBase, UserWsDTO
|
||||
from common.core.deps import SessionDep, Trans
|
||||
from apps.system.models.user import UserModel
|
||||
from apps.system.schemas.system_schema import UserWsBase, UserWsDTO, WorkspaceUser
|
||||
from common.core.deps import CurrentUser, SessionDep, Trans
|
||||
from common.core.pagination import Paginator
|
||||
from common.core.schemas import PaginatedResponse, PaginationParams
|
||||
from common.utils.time import get_timestamp
|
||||
|
||||
router = APIRouter(tags=["system/workspace"], prefix="/system/workspace")
|
||||
|
||||
|
||||
@router.get("/uws/pager/{pageNum}/{pageSize}", response_model=PaginatedResponse[WorkspaceUser])
|
||||
async def pager(
|
||||
session: SessionDep,
|
||||
current_user: CurrentUser,
|
||||
pageNum: int,
|
||||
pageSize: int,
|
||||
keyword: Optional[str] = Query(None, description="搜索关键字(可选)"),
|
||||
oid: Optional[int] = Query(None, description="空间ID(仅admin用户生效)"),
|
||||
):
|
||||
if current_user.isAdmin:
|
||||
if not oid:
|
||||
raise RuntimeError('oid miss error')
|
||||
workspace_id = oid
|
||||
else:
|
||||
workspace_id = current_user.oid
|
||||
pagination = PaginationParams(page=pageNum, size=pageSize)
|
||||
paginator = Paginator(session)
|
||||
stmt = select(UserModel.id, UserModel.account, UserModel.name, UserModel.email, UserModel.status, UserModel.create_time, UserModel.oid, UserWsModel.weight).join(
|
||||
UserWsModel, UserModel.id == UserWsModel.uid
|
||||
).where(
|
||||
UserWsModel.oid == workspace_id,
|
||||
).order_by(UserModel.create_time)
|
||||
|
||||
if keyword:
|
||||
keyword_pattern = f"%{keyword}%"
|
||||
stmt = stmt.where(
|
||||
or_(
|
||||
UserModel.account.ilike(keyword_pattern),
|
||||
UserModel.name.ilike(keyword_pattern),
|
||||
UserModel.email.ilike(keyword_pattern)
|
||||
)
|
||||
)
|
||||
return await paginator.get_paginated_response(
|
||||
stmt=stmt,
|
||||
pagination=pagination,
|
||||
)
|
||||
|
||||
|
||||
@router.post("/uws")
|
||||
async def create(session: SessionDep, creator: UserWsDTO):
|
||||
# 判断uid_list以及oid合法性
|
||||
db_model_list = [
|
||||
UserWsModel.model_validate({
|
||||
"oid": creator.oid,
|
||||
"uid": uid,
|
||||
"weight": creator.weight
|
||||
})
|
||||
for uid in creator.uid_list
|
||||
]
|
||||
session.add_all(db_model_list)
|
||||
session.commit()
|
||||
|
||||
@router.delete("/uws")
|
||||
async def delete(session: SessionDep, dto: UserWsBase):
|
||||
db_model_list: list[UserWsModel] = session.exec(select(UserWsModel).where(UserWsModel.uid.in_(dto.uid_list), UserWsModel.oid == dto.oid)).all()
|
||||
if not db_model_list:
|
||||
raise ValueError(f"UserWsModel not found")
|
||||
for db_model in db_model_list:
|
||||
session.delete(db_model)
|
||||
session.commit()
|
||||
|
||||
@router.get("", response_model=list[WorkspaceModel])
|
||||
async def query(session: SessionDep, trans: Trans):
|
||||
list_result = session.exec(select(WorkspaceModel).order_by(WorkspaceModel.create_time)).all()
|
||||
@@ -50,16 +116,4 @@ async def delete(session: SessionDep, id: int):
|
||||
session.delete(db_model)
|
||||
session.commit()
|
||||
|
||||
@router.post("/uws")
|
||||
async def create(session: SessionDep, creator: UserWsDTO):
|
||||
db_model = UserWsModel.model_validate(creator)
|
||||
session.add(db_model)
|
||||
session.commit()
|
||||
|
||||
@router.delete("/uws")
|
||||
async def delete(session: SessionDep, dto: UserWsBase):
|
||||
db_model: UserWsModel = session.exec(select(UserWsModel).where(UserWsModel.uid == dto.uid, UserWsModel.oid == dto.oid)).first()
|
||||
if not db_model:
|
||||
raise ValueError(f"UserWsModel not found")
|
||||
session.delete(db_model)
|
||||
session.commit()
|
||||
@@ -1,10 +1,12 @@
|
||||
|
||||
from typing import Optional
|
||||
from sqlmodel import Session, select
|
||||
|
||||
from apps.system.models.system_model import UserWsModel
|
||||
from apps.system.models.system_model import UserWsModel, WorkspaceModel
|
||||
from apps.system.schemas.auth import CacheName, CacheNamespace
|
||||
from apps.system.schemas.system_schema import BaseUserDTO, UserInfoDTO
|
||||
from apps.system.schemas.system_schema import BaseUserDTO, UserInfoDTO, UserWs
|
||||
from common.core.sqlbot_cache import cache
|
||||
from common.utils.locale import I18n
|
||||
from ..models.user import UserModel
|
||||
from common.core.security import verify_md5pwd
|
||||
|
||||
@@ -36,4 +38,21 @@ def authenticate(*, session: Session, account: str, password: str) -> BaseUserDT
|
||||
return None
|
||||
if not verify_md5pwd(password, db_user.password):
|
||||
return None
|
||||
return db_user
|
||||
return db_user
|
||||
|
||||
async def user_ws_options(session: Session, uid: int, trans: Optional[I18n] = None) -> list[UserWs]:
|
||||
if uid == 1:
|
||||
stmt = select(WorkspaceModel.id, WorkspaceModel.name).order_by(WorkspaceModel.create_time)
|
||||
else:
|
||||
stmt = select(UserWsModel.oid, WorkspaceModel.name).join(
|
||||
WorkspaceModel, UserWsModel.oid == WorkspaceModel.id
|
||||
).where(
|
||||
UserWsModel.uid == uid,
|
||||
).order_by(WorkspaceModel.create_time)
|
||||
result = session.exec(stmt)
|
||||
if not trans:
|
||||
return result.all()
|
||||
return [
|
||||
UserWs(id = id, name = trans(name) if name.startswith('i18n') else name)
|
||||
for id, name in result.all()
|
||||
]
|
||||
@@ -43,7 +43,7 @@ class PwdEditor(BaseModel):
|
||||
new_pwd: str
|
||||
|
||||
class UserWsBase(BaseModel):
|
||||
uid: int
|
||||
uid_list: list[int]
|
||||
oid: int
|
||||
class UserWsDTO(UserWsBase):
|
||||
weight: int = 0
|
||||
@@ -83,4 +83,11 @@ class AssistantValidator(BaseModel):
|
||||
domain_match=domain_match,
|
||||
token=token,
|
||||
**kwargs
|
||||
)
|
||||
)
|
||||
|
||||
class WorkspaceUser(UserEditor):
|
||||
weight: int
|
||||
create_time: int
|
||||
|
||||
class UserWs(BaseCreatorDTO):
|
||||
name: str
|
||||
@@ -1,57 +1,86 @@
|
||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
from sqlmodel import select, func, SQLModel
|
||||
from typing import Type, TypeVar, Sequence, Optional
|
||||
from sqlalchemy import Row, Select
|
||||
from sqlmodel import Session, select, func, SQLModel
|
||||
from typing import Dict, Type, TypeVar, Sequence, Optional
|
||||
from common.core.schemas import PaginationParams, PaginatedResponse
|
||||
from sqlmodel.sql.expression import SelectOfScalar
|
||||
from typing import Union, Any
|
||||
|
||||
ModelT = TypeVar('ModelT', bound=SQLModel)
|
||||
|
||||
class Paginator:
|
||||
def __init__(self, session: AsyncSession):
|
||||
def __init__(self, session: Session):
|
||||
self.session = session
|
||||
|
||||
def _process_result_row(self, row: Row) -> Dict[str, Any]:
|
||||
result_dict = {}
|
||||
for item, key in zip(row, row._fields):
|
||||
if isinstance(item, SQLModel):
|
||||
result_dict.update(item.dict())
|
||||
else:
|
||||
result_dict[key] = item
|
||||
|
||||
return result_dict
|
||||
async def paginate(
|
||||
self,
|
||||
model: Type[ModelT],
|
||||
stmt: Union[Select, SelectOfScalar, Type[ModelT]],
|
||||
page: int = 1,
|
||||
size: int = 20,
|
||||
order_by: Optional[str] = None,
|
||||
desc: bool = False,
|
||||
**filters
|
||||
) -> tuple[Sequence[ModelT], int]:
|
||||
) -> tuple[Sequence[Any], int]:
|
||||
offset = (page - 1) * size
|
||||
stmt = select(model)
|
||||
single_model: bool = False
|
||||
if isinstance(stmt, type) and issubclass(stmt, SQLModel):
|
||||
stmt = select(stmt)
|
||||
single_model = True
|
||||
|
||||
# 应用过滤条件
|
||||
for field, value in filters.items():
|
||||
if value is not None:
|
||||
stmt = stmt.where(getattr(model, field) == value)
|
||||
# 处理关联模型的字段 (如 user.name)
|
||||
if '.' in field:
|
||||
related_model, related_field = field.split('.')
|
||||
# 这里需要根据实际关联关系调整
|
||||
stmt = stmt.where(getattr(getattr(stmt.selected_columns, related_model), related_field) == value)
|
||||
else:
|
||||
stmt = stmt.where(getattr(stmt.selected_columns, field) == value)
|
||||
|
||||
# 应用排序
|
||||
if order_by:
|
||||
column = getattr(model, order_by)
|
||||
if '.' in order_by:
|
||||
related_model, related_field = order_by.split('.')
|
||||
column = getattr(getattr(stmt.selected_columns, related_model), related_field)
|
||||
else:
|
||||
column = getattr(stmt.selected_columns, order_by)
|
||||
stmt = stmt.order_by(column.desc() if desc else column.asc())
|
||||
|
||||
count_stmt = select(func.count()).select_from(model)
|
||||
for field, value in filters.items():
|
||||
if value is not None:
|
||||
count_stmt = count_stmt.where(getattr(model, field) == value)
|
||||
|
||||
# 计算总数
|
||||
""" count_stmt = stmt.with_only_columns(func.count(), maintain_column_froms=True)
|
||||
result = self.session.exec(count_stmt)
|
||||
total = result.first()
|
||||
total: int = result.first() """
|
||||
count_stmt = select(func.count()).select_from(stmt.subquery())
|
||||
total_result = self.session.exec(count_stmt)
|
||||
total: int = total_result.first()
|
||||
|
||||
# 应用分页
|
||||
stmt = stmt.offset(offset).limit(size)
|
||||
|
||||
# 执行查询
|
||||
result = self.session.exec(stmt)
|
||||
items = result.all()
|
||||
|
||||
if not single_model:
|
||||
items = [self._process_result_row(row) for row in result]
|
||||
else:
|
||||
items = result.all()
|
||||
return items, total
|
||||
|
||||
async def get_paginated_response(
|
||||
self,
|
||||
model: Type[ModelT],
|
||||
stmt: Union[Select, SelectOfScalar, Type[ModelT]],
|
||||
pagination: PaginationParams,
|
||||
**filters
|
||||
) -> PaginatedResponse[ModelT]:
|
||||
) -> PaginatedResponse[Any]:
|
||||
items, total = await self.paginate(
|
||||
model=model,
|
||||
stmt=stmt,
|
||||
page=pagination.page,
|
||||
size=pagination.size,
|
||||
order_by=pagination.order_by,
|
||||
@@ -61,7 +90,7 @@ class Paginator:
|
||||
|
||||
total_pages = (total + pagination.size - 1) // pagination.size
|
||||
|
||||
return PaginatedResponse[ModelT](
|
||||
return PaginatedResponse[Any](
|
||||
items=items,
|
||||
total=total,
|
||||
page=pagination.page,
|
||||
|
||||
@@ -9,4 +9,6 @@ export const userApi = {
|
||||
query: (id: number) => request.get(`/settings/terminology/${id}`),
|
||||
language: (data: any) => request.put('/user/language', data),
|
||||
pwd: (data: any) => request.put('/user/pwd', data),
|
||||
ws_options: () => request.get('/user/ws'),
|
||||
ws_change: (oid: number) => request.put(`/user/ws/${oid}`),
|
||||
}
|
||||
|
||||
@@ -1,21 +1,24 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import icon_expand_down_filled from '@/assets/svg/icon_expand-down_filled.svg'
|
||||
import icon_moments_categories_outlined from '@/assets/svg/icon_moments-categories_outlined.svg'
|
||||
import icon_done_outlined from '@/assets/svg/icon_done_outlined.svg'
|
||||
import icon_searchOutline_outlined from '@/assets/svg/icon_search-outline_outlined.svg'
|
||||
|
||||
import { userApi } from '@/api/auth'
|
||||
import { ElMessage } from 'element-plus-secondary'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
const userStore = useUserStore()
|
||||
const { t } = useI18n()
|
||||
defineProps({
|
||||
collapse: { type: [Boolean], required: true },
|
||||
})
|
||||
|
||||
const name = ref('默认工作空间')
|
||||
const currentWorkspace = ref('默认工作空间')
|
||||
const defaultDatasourceList = ref([
|
||||
{
|
||||
name: '默认工作空间',
|
||||
},
|
||||
] as any[])
|
||||
const currentWorkspace = ref({
|
||||
id: '',
|
||||
name: '',
|
||||
})
|
||||
const defaultDatasourceList = ref([] as any[])
|
||||
const workspaceKeywords = ref('')
|
||||
const defaultWorkspaceListWithSearch = computed(() => {
|
||||
if (!workspaceKeywords.value) return defaultDatasourceList.value
|
||||
@@ -34,9 +37,28 @@ const formatKeywords = (item: string) => {
|
||||
const emit = defineEmits(['selectWorkspace'])
|
||||
|
||||
const handleDefaultWorkspaceChange = (item: any) => {
|
||||
currentWorkspace.value = item.name
|
||||
currentWorkspace.value = { id: item.id, name: item.name }
|
||||
userApi.ws_change(item.id).then(() => {
|
||||
ElMessage.success(t('common.switch_success'))
|
||||
})
|
||||
emit('selectWorkspace', item)
|
||||
}
|
||||
|
||||
const init_ws_data = async () => {
|
||||
const res = await userApi.ws_options()
|
||||
defaultDatasourceList.value = res
|
||||
}
|
||||
|
||||
const init_current_ws = () => {
|
||||
const oid = userStore.getOid
|
||||
currentWorkspace.value = defaultDatasourceList.value.find(
|
||||
(item) => item.id.toString() === oid.toString()
|
||||
)
|
||||
}
|
||||
onMounted(async () => {
|
||||
await init_ws_data()
|
||||
init_current_ws()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -46,7 +68,7 @@ const handleDefaultWorkspaceChange = (item: any) => {
|
||||
<el-icon size="16">
|
||||
<icon_moments_categories_outlined></icon_moments_categories_outlined>
|
||||
</el-icon>
|
||||
<span v-if="!collapse" class="name">{{ name }}</span>
|
||||
<span v-if="!collapse" class="name">{{ currentWorkspace.name }}</span>
|
||||
<el-icon v-if="!collapse" style="transform: scale(0.5)" class="expand" size="24">
|
||||
<icon_expand_down_filled></icon_expand_down_filled>
|
||||
</el-icon></button
|
||||
@@ -69,7 +91,7 @@ const handleDefaultWorkspaceChange = (item: any) => {
|
||||
v-for="ele in defaultWorkspaceListWithSearch"
|
||||
:key="ele.name"
|
||||
class="popover-item"
|
||||
:class="currentWorkspace === ele.name && 'isActive'"
|
||||
:class="currentWorkspace.id === ele.id && 'isActive'"
|
||||
@click="handleDefaultWorkspaceChange(ele)"
|
||||
>
|
||||
<el-icon size="16">
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
"next": "下一步",
|
||||
"save": "保存",
|
||||
"logout": "退出登录",
|
||||
"please_input": "请输入{msg}"
|
||||
"please_input": "请输入{msg}",
|
||||
"switch_success": "切换成功"
|
||||
},
|
||||
"dashboard": {
|
||||
"select_dashboard": "请选择仪表板",
|
||||
|
||||
Reference in New Issue
Block a user