fix:优化数据

This commit is contained in:
丹尼尔
2026-03-15 16:38:59 +08:00
parent a609f81a36
commit 3aa1a586e5
43 changed files with 14565 additions and 294 deletions

View File

@@ -0,0 +1,93 @@
from pathlib import Path
from typing import List
from fastapi import APIRouter, File, HTTPException, UploadFile, status
router = APIRouter(prefix="/settings", tags=["settings"])
TEMPLATES_DIR = Path("data/templates")
ALLOWED_EXCEL = {".xlsx", ".xltx"}
ALLOWED_WORD = {".docx", ".dotx"}
ALLOWED_EXTENSIONS = ALLOWED_EXCEL | ALLOWED_WORD
# Allowed MIME types when client sends Content-Type (validate if present)
ALLOWED_MIME_TYPES = frozenset({
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", # .xlsx
"application/vnd.openxmlformats-officedocument.spreadsheetml.template", # .xltx
"application/vnd.openxmlformats-officedocument.wordprocessingml.document", # .docx
"application/vnd.openxmlformats-officedocument.wordprocessingml.template", # .dotx
})
def _ensure_templates_dir() -> Path:
TEMPLATES_DIR.mkdir(parents=True, exist_ok=True)
return TEMPLATES_DIR
@router.get("/templates", response_model=List[dict])
async def list_templates():
"""List uploaded template files (name, type, size, mtime)."""
_ensure_templates_dir()
out: List[dict] = []
for f in sorted(TEMPLATES_DIR.iterdir(), key=lambda p: p.stat().st_mtime, reverse=True):
if not f.is_file():
continue
suf = f.suffix.lower()
if suf not in ALLOWED_EXTENSIONS:
continue
st = f.stat()
out.append({
"name": f.name,
"type": "excel" if suf in ALLOWED_EXCEL else "word",
"size": st.st_size,
"uploaded_at": st.st_mtime,
})
return out
@router.post("/templates/upload", status_code=status.HTTP_201_CREATED)
async def upload_template(file: UploadFile = File(...)):
"""Upload a .xlsx, .xltx, .docx or .dotx template to data/templates/."""
suf = Path(file.filename or "").suffix.lower()
if suf not in ALLOWED_EXTENSIONS:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Only .xlsx, .xltx, .docx and .dotx files are allowed.",
)
content_type = (file.content_type or "").strip().split(";")[0].strip().lower()
if content_type and content_type not in ALLOWED_MIME_TYPES:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Invalid content type. Allowed: .xlsx, .xltx, .docx, .dotx Office formats.",
)
dir_path = _ensure_templates_dir()
dest = dir_path / (file.filename or "template" + suf)
content = await file.read()
dest.write_bytes(content)
return {"name": dest.name, "path": str(dest)}
def get_latest_excel_template() -> Path | None:
"""Return path to the most recently modified .xlsx or .xltx in data/templates, or None."""
if not TEMPLATES_DIR.exists():
return None
excel_files = [
f for f in TEMPLATES_DIR.iterdir()
if f.is_file() and f.suffix.lower() in ALLOWED_EXCEL
]
if not excel_files:
return None
return max(excel_files, key=lambda p: p.stat().st_mtime)
def get_quote_template_path(template_filename: str | None) -> Path:
"""Resolve quote template path: optional filename in data/templates or latest excel template or default."""
if template_filename:
candidate = TEMPLATES_DIR / template_filename
if candidate.is_file() and candidate.suffix.lower() in ALLOWED_EXCEL:
return candidate
latest = get_latest_excel_template()
if latest:
return latest
default = Path("templates/quote_template.xlsx")
return default