diff --git a/backend/apps/settings/api/terminology.py b/backend/apps/settings/api/terminology.py
index 62b6a1a3..708e6ee7 100644
--- a/backend/apps/settings/api/terminology.py
+++ b/backend/apps/settings/api/terminology.py
@@ -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)
diff --git a/backend/apps/system/api/user.py b/backend/apps/system/api/user.py
index 30b5909b..53408c1b 100644
--- a/backend/apps/system/api/user.py
+++ b/backend/apps/system/api/user.py
@@ -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")
diff --git a/backend/apps/system/api/workspace.py b/backend/apps/system/api/workspace.py
index 4afcae22..4f60241c 100644
--- a/backend/apps/system/api/workspace.py
+++ b/backend/apps/system/api/workspace.py
@@ -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()
\ No newline at end of file
diff --git a/backend/apps/system/crud/user.py b/backend/apps/system/crud/user.py
index 5f34daf9..1af1c9a8 100644
--- a/backend/apps/system/crud/user.py
+++ b/backend/apps/system/crud/user.py
@@ -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
\ No newline at end of file
+ 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()
+ ]
\ No newline at end of file
diff --git a/backend/apps/system/schemas/system_schema.py b/backend/apps/system/schemas/system_schema.py
index b524e24b..4511b1d2 100644
--- a/backend/apps/system/schemas/system_schema.py
+++ b/backend/apps/system/schemas/system_schema.py
@@ -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
- )
\ No newline at end of file
+ )
+
+class WorkspaceUser(UserEditor):
+ weight: int
+ create_time: int
+
+class UserWs(BaseCreatorDTO):
+ name: str
\ No newline at end of file
diff --git a/backend/common/core/pagination.py b/backend/common/core/pagination.py
index dbd95fa7..783e3198 100644
--- a/backend/common/core/pagination.py
+++ b/backend/common/core/pagination.py
@@ -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,
diff --git a/frontend/src/api/auth.ts b/frontend/src/api/auth.ts
index 98aa21df..c55a0b89 100644
--- a/frontend/src/api/auth.ts
+++ b/frontend/src/api/auth.ts
@@ -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}`),
}
diff --git a/frontend/src/components/layout/Workspace.vue b/frontend/src/components/layout/Workspace.vue
index 31cc242f..5d3c6982 100644
--- a/frontend/src/components/layout/Workspace.vue
+++ b/frontend/src/components/layout/Workspace.vue
@@ -1,21 +1,24 @@
@@ -46,7 +68,7 @@ const handleDefaultWorkspaceChange = (item: any) => {
- {{ name }}
+ {{ currentWorkspace.name }}
{
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)"
>
diff --git a/frontend/src/i18n/zh-CN.json b/frontend/src/i18n/zh-CN.json
index 60213ed6..db07ccb3 100644
--- a/frontend/src/i18n/zh-CN.json
+++ b/frontend/src/i18n/zh-CN.json
@@ -18,7 +18,8 @@
"next": "下一步",
"save": "保存",
"logout": "退出登录",
- "please_input": "请输入{msg}"
+ "please_input": "请输入{msg}",
+ "switch_success": "切换成功"
},
"dashboard": {
"select_dashboard": "请选择仪表板",