fix: sync/barcode/memory overhaul + detailed logs + preview + result tracking

- Sync: fix GiteaSync constructor + add push()/pull() methods
- Barcode: two-tab layout matching GUI (mapping + special rules)
- Memory: spec→specification unification, manual add, confidence/price tracking
- Processing: TaskLogHandler captures detailed logs (barcode mapping, unit conversion)
- Preview: fullscreen dialog for file preview (image/Excel) in Orders/Tables/Images
- Detail: per-file log filtering in file pages
- Tasks: result files now per-task, add copy path button
- Config: reactive edited state + save_config fix
- Dashboard: sync task isolation, log limit 10

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-05 19:37:10 +08:00
parent c18039f790
commit 81bafaf557
20 changed files with 1610 additions and 502 deletions
+64 -10
View File
@@ -17,13 +17,22 @@ _mappings_file = _project_root / "config" / "barcode_mappings.json"
class BarcodeMapping(BaseModel):
barcode: str
target: str
target: Optional[str] = None
description: Optional[str] = None
# Special rule fields
multiplier: Optional[int] = None
target_unit: Optional[str] = None
fixed_price: Optional[float] = None
specification: Optional[str] = None
class BarcodeMappingUpdate(BaseModel):
target: Optional[str] = None
description: Optional[str] = None
multiplier: Optional[int] = None
target_unit: Optional[str] = None
fixed_price: Optional[float] = None
specification: Optional[str] = None
def _load_mappings() -> Dict:
@@ -51,12 +60,29 @@ async def list_barcodes(
if isinstance(info, dict):
target = info.get("map_to", info.get("target", ""))
desc = info.get("description", "")
item = {
"barcode": barcode,
"target": target,
"description": desc,
"multiplier": info.get("multiplier"),
"target_unit": info.get("target_unit"),
"fixed_price": info.get("fixed_price"),
"specification": info.get("specification"),
}
else:
target = str(info)
desc = ""
if search and search not in barcode and search not in target and search not in desc:
item = {
"barcode": barcode,
"target": str(info),
"description": "",
"multiplier": None,
"target_unit": None,
"fixed_price": None,
"specification": None,
}
s = search.lower() if search else ""
if s and s not in barcode.lower() and s not in item["target"].lower() and s not in (desc or "").lower():
continue
items.append({"barcode": barcode, "target": target, "description": desc})
items.append(item)
return {"items": items, "total": len(items)}
@@ -82,9 +108,22 @@ async def create_barcode(
mappings = _load_mappings()
if body.barcode in mappings:
raise HTTPException(409, f"条码 {body.barcode} 已存在")
mappings[body.barcode] = {"map_to": body.target, "description": body.description or ""}
entry: dict = {"description": body.description or ""}
if body.multiplier:
entry["multiplier"] = body.multiplier
if body.target_unit:
entry["target_unit"] = body.target_unit
if body.fixed_price is not None:
entry["fixed_price"] = body.fixed_price
if body.specification:
entry["specification"] = body.specification
else:
entry["map_to"] = body.target or ""
mappings[body.barcode] = entry
_save_mappings(mappings)
return {"message": f"已创建映射 {body.barcode}{body.target}"}
return {"message": f"已创建规则 {body.barcode}"}
@router.put("/{barcode}")
@@ -95,20 +134,35 @@ async def update_barcode(
):
mappings = _load_mappings()
if barcode not in mappings:
raise HTTPException(404, f"未找到条码映射 {barcode}")
raise HTTPException(404, f"未找到条码规则 {barcode}")
existing = mappings[barcode]
if not isinstance(existing, dict):
existing = {"map_to": str(existing), "description": ""}
if body.target is not None:
# Check if this is a special rule (has multiplier) or being converted to one
if body.multiplier is not None:
# Convert to special rule: remove map_to, add multiplier fields
existing.pop("map_to", None)
existing["multiplier"] = body.multiplier
if body.target_unit is not None:
existing["target_unit"] = body.target_unit
if body.fixed_price is not None:
existing["fixed_price"] = body.fixed_price
if body.specification is not None:
existing["specification"] = body.specification
elif body.target is not None:
# Convert to simple mapping: remove special fields, add map_to
for k in ("multiplier", "target_unit", "fixed_price", "specification"):
existing.pop(k, None)
existing["map_to"] = body.target
if body.description is not None:
existing["description"] = body.description
mappings[barcode] = existing
_save_mappings(mappings)
return {"message": f"已更新映射 {barcode}"}
return {"message": f"已更新规则 {barcode}"}
@router.delete("/{barcode}")