1. 去除重复的pages.py
2. 编辑和删除操作增加鼠标悬停提示 3. 搜索栏布局的优化工作: - 调整了搜索框容器的宽度和间距 - 优化了搜索框组件的弹性布局 - 设置了下拉框和按钮的最小宽度 - 改进了按钮的对齐方式和间距 - 优化了移动端的响应式布局
This commit is contained in:
parent
8a0925ec1a
commit
e71d485629
@ -1,10 +1,9 @@
|
|||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
from app.api.endpoints import types, configs, pages
|
from app.api.endpoints import types, configs
|
||||||
|
|
||||||
api_router = APIRouter()
|
api_router = APIRouter()
|
||||||
|
|
||||||
# 添加页面路由,不带前缀
|
# 页面路由已移至 app.api.pages.py
|
||||||
api_router.include_router(pages.router, tags=["pages"])
|
|
||||||
|
|
||||||
# API路由,不带前缀(因为前缀在main.py中添加)
|
# API路由,不带前缀(因为前缀在main.py中添加)
|
||||||
api_router.include_router(types.router, prefix="/types", tags=["types"])
|
api_router.include_router(types.router, prefix="/types", tags=["types"])
|
||||||
|
|||||||
@ -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
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
from app.core.config import settings
|
|
||||||
|
|
||||||
def is_privilege_mode():
|
|
||||||
"""检查是否处于特权模式"""
|
|
||||||
return settings.PRIVILEGE_MODE
|
|
||||||
@ -6,9 +6,6 @@ from app.models.database import get_db
|
|||||||
from app.models.config import Config
|
from app.models.config import Config
|
||||||
from app.models.type import Type
|
from app.models.type import Type
|
||||||
from app.schemas.config import ConfigCreate, ConfigUpdate, Config as ConfigSchema, ConfigList, ConfigSearch
|
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()
|
router = APIRouter()
|
||||||
|
|
||||||
@ -378,6 +375,48 @@ async def delete_config_by_type_and_key(
|
|||||||
raise HTTPException(status_code=404, detail=f"类型 '{type_name}' 下不存在键 '{key}'")
|
raise HTTPException(status_code=404, detail=f"类型 '{type_name}' 下不存在键 '{key}'")
|
||||||
|
|
||||||
# 修改删除配置项的端点s
|
# 修改删除配置项的端点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)
|
@router.delete("/{type_name}/{key}", response_model=dict)
|
||||||
async def delete_config(
|
async def delete_config(
|
||||||
type_name: str,
|
type_name: str,
|
||||||
|
|||||||
@ -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
|
|
||||||
}
|
|
||||||
)
|
|
||||||
@ -6,7 +6,6 @@ from app.models.database import get_db
|
|||||||
from app.models.type import Type
|
from app.models.type import Type
|
||||||
from app.models.config import Config
|
from app.models.config import Config
|
||||||
from app.schemas.type import TypeCreate, TypeUpdate, Type as TypeSchema, TypeList
|
from app.schemas.type import TypeCreate, TypeUpdate, Type as TypeSchema, TypeList
|
||||||
from app.api.deps import is_privilege_mode
|
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
from fastapi import APIRouter, Request, Depends
|
from fastapi import APIRouter, Request, Depends
|
||||||
from fastapi.templating import Jinja2Templates
|
from fastapi.templating import Jinja2Templates
|
||||||
|
from fastapi.responses import HTMLResponse
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
from sqlalchemy import select, func
|
from sqlalchemy import select, func
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
from app.models.database import get_db
|
from app.models.database import get_db
|
||||||
from app.models.type import Type
|
from app.models.type import Type
|
||||||
@ -14,30 +16,31 @@ page_router = APIRouter()
|
|||||||
# 设置模板目录
|
# 设置模板目录
|
||||||
templates = Jinja2Templates(directory=Path(__file__).parent.parent / "templates")
|
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)):
|
async def index_page(request: Request, db: AsyncSession = Depends(get_db)):
|
||||||
"""首页"""
|
"""首页"""
|
||||||
# 查询类型数量
|
# 查询类型数量
|
||||||
result = await db.execute(select(func.count()).select_from(Type))
|
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))
|
result = await db.execute(select(func.count()).select_from(Config))
|
||||||
configs_count = result.scalar()
|
config_count = result.scalar()
|
||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
"index.html",
|
"index.html",
|
||||||
{"request": request,
|
{
|
||||||
"types_count": types_count,
|
"request": request,
|
||||||
"configs_count": configs_count
|
"types_count": type_count,
|
||||||
|
"configs_count": config_count
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@page_router.get("/types")
|
@page_router.get("/types", response_class=HTMLResponse)
|
||||||
async def types_page(
|
async def types_page(
|
||||||
request: Request,
|
request: Request,
|
||||||
search: str = None,
|
search: Optional[str] = None,
|
||||||
db: AsyncSession = Depends(get_db)
|
db: AsyncSession = Depends(get_db)
|
||||||
):
|
):
|
||||||
"""配置类型页面"""
|
"""配置类型页面"""
|
||||||
@ -52,39 +55,56 @@ async def types_page(
|
|||||||
"types.html",
|
"types.html",
|
||||||
{
|
{
|
||||||
"request": request,
|
"request": request,
|
||||||
"types": types
|
"types": types,
|
||||||
|
"search": search
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@page_router.get("/configs")
|
@page_router.get("/configs", response_class=HTMLResponse)
|
||||||
async def configs_page(
|
async def configs_page(
|
||||||
request: Request,
|
request: Request,
|
||||||
type_name: str = None,
|
type_name: Optional[str] = None,
|
||||||
search: str = None,
|
key: Optional[str] = None,
|
||||||
|
value: Optional[str] = None,
|
||||||
db: AsyncSession = Depends(get_db)
|
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_result = await db.execute(select(Type))
|
||||||
types = types_result.scalars().all()
|
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(
|
return templates.TemplateResponse(
|
||||||
"configs.html",
|
"configs.html",
|
||||||
{
|
{
|
||||||
"request": request,
|
"request": request,
|
||||||
"configs": configs,
|
"configs": configs,
|
||||||
"types": types,
|
"types": types,
|
||||||
"current_type": type_name
|
"current_type": type_name
|
||||||
}
|
}
|
||||||
|
|||||||
@ -260,29 +260,32 @@ tbody tr:hover {
|
|||||||
background-color: rgba(99, 164, 255, 0.1);
|
background-color: rgba(99, 164, 255, 0.1);
|
||||||
transition: background-color 0.2s;
|
transition: background-color 0.2s;
|
||||||
}
|
}
|
||||||
|
.btn-group .btn {
|
||||||
.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 {
|
|
||||||
position: relative;
|
position: relative;
|
||||||
max-width: 300px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-group {
|
.btn-group .btn::before {
|
||||||
display: inline-flex;
|
content: attr(data-tooltip);
|
||||||
gap: 0.5rem;
|
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 {
|
.btn-group .btn {
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
@ -400,10 +403,10 @@ tr:hover {
|
|||||||
border-color: var(--primary-color);
|
border-color: var(--primary-color);
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 搜索框 */
|
/* 搜索框 */
|
||||||
.search-container {
|
.search-container {
|
||||||
margin: 1.5rem 0;
|
margin-bottom: 1.5rem;
|
||||||
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-box {
|
.search-box {
|
||||||
@ -411,12 +414,39 @@ tr:hover {
|
|||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: white;
|
width: 100%;
|
||||||
padding: 1rem;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.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 {
|
.input-group {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
@ -432,7 +462,6 @@ tr:hover {
|
|||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
color: var(--text-light);
|
color: var(--text-light);
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-box select {
|
.search-box select {
|
||||||
min-width: 150px;
|
min-width: 150px;
|
||||||
max-width: 200px;
|
max-width: 200px;
|
||||||
@ -441,7 +470,6 @@ tr:hover {
|
|||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
background: white;
|
background: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-input {
|
.search-input {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 200px;
|
min-width: 200px;
|
||||||
@ -450,12 +478,10 @@ tr:hover {
|
|||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
transition: border-color 0.3s;
|
transition: border-color 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-input:focus {
|
.search-input:focus {
|
||||||
border-color: var(--primary-color);
|
border-color: var(--primary-color);
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 标签样式 */
|
/* 标签样式 */
|
||||||
.tag {
|
.tag {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|||||||
@ -48,7 +48,7 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
{% for config in configs %}
|
{% for config in configs %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><span class="badge bg-primary">{{ config.type.type_name }}</span></td>
|
<td><span class="badge bg-primary">{{ config.type_name }}</span></td>
|
||||||
<td>{{ config.key }}</td>
|
<td>{{ config.key }}</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="value-container">
|
<div class="value-container">
|
||||||
@ -60,10 +60,10 @@
|
|||||||
<td>{{ config.updated_at.strftime('%Y-%m-%d %H:%M:%S') }}</td>
|
<td>{{ config.updated_at.strftime('%Y-%m-%d %H:%M:%S') }}</td>
|
||||||
<td class="text-end">
|
<td class="text-end">
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<button class="btn btn-sm btn-secondary" onclick="showEditConfigModal('{{ config.type.type_name }}', '{{ config.key }}', '{{ config.value }}', '{{ config.key_description }}')">
|
<button class="btn btn-sm btn-secondary" data-tooltip="编辑" onclick="showEditConfigModal('{{ config.type_name }}', '{{ config.key }}', '{{ config.value }}', '{{ config.key_description }}')">
|
||||||
<i class="fas fa-edit"></i>
|
<i class="fas fa-edit"></i>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-sm btn-danger" onclick="confirmDeleteConfig('{{ config.type.type_name }}', '{{ config.key }}')">
|
<button class="btn btn-sm btn-danger" data-tooltip="删除" onclick="confirmDeleteConfig('{{ config.type_name }}', '{{ config.key }}')">
|
||||||
<i class="fas fa-trash"></i>
|
<i class="fas fa-trash"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -46,10 +46,10 @@
|
|||||||
<td>{{ type.created_at.strftime('%Y-%m-%d %H:%M:%S') }}</td>
|
<td>{{ type.created_at.strftime('%Y-%m-%d %H:%M:%S') }}</td>
|
||||||
<td class="text-end">
|
<td class="text-end">
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<button class="btn btn-sm btn-secondary" onclick="showEditTypeModal('{{ type.type_name }}', '{{ type.description }}')">
|
<button class="btn btn-sm btn-secondary" data-tooltip="编辑" onclick="showEditTypeModal('{{ type.type_name }}', '{{ type.description }}')">
|
||||||
<i class="fas fa-edit"></i>
|
<i class="fas fa-edit"></i>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-sm btn-danger" onclick="confirmDeleteType('{{ type.type_name }}')">
|
<button class="btn btn-sm btn-danger" data-tooltip="删除" onclick="confirmDeleteType('{{ type.type_name }}')">
|
||||||
<i class="fas fa-trash"></i>
|
<i class="fas fa-trash"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
BIN
config_center.db
BIN
config_center.db
Binary file not shown.
Loading…
Reference in New Issue
Block a user