From e71d485629c445652020547d4db40442461f5fcc Mon Sep 17 00:00:00 2001 From: guyue55 <1724246050@qq.com> Date: Tue, 4 Mar 2025 13:23:43 +0800 Subject: [PATCH] =?UTF-8?q?1.=20=E5=8E=BB=E9=99=A4=E9=87=8D=E5=A4=8D?= =?UTF-8?q?=E7=9A=84pages.py=202.=20=E7=BC=96=E8=BE=91=E5=92=8C=E5=88=A0?= =?UTF-8?q?=E9=99=A4=E6=93=8D=E4=BD=9C=E5=A2=9E=E5=8A=A0=E9=BC=A0=E6=A0=87?= =?UTF-8?q?=E6=82=AC=E5=81=9C=E6=8F=90=E7=A4=BA=203.=20=E6=90=9C=E7=B4=A2?= =?UTF-8?q?=E6=A0=8F=E5=B8=83=E5=B1=80=E7=9A=84=E4=BC=98=E5=8C=96=E5=B7=A5?= =?UTF-8?q?=E4=BD=9C=EF=BC=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 调整了搜索框容器的宽度和间距 - 优化了搜索框组件的弹性布局 - 设置了下拉框和按钮的最小宽度 - 改进了按钮的对齐方式和间距 - 优化了移动端的响应式布局 --- app/api/api.py | 5 +- app/api/deps.py | 72 ------------------------- app/api/deps/__init__.py | 5 -- app/api/endpoints/configs.py | 45 ++++++++++++++-- app/api/endpoints/pages.py | 99 ----------------------------------- app/api/endpoints/types.py | 1 - app/api/pages.py | 70 ++++++++++++++++--------- app/static/css/style.css | 82 +++++++++++++++++++---------- app/templates/configs.html | 6 +-- app/templates/types.html | 4 +- config_center.db | Bin 45056 -> 45056 bytes 11 files changed, 148 insertions(+), 241 deletions(-) delete mode 100644 app/api/deps.py delete mode 100644 app/api/deps/__init__.py delete mode 100644 app/api/endpoints/pages.py diff --git a/app/api/api.py b/app/api/api.py index 8a533be..91194a5 100644 --- a/app/api/api.py +++ b/app/api/api.py @@ -1,10 +1,9 @@ from fastapi import APIRouter -from app.api.endpoints import types, configs, pages +from app.api.endpoints import types, configs api_router = APIRouter() -# 添加页面路由,不带前缀 -api_router.include_router(pages.router, tags=["pages"]) +# 页面路由已移至 app.api.pages.py # API路由,不带前缀(因为前缀在main.py中添加) api_router.include_router(types.router, prefix="/types", tags=["types"]) diff --git a/app/api/deps.py b/app/api/deps.py deleted file mode 100644 index 973651a..0000000 --- a/app/api/deps.py +++ /dev/null @@ -1,72 +0,0 @@ -from fastapi import Depends -from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy import select -from typing import Optional - -from app.core.config import settings -from app.models.database import get_db -from app.models.config import Config - -async def is_privilege_mode(db: AsyncSession) -> bool: - """检查是否启用了特权模式""" - # 首先检查环境变量中的特权模式设置 - if settings.PRIVILEGE_MODE: - return True - - # 如果环境变量中没有启用特权模式,则检查数据库中的配置 - try: - result = await db.execute( - select(Config).where( - Config.key == "privilege_mode" - ) - ) - config = result.scalar_one_or_none() - return config is not None and config.value.lower() == "true" - except Exception as e: - print(f"检查特权模式出错: {e}") - return False - -# 以下函数用于兼容性目的,返回None表示无用户 -async def get_current_user_optional(db: AsyncSession = Depends(get_db)) -> None: - """获取当前用户(可选,未登录返回None)""" - return None - -# 兼容性函数 -async def get_current_active_user(db: AsyncSession = Depends(get_db)) -> None: - """获取当前活跃用户(兼容性函数)""" - return None - -# 兼容性函数 -async def get_current_admin_user(db: AsyncSession = Depends(get_db)) -> None: - """获取当前管理员用户(兼容性函数)""" - return None - - if not token: - return None - - try: - payload = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"]) - username: str = payload.get("sub") - if username is None: - return None - except JWTError: - return None - - result = await db.execute(select(User).where(User.username == username)) - user = result.scalar_one_or_none() - - return user - -async def get_current_active_user( - current_user: User = Depends(get_current_user), -) -> User: - """获取当前活跃用户""" - return current_user - -async def get_current_admin_user( - current_user: User = Depends(get_current_user), -) -> User: - """获取当前管理员用户""" - if current_user.role != "admin": - raise HTTPException(status_code=403, detail="权限不足") - return current_user \ No newline at end of file diff --git a/app/api/deps/__init__.py b/app/api/deps/__init__.py deleted file mode 100644 index 5e55a74..0000000 --- a/app/api/deps/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from app.core.config import settings - -def is_privilege_mode(): - """检查是否处于特权模式""" - return settings.PRIVILEGE_MODE \ No newline at end of file diff --git a/app/api/endpoints/configs.py b/app/api/endpoints/configs.py index 93da928..f6423a6 100644 --- a/app/api/endpoints/configs.py +++ b/app/api/endpoints/configs.py @@ -6,9 +6,6 @@ from app.models.database import get_db from app.models.config import Config from app.models.type import Type from app.schemas.config import ConfigCreate, ConfigUpdate, Config as ConfigSchema, ConfigList, ConfigSearch -from app.api.deps import is_privilege_mode -# 添加缺少的导入 -from app import schemas router = APIRouter() @@ -378,6 +375,48 @@ async def delete_config_by_type_and_key( raise HTTPException(status_code=404, detail=f"类型 '{type_name}' 下不存在键 '{key}'") # 修改删除配置项的端点s +@router.put("/{type_name}/{key}", response_model=ConfigSchema) +async def update_config_by_type_and_key( + type_name: str, + key: str, + config_data: ConfigUpdate, + db: AsyncSession = Depends(get_db) + ): + """ + 通过类型名称和键更新配置 + """ + # 查询配置项 + result = await db.execute( + select(Config).join(Type).where( + Type.type_name == type_name, + Config.key == key + ) + ) + config = result.scalar_one_or_none() + + if not config: + raise HTTPException(status_code=404, detail=f"找不到配置项: {type_name}.{key}") + + # 更新配置 + if config_data.value is not None: + config.value = config_data.value + if config_data.key_description is not None: + config.key_description = config_data.key_description + + await db.commit() + await db.refresh(config) + + return ConfigSchema( + config_id=config.config_id, + type_id=config.type_id, + key=config.key, + value=config.value, + key_description=config.key_description, + created_at=config.created_at, + updated_at=config.updated_at, + type_name=type_name + ) + @router.delete("/{type_name}/{key}", response_model=dict) async def delete_config( type_name: str, diff --git a/app/api/endpoints/pages.py b/app/api/endpoints/pages.py deleted file mode 100644 index 31d65db..0000000 --- a/app/api/endpoints/pages.py +++ /dev/null @@ -1,99 +0,0 @@ -from fastapi import APIRouter, Request, Depends -from fastapi.templating import Jinja2Templates -from fastapi.responses import HTMLResponse -from pathlib import Path -from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy import select, func -from app.models.database import get_db -from app.models.type import Type -from app.models.config import Config -from typing import Optional - -router = APIRouter() -templates = Jinja2Templates(directory=str(Path(__file__).parents[2] / "templates")) - -# 修改根路径处理函数 -@router.get("/", response_class=HTMLResponse) -async def index(request: Request, db: AsyncSession = Depends(get_db)): - """首页""" - # 查询类型数量 - result = await db.execute(select(func.count()).select_from(Type)) - type_count = result.scalar() - - # 查询配置项数量 - result = await db.execute(select(func.count()).select_from(Config)) - config_count = result.scalar() - - return templates.TemplateResponse( - "index.html", - { - "request": request, - "type_count": type_count, - "config_count": config_count - } - ) - -@router.get("/types", response_class=HTMLResponse) -async def types_page( - request: Request, - search: Optional[str] = None, - db: AsyncSession = Depends(get_db) -): - """配置类型页面""" - # 构建查询 - query = select(Type) - if search: - query = query.where(Type.type_name.contains(search) | Type.description.contains(search)) - - # 执行查询 - result = await db.execute(query) - types = result.scalars().all() - - return templates.TemplateResponse( - "types.html", - { - "request": request, - "types": types, - "search": search - } - ) - -@router.get("/configs", response_class=HTMLResponse) -async def configs_page( - request: Request, - type_name: Optional[str] = None, - key: Optional[str] = None, - value: Optional[str] = None, - db: AsyncSession = Depends(get_db) -): - """配置项页面""" - # 构建查询 - query = select(Config, Type).join(Type) - - # 添加筛选条件 - if type_name: - query = query.where(Type.type_name.contains(type_name)) - if key: - query = query.where(Config.key.contains(key)) - if value: - query = query.where(Config.value.contains(value)) - - # 执行查询 - result = await db.execute(query) - rows = result.all() - - # 查询所有类型(用于筛选) - types_result = await db.execute(select(Type)) - types = types_result.scalars().all() - - return templates.TemplateResponse( - "configs.html", - { - "request": request, - "configs": rows, - "types": types, - "type_name": type_name, - "key": key, - "value": value - } - ) \ No newline at end of file diff --git a/app/api/endpoints/types.py b/app/api/endpoints/types.py index ab31afe..76c273b 100644 --- a/app/api/endpoints/types.py +++ b/app/api/endpoints/types.py @@ -6,7 +6,6 @@ from app.models.database import get_db from app.models.type import Type from app.models.config import Config from app.schemas.type import TypeCreate, TypeUpdate, Type as TypeSchema, TypeList -from app.api.deps import is_privilege_mode router = APIRouter() diff --git a/app/api/pages.py b/app/api/pages.py index 9d097f1..562dd15 100644 --- a/app/api/pages.py +++ b/app/api/pages.py @@ -1,8 +1,10 @@ from fastapi import APIRouter, Request, Depends from fastapi.templating import Jinja2Templates +from fastapi.responses import HTMLResponse from pathlib import Path from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, func +from typing import Optional from app.models.database import get_db from app.models.type import Type @@ -14,30 +16,31 @@ page_router = APIRouter() # 设置模板目录 templates = Jinja2Templates(directory=Path(__file__).parent.parent / "templates") -@page_router.get("/") +@page_router.get("/", response_class=HTMLResponse) async def index_page(request: Request, db: AsyncSession = Depends(get_db)): """首页""" # 查询类型数量 result = await db.execute(select(func.count()).select_from(Type)) - types_count = result.scalar() + type_count = result.scalar() # 查询配置项数量 result = await db.execute(select(func.count()).select_from(Config)) - configs_count = result.scalar() + config_count = result.scalar() return templates.TemplateResponse( "index.html", - {"request": request, - "types_count": types_count, - "configs_count": configs_count + { + "request": request, + "types_count": type_count, + "configs_count": config_count } ) -@page_router.get("/types") +@page_router.get("/types", response_class=HTMLResponse) async def types_page( request: Request, - search: str = None, + search: Optional[str] = None, db: AsyncSession = Depends(get_db) ): """配置类型页面""" @@ -52,39 +55,56 @@ async def types_page( "types.html", { "request": request, - "types": types + "types": types, + "search": search } ) -@page_router.get("/configs") +@page_router.get("/configs", response_class=HTMLResponse) async def configs_page( request: Request, - type_name: str = None, - search: str = None, + type_name: Optional[str] = None, + key: Optional[str] = None, + value: Optional[str] = None, db: AsyncSession = Depends(get_db) ): """配置项页面""" + # 构建查询 + query = select(Config, Type).join(Type) + + # 添加筛选条件 + if type_name: + query = query.where(Type.type_name.contains(type_name)) + if key: + query = query.where(Config.key.contains(key)) + if value: + query = query.where(Config.value.contains(value)) + + # 执行查询 + result = await db.execute(query) + rows = result.all() + + # 处理查询结果,将Config对象的属性转换为字典 + configs = [{ + "config_id": row[0].config_id, + "type_id": row[0].type_id, + "key": row[0].key, + "value": row[0].value, + "key_description": row[0].key_description, + "created_at": row[0].created_at, + "updated_at": row[0].updated_at, + "type_name": row[1].type_name + } for row in rows] + # 获取所有类型 types_result = await db.execute(select(Type)) types = types_result.scalars().all() - # 构建查询 - query = select(Config).join(Type) - - if type_name: - query = query.where(Type.type_name == type_name) - - if search: - query = query.where(Config.key.contains(search) | Config.value.contains(search) | Config.key_description.contains(search)) - - result = await db.execute(query) - configs = result.scalars().all() - return templates.TemplateResponse( "configs.html", { "request": request, - "configs": configs, + "configs": configs, "types": types, "current_type": type_name } diff --git a/app/static/css/style.css b/app/static/css/style.css index 3dbee11..edb207e 100644 --- a/app/static/css/style.css +++ b/app/static/css/style.css @@ -260,29 +260,32 @@ tbody tr:hover { background-color: rgba(99, 164, 255, 0.1); transition: background-color 0.2s; } - -.config-value { - display: inline-block; - padding: 0.25rem 0.5rem; - background: var(--secondary-color); - border-radius: 4px; - font-family: monospace; - max-width: 300px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.value-container { +.btn-group .btn { position: relative; - max-width: 300px; } -.btn-group { - display: inline-flex; - gap: 0.5rem; +.btn-group .btn::before { + content: attr(data-tooltip); + position: absolute; + bottom: 100%; + left: 50%; + transform: translateX(-50%); + padding: 4px 8px; + background-color: rgba(0, 0, 0, 0.8); + color: white; + font-size: 12px; + border-radius: 4px; + white-space: nowrap; + opacity: 0; + visibility: hidden; + transition: opacity 0.3s, visibility 0.3s; + z-index: 1000; } +.btn-group .btn:hover::before { + opacity: 1; + visibility: visible; +} .btn-group .btn { border-radius: 4px; } @@ -400,10 +403,10 @@ tr:hover { border-color: var(--primary-color); outline: none; } - /* 搜索框 */ .search-container { - margin: 1.5rem 0; + margin-bottom: 1.5rem; + padding: 0; } .search-box { @@ -411,12 +414,39 @@ tr:hover { gap: 1rem; flex-wrap: wrap; align-items: center; - background: white; - padding: 1rem; - border-radius: 8px; - box-shadow: 0 2px 4px rgba(0,0,0,0.1); + width: 100%; } +.search-box select { + min-width: 150px; + max-width: 200px; +} +.search-input { + flex: 1; + min-width: 200px; +} +.search-box .btn { + flex-shrink: 0; + white-space: nowrap; + min-width: 80px; +} + +@media (max-width: 768px) { + .search-box { + gap: 0.5rem; + } + + .search-box select, + .search-input, + .search-box .btn { + width: 100%; + margin: 0; + } + + .search-box select { + max-width: 100%; + } +} .input-group { display: flex; gap: 0.5rem; @@ -432,7 +462,6 @@ tr:hover { border-radius: 4px; color: var(--text-light); } - .search-box select { min-width: 150px; max-width: 200px; @@ -441,7 +470,6 @@ tr:hover { padding: 0.5rem; background: white; } - .search-input { flex: 1; min-width: 200px; @@ -450,12 +478,10 @@ tr:hover { padding: 0.5rem; transition: border-color 0.3s; } - .search-input:focus { border-color: var(--primary-color); outline: none; } - /* 标签样式 */ .tag { display: inline-block; diff --git a/app/templates/configs.html b/app/templates/configs.html index 5e357eb..f30908d 100644 --- a/app/templates/configs.html +++ b/app/templates/configs.html @@ -48,7 +48,7 @@
{% for config in configs %}