This commit is contained in:
zlwu committed 2026-05-21 12:53:37 +08:00
1 parent ad0e849f6d
commit 14f1a84bb3
32 files changed
+8199 -5193

No files matched your search

+1 -1
View File
@@ -1 +1 @@
37832
39408
+21 -3
View File
@@ -1547,22 +1547,36 @@ async def set_production_task_outputs(
# ---------------------------------------------------------------------------
@router.get("/cell-products/next-code")
async def get_next_cell_product_code(
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
tenant_no = _get_tenant_no(db, user)
code = cell_product_svc.get_next_code(db, tenant_no)
return success_response({"code": code})
@router.get("/cell-products")
async def list_cell_products(
page: int = Query(1, ge=1),
pageSize: int = Query(10, ge=1, le=100, alias="pageSize"),
keyword: Optional[str] = None,
productCode: Optional[str] = None,
productName: Optional[str] = None,
productTypeId: Optional[str] = None,
productStatus: Optional[str] = None,
passage: Optional[str] = None,
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
tenant_no = _get_tenant_no(db, user)
result = cell_product_svc.list_cell_products(
db, tenant_no=tenant_no, page=page, page_size=pageSize,
keyword=keyword,
product_code=productCode, product_name=productName,
product_type_id=productTypeId, product_status=productStatus,
passage=passage,
user_info=user,
)
items = result.get("items", [])
@@ -1581,7 +1595,7 @@ async def get_cell_product_detail(
obj = cell_product_svc.get_cell_product(db, product_id, tenant_no)
if not obj:
return error_response(404, "细胞产品不存在")
return success_response(cell_product_svc._row_to_dict(obj))
return success_response(cell_product_svc.product_to_response(db, tenant_no, obj))
@router.post("/cell-products")
@@ -1596,7 +1610,9 @@ async def create_cell_product(
db, tenant_no=tenant_no, user_id=user.get("user_id"),
create_dept_id="", data=body,
)
return success_response(cell_product_svc._row_to_dict(obj), message="创建成功")
return success_response(
cell_product_svc.product_to_response(db, tenant_no, obj), message="创建成功"
)
except ValueError as e:
return error_response(400, str(e))
@@ -1613,7 +1629,9 @@ async def update_cell_product(
obj = cell_product_svc.update_cell_product(
db, product_id, tenant_no, user.get("user_id"), data=body,
)
return success_response(cell_product_svc._row_to_dict(obj), message="更新成功")
return success_response(
cell_product_svc.product_to_response(db, tenant_no, obj), message="更新成功"
)
except ValueError as e:
return error_response(400, str(e))
+165 -4
View File
@@ -9,7 +9,13 @@ from sqlalchemy import and_, func, or_
from sqlalchemy.orm import Session
from app.core.utils import generate_uuid
from app.models.models import BizCellProduct
from app.models.models import (
BizCellProduct,
BizCellProductSpec,
BizCellProductType,
BizStorageFacility,
BizStorageFacilitySlot,
)
def _row_to_dict(c: BizCellProduct) -> Dict[str, Any]:
@@ -18,9 +24,16 @@ def _row_to_dict(c: BizCellProduct) -> Dict[str, Any]:
"productCode": c.product_code,
"productName": c.product_name,
"productTypeId": c.product_type_id or "",
"productTypeName": "",
"specId": c.spec_id or "",
"specName": "",
"batchNo": c.batch_no or "",
"storageSlotId": c.storage_slot_id or "",
"storageSlotName": "",
"passage": c.passage or "",
"freezeDate": c.freeze_date.strftime("%Y-%m-%d %H:%M:%S") if c.freeze_date else "",
"expectedCellCount": float(c.expected_cell_count) if c.expected_cell_count is not None else None,
"viability": float(c.viability) if c.viability is not None else None,
"productStatus": c.product_status or "",
"sourceType": c.source_type or "",
"productionTaskId": c.production_task_id or "",
@@ -38,16 +51,111 @@ def _row_to_dict(c: BizCellProduct) -> Dict[str, Any]:
}
def _build_facility_path_label(node_id: str, by_id: Dict[str, BizStorageFacility]) -> str:
parts: List[str] = []
cur_id = (node_id or "").strip()
visited: set[str] = set()
while cur_id and cur_id != "0" and cur_id not in visited:
visited.add(cur_id)
node = by_id.get(cur_id)
if not node:
break
parts.append(node.node_name or "")
cur_id = (node.parent_id or "").strip()
parts.reverse()
return " / ".join(p for p in parts if p)
def _enrich_product_dicts(
db: Session,
tenant_no: str,
items: List[Dict[str, Any]],
) -> List[Dict[str, Any]]:
if not items:
return items
type_ids = {i.get("productTypeId") for i in items if i.get("productTypeId")}
spec_ids = {i.get("specId") for i in items if i.get("specId")}
slot_ids = {i.get("storageSlotId") for i in items if i.get("storageSlotId")}
type_map: Dict[str, str] = {}
if type_ids:
rows = (
db.query(BizCellProductType)
.filter(
BizCellProductType.id.in_(type_ids),
BizCellProductType.del_flag == 0,
)
.all()
)
type_map = {r.id: r.product_type_name or "" for r in rows}
spec_map: Dict[str, str] = {}
if spec_ids:
rows = (
db.query(BizCellProductSpec)
.filter(
BizCellProductSpec.id.in_(spec_ids),
BizCellProductSpec.del_flag == 0,
)
.all()
)
spec_map = {r.id: r.spec_name or "" for r in rows}
slot_map: Dict[str, str] = {}
if slot_ids:
slots = (
db.query(BizStorageFacilitySlot)
.filter(
BizStorageFacilitySlot.id.in_(slot_ids),
BizStorageFacilitySlot.del_flag == 0,
)
.all()
)
facilities = (
db.query(BizStorageFacility)
.filter(
BizStorageFacility.tenant_no == tenant_no,
BizStorageFacility.del_flag == 0,
)
.all()
)
facility_by_id = {f.id: f for f in facilities}
for slot in slots:
node_path = _build_facility_path_label(slot.facility_node_id, facility_by_id)
code = (slot.slot_code or "").strip()
if node_path and code:
slot_map[slot.id] = f"{node_path} / {code}"
else:
slot_map[slot.id] = code or node_path or ""
for item in items:
tid = item.get("productTypeId") or ""
sid = item.get("specId") or ""
slot_id = item.get("storageSlotId") or ""
item["productTypeName"] = type_map.get(tid, "") if tid else ""
item["specName"] = spec_map.get(sid, "") if sid else ""
item["storageSlotName"] = slot_map.get(slot_id, "") if slot_id else ""
return items
def product_to_response(db: Session, tenant_no: str, obj: BizCellProduct) -> Dict[str, Any]:
enriched = _enrich_product_dicts(db, tenant_no, [_row_to_dict(obj)])
return enriched[0] if enriched else _row_to_dict(obj)
def list_cell_products(
db: Session,
*,
tenant_no: str,
page: int = 1,
page_size: int = 20,
keyword: Optional[str] = None,
product_code: Optional[str] = None,
product_name: Optional[str] = None,
product_type_id: Optional[str] = None,
product_status: Optional[str] = None,
passage: Optional[str] = None,
user_info=None,
) -> Dict[str, Any]:
def _filtered():
@@ -55,6 +163,15 @@ def list_cell_products(
BizCellProduct.del_flag == 0,
BizCellProduct.tenant_no == tenant_no,
)
kw = (keyword or "").strip()
if kw:
like = f"%{kw}%"
q0 = q0.filter(
or_(
BizCellProduct.product_code.ilike(like),
BizCellProduct.product_name.ilike(like),
)
)
pc = (product_code or "").strip()
if pc:
q0 = q0.filter(BizCellProduct.product_code.like(f"%{pc}%"))
@@ -65,6 +182,8 @@ def list_cell_products(
q0 = q0.filter(BizCellProduct.product_type_id == product_type_id)
if product_status:
q0 = q0.filter(BizCellProduct.product_status == product_status)
if passage:
q0 = q0.filter(BizCellProduct.passage == passage)
if user_info:
from app.core.data_scope import apply_data_scope
q0 = apply_data_scope(q0, BizCellProduct, user_info, db, tenant_no)
@@ -80,7 +199,10 @@ def list_cell_products(
.limit(page_size)
.all()
)
return {"items": [_row_to_dict(x) for x in rows], "total": int(total)}
items = _enrich_product_dicts(
db, tenant_no, [_row_to_dict(x) for x in rows]
)
return {"items": items, "total": int(total)}
def get_cell_product(db: Session, id: str, tenant_no: str) -> Optional[BizCellProduct]:
@@ -125,7 +247,11 @@ def create_cell_product(
spec_id=(data.spec_id or "").strip() or None,
batch_no=(data.batch_no or "").strip() or None,
storage_slot_id=(data.storage_slot_id or "").strip() or None,
product_status=data.product_status or "zk",
passage=((data.passage or "").strip() or None) if getattr(data, "passage", None) else None,
freeze_date=data.freeze_date if hasattr(data, "freeze_date") else None,
expected_cell_count=data.expected_cell_count if hasattr(data, "expected_cell_count") else None,
viability=data.viability if hasattr(data, "viability") else None,
product_status=data.product_status or "frozen",
source_type=data.source_type or "manual",
quantity=data.quantity,
unit=(data.unit or "").strip() or None,
@@ -168,7 +294,20 @@ def update_cell_product(
obj.batch_no = (data.batch_no or "").strip() or None
if data.storage_slot_id is not None:
obj.storage_slot_id = (data.storage_slot_id or "").strip() or None
if data.product_status is not None:
if hasattr(data, "passage") and data.passage is not None:
obj.passage = (data.passage or "").strip() or None
if hasattr(data, "freeze_date") and data.freeze_date is not None:
obj.freeze_date = data.freeze_date
if hasattr(data, "expected_cell_count") and data.expected_cell_count is not None:
obj.expected_cell_count = data.expected_cell_count
if hasattr(data, "viability") and data.viability is not None:
obj.viability = data.viability
if data.product_status is not None and data.product_status != obj.product_status:
allowed = {"frozen": {"sold", "used"}}
from_status = obj.product_status
to_status = data.product_status
if from_status not in allowed or to_status not in allowed[from_status]:
raise ValueError(f"不允许从状态 {from_status} 变更为 {to_status}")
obj.product_status = data.product_status
if data.quantity is not None:
obj.quantity = data.quantity
@@ -205,6 +344,28 @@ def delete_cell_product(
db.commit()
def get_next_code(db: Session, tenant_no: str) -> str:
today_str = datetime.now().strftime("%Y%m%d")
prefix = f"CP{today_str}"
last = (
db.query(BizCellProduct)
.filter(
BizCellProduct.tenant_no == tenant_no,
BizCellProduct.product_code.like(f"{prefix}%"),
BizCellProduct.del_flag == 0,
)
.order_by(BizCellProduct.product_code.desc())
.first()
)
seq = 1
if last and last.product_code:
try:
seq = int(last.product_code[-6:]) + 1
except (ValueError, IndexError):
seq = 1
return f"{prefix}{seq:06d}"
def total_pages(total: int, page_size: int) -> int:
if total <= 0:
return 0
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_backend_app_crud_cell_product_py", "label": "cell_product.py", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_product.py", "source_location": "L1"}, {"id": "crud_cell_product_row_to_dict", "label": "_row_to_dict()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_product.py", "source_location": "L15"}, {"id": "crud_cell_product_list_cell_products", "label": "list_cell_products()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_product.py", "source_location": "L45"}, {"id": "crud_cell_product_get_cell_product", "label": "get_cell_product()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_product.py", "source_location": "L93"}, {"id": "crud_cell_product_create_cell_product", "label": "create_cell_product()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_product.py", "source_location": "L107"}, {"id": "crud_cell_product_update_cell_product", "label": "update_cell_product()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_product.py", "source_location": "L161"}, {"id": "crud_cell_product_delete_cell_product", "label": "delete_cell_product()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_product.py", "source_location": "L216"}, {"id": "crud_cell_product_get_next_code", "label": "get_next_code()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_product.py", "source_location": "L232"}, {"id": "crud_cell_product_total_pages", "label": "total_pages()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_product.py", "source_location": "L254"}, {"id": "crud_cell_product_rationale_1", "label": "\u7ec6\u80de\u4ea7\u54c1 CRUD\uff08biz_cell_product\uff09", "file_type": "rationale", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_product.py", "source_location": "L1"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_backend_app_crud_cell_product_py", "target": "math", "relation": "imports", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_product.py", "source_location": "L4", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_crud_cell_product_py", "target": "datetime", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_product.py", "source_location": "L5", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_crud_cell_product_py", "target": "typing", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_product.py", "source_location": "L6", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_crud_cell_product_py", "target": "sqlalchemy", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_product.py", "source_location": "L8", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_crud_cell_product_py", "target": "sqlalchemy_orm", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_product.py", "source_location": "L9", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_crud_cell_product_py", "target": "app_core_utils", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_product.py", "source_location": "L11", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_crud_cell_product_py", "target": "app_models_models", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_product.py", "source_location": "L12", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_crud_cell_product_py", "target": "crud_cell_product_row_to_dict", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_product.py", "source_location": "L15", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_crud_cell_product_py", "target": "crud_cell_product_list_cell_products", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_product.py", "source_location": "L45", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_crud_cell_product_py", "target": "crud_cell_product_get_cell_product", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/crud/cLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_backend_app_api_production_py", "label": "production.py", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L1"}, {"id": "api_production_get_tenant_no", "label": "_get_tenant_no()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L52"}, {"id": "api_production_consumablecreatebody", "label": "ConsumableCreateBody", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L65"}, {"id": "basemodel", "label": "BaseModel", "file_type": "code", "source_file": "", "source_location": ""}, {"id": "api_production_consumableupdatebody", "label": "ConsumableUpdateBody", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L77"}, {"id": "api_production_list_consumables", "label": "list_consumables()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L90"}, {"id": "api_production_list_all_consumables", "label": "list_all_consumables()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L104"}, {"id": "api_production_create_consumable", "label": "create_consumable()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L114"}, {"id": "api_production_update_consumable", "label": "update_consumable()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L140"}, {"id": "api_production_delete_consumable", "label": "delete_consumable()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L167"}, {"id": "api_production_reagentcreatebody", "label": "ReagentCreateBody", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L184"}, {"id": "api_production_reagentupdatebody", "label": "ReagentUpdateBody", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L196"}, {"id": "api_production_list_reagents", "label": "list_reagents()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L209"}, {"id": "api_production_list_all_reagents", "label": "list_all_reagents()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L223"}, {"id": "api_production_create_reagent", "label": "create_reagent()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L233"}, {"id": "api_production_update_reagent", "label": "update_reagent()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L259"}, {"id": "api_production_delete_reagent", "label": "delete_reagent()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L286"}, {"id": "api_production_sopdocumentcreatebody", "label": "SopDocumentCreateBody", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L303"}, {"id": "api_production_sopdocumentupdatebody", "label": "SopDocumentUpdateBody", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L313"}, {"id": "api_production_list_sop_documents", "label": "list_sop_documents()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L324"}, {"id": "api_production_list_all_sop_documents", "label": "list_all_sop_documents()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L338"}, {"id": "api_production_create_sop_document", "label": "create_sop_document()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L348"}, {"id": "api_production_update_sop_document", "label": "update_sop_document()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L375"}, {"id": "api_production_delete_sop_document", "label": "delete_sop_document()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py", "source_location": "L404"}, {"id": "api_production_processcategorycreatebody", "label": "ProcessCategoryCreateBody", "file_type": "code", "source_file": "/Users/wuziloLine truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_backend_app_models_models_py", "label": "models.py", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py", "source_location": "L1"}, {"id": "models_models_userrole", "label": "UserRole", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py", "source_location": "L10"}, {"id": "str", "label": "str", "file_type": "code", "source_file": "", "source_location": ""}, {"id": "models_models_sysuser", "label": "SysUser", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py", "source_location": "L22"}, {"id": "base", "label": "Base", "file_type": "code", "source_file": "", "source_location": ""}, {"id": "models_models_sysrole", "label": "SysRole", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py", "source_location": "L48"}, {"id": "models_models_sysuserrole", "label": "SysUserRole", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py", "source_location": "L68"}, {"id": "models_models_sysrolemenu", "label": "SysRoleMenu", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py", "source_location": "L87"}, {"id": "models_models_sysroledatascopedetail", "label": "SysRoleDataScopeDetail", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py", "source_location": "L106"}, {"id": "models_models_sysmenu", "label": "SysMenu", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py", "source_location": "L126"}, {"id": "models_models_sysdept", "label": "SysDept", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py", "source_location": "L152"}, {"id": "models_models_systenant", "label": "SysTenant", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py", "source_location": "L175"}, {"id": "models_models_syssite", "label": "SysSite", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py", "source_location": "L195"}, {"id": "models_models_syspost", "label": "SysPost", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py", "source_location": "L220"}, {"id": "models_models_sysuserpost", "label": "SysUserPost", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py", "source_location": "L240"}, {"id": "models_models_bizitemunit", "label": "BizItemUnit", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py", "source_location": "L259"}, {"id": "models_models_bizexpresscompany", "label": "BizExpressCompany", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py", "source_location": "L278"}, {"id": "models_models_bizcustomer", "label": "BizCustomer", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py", "source_location": "L297"}, {"id": "models_models_bizcellsampletype", "label": "BizCellSampleType", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py", "source_location": "L320"}, {"id": "models_models_bizcollectionregion", "label": "BizCollectionRegion", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py", "source_location": "L341"}, {"id": "models_models_bizcollectionhospital", "label": "BizCollectionHospital", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py", "source_location": "L359"}, {"id": "models_models_bizcollectionprotocolno", "label": "BizCollectionProtocolNo", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py", "source_location": "L378"}, {"id": "models_models_bizsamplecode", "label": "BizSampleCode", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py", "source_location": "L400"}, {"id": "models_models_sysdicttype", "label": "SysDictType", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py", "source_location": "L428"}, {"id": "models_models_sysethnic", "label": "SysEthnic", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py", "source_location": "L447"}, {"id": "models_models_sysdictdata", "label": "SysDictData", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py", "source_location": "L466"}, {"id": "models_models_sysapirequestlog", "label": "SysApiRequestLog", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py", "source_location"Line truncated
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_backend_app_schemas_cell_product_py", "label": "cell_product.py", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/cell_product.py", "source_location": "L1"}, {"id": "schemas_cell_product_cellproductcreate", "label": "CellProductCreate", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/cell_product.py", "source_location": "L6"}, {"id": "basemodel", "label": "BaseModel", "file_type": "code", "source_file": "", "source_location": ""}, {"id": "schemas_cell_product_validate_viability", "label": "validate_viability()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/cell_product.py", "source_location": "L27"}, {"id": "schemas_cell_product_validate_passage", "label": "validate_passage()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/cell_product.py", "source_location": "L34"}, {"id": "schemas_cell_product_cellproductupdate", "label": "CellProductUpdate", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/cell_product.py", "source_location": "L43"}, {"id": "schemas_cell_product_validate_status", "label": "validate_status()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/cell_product.py", "source_location": "L79"}, {"id": "schemas_cell_product_cellproductlistquery", "label": "CellProductListQuery", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/cell_product.py", "source_location": "L85"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_backend_app_schemas_cell_product_py", "target": "pydantic", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/cell_product.py", "source_location": "L1", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_schemas_cell_product_py", "target": "typing", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/cell_product.py", "source_location": "L2", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_schemas_cell_product_py", "target": "datetime", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/cell_product.py", "source_location": "L3", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_schemas_cell_product_py", "target": "schemas_cell_product_cellproductcreate", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/cell_product.py", "source_location": "L6", "weight": 1.0}, {"source": "schemas_cell_product_cellproductcreate", "target": "basemodel", "relation": "inherits", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/cell_product.py", "source_location": "L6", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_schemas_cell_product_py", "target": "schemas_cell_product_validate_viability", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/cell_product.py", "source_location": "L27", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_schemas_cell_product_py", "target": "schemas_cell_product_validate_passage", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/cell_product.py", "source_location": "L34", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_schemas_cell_product_py", "target": "schemas_cell_product_cellproductupdate", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/cell_product.py", "source_location": "L43", "weight": 1.0}, {"source": "schemas_cell_product_cellproductupdate", "target": "basemodel", "relation": "inherits", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/cell_product.py", "source_location": "L43", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_schemas_cell_product_py", "target": "schemas_cell_product_validate_viability", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/cell_product.py", "source_location": "L62", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_schemas_cell_product_py", "target": "schemas_cell_product_validate_passage", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/backend/app/schemas/cell_product.py", "source_location": "L69", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_backend_app_schemas_cell_product_py", "targLine truncated
+5 -1
View File
@@ -2131,7 +2131,11 @@ class BizCellProduct(Base):
spec_id = Column(String(64), nullable=True, comment="规格ID")
batch_no = Column(String(64), nullable=True, comment="批号")
storage_slot_id = Column(String(64), nullable=True, comment="库位ID")
product_status = Column(String(16), nullable=True, default="zk", comment="产品状态 zk在库 ck出库")
passage = Column(String(8), nullable=True, comment="代次(P1~P8)")
freeze_date = Column(DateTime, nullable=True, comment="冻存日期")
expected_cell_count = Column(Numeric(18, 4), nullable=True, comment="预计细胞数量(单位:亿)")
viability = Column(Numeric(5, 2), nullable=True, comment="活性(百分比,0~100)")
product_status = Column(String(16), nullable=True, default="frozen", comment="产品状态 frozen已冻存/sold已售卖/used已使用")
source_type = Column(String(32), nullable=True, default="production", comment="来源类型 production生产 qc质检")
production_task_id = Column(String(64), nullable=True, comment="生产任务ID")
quantity = Column(Numeric(18, 4), nullable=True, comment="数量")
+83 -25
View File
@@ -1,42 +1,100 @@
from pydantic import BaseModel
from typing import Optional
from datetime import datetime
from typing import Optional
from pydantic import BaseModel, ConfigDict, Field, field_validator
class CellProductCreate(BaseModel):
product_code: str
product_name: str
product_type_id: Optional[str] = None
spec_id: Optional[str] = None
batch_no: Optional[str] = None
storage_slot_id: Optional[str] = None
product_status: Optional[str] = 'zk'
source_type: Optional[str] = 'manual'
model_config = ConfigDict(populate_by_name=True)
product_code: str = Field(..., alias="productCode")
product_name: str = Field(..., alias="productName")
product_type_id: Optional[str] = Field(None, alias="productTypeId")
spec_id: Optional[str] = Field(None, alias="specId")
batch_no: Optional[str] = Field(None, alias="batchNo")
storage_slot_id: Optional[str] = Field(None, alias="storageSlotId")
passage: Optional[str] = None
freeze_date: Optional[datetime] = Field(None, alias="freezeDate")
expected_cell_count: Optional[float] = Field(None, alias="expectedCellCount")
viability: Optional[float] = None
product_status: Optional[str] = Field("frozen", alias="productStatus")
source_type: Optional[str] = Field("manual", alias="sourceType")
quantity: Optional[float] = None
unit: Optional[str] = None
expiry_date: Optional[datetime] = None
product_images: Optional[str] = None
expiry_date: Optional[datetime] = Field(None, alias="expiryDate")
product_images: Optional[str] = Field(None, alias="productImages")
remark: Optional[str] = None
@field_validator("viability")
@classmethod
def validate_viability(cls, v):
if v is not None and (v < 0 or v > 100):
raise ValueError("活性必须在 0~100 之间")
return v
@field_validator("passage")
@classmethod
def validate_passage(cls, v):
if v is not None:
valid = {f"P{i}" for i in range(1, 9)}
if v.upper() not in valid:
raise ValueError("代次必须为 P1~P8")
return v.upper()
return v
class CellProductUpdate(BaseModel):
product_name: Optional[str] = None
product_type_id: Optional[str] = None
spec_id: Optional[str] = None
batch_no: Optional[str] = None
storage_slot_id: Optional[str] = None
product_status: Optional[str] = None
model_config = ConfigDict(populate_by_name=True)
product_name: Optional[str] = Field(None, alias="productName")
product_type_id: Optional[str] = Field(None, alias="productTypeId")
spec_id: Optional[str] = Field(None, alias="specId")
batch_no: Optional[str] = Field(None, alias="batchNo")
storage_slot_id: Optional[str] = Field(None, alias="storageSlotId")
passage: Optional[str] = None
freeze_date: Optional[datetime] = Field(None, alias="freezeDate")
expected_cell_count: Optional[float] = Field(None, alias="expectedCellCount")
viability: Optional[float] = None
product_status: Optional[str] = Field(None, alias="productStatus")
quantity: Optional[float] = None
unit: Optional[str] = None
expiry_date: Optional[datetime] = None
product_images: Optional[str] = None
expiry_date: Optional[datetime] = Field(None, alias="expiryDate")
product_images: Optional[str] = Field(None, alias="productImages")
remark: Optional[str] = None
@field_validator("viability")
@classmethod
def validate_viability(cls, v):
if v is not None and (v < 0 or v > 100):
raise ValueError("活性必须在 0~100 之间")
return v
@field_validator("passage")
@classmethod
def validate_passage(cls, v):
if v is not None:
valid = {f"P{i}" for i in range(1, 9)}
if v.upper() not in valid:
raise ValueError("代次必须为 P1~P8")
return v.upper()
return v
@field_validator("product_status")
@classmethod
def validate_status(cls, v):
if v is not None and v not in ("frozen", "sold", "used"):
raise ValueError("状态必须为 frozen/sold/used")
return v
class CellProductListQuery(BaseModel):
model_config = ConfigDict(populate_by_name=True)
page: int = 1
page_size: int = 20
product_code: Optional[str] = None
product_name: Optional[str] = None
product_type_id: Optional[str] = None
product_status: Optional[str] = None
page_size: int = Field(20, alias="pageSize")
keyword: Optional[str] = None
product_code: Optional[str] = Field(None, alias="productCode")
product_name: Optional[str] = Field(None, alias="productName")
product_type_id: Optional[str] = Field(None, alias="productTypeId")
product_status: Optional[str] = Field(None, alias="productStatus")
passage: Optional[str] = None
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+91 -91
View File
@@ -1,12 +1,12 @@
# Graph Report - app (2026-05-21)
## Corpus Check
- 135 files · ~117,795 words
- 135 files · ~118,210 words
- Verdict: corpus is large enough that graph structure adds value.
## Summary
- 2375 nodes · 9260 edges · 52 communities detected
- Extraction: 42% EXTRACTED · 58% INFERRED · 0% AMBIGUOUS · INFERRED: 5364 edges (avg confidence: 0.64)
- 2380 nodes · 9268 edges · 52 communities detected
- Extraction: 42% EXTRACTED · 58% INFERRED · 0% AMBIGUOUS · INFERRED: 5366 edges (avg confidence: 0.64)
- Token cost: 0 input · 0 output
## Community Hubs (Navigation)
@@ -64,7 +64,7 @@
- [[_COMMUNITY_Community 53|Community 53]]
## God Nodes (most connected - your core abstractions)
1. `success_response()` - 376 edges
1. `success_response()` - 377 edges
2. `error_response()` - 302 edges
3. `SysUser` - 217 edges
4. `BizQcMaterialUsage` - 197 edges
@@ -91,159 +91,159 @@
### Community 0 - "Community 0"
Cohesion: 0.02
Nodes (423): attachment_download_url(), create_dept(), create_dict_data(), create_dict_type(), create_express_company(), create_item_unit(), create_menu(), create_post() (+415 more)
Nodes (426): create_dept(), create_dict_data(), create_dict_type(), create_express_company(), create_item_unit(), create_menu(), create_post(), create_role_management() (+418 more)
### Community 1 - "Community 1"
Cohesion: 0.01
Nodes (368): AttachmentDownloadUrlBody, DeptCreateBody, DeptUpdateBody, DictDataCreateBody, DictDataUpdateBody, DictTypeCreateBody, DictTypeUpdateBody, ExpressCompanyCreateBody (+360 more)
Nodes (393): attachment_download_url(), AttachmentDownloadUrlBody, DeptCreateBody, DeptUpdateBody, DictDataCreateBody, DictDataUpdateBody, DictTypeCreateBody, DictTypeUpdateBody (+385 more)
### Community 2 - "Community 2"
Cohesion: 0.06
Nodes (196): InsuranceCompanyCreateBody, InsuranceCompanyInvoiceCreateBody, InsuranceCompanyInvoiceUpdateBody, InsuranceCompanyUpdateBody, InsuranceProjectPriceCreateBody, InsuranceProjectPriceUpdateBody, 保险公司及险种价格管理 API(保险公司 + 开票信息 + 险种价格), Base (+188 more)
Nodes (213): InsuranceCompanyCreateBody, InsuranceCompanyInvoiceCreateBody, InsuranceCompanyInvoiceUpdateBody, InsuranceCompanyUpdateBody, InsuranceProjectPriceCreateBody, InsuranceProjectPriceUpdateBody, 保险公司及险种价格管理 API(保险公司 + 开票信息 + 险种价格), ProcessDefCreateBody (+205 more)
### Community 3 - "Community 3"
Cohesion: 0.03
Nodes (135): generate_uuid(), create_consumable_pack(), get_consumable_pack(), get_pack_detail(), _item_row_to_dict(), list_all_packs(), list_consumable_packs(), pack_code_exists() (+127 more)
Cohesion: 0.02
Nodes (153): generate_uuid(), create_consumable_pack(), get_consumable_pack(), get_pack_detail(), _item_row_to_dict(), list_all_packs(), list_consumable_packs(), pack_code_exists() (+145 more)
### Community 4 - "Community 4"
Cohesion: 0.03
Nodes (123): list_collection_regions(), _persist_protocol_sub_resources(), list_ethnics_for_select(), list_access_logs(), apply_data_scope(), _get_child_dept_ids(), get_user_dept_id(), _resolve_user_role_scopes() (+115 more)
Nodes (120): _persist_protocol_sub_resources(), list_ethnics_for_select(), _list_insurance_companies(), _total_pages(), list_access_logs(), apply_data_scope(), _get_child_dept_ids(), get_user_dept_id() (+112 more)
### Community 5 - "Community 5"
Cohesion: 0.09
Nodes (125): CellSampleTypeCreateBody, CellSampleTypeUpdateBody, CollectionRegionCreateBody, CollectionRegionUpdateBody, CustomerCreateBody, CustomerUpdateBody, HospitalCreateBody, HospitalUpdateBody (+117 more)
Cohesion: 0.08
Nodes (126): AuditCreateBody, BatchDetailIdsBody, BatchRefundFinanceSubmitBody, FinanceConfirmBody, FullmoonTodoInsuranceBody, ProtocolCreateBody, ProtocolUpdateBody, 细胞存储 — 采集协议单 / 审核 / 财务确认 / 分期预警 API (+118 more)
### Community 6 - "Community 6"
Cohesion: 0.05
Nodes (83): _format_attachment_file_size(), get_protocol_nos_used_in_protocols(), urls_to_attachment_list(), complete_production_work_order(), create_production_work_order_from_reception(), _fmt_dt(), _generate_work_order_no(), get_production_work_order() (+75 more)
Cohesion: 0.04
Nodes (94): get_role_data_scope(), get_role_menus(), _format_attachment_file_size(), get_protocol_nos_used_in_protocols(), urls_to_attachment_list(), create_dict_type(), dict_type_code_exists(), get_dict_type() (+86 more)
### Community 7 - "Community 7"
Cohesion: 0.04
Nodes (80): activate_channel_agreement(), cell_product_price_code_exists(), cell_product_spec_code_exists(), cell_product_type_code_exists(), _channel_agreement_price_exists(), _channel_agreement_price_item_key(), checkup_package_code_exists(), create_cell_product_price() (+72 more)
Cohesion: 0.03
Nodes (54): get_designer_data(), BaseStorage, convert_heic_to_jpeg(), get_storage(), LocalStorage, MinIOStorage, OSSStorage, process_image_content() (+46 more)
### Community 8 - "Community 8"
Cohesion: 0.06
Nodes (61): _business_type_label(), _enrich_task_list(), get_business_all_tasks(), _get_business_form_fields(), get_designer_data(), get_instance_detail(), list_completed_tasks(), list_my_instances() (+53 more)
Cohesion: 0.04
Nodes (77): _avatar_url_field(), _build_user_info_payload(), _jwt_user_payload(), logout(), 公共接口(Web 登录、刷新 Token、登出;用户模型对齐 docs/sql/zkxw.sql 基线), 登出:吊销 Redis 会话(若已配置 REDIS_HOST)。, 与 web-admin `userInfo` 字段约定一致。, Web 端登录用 RSA 公钥(PKCS#1 v1.5,与 JSEncrypt 一致) (+69 more)
### Community 9 - "Community 9"
Cohesion: 0.05
Nodes (59): list_roles_for_select(), _avatar_url_field(), _build_user_info_payload(), get_web_auth_menu_codes(), _jwt_user_payload(), logout(), 公共接口(Web 登录、刷新 Token、登出;用户模型对齐 docs/sql/zkxw.sql 基线), 登出:吊销 Redis 会话(若已配置 REDIS_HOST)。 (+51 more)
Cohesion: 0.07
Nodes (67): AccessLogCreateBody, AccessLogUpdateBody, CleanRoomCreateBody, CleanRoomUpdateBody, ExhibitionAppointmentCreateBody, ExhibitionAppointmentUpdateBody, list_storage_slots(), 运营支撑 API(洁净室、进出记录、仓储设施、储位、展厅预约) (+59 more)
### Community 10 - "Community 10"
Cohesion: 0.07
Nodes (66): AccessLogCreateBody, AccessLogUpdateBody, CleanRoomCreateBody, CleanRoomUpdateBody, ExhibitionAppointmentCreateBody, ExhibitionAppointmentUpdateBody, list_storage_slots(), 运营支撑 API(洁净室、进出记录、仓储设施、储位、展厅预约) (+58 more)
Cohesion: 0.29
Nodes (53): ConsumableCreateBody, ConsumablePackCreateBody, ConsumablePackUpdateBody, ConsumableUpdateBody, OutputTypeCreateBody, OutputTypeUpdateBody, PackItemInput, ProcessCategoryCreateBody (+45 more)
### Community 11 - "Community 11"
Cohesion: 0.05
Nodes (26): BusinessException, InvalidStatusTransitionException, log_delivery_operation(), log_operation(), log_order_operation(), log_settlement_operation(), log_user_operation(), OrderLockedException (+18 more)
Cohesion: 0.07
Nodes (33): BaseHTTPMiddleware, ApiCryptoError, _b64url_decode(), _b64url_encode(), decrypt_json(), derive_key(), encrypt_json(), gen_nonce_hex() (+25 more)
### Community 12 - "Community 12"
Cohesion: 0.06
Nodes (35): BaseHTTPMiddleware, ApiCryptoError, _b64url_decode(), _b64url_encode(), decrypt_json(), derive_key(), encrypt_json(), gen_nonce_hex() (+27 more)
### Community 13 - "Community 13"
Cohesion: 0.43
Nodes (45): ConsumableCreateBody, ConsumablePackCreateBody, ConsumablePackUpdateBody, ConsumableUpdateBody, OutputTypeCreateBody, OutputTypeUpdateBody, PackItemInput, ProcessCategoryCreateBody (+37 more)
### Community 14 - "Community 14"
Cohesion: 0.14
Nodes (35): list_research_donation_audits(), _audit_to_dict(), batch_delete_donations(), _collection_to_dict(), convert_to_storage(), _create_collection(), create_donation(), _create_party() (+27 more)
### Community 15 - "Community 15"
Cohesion: 0.12
Nodes (11): get_wechat_pay_client(), get_wechat_pay_v2(), 微信支付工具模块 支持微信支付 V3 API 文档参考: https://pay.weixin.qq.com/docs/v3/apis/index.ht, 统一下单接口 (V3) Args: out_trade_no: 商户订单号 total, 申请退款 Args: out_trade_no: 商户订单号 total_amount, 小程序调起支付的二次签名参数(与公众号 JSAPI 规则一致), 统一下单 (V2 API) Args: out_trade_no: 商户订单号 tot, 生成签名 V3 (SHA256 with RSA) (+3 more)
### Community 16 - "Community 16"
### Community 13 - "Community 13"
Cohesion: 0.17
Nodes (12): BusinessException, InvalidStatusTransitionException, log_delivery_operation(), log_operation(), log_order_operation(), log_settlement_operation(), log_user_operation(), OrderLockedException (+4 more)
### Community 14 - "Community 14"
Cohesion: 0.21
Nodes (19): _consumable_row_to_dict(), create_process(), _create_sub_consumable(), _create_sub_data_collection(), _create_sub_output(), _create_sub_reagent(), _data_collection_row_to_dict(), get_process() (+11 more)
### Community 15 - "Community 15"
Cohesion: 0.18
Nodes (18): company_code_exists(), create_company(), get_company(), list_companies(), list_company_options(), _row_to_dict(), soft_delete_company(), update_company() (+10 more)
### Community 17 - "Community 17"
### Community 16 - "Community 16"
Cohesion: 0.25
Nodes (13): batch_issue_protocol_nos(), batch_revoke_protocol_nos(), create_protocol_no(), get_protocol_no(), list_protocol_nos(), _parse_issued_time(), protocol_no_exists(), 采集协议号池 CRUD(biz_collection_protocol_no) (+5 more)
Nodes (13): code_exists(), create_material(), _dec(), _fmt_dt(), get_material(), list_materials(), _norm_status(), options_list() (+5 more)
### Community 17 - "Community 17"
Cohesion: 0.23
Nodes (12): create_collection_region(), create_hospital(), get_collection_region(), get_hospital(), _hospital_names_by_region_ids(), _hospital_to_dict(), list_collection_regions(), list_hospitals() (+4 more)
### Community 18 - "Community 18"
Cohesion: 0.26
Nodes (12): batch_issue_sample_codes(), batch_revoke_sample_codes(), create_sample_code(), get_sample_code(), list_sample_codes(), _parse_issued_time(), 样本编码池 CRUD(biz_sample_code), _resolve_issued_user_display_name() (+4 more)
### Community 19 - "Community 19"
Cohesion: 0.23
Nodes (12): Config, ProductBase, ProductCategoryBase, ProductCategoryCreate, ProductCategoryResponse, ProductCreate, ProductResponse, ProductUnitBase (+4 more)
### Community 19 - "Community 19"
Cohesion: 0.29
Nodes (11): batch_issue_sample_codes(), batch_revoke_sample_codes(), create_sample_code(), get_sample_code(), list_sample_codes(), _parse_issued_time(), _resolve_issued_user_display_name(), _row_to_dict() (+3 more)
### Community 20 - "Community 20"
Cohesion: 0.27
Nodes (11): _build_tree(), _cascade_soft_delete(), _collect_with_ancestors(), create_dept(), get_dept(), list_depts(), list_depts_tree(), 保留匹配节点及其全部上级,供关键词检索后仍能正确组树。 (+3 more)
Cohesion: 0.31
Nodes (12): _code_exists(), create_material(), _dec(), _fmt_dt(), get_material(), list_materials(), options_list(), _row_to_dict() (+4 more)
### Community 21 - "Community 21"
Cohesion: 0.29
Nodes (9): _build_tree(), _cascade_soft_delete(), create_menu(), get_menu(), list_menus(), list_menus_tree(), _row_to_dict(), soft_delete_menu() (+1 more)
Cohesion: 0.31
Nodes (11): batch_issue_protocol_nos(), batch_revoke_protocol_nos(), create_protocol_no(), get_protocol_no(), list_protocol_nos(), _parse_issued_time(), protocol_no_exists(), _resolve_issued_user_display_name() (+3 more)
### Community 22 - "Community 22"
Cohesion: 0.31
Nodes (8): consumable_code_exists(), create_consumable(), get_consumable(), list_consumables(), 耗材 CRUD(biz_consumable), _row_to_dict(), soft_delete_consumable(), update_consumable()
Cohesion: 0.24
Nodes (12): list_depts(), _build_tree(), _cascade_soft_delete(), _collect_with_ancestors(), create_dept(), get_dept(), list_depts(), list_depts_tree() (+4 more)
### Community 23 - "Community 23"
Cohesion: 0.31
Nodes (8): create_sop_document(), get_sop_document(), list_sop_documents(), SOP文档 CRUD(biz_sop_document), _row_to_dict(), soft_delete_sop_document(), sop_code_exists(), update_sop_document()
Cohesion: 0.26
Nodes (10): list_menus(), _build_tree(), _cascade_soft_delete(), create_menu(), get_menu(), list_menus(), list_menus_tree(), _row_to_dict() (+2 more)
### Community 24 - "Community 24"
Cohesion: 0.36
Nodes (9): deduct_for_task(), _display_remark(), list_usages(), list_usages_by_task(), _parse_receiver_purpose_from_remark(), _resolve_receiver_purpose(), revert_task_material_usages(), _row_to_dict() (+1 more)
Cohesion: 0.24
Nodes (9): dict_data_options(), create_dict_data(), get_dict_data(), list_dict_data(), list_dict_data_options(), 字典数据 CRUD(sys_dict_data), _row_to_dict(), soft_delete_dict_data() (+1 more)
### Community 25 - "Community 25"
Cohesion: 0.31
Nodes (8): create_express_company(), express_code_exists(), get_express_company(), list_express_companies(), 快递公司 CRUD(biz_express_company), _row_to_dict(), soft_delete_express_company(), update_express_company()
Nodes (8): consumable_code_exists(), create_consumable(), get_consumable(), list_consumables(), 耗材 CRUD(biz_consumable), _row_to_dict(), soft_delete_consumable(), update_consumable()
### Community 26 - "Community 26"
Cohesion: 0.31
Nodes (8): create_sop_document(), get_sop_document(), list_sop_documents(), SOP文档 CRUD(biz_sop_document), _row_to_dict(), soft_delete_sop_document(), sop_code_exists(), update_sop_document()
### Community 27 - "Community 27"
Cohesion: 0.31
Nodes (8): create_express_company(), express_code_exists(), get_express_company(), list_express_companies(), 快递公司 CRUD(biz_express_company), _row_to_dict(), soft_delete_express_company(), update_express_company()
### Community 28 - "Community 28"
Cohesion: 0.27
Nodes (8): create_cell_product(), delete_cell_product(), get_cell_product(), get_next_code(), list_cell_products(), 细胞产品 CRUD(biz_cell_product), _row_to_dict(), update_cell_product()
### Community 29 - "Community 29"
Cohesion: 0.38
Nodes (9): create_check_item(), get_check_item(), item_code_exists(), list_active_check_items(), list_check_items(), _order_query(), _row_to_dict(), soft_delete_check_item() (+1 more)
### Community 27 - "Community 27"
Cohesion: 0.27
Nodes (8): create_dict_data(), get_dict_data(), list_dict_data(), list_dict_data_options(), 字典数据 CRUD(sys_dict_data), _row_to_dict(), soft_delete_dict_data(), update_dict_data()
### Community 28 - "Community 28"
Cohesion: 0.31
Nodes (8): create_item_unit(), get_item_unit(), list_item_units(), 物品单位 CRUD(biz_item_unit), _row_to_dict(), soft_delete_item_unit(), unit_code_exists(), update_item_unit()
### Community 29 - "Community 29"
Cohesion: 0.31
Nodes (8): create_customer(), get_customer(), id_card_taken(), list_customers(), 客户 CRUD(biz_customer), _row_to_dict(), soft_delete_customer(), update_customer()
### Community 30 - "Community 30"
Cohesion: 0.31
Nodes (8): create_cell_sample_type(), get_cell_sample_type(), list_cell_sample_types(), 细胞样本类型 CRUD(biz_cell_sample_type), _row_to_dict(), soft_delete_cell_sample_type(), type_code_exists(), update_cell_sample_type()
### Community 31 - "Community 31"
Cohesion: 0.31
Nodes (8): create_output_type(), get_output_type(), list_output_types(), 工艺产出物类型 CRUD(biz_output_type), _row_to_dict(), soft_delete_output_type(), type_code_exists(), update_output_type()
### Community 32 - "Community 32"
Cohesion: 0.31
Nodes (8): category_code_exists(), create_process_category(), get_process_category(), list_process_categories(), 工艺工序分类 CRUD(biz_process_category), _row_to_dict(), soft_delete_process_category(), update_process_category()
### Community 31 - "Community 31"
Cohesion: 0.31
Nodes (8): create_item_unit(), get_item_unit(), list_item_units(), 物品单位 CRUD(biz_item_unit), _row_to_dict(), soft_delete_item_unit(), unit_code_exists(), update_item_unit()
### Community 32 - "Community 32"
Cohesion: 0.31
Nodes (8): create_customer(), get_customer(), id_card_taken(), list_customers(), 客户 CRUD(biz_customer), _row_to_dict(), soft_delete_customer(), update_customer()
### Community 33 - "Community 33"
Cohesion: 0.36
Nodes (7): create_dict_type(), dict_type_code_exists(), get_dict_type(), list_dict_types(), _row_to_dict(), soft_delete_dict_type(), update_dict_type()
Cohesion: 0.31
Nodes (8): create_cell_sample_type(), get_cell_sample_type(), list_cell_sample_types(), 细胞样本类型 CRUD(biz_cell_sample_type), _row_to_dict(), soft_delete_cell_sample_type(), type_code_exists(), update_cell_sample_type()
### Community 34 - "Community 34"
Cohesion: 0.36
Nodes (7): create_post(), get_post(), list_posts(), post_code_exists(), _row_to_dict(), soft_delete_post(), update_post()
Cohesion: 0.31
Nodes (8): create_output_type(), get_output_type(), list_output_types(), 工艺产出物类型 CRUD(biz_output_type), _row_to_dict(), soft_delete_output_type(), type_code_exists(), update_output_type()
### Community 35 - "Community 35"
Cohesion: 0.36
Nodes (7): create_reagent(), get_reagent(), list_reagents(), reagent_code_exists(), _row_to_dict(), soft_delete_reagent(), update_reagent()
Nodes (7): create_post(), get_post(), list_posts(), post_code_exists(), _row_to_dict(), soft_delete_post(), update_post()
### Community 36 - "Community 36"
Cohesion: 0.31
Nodes (7): create_cell_product(), delete_cell_product(), get_cell_product(), list_cell_products(), 细胞产品 CRUD(biz_cell_product), _row_to_dict(), update_cell_product()
Cohesion: 0.36
Nodes (7): create_tenant(), get_tenant(), list_tenants(), _row_to_dict(), soft_delete_tenant(), tenant_no_exists(), update_tenant()
### Community 37 - "Community 37"
Cohesion: 0.36
Nodes (7): create_site(), get_site(), list_sites(), _row_to_dict(), site_code_exists(), soft_delete_site(), update_site()
Nodes (7): create_reagent(), get_reagent(), list_reagents(), reagent_code_exists(), _row_to_dict(), soft_delete_reagent(), update_reagent()
### Community 38 - "Community 38"
Cohesion: 0.36
Nodes (7): create_tenant(), get_tenant(), list_tenants(), _row_to_dict(), soft_delete_tenant(), tenant_no_exists(), update_tenant()
Nodes (7): create_site(), get_site(), list_sites(), _row_to_dict(), site_code_exists(), soft_delete_site(), update_site()
### Community 39 - "Community 39"
Cohesion: 0.29
@@ -326,14 +326,14 @@ Nodes (1): 发送催单通知给配送员 Args: del
## Suggested Questions
_Questions this graph is uniquely positioned to answer:_
- **Why does `generate_uuid()` connect `Community 3` to `Community 0`, `Community 2`, `Community 4`, `Community 5`, `Community 6`, `Community 7`, `Community 8`, `Community 9`, `Community 10`, `Community 12`, `Community 14`, `Community 16`, `Community 17`, `Community 18`, `Community 20`, `Community 21`, `Community 22`, `Community 23`, `Community 24`, `Community 25`, `Community 26`, `Community 27`, `Community 28`, `Community 29`, `Community 30`, `Community 31`, `Community 32`, `Community 33`, `Community 34`, `Community 35`, `Community 36`, `Community 37`, `Community 38`?**
_High betweenness centrality (0.096) - this node is a cross-community bridge._
- **Why does `SysUser` connect `Community 1` to `Community 2`, `Community 3`, `Community 5`, `Community 8`, `Community 9`, `Community 10`, `Community 13`, `Community 17`, `Community 18`?**
_High betweenness centrality (0.095) - this node is a cross-community bridge._
- **Why does `success_response()` connect `Community 0` to `Community 1`, `Community 4`, `Community 8`, `Community 9`, `Community 10`, `Community 43`, `Community 14`?**
_High betweenness centrality (0.075) - this node is a cross-community bridge._
- **Are the 375 inferred relationships involving `success_response()` (e.g. with `web_login_rsa_public_key()` and `web_login()`) actually correct?**
_`success_response()` has 375 INFERRED edges - model-reasoned connections that need verification._
- **Why does `SysUser` connect `Community 1` to `Community 2`, `Community 5`, `Community 8`, `Community 9`, `Community 10`?**
_High betweenness centrality (0.098) - this node is a cross-community bridge._
- **Why does `generate_uuid()` connect `Community 3` to `Community 0`, `Community 1`, `Community 2`, `Community 4`, `Community 5`, `Community 6`, `Community 7`, `Community 8`, `Community 9`, `Community 11`, `Community 14`, `Community 15`, `Community 16`, `Community 17`, `Community 19`, `Community 20`, `Community 21`, `Community 22`, `Community 23`, `Community 24`, `Community 25`, `Community 26`, `Community 27`, `Community 28`, `Community 29`, `Community 30`, `Community 31`, `Community 32`, `Community 33`, `Community 34`, `Community 35`, `Community 36`, `Community 37`, `Community 38`?**
_High betweenness centrality (0.097) - this node is a cross-community bridge._
- **Why does `success_response()` connect `Community 0` to `Community 1`, `Community 4`, `Community 6`, `Community 7`, `Community 8`, `Community 9`, `Community 43`, `Community 22`, `Community 23`, `Community 24`?**
_High betweenness centrality (0.065) - this node is a cross-community bridge._
- **Are the 376 inferred relationships involving `success_response()` (e.g. with `web_login_rsa_public_key()` and `web_login()`) actually correct?**
_`success_response()` has 376 INFERRED edges - model-reasoned connections that need verification._
- **Are the 348 inferred relationships involving `str` (e.g. with `extract_login_credentials()` and `._make_request()`) actually correct?**
_`str` has 348 INFERRED edges - model-reasoned connections that need verification._
- **Are the 301 inferred relationships involving `error_response()` (e.g. with `_json_error_response()` and `web_login()`) actually correct?**
+4 -4
View File
@@ -57,12 +57,12 @@
</div>
<div id="legend"></div>
</div>
<div id="stats">2375 nodes &middot; 9260 edges &middot; 58 communities</div>
<div id="stats">2380 nodes &middot; 9268 edges &middot; 58 communities</div>
</div>
<script>
const RAW_NODES = [{"id": "init_py", "label": "__init__.py", "color": {"background": "#B07AA1", "border": "#B07AA1", "highlight": {"background": "#ffffff", "border": "#B07AA1"}}, "size": 10.0, "font": {"size": 0, "color": "#ffffff"}, "title": "__init__.py", "community": 46, "community_name": "Community 46", "source_file": "__init__.py", "file_type": "code", "degree": 0}, {"id": "main_py", "label": "main.py", "color": {"background": "#BAB0AC", "border": "#BAB0AC", "highlight": {"background": "#ffffff", "border": "#BAB0AC"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "main.py", "community": 9, "community_name": "Community 9", "source_file": "main.py", "file_type": "code", "degree": 4}, {"id": "app_main_lifespan", "label": "lifespan()", "color": {"background": "#BAB0AC", "border": "#BAB0AC", "highlight": {"background": "#ffffff", "border": "#BAB0AC"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "lifespan()", "community": 9, "community_name": "Community 9", "source_file": "main.py", "file_type": "code", "degree": 4}, {"id": "app_main_mask_secret", "label": "_mask_secret()", "color": {"background": "#BAB0AC", "border": "#BAB0AC", "highlight": {"background": "#ffffff", "border": "#BAB0AC"}}, "size": 10.1, "font": {"size": 0, "color": "#ffffff"}, "title": "_mask_secret()", "community": 9, "community_name": "Community 9", "source_file": "main.py", "file_type": "code", "degree": 1}, {"id": "app_main_root", "label": "root()", "color": {"background": "#BAB0AC", "border": "#BAB0AC", "highlight": {"background": "#ffffff", "border": "#BAB0AC"}}, "size": 10.1, "font": {"size": 0, "color": "#ffffff"}, "title": "root()", "community": 9, "community_name": "Community 9", "source_file": "main.py", "file_type": "code", "degree": 1}, {"id": "app_main_health_check", "label": "health_check()", "color": {"background": "#BAB0AC", "border": "#BAB0AC", "highlight": {"background": "#ffffff", "border": "#BAB0AC"}}, "size": 10.1, "font": {"size": 0, "color": "#ffffff"}, "title": "health_check()", "community": 9, "community_name": "Community 9", "source_file": "main.py", "file_type": "code", "degree": 1}, {"id": "tasks_init_py", "label": "__init__.py", "color": {"background": "#BAB0AC", "border": "#BAB0AC", "highlight": {"background": "#ffffff", "border": "#BAB0AC"}}, "size": 10.2, "font": {"size": 0, "color": "#ffffff"}, "title": "__init__.py", "community": 9, "community_name": "Community 9", "source_file": "tasks/__init__.py", "file_type": "code", "degree": 2}, {"id": "tasks_init_init_scheduled_tasks", "label": "init_scheduled_tasks()", "color": {"background": "#BAB0AC", "border": "#BAB0AC", "highlight": {"background": "#ffffff", "border": "#BAB0AC"}}, "size": 10.2, "font": {"size": 0, "color": "#ffffff"}, "title": "init_scheduled_tasks()", "community": 9, "community_name": "Community 9", "source_file": "tasks/__init__.py", "file_type": "code", "degree": 2}, {"id": "tasks_init_shutdown_scheduler", "label": "shutdown_scheduler()", "color": {"background": "#BAB0AC", "border": "#BAB0AC", "highlight": {"background": "#ffffff", "border": "#BAB0AC"}}, "size": 10.2, "font": {"size": 0, "color": "#ffffff"}, "title": "shutdown_scheduler()", "community": 9, "community_name": "Community 9", "source_file": "tasks/__init__.py", "file_type": "code", "degree": 2}, {"id": "core_auth_py", "label": "auth.py", "color": {"background": "#BAB0AC", "border": "#BAB0AC", "highlight": {"background": "#ffffff", "border": "#BAB0AC"}}, "size": 11.4, "font": {"size": 0, "color": "#ffffff"}, "title": "auth.py", "community": 9, "community_name": "Community 9", "source_file": "core/auth.py", "file_type": "code", "degree": 18}, {"id": "core_auth_redis_session_enabled", "label": "_redis_session_enabled()", "color": {"background": "#BAB0AC", "border": "#BAB0AC", "highlight": {"background": "#ffffff", "border": "#BAB0AC"}}, "size": 10.2, "font": {"size": 0, "color": "#ffffff"}, "title": "_redis_session_enabled()", "community": 9, "community_name": "Community 9", "source_file": "core/auth.py", "file_type": "code", "degree": 2}, {"id": "core_auth_get_redis_client", "label": "_get_redis_client()", "color": {"background": "#BAB0AC", "border": "#BAB0AC", "highlight": {"background": "#ffffff", "border": "#BAB0AC"}}, "size": 10.7, "font": {"size": 0, "color": "#ffffff"}, "title": "_get_redis_client()", "community": 9, "community_name": "Community 9", "source_file": "core/auth.py", "file_type": "code", "degree": 9}, {"id": "core_auth_session_key", "label": "_session_key()", "color": {"background": "#BAB0AC", "border": "#BAB0AC", "highlight": {"background": "#ffffff", "border": "#BAB0AC"}}, "size": 10.4, "font": {"size": 0, "color": "#ffffff"}, "title": "_session_key()", "community": 9, "community_name": "Community 9", "source_file": "core/auth.py", "file_type": "code", "degree": 5}, {"id": "core_auth_refresh_key", "label": "_refresh_key()", "color": {"background": "#BAB0AC", "border": "#BAB0AC", "highlight": {"background": "#ffffff", "boLine truncated
const RAW_EDGES = [{"from": "main_py", "to": "app_main_lifespan", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "main_py", "to": "app_main_mask_secret", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "main_py", "to": "app_main_root", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "main_py", "to": "app_main_health_check", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_main_lifespan", "to": "core_login_crypto_ensure_initialized", "label": "calls", "title": "calls [INFERRED]", "dashes": true, "width": 1, "color": {"opacity": 0.35}, "confidence": "INFERRED"}, {"from": "app_main_lifespan", "to": "tasks_init_init_scheduled_tasks", "label": "calls", "title": "calls [INFERRED]", "dashes": true, "width": 1, "color": {"opacity": 0.35}, "confidence": "INFERRED"}, {"from": "app_main_lifespan", "to": "tasks_init_shutdown_scheduler", "label": "calls", "title": "calls [INFERRED]", "dashes": true, "width": 1, "color": {"opacity": 0.35}, "confidence": "INFERRED"}, {"from": "tasks_init_py", "to": "tasks_init_init_scheduled_tasks", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "tasks_init_py", "to": "tasks_init_shutdown_scheduler", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_redis_session_enabled", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_get_redis_client", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_session_key", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_refresh_key", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_store_session", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_revoke_session", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_extend_session", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_is_session_valid", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_store_refresh_token", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_is_refresh_token_valid", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_revoke_refresh_token", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_create_access_token", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_create_refresh_token", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_decode_token", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_get_current_user_by_token", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_get_current_user", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "Line truncated
const LEGEND = [{"cid": 0, "color": "#4E79A7", "label": "Community 0", "count": 439}, {"cid": 1, "color": "#F28E2B", "label": "Community 1", "count": 393}, {"cid": 2, "color": "#E15759", "label": "Community 2", "count": 202}, {"cid": 3, "color": "#76B7B2", "label": "Community 3", "count": 148}, {"cid": 4, "color": "#59A14F", "label": "Community 4", "count": 136}, {"cid": 5, "color": "#EDC948", "label": "Community 5", "count": 128}, {"cid": 6, "color": "#B07AA1", "label": "Community 6", "count": 92}, {"cid": 7, "color": "#FF9DA7", "label": "Community 7", "count": 90}, {"cid": 8, "color": "#9C755F", "label": "Community 8", "count": 76}, {"cid": 9, "color": "#BAB0AC", "label": "Community 9", "count": 72}, {"cid": 10, "color": "#4E79A7", "label": "Community 10", "count": 71}, {"cid": 11, "color": "#F28E2B", "label": "Community 11", "count": 68}, {"cid": 12, "color": "#E15759", "label": "Community 12", "count": 53}, {"cid": 13, "color": "#76B7B2", "label": "Community 13", "count": 49}, {"cid": 14, "color": "#59A14F", "label": "Community 14", "count": 36}, {"cid": 15, "color": "#EDC948", "label": "Community 15", "count": 31}, {"cid": 16, "color": "#B07AA1", "label": "Community 16", "count": 20}, {"cid": 17, "color": "#FF9DA7", "label": "Community 17", "count": 15}, {"cid": 18, "color": "#9C755F", "label": "Community 18", "count": 14}, {"cid": 19, "color": "#BAB0AC", "label": "Community 19", "count": 13}, {"cid": 20, "color": "#4E79A7", "label": "Community 20", "count": 12}, {"cid": 21, "color": "#F28E2B", "label": "Community 21", "count": 11}, {"cid": 22, "color": "#E15759", "label": "Community 22", "count": 10}, {"cid": 23, "color": "#76B7B2", "label": "Community 23", "count": 10}, {"cid": 24, "color": "#59A14F", "label": "Community 24", "count": 10}, {"cid": 25, "color": "#EDC948", "label": "Community 25", "count": 10}, {"cid": 26, "color": "#B07AA1", "label": "Community 26", "count": 10}, {"cid": 27, "color": "#FF9DA7", "label": "Community 27", "count": 10}, {"cid": 28, "color": "#9C755F", "label": "Community 28", "count": 10}, {"cid": 29, "color": "#BAB0AC", "label": "Community 29", "count": 10}, {"cid": 30, "color": "#4E79A7", "label": "Community 30", "count": 10}, {"cid": 31, "color": "#F28E2B", "label": "Community 31", "count": 10}, {"cid": 32, "color": "#E15759", "label": "Community 32", "count": 10}, {"cid": 33, "color": "#76B7B2", "label": "Community 33", "count": 9}, {"cid": 34, "color": "#59A14F", "label": "Community 34", "count": 9}, {"cid": 35, "color": "#EDC948", "label": "Community 35", "count": 9}, {"cid": 36, "color": "#B07AA1", "label": "Community 36", "count": 9}, {"cid": 37, "color": "#FF9DA7", "label": "Community 37", "count": 9}, {"cid": 38, "color": "#9C755F", "label": "Community 38", "count": 9}, {"cid": 39, "color": "#BAB0AC", "label": "Community 39", "count": 8}, {"cid": 40, "color": "#4E79A7", "label": "Community 40", "count": 7}, {"cid": 41, "color": "#F28E2B", "label": "Community 41", "count": 4}, {"cid": 42, "color": "#E15759", "label": "Community 42", "count": 4}, {"cid": 43, "color": "#76B7B2", "label": "Community 43", "count": 3}, {"cid": 44, "color": "#59A14F", "label": "Community 44", "count": 2}, {"cid": 45, "color": "#EDC948", "label": "Community 45", "count": 2}, {"cid": 46, "color": "#B07AA1", "label": "Community 46", "count": 1}, {"cid": 47, "color": "#FF9DA7", "label": "Community 47", "count": 1}, {"cid": 48, "color": "#9C755F", "label": "Community 48", "count": 1}, {"cid": 49, "color": "#BAB0AC", "label": "Community 49", "count": 1}, {"cid": 50, "color": "#4E79A7", "label": "Community 50", "count": 1}, {"cid": 51, "color": "#F28E2B", "label": "Community 51", "count": 1}, {"cid": 52, "color": "#E15759", "label": "Community 52", "count": 1}, {"cid": 53, "color": "#76B7B2", "label": "Community 53", "count": 1}, {"cid": 54, "color": "#59A14F", "label": "Community 54", "count": 1}, {"cid": 55, "color": "#EDC948", "label": "Community 55", "count": 1}, {"cid": 56, "color": "#B07AA1", "label": "Community 56", "count": 1}, {"cid": 57, "color": "#FF9DA7", "label": "Community 57", "count": 1}, {"cid": 58, "color": "#9C755F", "label": "Community 58", "count": 0}];
const RAW_NODES = [{"id": "init_py", "label": "__init__.py", "color": {"background": "#B07AA1", "border": "#B07AA1", "highlight": {"background": "#ffffff", "border": "#B07AA1"}}, "size": 10.0, "font": {"size": 0, "color": "#ffffff"}, "title": "__init__.py", "community": 46, "community_name": "Community 46", "source_file": "__init__.py", "file_type": "code", "degree": 0}, {"id": "main_py", "label": "main.py", "color": {"background": "#9C755F", "border": "#9C755F", "highlight": {"background": "#ffffff", "border": "#9C755F"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "main.py", "community": 8, "community_name": "Community 8", "source_file": "main.py", "file_type": "code", "degree": 4}, {"id": "app_main_lifespan", "label": "lifespan()", "color": {"background": "#9C755F", "border": "#9C755F", "highlight": {"background": "#ffffff", "border": "#9C755F"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "lifespan()", "community": 8, "community_name": "Community 8", "source_file": "main.py", "file_type": "code", "degree": 4}, {"id": "app_main_mask_secret", "label": "_mask_secret()", "color": {"background": "#9C755F", "border": "#9C755F", "highlight": {"background": "#ffffff", "border": "#9C755F"}}, "size": 10.1, "font": {"size": 0, "color": "#ffffff"}, "title": "_mask_secret()", "community": 8, "community_name": "Community 8", "source_file": "main.py", "file_type": "code", "degree": 1}, {"id": "app_main_root", "label": "root()", "color": {"background": "#9C755F", "border": "#9C755F", "highlight": {"background": "#ffffff", "border": "#9C755F"}}, "size": 10.1, "font": {"size": 0, "color": "#ffffff"}, "title": "root()", "community": 8, "community_name": "Community 8", "source_file": "main.py", "file_type": "code", "degree": 1}, {"id": "app_main_health_check", "label": "health_check()", "color": {"background": "#9C755F", "border": "#9C755F", "highlight": {"background": "#ffffff", "border": "#9C755F"}}, "size": 10.1, "font": {"size": 0, "color": "#ffffff"}, "title": "health_check()", "community": 8, "community_name": "Community 8", "source_file": "main.py", "file_type": "code", "degree": 1}, {"id": "tasks_init_py", "label": "__init__.py", "color": {"background": "#9C755F", "border": "#9C755F", "highlight": {"background": "#ffffff", "border": "#9C755F"}}, "size": 10.2, "font": {"size": 0, "color": "#ffffff"}, "title": "__init__.py", "community": 8, "community_name": "Community 8", "source_file": "tasks/__init__.py", "file_type": "code", "degree": 2}, {"id": "tasks_init_init_scheduled_tasks", "label": "init_scheduled_tasks()", "color": {"background": "#9C755F", "border": "#9C755F", "highlight": {"background": "#ffffff", "border": "#9C755F"}}, "size": 10.2, "font": {"size": 0, "color": "#ffffff"}, "title": "init_scheduled_tasks()", "community": 8, "community_name": "Community 8", "source_file": "tasks/__init__.py", "file_type": "code", "degree": 2}, {"id": "tasks_init_shutdown_scheduler", "label": "shutdown_scheduler()", "color": {"background": "#9C755F", "border": "#9C755F", "highlight": {"background": "#ffffff", "border": "#9C755F"}}, "size": 10.2, "font": {"size": 0, "color": "#ffffff"}, "title": "shutdown_scheduler()", "community": 8, "community_name": "Community 8", "source_file": "tasks/__init__.py", "file_type": "code", "degree": 2}, {"id": "core_auth_py", "label": "auth.py", "color": {"background": "#9C755F", "border": "#9C755F", "highlight": {"background": "#ffffff", "border": "#9C755F"}}, "size": 11.4, "font": {"size": 0, "color": "#ffffff"}, "title": "auth.py", "community": 8, "community_name": "Community 8", "source_file": "core/auth.py", "file_type": "code", "degree": 18}, {"id": "core_auth_redis_session_enabled", "label": "_redis_session_enabled()", "color": {"background": "#9C755F", "border": "#9C755F", "highlight": {"background": "#ffffff", "border": "#9C755F"}}, "size": 10.2, "font": {"size": 0, "color": "#ffffff"}, "title": "_redis_session_enabled()", "community": 8, "community_name": "Community 8", "source_file": "core/auth.py", "file_type": "code", "degree": 2}, {"id": "core_auth_get_redis_client", "label": "_get_redis_client()", "color": {"background": "#9C755F", "border": "#9C755F", "highlight": {"background": "#ffffff", "border": "#9C755F"}}, "size": 10.7, "font": {"size": 0, "color": "#ffffff"}, "title": "_get_redis_client()", "community": 8, "community_name": "Community 8", "source_file": "core/auth.py", "file_type": "code", "degree": 9}, {"id": "core_auth_session_key", "label": "_session_key()", "color": {"background": "#9C755F", "border": "#9C755F", "highlight": {"background": "#ffffff", "border": "#9C755F"}}, "size": 10.4, "font": {"size": 0, "color": "#ffffff"}, "title": "_session_key()", "community": 8, "community_name": "Community 8", "source_file": "core/auth.py", "file_type": "code", "degree": 5}, {"id": "core_auth_refresh_key", "label": "_refresh_key()", "color": {"background": "#9C755F", "border": "#9C755F", "highlight": {"background": "#ffffff", "boLine truncated
const RAW_EDGES = [{"from": "main_py", "to": "app_main_lifespan", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "main_py", "to": "app_main_mask_secret", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "main_py", "to": "app_main_root", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "main_py", "to": "app_main_health_check", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_main_lifespan", "to": "core_login_crypto_ensure_initialized", "label": "calls", "title": "calls [INFERRED]", "dashes": true, "width": 1, "color": {"opacity": 0.35}, "confidence": "INFERRED"}, {"from": "app_main_lifespan", "to": "tasks_init_init_scheduled_tasks", "label": "calls", "title": "calls [INFERRED]", "dashes": true, "width": 1, "color": {"opacity": 0.35}, "confidence": "INFERRED"}, {"from": "app_main_lifespan", "to": "tasks_init_shutdown_scheduler", "label": "calls", "title": "calls [INFERRED]", "dashes": true, "width": 1, "color": {"opacity": 0.35}, "confidence": "INFERRED"}, {"from": "tasks_init_py", "to": "tasks_init_init_scheduled_tasks", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "tasks_init_py", "to": "tasks_init_shutdown_scheduler", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_redis_session_enabled", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_get_redis_client", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_session_key", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_refresh_key", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_store_session", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_revoke_session", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_extend_session", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_is_session_valid", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_store_refresh_token", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_is_refresh_token_valid", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_revoke_refresh_token", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_create_access_token", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_create_refresh_token", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_decode_token", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_get_current_user_by_token", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "to": "core_auth_get_current_user", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_py", "Line truncated
const LEGEND = [{"cid": 0, "color": "#4E79A7", "label": "Community 0", "count": 444}, {"cid": 1, "color": "#F28E2B", "label": "Community 1", "count": 418}, {"cid": 2, "color": "#E15759", "label": "Community 2", "count": 218}, {"cid": 3, "color": "#76B7B2", "label": "Community 3", "count": 169}, {"cid": 4, "color": "#59A14F", "label": "Community 4", "count": 134}, {"cid": 5, "color": "#EDC948", "label": "Community 5", "count": 129}, {"cid": 6, "color": "#B07AA1", "label": "Community 6", "count": 105}, {"cid": 7, "color": "#FF9DA7", "label": "Community 7", "count": 100}, {"cid": 8, "color": "#9C755F", "label": "Community 8", "count": 97}, {"cid": 9, "color": "#BAB0AC", "label": "Community 9", "count": 72}, {"cid": 10, "color": "#4E79A7", "label": "Community 10", "count": 60}, {"cid": 11, "color": "#F28E2B", "label": "Community 11", "count": 46}, {"cid": 12, "color": "#E15759", "label": "Community 12", "count": 31}, {"cid": 13, "color": "#76B7B2", "label": "Community 13", "count": 21}, {"cid": 14, "color": "#59A14F", "label": "Community 14", "count": 21}, {"cid": 15, "color": "#EDC948", "label": "Community 15", "count": 20}, {"cid": 16, "color": "#B07AA1", "label": "Community 16", "count": 15}, {"cid": 17, "color": "#FF9DA7", "label": "Community 17", "count": 14}, {"cid": 18, "color": "#9C755F", "label": "Community 18", "count": 13}, {"cid": 19, "color": "#BAB0AC", "label": "Community 19", "count": 13}, {"cid": 20, "color": "#4E79A7", "label": "Community 20", "count": 13}, {"cid": 21, "color": "#F28E2B", "label": "Community 21", "count": 13}, {"cid": 22, "color": "#E15759", "label": "Community 22", "count": 13}, {"cid": 23, "color": "#76B7B2", "label": "Community 23", "count": 12}, {"cid": 24, "color": "#59A14F", "label": "Community 24", "count": 11}, {"cid": 25, "color": "#EDC948", "label": "Community 25", "count": 10}, {"cid": 26, "color": "#B07AA1", "label": "Community 26", "count": 10}, {"cid": 27, "color": "#FF9DA7", "label": "Community 27", "count": 10}, {"cid": 28, "color": "#9C755F", "label": "Community 28", "count": 10}, {"cid": 29, "color": "#BAB0AC", "label": "Community 29", "count": 10}, {"cid": 30, "color": "#4E79A7", "label": "Community 30", "count": 10}, {"cid": 31, "color": "#F28E2B", "label": "Community 31", "count": 10}, {"cid": 32, "color": "#E15759", "label": "Community 32", "count": 10}, {"cid": 33, "color": "#76B7B2", "label": "Community 33", "count": 10}, {"cid": 34, "color": "#59A14F", "label": "Community 34", "count": 10}, {"cid": 35, "color": "#EDC948", "label": "Community 35", "count": 9}, {"cid": 36, "color": "#B07AA1", "label": "Community 36", "count": 9}, {"cid": 37, "color": "#FF9DA7", "label": "Community 37", "count": 9}, {"cid": 38, "color": "#9C755F", "label": "Community 38", "count": 9}, {"cid": 39, "color": "#BAB0AC", "label": "Community 39", "count": 8}, {"cid": 40, "color": "#4E79A7", "label": "Community 40", "count": 7}, {"cid": 41, "color": "#F28E2B", "label": "Community 41", "count": 4}, {"cid": 42, "color": "#E15759", "label": "Community 42", "count": 4}, {"cid": 43, "color": "#76B7B2", "label": "Community 43", "count": 3}, {"cid": 44, "color": "#59A14F", "label": "Community 44", "count": 2}, {"cid": 45, "color": "#EDC948", "label": "Community 45", "count": 2}, {"cid": 46, "color": "#B07AA1", "label": "Community 46", "count": 1}, {"cid": 47, "color": "#FF9DA7", "label": "Community 47", "count": 1}, {"cid": 48, "color": "#9C755F", "label": "Community 48", "count": 1}, {"cid": 49, "color": "#BAB0AC", "label": "Community 49", "count": 1}, {"cid": 50, "color": "#4E79A7", "label": "Community 50", "count": 1}, {"cid": 51, "color": "#F28E2B", "label": "Community 51", "count": 1}, {"cid": 52, "color": "#E15759", "label": "Community 52", "count": 1}, {"cid": 53, "color": "#76B7B2", "label": "Community 53", "count": 1}, {"cid": 54, "color": "#59A14F", "label": "Community 54", "count": 1}, {"cid": 55, "color": "#EDC948", "label": "Community 55", "count": 1}, {"cid": 56, "color": "#B07AA1", "label": "Community 56", "count": 1}, {"cid": 57, "color": "#FF9DA7", "label": "Community 57", "count": 1}, {"cid": 58, "color": "#9C755F", "label": "Community 58", "count": 0}];
// HTML-escape helper — prevents XSS when injecting graph data into innerHTML
function esc(s) {
File diff suppressed because it is too large. Load diff
+4 -4
View File
@@ -25,7 +25,7 @@
"/Users/wuzilong/work/00code/zkxw/backend/app/core/wechat_message.py": 1778578258.0458734,
"/Users/wuzilong/work/00code/zkxw/backend/app/models/material.py": 1779283385.245847,
"/Users/wuzilong/work/00code/zkxw/backend/app/models/qc_param.py": 1779022399.0175831,
"/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py": 1779249191.0814734,
"/Users/wuzilong/work/00code/zkxw/backend/app/models/models.py": 1779336613.0254703,
"/Users/wuzilong/work/00code/zkxw/backend/app/models/__init__.py": 1779206668.0388827,
"/Users/wuzilong/work/00code/zkxw/backend/app/models/research_donation.py": 1779177348.5945773,
"/Users/wuzilong/work/00code/zkxw/backend/app/models/qc_material.py": 1779284411.5179992,
@@ -48,7 +48,7 @@
"/Users/wuzilong/work/00code/zkxw/backend/app/schemas/order.py": 1778578258.0960286,
"/Users/wuzilong/work/00code/zkxw/backend/app/schemas/__init__.py": 1778578258.0864499,
"/Users/wuzilong/work/00code/zkxw/backend/app/schemas/order_adjust.py": 1778578258.0963318,
"/Users/wuzilong/work/00code/zkxw/backend/app/schemas/cell_product.py": 1779206912.4108655,
"/Users/wuzilong/work/00code/zkxw/backend/app/schemas/cell_product.py": 1779336629.616835,
"/Users/wuzilong/work/00code/zkxw/backend/app/schemas/menu.py": 1778578258.0957444,
"/Users/wuzilong/work/00code/zkxw/backend/app/schemas/attachment.py": 1778763456.6244864,
"/Users/wuzilong/work/00code/zkxw/backend/app/schemas/category_parameter.py": 1778578258.0944862,
@@ -84,7 +84,7 @@
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/express_company.py": 1779265659.3453207,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/qc_lab_test_item.py": 1779265673.3888667,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/role.py": 1779265616.3701816,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_product.py": 1779265684.1870236,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/cell_product.py": 1779336727.023542,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/menu.py": 1779265729.0381103,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/collection_region.py": 1779277867.6408322,
"/Users/wuzilong/work/00code/zkxw/backend/app/crud/sample_reception_check_item.py": 1779007909.2933013,
@@ -128,7 +128,7 @@
"/Users/wuzilong/work/00code/zkxw/backend/app/api/workflow.py": 1779266232.4568832,
"/Users/wuzilong/work/00code/zkxw/backend/app/api/sample_reception.py": 1779304425.1104686,
"/Users/wuzilong/work/00code/zkxw/backend/app/api/insurance_company.py": 1779281097.279193,
"/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py": 1779265782.5075505,
"/Users/wuzilong/work/00code/zkxw/backend/app/api/production.py": 1779336678.7838151,
"/Users/wuzilong/work/00code/zkxw/backend/app/api/leave.py": 1779266168.8693042,
"/Users/wuzilong/work/00code/zkxw/backend/app/services/workflow_callback.py": 1778727966.7277644,
"/Users/wuzilong/work/00code/zkxw/backend/app/services/workflow_engine.py": 1778634664.6293883,
@@ -0,0 +1,19 @@
-- 细胞产品库字段重设计迁移
-- 日期: 2026-05-21
-- 设计文档: docs/superpowers/specs/2026-05-21-cell-product-library-design.md
-- 1. 新增 4 个业务字段(放在 storage_slot_id 之后,product_status 之前)
ALTER TABLE biz_cell_product
ADD COLUMN `passage` VARCHAR(8) DEFAULT NULL COMMENT '代次(P1~P8)' AFTER `storage_slot_id`,
ADD COLUMN `freeze_date` DATETIME DEFAULT NULL COMMENT '冻存日期' AFTER `passage`,
ADD COLUMN `expected_cell_count` DECIMAL(18,4) DEFAULT NULL COMMENT '预计细胞数量(单位:亿)' AFTER `freeze_date`,
ADD COLUMN `viability` DECIMAL(5,2) DEFAULT NULL COMMENT '活性(百分比,0~100)' AFTER `expected_cell_count`;
-- 2. 更新 product_status 默认值和注释
ALTER TABLE biz_cell_product
MODIFY COLUMN `product_status` VARCHAR(16) DEFAULT 'frozen' COMMENT '状态: frozen已冻存/sold已售卖/used已使用';
-- 3. 迁移旧状态数据(如有)
UPDATE biz_cell_product SET product_status = 'frozen' WHERE product_status = 'zk';
UPDATE biz_cell_product SET product_status = 'sold' WHERE product_status = 'yc';
UPDATE biz_cell_product SET product_status = 'used' WHERE product_status = 'ysy';
+168
View File
@@ -0,0 +1,168 @@
-- 细胞产品库初始化测试数据(20 条,覆盖各种场景)
-- 日期: 2026-05-21
-- 前提: 已执行 2026-05-21-cell-product-alter.sql 迁移
-- 场景覆盖:
-- 状态: frozen(12条) / sold(5条) / used(3条)
-- 代次: P1~P8 均有覆盖
-- 活性: 70.0 ~ 99.5,含 NULL
-- 预计细胞数量: 0.5 ~ 50.0 亿,含 NULL
-- 冻存日期: 有值 / NULL / 未来日期
-- 有效期: 已过期 / 未过期 / NULL
-- 产品图片: 有值 / NULL
-- 备注: 有值 / NULL
-- 数量/单位: 1~100 份
INSERT INTO biz_cell_product
(id, product_code, product_name, product_type_id, spec_id, batch_no, storage_slot_id,
passage, freeze_date, expected_cell_count, viability,
product_status, source_type, production_task_id,
quantity, unit, expiry_date, product_images,
tenant_no, order_num, remark, create_by, create_dept_id, create_time, update_by, update_time, del_flag)
VALUES
-- ==================== 已冻存 frozen (12条) ====================
-- 1. 典型冻存产品:高活性、P2代次、有图片有备注
('60eaac11fceb4fd6b62e58e8be2c51cc', 'CP20260501000001', '间充质干细胞-MSC-P2-001', NULL, NULL, 'B20260501-001', NULL,
'P2', '2026-05-01 10:30:00', 5.0000, 95.50,
'frozen', 'manual', NULL,
10, '份', '2027-05-01 00:00:00', 'https://minio.example.com/cell-product/msc-p2-001.jpg',
'zkxw', 1, '高活性批次,来源于健康供体', 'admin', '', '2026-05-01 10:30:00', 'admin', '2026-05-01 10:30:00', 0),
-- 2. P1初代产品:数量大、高活性
('5ea198fede454198be443211d2687ab3', 'CP20260502000001', '脐带血造血干细胞-HSC-P1-002', NULL, NULL, 'B20260502-002', NULL,
'P1', '2026-05-02 14:20:00', 10.0000, 98.00,
'frozen', 'manual', NULL,
20, '份', '2027-11-02 00:00:00', NULL,
'zkxw', 2, NULL, 'admin', '', '2026-05-02 14:20:00', 'admin', '2026-05-02 14:20:00', 0),
-- 3. P3代次、低活性、无冻存日期
('661bcbadd41c4f478b16f3b9dd577914', 'CP20260503000001', '脂肪干细胞-ADSC-P3-003', NULL, NULL, 'B20260503-003', NULL,
'P3', NULL, 3.0000, 72.00,
'frozen', 'manual', NULL,
5, '份', '2026-08-03 00:00:00', NULL,
'zkxw', 3, '活性偏低,需进一步检测', 'admin', '', '2026-05-03 09:00:00', 'admin', '2026-05-03 09:00:00', 0),
-- 4. P4、中等活性、无预计细胞数量
('dd8d414f36a244bca611a51a96581a85', 'CP20260505000001', '神经干细胞-NSC-P4-004', NULL, NULL, 'B20260505-004', NULL,
'P4', '2026-05-05 08:15:00', NULL, 88.00,
'frozen', 'manual', NULL,
8, '份', '2027-05-05 00:00:00', 'https://minio.example.com/cell-product/nsc-p4-004.jpg',
'zkxw', 4, NULL, 'admin', '', '2026-05-05 08:15:00', 'admin', '2026-05-05 08:15:00', 0),
-- 5. P5、活性为NULL(未检测)
('815d5312900140c99e453c685c6d81a6', 'CP20260506000001', '诱导多能干细胞-iPSC-P5-005', NULL, NULL, 'B20260506-005', NULL,
'P5', '2026-05-06 16:45:00', 2.5000, NULL,
'frozen', 'manual', NULL,
3, '份', '2027-02-06 00:00:00', NULL,
'zkxw', 5, '待活性检测', 'admin', '', '2026-05-06 16:45:00', 'admin', '2026-05-06 16:45:00', 0),
-- 6. P6、大数量、有效期已过期(异常数据)
('da9b6201f2a74921b475c234e0d72841', 'CP20260507000001', '软骨细胞-CC-P6-006', NULL, NULL, 'B20260507-006', NULL,
'P6', '2026-05-07 11:00:00', 1.2000, 85.00,
'frozen', 'manual', NULL,
15, '份', '2025-05-07 00:00:00', NULL,
'zkxw', 6, '有效期已过,需处理', 'admin', '', '2026-05-07 11:00:00', 'admin', '2026-05-07 11:00:00', 0),
-- 7. P7、极小数量、极高活性
('e26ac18706b84c3da114f24eb50ec7e9', 'CP20260508000001', '心肌细胞-CM-P7-007', NULL, NULL, 'B20260508-007', NULL,
'P7', '2026-05-08 09:30:00', 0.5000, 99.50,
'frozen', 'manual', NULL,
1, '份', '2027-11-08 00:00:00', NULL,
'zkxw', 7, NULL, 'admin', '', '2026-05-08 09:30:00', 'admin', '2026-05-08 09:30:00', 0),
-- 8. P8、最大代次、多张图片
('177e808ce4b8454cb074e5965ed94bb1', 'CP20260509000001', '肝细胞-HC-P8-008', NULL, NULL, 'B20260509-008', NULL,
'P8', '2026-05-09 13:00:00', 8.0000, 80.00,
'frozen', 'manual', NULL,
12, '份', '2027-05-09 00:00:00', 'https://minio.example.com/cell-product/hc-p8-008a.jpg,https://minio.example.com/cell-product/hc-p8-008b.jpg',
'zkxw', 8, '代次较高,建议尽快使用', 'admin', '', '2026-05-09 13:00:00', 'admin', '2026-05-09 13:00:00', 0),
-- 9. P1、大预计细胞数量50亿
('0fe62984ef6c4d3eafa8bcff70c6e151', 'CP20260510000001', 'NK细胞-NK-P1-009', NULL, NULL, 'B20260510-009', NULL,
'P1', '2026-05-10 07:00:00', 50.0000, 96.00,
'frozen', 'manual', NULL,
30, '份', '2027-11-10 00:00:00', 'https://minio.example.com/cell-product/nk-p1-009.jpg',
'zkxw', 9, '大批次生产', 'admin', '', '2026-05-10 07:00:00', 'admin', '2026-05-10 07:00:00', 0),
-- 10. P3、无有效期
('cb8e8ea1581348f18c119e1611fba39f', 'CP20260511000001', 'T细胞-TC-P3-010', NULL, NULL, 'B20260511-010', NULL,
'P3', '2026-05-11 15:30:00', 15.0000, 91.00,
'frozen', 'manual', NULL,
25, '份', NULL, NULL,
'zkxw', 10, NULL, 'admin', '', '2026-05-11 15:30:00', 'admin', '2026-05-11 15:30:00', 0),
-- 11. P2、活性偏低70%
('28c6b50ccd3d4cf8ae9bbbbffef96418', 'CP20260512000001', '树突状细胞-DC-P2-011', NULL, NULL, 'B20260512-011', NULL,
'P2', '2026-05-12 10:00:00', 4.0000, 70.00,
'frozen', 'manual', NULL,
6, '份', '2026-11-12 00:00:00', NULL,
'zkxw', 11, '活性偏低,需复检', 'admin', '', '2026-05-12 10:00:00', 'admin', '2026-05-12 10:00:00', 0),
-- 12. P4、未来冻存日期(计划冻存)
('940c82c006954865adb09e31fae36038', 'CP20260513000001', '表皮细胞-EC-P4-012', NULL, NULL, 'B20260513-012', NULL,
'P4', '2026-06-15 00:00:00', 6.0000, 92.50,
'frozen', 'manual', NULL,
4, '份', '2027-06-15 00:00:00', NULL,
'zkxw', 12, '计划冻存,待入库', 'admin', '', '2026-05-13 08:00:00', 'admin', '2026-05-13 08:00:00', 0),
-- ==================== 已售卖 sold (5条) ====================
-- 13. P2、已售卖、高活性
('338ae843fb4a4914a1eb672fd7a5ee38', 'CP20260420000001', '间充质干细胞-MSC-P2-S01', NULL, NULL, 'B20260420-S01', NULL,
'P2', '2026-04-20 09:00:00', 5.0000, 97.00,
'sold', 'manual', NULL,
10, '份', '2027-04-20 00:00:00', 'https://minio.example.com/cell-product/msc-p2-s01.jpg',
'zkxw', 13, '已出售给合作医院', 'admin', '', '2026-04-20 09:00:00', 'admin', '2026-05-15 14:00:00', 0),
-- 14. P1、已售卖、最大批次
('237d80589852465180706d1f7a3d3586', 'CP20260418000001', '脐带血造血干细胞-HSC-P1-S02', NULL, NULL, 'B20260418-S02', NULL,
'P1', '2026-04-18 11:30:00', 20.0000, 99.00,
'sold', 'manual', NULL,
50, '份', '2027-10-18 00:00:00', NULL,
'zkxw', 14, NULL, 'admin', '', '2026-04-18 11:30:00', 'admin', '2026-05-16 09:30:00', 0),
-- 15. P3、已售卖、中活性
('a6380097234e4c15b04349f27348a9dd', 'CP20260415000001', '脂肪干细胞-ADSC-P3-S03', NULL, NULL, 'B20260415-S03', NULL,
'P3', '2026-04-15 08:00:00', 8.0000, 88.50,
'sold', 'manual', NULL,
15, '份', '2027-04-15 00:00:00', 'https://minio.example.com/cell-product/adsc-p3-s03a.jpg,https://minio.example.com/cell-product/adsc-p3-s03b.jpg',
'zkxw', 15, '科研合作项目使用', 'admin', '', '2026-04-15 08:00:00', 'admin', '2026-05-17 10:00:00', 0),
-- 16. P5、已售卖、低活性
('853e0e67632c4291bf9d111a43a17b71', 'CP20260410000001', '神经干细胞-NSC-P5-S04', NULL, NULL, 'B20260410-S04', NULL,
'P5', '2026-04-10 14:00:00', 2.0000, 75.00,
'sold', 'manual', NULL,
5, '份', '2026-10-10 00:00:00', NULL,
'zkxw', 16, '低价出售,活性一般', 'admin', '', '2026-04-10 14:00:00', 'admin', '2026-05-18 11:00:00', 0),
-- 17. P1、已售卖、未填活性
('ec5309d6870f486695cf5b5f63fd91d1', 'CP20260405000001', 'NK细胞-NK-P1-S05', NULL, NULL, 'B20260405-S05', NULL,
'P1', '2026-04-05 10:00:00', 30.0000, NULL,
'sold', 'manual', NULL,
20, '份', '2027-10-05 00:00:00', NULL,
'zkxw', 17, NULL, 'admin', '', '2026-04-05 10:00:00', 'admin', '2026-05-19 16:00:00', 0),
-- ==================== 已使用 used (3条) ====================
-- 18. P3、已使用(用于生产分化)
('5e1f8cf0bab14e3f964d5a7aafb09d0d', 'CP20260301000001', '间充质干细胞-MSC-P3-U01', NULL, NULL, 'B20260301-U01', NULL,
'P3', '2026-03-01 08:30:00', 10.0000, 93.00,
'used', 'manual', NULL,
8, '份', '2027-03-01 00:00:00', 'https://minio.example.com/cell-product/msc-p3-u01.jpg',
'zkxw', 18, '用于生产分化为软骨细胞', 'admin', '', '2026-03-01 08:30:00', 'admin', '2026-05-10 09:00:00', 0),
-- 19. P4、已使用、来源生产任务
('292c2ec830084ba08daf151845043dba', 'CP20260215000001', '诱导多能干细胞-iPSC-P4-U02', NULL, NULL, 'B20260215-U02', NULL,
'P4', '2026-02-15 12:00:00', 12.0000, 90.00,
'used', 'production', 'task-uuid-placeholder-001',
6, '份', '2027-02-15 00:00:00', NULL,
'zkxw', 19, '生产任务产出的细胞被用于后续分化实验', 'admin', '', '2026-02-15 12:00:00', 'admin', '2026-04-20 15:00:00', 0),
-- 20. P6、已使用、高代次低活性(极限场景)
('e1cb9f8cbe6a40998686a8bb8fb76c9a', 'CP20260201000001', '肝细胞-HC-P6-U03', NULL, NULL, 'B20260201-U03', NULL,
'P6', '2026-02-01 09:00:00', 1.5000, 78.50,
'used', 'manual', NULL,
2, '份', '2026-08-01 00:00:00', 'https://minio.example.com/cell-product/hc-p6-u03.jpg,https://minio.example.com/cell-product/hc-p6-u03-report.pdf',
'zkxw', 20, '高代次细胞,已用于药物毒性测试', 'admin', '', '2026-02-01 09:00:00', 'admin', '2026-03-15 10:00:00', 0);
@@ -0,0 +1,913 @@
# 细胞产品库字段重设计 实施计划
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 为细胞产品库新增代次、冻存日期、预计细胞数量、活性字段,重新定义状态枚举,实现产品编码自动生成,产品图片改为上传方式,新增查看弹窗。
**Architecture:** 后端在现有 Model/Schema/CRUD/API 层增量修改;前端全面重写 cell-product/index.vue 页面(表格列、搜索栏、新增/编辑弹窗、查看弹窗)。编码生成采用实时自增模式(参考 `_generate_work_order_no`)。
**Tech Stack:** Python 3.9 + FastAPI + SQLAlchemy + Pydantic v2(后端);Vue 3 + TypeScript + Element Plus + Tailwind CSS 4(前端)
**Design Spec:** `docs/superpowers/specs/2026-05-21-cell-product-library-design.md`
---
## File Structure
| 操作 | 文件路径 | 职责 |
|------|---------|------|
| 新增 | `docs/sql/2026-05-21-cell-product-alter.sql` | 数据库迁移:新增 4 字段 + 更新状态默认值 |
| 修改 | `backend/app/models/models.py` (BizCellProduct 类, L2122-2149) | 新增 4 个 Column,更新 product_status 默认值 |
| 修改 | `backend/app/schemas/cell_product.py` | Create/Update schema 新增字段 + 状态校验 |
| 修改 | `backend/app/crud/cell_product.py` | _row_to_dict 新增映射 + create/update 新增字段 + 新增 get_next_code |
| 修改 | `backend/app/api/production.py` (L1550-1632) | 新增 next-code 端点 + update 端点增加状态校验 |
| 修改 | `web-admin/src/api/cell-product.ts` | 新增 fetchNextCellProductCode 接口 |
| 重写 | `web-admin/src/views/production/cell-product/index.vue` | 表格列 + 搜索 + 弹窗 + 查看弹窗 全面重写 |
---
### Task 1: 数据库迁移 SQL
**Files:**
- Create: `docs/sql/2026-05-21-cell-product-alter.sql`
- [ ] **Step 1: 编写 ALTER TABLE SQL**
```sql
-- 细胞产品库字段重设计迁移
-- 日期: 2026-05-21
-- 1. 新增 4 个业务字段(放在 storage_slot_id 之后,product_status 之前)
ALTER TABLE biz_cell_product
ADD COLUMN `passage` VARCHAR(8) DEFAULT NULL COMMENT '代次(P1~P8)' AFTER `storage_slot_id`,
ADD COLUMN `freeze_date` DATETIME DEFAULT NULL COMMENT '冻存日期' AFTER `passage`,
ADD COLUMN `expected_cell_count` DECIMAL(18,4) DEFAULT NULL COMMENT '预计细胞数量(单位:亿)' AFTER `freeze_date`,
ADD COLUMN `viability` DECIMAL(5,2) DEFAULT NULL COMMENT '活性(百分比,0~100)' AFTER `expected_cell_count`;
-- 2. 更新 product_status 默认值和注释
ALTER TABLE biz_cell_product
MODIFY COLUMN `product_status` VARCHAR(16) DEFAULT 'frozen' COMMENT '状态: frozen已冻存/sold已售卖/used已使用';
-- 3. 迁移旧状态数据(如有)
UPDATE biz_cell_product SET product_status = 'frozen' WHERE product_status = 'zk';
UPDATE biz_cell_product SET product_status = 'sold' WHERE product_status = 'yc';
UPDATE biz_cell_product SET product_status = 'used' WHERE product_status = 'ysy';
```
- [ ] **Step 2: 在数据库执行 SQL(由 DBA 或手动执行)**
- [ ] **Step 3: 提交**
```bash
git add docs/sql/2026-05-21-cell-product-alter.sql
git commit -m "feat(cell-product): add migration SQL for new fields and status enum"
```
---
### Task 2: 后端 Model 更新
**Files:**
- Modify: `backend/app/models/models.py` (BizCellProduct 类, 约第 2122-2149 行)
- [ ] **Step 1: 在 BizCellProduct 类中新增 4 个 Column,更新 product_status 默认值**
在 `storage_slot_id` 行之后、`product_status` 行之前插入新字段,同时更新 `product_status` 默认值:
```python
# 在 storage_slot_id 行之后新增:
passage = Column(String(8), nullable=True, comment="代次(P1~P8)")
freeze_date = Column(DateTime, nullable=True, comment="冻存日期")
expected_cell_count = Column(Numeric(18, 4), nullable=True, comment="预计细胞数量(单位:亿)")
viability = Column(Numeric(5, 2), nullable=True, comment="活性(百分比,0~100)")
# 修改 product_status 行:
product_status = Column(String(16), nullable=True, default="frozen", comment="产品状态 frozen已冻存/sold已售卖/used已使用")
```
- [ ] **Step 2: 验证无语法错误**
Run: `cd backend && source venv/bin/activate && python -c "from app.models.models import BizCellProduct; print('OK')"`
Expected: `OK`
- [ ] **Step 3: 提交**
```bash
git add backend/app/models/models.py
git commit -m "feat(cell-product): add passage, freeze_date, expected_cell_count, viability columns to model"
```
---
### Task 3: 后端 Schema 更新
**Files:**
- Modify: `backend/app/schemas/cell_product.py`
- [ ] **Step 1: 更新 CellProductCreate、CellProductUpdate、CellProductListQuery**
完整替换 `backend/app/schemas/cell_product.py`:
```python
from pydantic import BaseModel, field_validator
from typing import Optional
from datetime import datetime
class CellProductCreate(BaseModel):
product_code: str
product_name: str
product_type_id: Optional[str] = None
spec_id: Optional[str] = None
batch_no: Optional[str] = None
storage_slot_id: Optional[str] = None
passage: Optional[str] = None
freeze_date: Optional[datetime] = None
expected_cell_count: Optional[float] = None
viability: Optional[float] = None
product_status: Optional[str] = 'frozen'
source_type: Optional[str] = 'manual'
quantity: Optional[float] = None
unit: Optional[str] = None
expiry_date: Optional[datetime] = None
product_images: Optional[str] = None
remark: Optional[str] = None
@field_validator('viability')
@classmethod
def validate_viability(cls, v):
if v is not None and (v < 0 or v > 100):
raise ValueError('活性必须在 0~100 之间')
return v
@field_validator('passage')
@classmethod
def validate_passage(cls, v):
if v is not None:
valid = {f'P{i}' for i in range(1, 9)}
if v.upper() not in valid:
raise ValueError('代次必须为 P1~P8')
return v.upper()
return v
class CellProductUpdate(BaseModel):
product_name: Optional[str] = None
product_type_id: Optional[str] = None
spec_id: Optional[str] = None
batch_no: Optional[str] = None
storage_slot_id: Optional[str] = None
passage: Optional[str] = None
freeze_date: Optional[datetime] = None
expected_cell_count: Optional[float] = None
viability: Optional[float] = None
product_status: Optional[str] = None
quantity: Optional[float] = None
unit: Optional[str] = None
expiry_date: Optional[datetime] = None
product_images: Optional[str] = None
remark: Optional[str] = None
@field_validator('viability')
@classmethod
def validate_viability(cls, v):
if v is not None and (v < 0 or v > 100):
raise ValueError('活性必须在 0~100 之间')
return v
@field_validator('passage')
@classmethod
def validate_passage(cls, v):
if v is not None:
valid = {f'P{i}' for i in range(1, 9)}
if v.upper() not in valid:
raise ValueError('代次必须为 P1~P8')
return v.upper()
return v
@field_validator('product_status')
@classmethod
def validate_status(cls, v):
if v is not None and v not in ('frozen', 'sold', 'used'):
raise ValueError('状态必须为 frozen/sold/used')
return v
class CellProductListQuery(BaseModel):
page: int = 1
page_size: int = 20
product_code: Optional[str] = None
product_name: Optional[str] = None
product_type_id: Optional[str] = None
product_status: Optional[str] = None
passage: Optional[str] = None
```
- [ ] **Step 2: 验证无语法错误**
Run: `cd backend && source venv/bin/activate && python -c "from app.schemas.cell_product import CellProductCreate, CellProductUpdate; print('OK')"`
Expected: `OK`
- [ ] **Step 3: 提交**
```bash
git add backend/app/schemas/cell_product.py
git commit -m "feat(cell-product): update schemas with new fields, status validation, and passage validator"
```
---
### Task 4: 后端 CRUD 更新
**Files:**
- Modify: `backend/app/crud/cell_product.py`
- [ ] **Step 1: 更新 `_row_to_dict` 新增 4 个字段映射**
在 `_row_to_dict` 函数中,在 `"storageSlotId"` 之后新增:
```python
"passage": c.passage or "",
"freezeDate": c.freeze_date.strftime("%Y-%m-%d %H:%M:%S") if c.freeze_date else "",
"expectedCellCount": float(c.expected_cell_count) if c.expected_cell_count is not None else None,
"viability": float(c.viability) if c.viability is not None else None,
```
- [ ] **Step 2: 更新 `create_cell_product` 新增字段写入**
在 `create_cell_product` 函数的 `BizCellProduct(...)` 构造中,在 `storage_slot_id=` 之后新增:
```python
passage=(data.passage or "").strip() or None if hasattr(data, "passage") and data.passage else None,
freeze_date=data.freeze_date if hasattr(data, "freeze_date") else None,
expected_cell_count=data.expected_cell_count if hasattr(data, "expected_cell_count") else None,
viability=data.viability if hasattr(data, "viability") else None,
```
同时更新 `product_status` 默认值:
```python
product_status=data.product_status or "frozen",
```
- [ ] **Step 3: 更新 `update_cell_product` 新增字段处理 + 状态校验**
在 `update_cell_product` 函数中,在 `storage_slot_id` 处理之后新增:
```python
if hasattr(data, "passage") and data.passage is not None:
obj.passage = (data.passage or "").strip() or None
if hasattr(data, "freeze_date") and data.freeze_date is not None:
obj.freeze_date = data.freeze_date
if hasattr(data, "expected_cell_count") and data.expected_cell_count is not None:
obj.expected_cell_count = data.expected_cell_count
if hasattr(data, "viability") and data.viability is not None:
obj.viability = data.viability
```
在 `product_status` 处理处增加状态流转校验:
```python
if data.product_status is not None and data.product_status != obj.product_status:
# 状态流转校验:只允许 frozen → sold/used,不可回退,不可互转
allowed = {"frozen": {"sold", "used"}}
from_status = obj.product_status
to_status = data.product_status
if from_status not in allowed or to_status not in allowed[from_status]:
raise ValueError(f"不允许从状态 {from_status} 变更为 {to_status}")
obj.product_status = data.product_status
```
- [ ] **Step 4: 新增 `get_next_code` 方法**
在文件末尾 `total_pages` 函数之前新增:
```python
def get_next_code(db: Session, tenant_no: str) -> str:
"""生成下一个产品编码: CP + YYYYMMDD + 6位序列号"""
today_str = datetime.now().strftime("%Y%m%d")
prefix = f"CP{today_str}"
last = (
db.query(BizCellProduct)
.filter(
BizCellProduct.tenant_no == tenant_no,
BizCellProduct.product_code.like(f"{prefix}%"),
BizCellProduct.del_flag == 0,
)
.order_by(BizCellProduct.product_code.desc())
.first()
)
seq = 1
if last and last.product_code:
try:
seq = int(last.product_code[-6:]) + 1
except (ValueError, IndexError):
seq = 1
return f"{prefix}{seq:06d}"
```
- [ ] **Step 5: 更新 `list_cell_products` 支持 passage 筛选**
在 `list_cell_products` 函数签名中新增 `passage` 参数,在筛选逻辑中新增:
```python
passage: Optional[str] = None,
```
在 `product_status` 筛选之后新增:
```python
if passage:
q0 = q0.filter(BizCellProduct.passage == passage)
```
- [ ] **Step 6: 验证无语法错误**
Run: `cd backend && source venv/bin/activate && python -c "from app.crud.cell_product import get_next_code, list_cell_products; print('OK')"`
Expected: `OK`
- [ ] **Step 7: 提交**
```bash
git add backend/app/crud/cell_product.py
git commit -m "feat(cell-product): update CRUD with new fields, status validation, next-code generation"
```
---
### Task 5: 后端 API 更新
**Files:**
- Modify: `backend/app/api/production.py` (约第 1550-1632 行)
- [ ] **Step 1: 新增 `GET /cell-products/next-code` 端点**
在 `list_cell_products` 端点(`@router.get("/cell-products")`)**之前**新增:
```python
@router.get("/cell-products/next-code")
async def get_next_cell_product_code(
user: dict = Depends(require_admin_or_super_admin),
db: Session = Depends(get_db),
):
tenant_no = _get_tenant_no(db, user)
code = cell_product_svc.get_next_code(db, tenant_no)
return success_response({"code": code})
```
**重要**: 此端点必须放在 `/cell-products` 之前(FastAPI 路由匹配顺序),否则 `{product_id}` 会匹配到 `next-code`。
- [ ] **Step 2: 更新 `list_cell_products` 端点新增 passage 参数**
在端点签名中新增:
```python
passage: Optional[str] = None,
```
在调用 `cell_product_svc.list_cell_products` 时新增参数:
```python
passage=passage,
```
- [ ] **Step 3: 验证 API 启动无报错**
Run: `cd backend && source venv/bin/activate && python -c "from app.api.production import router; print('OK')"`
Expected: `OK`
- [ ] **Step 4: 提交**
```bash
git add backend/app/api/production.py
git commit -m "feat(cell-product): add next-code endpoint and passage filter to list API"
```
---
### Task 6: 前端 API 层更新
**Files:**
- Modify: `web-admin/src/api/cell-product.ts`
- [ ] **Step 1: 新增 `fetchNextCellProductCode` 接口**
在文件末尾新增:
```typescript
export function fetchNextCellProductCode() {
return request.get<{ code: string }>({ url: '/web/admin/cell-products/next-code' })
}
```
- [ ] **Step 2: 提交**
```bash
git add web-admin/src/api/cell-product.ts
git commit -m "feat(cell-product): add fetchNextCellProductCode API"
```
---
### Task 7: 前端页面全面重写
**Files:**
- Rewrite: `web-admin/src/views/production/cell-product/index.vue`
**参考模板**: `src/views/system/order/index.vue`(复杂列表模式)
**参考组件**: `src/views/cell-storage/protocol/ProtocolFormDialog.vue`(附件上传卡片样式)
**下拉数据来源**:
- 产品类型: `fetchCellProductTypeList` from `@/api/admin`
- 规格: `fetchCellProductSpecList` from `@/api/admin`
- 储物格: `fetchStorageSlotList` from `@/api/admin`
- 代次: 前端硬编码 P1~P8
- [ ] **Step 1: 重写 `<template>` 部分**
主要改动点:
1. **搜索栏**: 新增代次下拉筛选(P1~P8),状态选项改为 frozen/sold/used
2. **表格列**: 新增代次、预计细胞数量(亿)、活性(%) 三列;状态 Tag 映射更新
3. **新增/编辑弹窗**: 新增代次(下拉 P1~P8)、冻存日期(日期选择器)、预计细胞数量(数字输入)、活性(数字输入 0~100)、状态(编辑时可选);产品图片改为上传方式;产品编码新增时自动填充可编辑、编辑时只读
4. **查看弹窗**: SystemViewDialog 新增代次、冻存日期、预计细胞数量、活性字段
**搜索栏状态选项更新**:
```html
<el-option label="已冻存" value="frozen" />
<el-option label="已售卖" value="sold" />
<el-option label="已使用" value="used" />
```
**新增代次搜索筛选项**:
```html
<el-form-item label="代次">
<el-select v-model="searchForm.passage" placeholder="全部" clearable style="width: 100%">
<el-option v-for="i in 8" :key="i" :label="`P${i}`" :value="`P${i}`" />
</el-select>
</el-form-item>
```
**表格新增列**(插在批次号之后、储物格位置之后):
```html
<el-table-column prop="passage" label="代次" width="80" align="center" />
<el-table-column prop="expectedCellCount" label="预计细胞数量(亿)" min-width="140" align="center">
<template #default="{ row }">
{{ row.expectedCellCount ?? '—' }}
</template>
</el-table-column>
<el-table-column prop="viability" label="活性(%)" width="90" align="center">
<template #default="{ row }">
{{ row.viability != null ? row.viability : '—' }}
</template>
</el-table-column>
```
**状态 Tag 映射更新**:
```typescript
function productStatusTag(status: string) {
const map: Record<string, { label: string; type: string }> = {
frozen: { label: '已冻存', type: 'success' },
sold: { label: '已售卖', type: 'warning' },
used: { label: '已使用', type: 'info' },
}
return map[status] || { label: status, type: 'info' }
}
```
**表单新增字段**(在储物格之后插入新行):
```html
<el-form-item label="代次" prop="passage">
<el-select v-model="formData.passage" placeholder="请选择代次" clearable style="width: 100%">
<el-option v-for="i in 8" :key="i" :label="`P${i}`" :value="`P${i}`" />
</el-select>
</el-form-item>
<el-form-item label="预计细胞数量(亿)" prop="expectedCellCount">
<el-input-number v-model="formData.expectedCellCount" :min="0" :precision="4" controls-position="right" style="width: 100%" />
</el-form-item>
<el-form-item label="活性(%)" prop="viability">
<el-input-number v-model="formData.viability" :min="0" :max="100" :precision="1" controls-position="right" style="width: 100%" />
</el-form-item>
<el-form-item label="冻存日期" prop="freezeDate">
<el-date-picker v-model="formData.freezeDate" type="date" placeholder="请选择冻存日期" value-format="YYYY-MM-DD" style="width: 100%" />
</el-form-item>
```
**状态字段(编辑时)**:
```html
<el-form-item v-if="isEdit" label="状态" prop="productStatus">
<el-select v-model="formData.productStatus" :disabled="formData.productStatus !== 'frozen'" style="width: 100%">
<el-option label="已冻存" value="frozen" :disabled="true" />
<el-option label="已售卖" value="sold" />
<el-option label="已使用" value="used" />
</el-select>
</el-form-item>
```
**产品编码字段**(新增时可编辑,编辑时只读):
```html
<el-form-item label="产品编码" prop="productCode">
<el-input v-model="formData.productCode" placeholder="自动生成,可修改" :readonly="isEdit" />
</el-form-item>
```
**产品图片改为上传方式**(参考 ProtocolFormDialog 附件上传卡片样式):
```html
<el-form-item label="产品图片" class="form-col-span-2">
<div class="image-upload">
<div class="image-list">
<el-upload
ref="imageUploadRef"
:auto-upload="false"
:show-file-list="false"
:multiple="true"
accept="image/*"
@change="handleImageChange"
>
<div class="upload-card">
<el-icon :size="28" color="#10b981"><Plus /></el-icon>
<span class="upload-card-text">上传图片</span>
</div>
</el-upload>
<div
v-for="(img, index) in imageList"
:key="img.id || index"
class="image-item"
>
<div class="image-preview" @click="handlePreviewImage(img)">
<el-image :src="img.fileUrl" fit="cover" class="preview-img" />
</div>
<div class="image-footer">
<span class="image-name">{{ img.fileName }}</span>
<div class="image-actions">
<el-icon class="action-icon" @click="handleDeleteImage(index)"><Close /></el-icon>
</div>
</div>
</div>
</div>
</div>
</el-form-item>
```
- [ ] **Step 2: 重写 `<script setup>` 部分**
主要改动点:
1. **import 新增**: `fetchNextCellProductCode` from `@/api/cell-product`;`fetchCellProductTypeList, fetchCellProductSpecList, fetchStorageSlotList` from `@/api/admin`;`fetchUploadAttachment` from `@/api/business`
2. **formData 新增字段**: `passage`, `freezeDate`, `expectedCellCount`, `viability`, `productStatus`
3. **searchForm 新增**: `passage`
4. **下拉数据加载**: `onMounted` 中调用三个下拉接口填充 `productTypeOptions`、`specOptions`、`storageSlotOptions`
5. **产品编码自动生成**: `handleAdd` 中调用 `fetchNextCellProductCode` 填充
6. **图片上传**: 新增 `imageList` ref、`handleImageChange`、`handleDeleteImage`、`handlePreviewImage`
7. **查看弹窗 sections**: 新增代次、冻存日期、预计细胞数量、活性字段
8. **编辑时填充新字段**: `handleEdit` 中填充 passage/freezeDate/expectedCellCount/viability/productStatus
9. **提交 payload**: 包含新字段
10. **图片列表与 formData.productImages 的同步**: 上传/删除时同步更新逗号分隔 URL 字符串
**下拉数据加载**:
```typescript
async function loadOptions() {
try {
const [typeRes, specRes, slotRes] = await Promise.all([
fetchCellProductTypeList({ page: 1, pageSize: 999 }),
fetchCellProductSpecList({ page: 1, pageSize: 999 }),
fetchStorageSlotList(),
])
productTypeOptions.value = (typeRes as any)?.list || []
specOptions.value = (specRes as any)?.list || []
storageSlotOptions.value = Array.isArray(slotRes) ? slotRes : (slotRes as any)?.list || []
} catch { /* 静默 */ }
}
```
**编码自动生成**:
```typescript
async function handleAdd() {
isEdit.value = false
dialogTitle.value = '新增产品'
resetForm()
dialogVisible.value = true
formRef.value?.clearValidate?.()
// 自动生成编码
try {
const res = await fetchNextCellProductCode()
formData.productCode = res.code || ''
} catch { /* 静默 */ }
}
```
**图片上传处理**:
```typescript
const imageList = ref<Array<{ id: string; fileName: string; fileUrl: string; _uploading?: boolean }>>([])
async function handleImageChange(uploadFile: any) {
const file = uploadFile.raw || uploadFile
if (!file) return
if (file.size / 1024 / 1024 > 10) {
ElMessage.error('图片大小不能超过 10MB')
return
}
const tempId = `temp_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
imageList.value.push({ id: tempId, fileName: file.name, fileUrl: '', _uploading: true })
const idx = imageList.value.length - 1
try {
const fd = new FormData()
fd.append('folder', 'cell-product')
fd.append('file', file)
const res = await fetchUploadAttachment(fd)
imageList.value[idx] = { id: res.id, fileName: res.fileName, fileUrl: res.fileUrl, _uploading: false }
} catch {
imageList.value.splice(idx, 1)
ElMessage.error('图片上传失败')
}
}
function handleDeleteImage(index: number) {
imageList.value.splice(index, 1)
}
function handlePreviewImage(img: any) {
if (img.fileUrl) window.open(img.fileUrl, '_blank')
}
// 同步图片 URL 到 formData
function syncImagesToForm() {
formData.productImages = imageList.value.map((img) => img.fileUrl).filter(Boolean).join(',')
}
// 从 formData.productImages 还原 imageList
function syncFormToImages(imagesStr: string) {
if (!imagesStr) { imageList.value = []; return }
imageList.value = imagesStr.split(',').filter(Boolean).map((url, i) => ({
id: `img_${i}`,
fileName: url.split('/').pop() || `图片${i + 1}`,
fileUrl: url,
}))
}
```
**查看弹窗 sections 更新**:
```typescript
const viewSections = computed(() => {
if (!viewRow.value) return []
const row = viewRow.value
return [
{
title: '基本信息',
fields: [
{ label: '产品编码', value: row.productCode },
{ label: '产品名称', value: row.productName },
{ label: '产品类型', value: row.productTypeName || '—' },
{ label: '规格', value: row.specName || '—' },
{ label: '代次', value: row.passage || '—' },
{ label: '批次号', value: row.batchNo },
{ label: '储物格位置', value: row.storageSlotName || '—' },
{ label: '预计细胞数量(亿)', value: row.expectedCellCount ?? '—' },
{ label: '活性(%)', value: row.viability != null ? row.viability : '—' },
{ label: '数量', value: row.quantity ?? '—' },
{ label: '单位', value: row.unit || '—' },
{ label: '冻存日期', value: row.freezeDate || '—' },
{ label: '有效期', value: row.expiryDate || '—' },
{
label: '状态',
value: productStatusTag(row.productStatus).label,
tagType: productStatusTag(row.productStatus).type,
},
{ label: '备注', value: row.remark || '—', span: true },
],
},
]
})
```
**提交时同步图片**:
```typescript
async function handleSubmit() {
await formRef.value?.validate?.()
submitLoading.value = true
try {
syncImagesToForm()
const payload = {
productCode: formData.productCode.trim(),
productName: formData.productName.trim(),
productTypeId: formData.productTypeId || undefined,
specId: formData.specId || undefined,
batchNo: formData.batchNo.trim(),
storageSlotId: formData.storageSlotId || undefined,
passage: formData.passage || undefined,
freezeDate: formData.freezeDate || undefined,
expectedCellCount: formData.expectedCellCount ?? undefined,
viability: formData.viability ?? undefined,
productStatus: isEdit.value ? formData.productStatus || undefined : 'frozen',
quantity: formData.quantity,
unit: formData.unit?.trim() || undefined,
expiryDate: formData.expiryDate || undefined,
productImages: formData.productImages?.trim() || undefined,
remark: formData.remark?.trim() || undefined,
}
if (isEdit.value && editingId.value) {
await fetchUpdateCellProduct(editingId.value, payload)
} else {
await fetchCreateCellProduct(payload)
}
ElMessage.success(isEdit.value ? '更新成功' : '创建成功')
dialogVisible.value = false
await loadData()
} catch {
/* 校验或接口错误已由全局处理 */
} finally {
submitLoading.value = false
}
}
```
**编辑时填充新字段**(在 `handleEdit` 中):
```typescript
formData.passage = row.passage || ''
formData.freezeDate = row.freezeDate || ''
formData.expectedCellCount = row.expectedCellCount ?? undefined
formData.viability = row.viability ?? undefined
formData.productStatus = row.productStatus || 'frozen'
syncFormToImages(row.productImages || '')
```
**resetForm 新增字段重置**:
```typescript
function resetForm() {
// ...existing fields...
formData.passage = ''
formData.freezeDate = ''
formData.expectedCellCount = undefined
formData.viability = undefined
formData.productStatus = 'frozen'
imageList.value = []
editingId.value = null
}
```
**搜索参数传递更新**(在 `loadData` 中):
```typescript
const res = await fetchCellProductList({
page: pagination.page,
pageSize: pagination.pageSize,
keyword: searchForm.keyword || undefined,
productStatus: searchForm.productStatus || undefined,
passage: searchForm.passage || undefined,
})
```
注意:需更新 `fetchCellProductList` 的 params 类型以支持 `productStatus` 和 `passage`。
- [ ] **Step 3: 编写 `<style>` 部分(图片上传卡片样式)**
在现有样式基础上新增图片上传卡片样式(参考 ProtocolFormDialog):
```scss
.image-upload {
.image-list {
display: flex;
flex-wrap: wrap;
gap: 12px;
}
.upload-card {
width: 140px;
height: 96px;
border: 2px dashed #a7f3d0;
border-radius: 8px;
background: #f0fdf4;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
transition: border-color 0.2s;
&:hover {
border-color: #10b981;
}
.upload-card-text {
margin-top: 4px;
font-size: 12px;
color: #64748b;
}
}
.image-item {
width: 140px;
border: 1px solid #e2e8f0;
border-radius: 8px;
overflow: hidden;
transition: border-color 0.2s;
&:hover {
border-color: #10b981;
}
}
.image-preview {
width: 140px;
height: 68px;
background: #f8fafc;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
.preview-img {
width: 100%;
height: 100%;
}
}
.image-footer {
padding: 4px 8px;
display: flex;
align-items: center;
justify-content: space-between;
.image-name {
font-size: 11px;
color: #64748b;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 90px;
}
.action-icon {
cursor: pointer;
color: #94a3b8;
&:hover { color: #ef4444; }
}
}
}
```
- [ ] **Step 4: 更新 `fetchCellProductList` 参数类型**
修改 `web-admin/src/api/cell-product.ts` 中的参数类型:
```typescript
export function fetchCellProductList(params?: {
page?: number
pageSize?: number
keyword?: string
productStatus?: string
passage?: string
}) {
```
- [ ] **Step 5: 验证前端编译无报错**
Run: `cd web-admin && pnpm build`
Expected: 构建成功
- [ ] **Step 6: 提交**
```bash
git add web-admin/src/views/production/cell-product/index.vue web-admin/src/api/cell-product.ts
git commit -m "feat(cell-product): rewrite page with new fields, image upload, view dialog, and status lifecycle"
```
---
### Task 8: 更新知识图谱
**Files:**
- 执行: `./scripts/update-graph.sh all`
- [ ] **Step 1: 更新前后端知识图谱**
Run: `./scripts/update-graph.sh all`
Expected: 图谱更新成功
- [ ] **Step 2: 提交图谱更新**
```bash
git add backend/graphify-out/ web-admin/graphify-out/
git commit -m "chore: update knowledge graphs after cell-product redesign"
```
---
## Self-Review Checklist
### 1. Spec Coverage
| Spec 需求 | 对应 Task |
|-----------|----------|
| 代次 P1~P8 下拉 | Task 2(Model) + Task 3(Schema) + Task 7(Frontend) |
| 冻存日期 | Task 2 + Task 3 + Task 7 |
| 预计细胞数量(亿) | Task 2 + Task 3 + Task 7 |
| 活性(%) 0~100 | Task 2 + Task 3(Schema validator) + Task 7 |
| 状态 frozen/sold/used | Task 1(SQL) + Task 2(Model) + Task 3(Schema) + Task 4(CRUD validation) + Task 7(Frontend) |
| 产品编码自动生成 CP+YYYYMMDD+6位 | Task 4(get_next_code) + Task 5(API) + Task 6(Frontend API) + Task 7(handleAdd) |
| 产品图片上传 | Task 7(Frontend image upload + SCSS) |
| 查看弹窗 | Task 7(viewSections) |
| 状态单向流转 | Task 4(update_cell_product status validation) |
| 下拉数据来源(类型/规格/储物格) | Task 7(loadOptions) |
| 搜索筛选(关键词+状态+代次) | Task 4(list_cell_products passage filter) + Task 5(API) + Task 7 |
### 2. Placeholder Scan
- 无 TBD/TODO
- 所有代码步骤均包含完整代码
### 3. Type Consistency
- `passage`: Model `String(8)` → Schema `Optional[str]` → CRUD `str` → Frontend `string`
- `freeze_date`: Model `DateTime` → Schema `Optional[datetime]` → Frontend `string(YYYY-MM-DD)`
- `expected_cell_count`: Model `Numeric(18,4)` → Schema `Optional[float]` → CRUD `float()` → Frontend `number`
- `viability`: Model `Numeric(5,2)` → Schema `Optional[float]` (validator 0~100) → Frontend `number`
- `product_status`: Model `String(16) default='frozen'` → Schema `Optional[str]` (validator) → Frontend `string`
- `_row_to_dict` 输出 camelCase key 与前端 formData 一致
@@ -0,0 +1,274 @@
# 细胞产品库(Cell Product Library)字段重设计
> 日期: 2026-05-21
> 状态: 已确认,待实施
> 范围: `web-admin/src/views/production/cell-product/` + `backend/app/` 相关文件
---
## 1. 背景与目标
### 1.1 现状
`biz_cell_product` 表已有基本 CRUD 功能:产品编码、产品名称、产品类型、规格、批次号、储物格位置、数量、单位、有效期、产品图片、备注。状态字段使用 `zk`/`yc`/`ysy` 缩写且代码中存在不一致。
### 1.2 新需求
1. 细胞产品有**代次**(P1~P8,下拉选择)
2. 有**冻存日期**(哪天开始冻存)
3. 每份产品有**预计细胞数量**(单位:亿)
4. 有**活性**百分比(用户填写 0~100 的数字)
5. **状态**重新定义为:已冻存 / 已售卖 / 已使用
6. **产品编码**自动生成(CP + YYYYMMDD + 6位序列号),生成后可编辑
7. **产品图片**改为上传方式,参考【采集协议单录入】附件上传 UI 样式
8. 新增**查看弹窗**(只读详情页)
### 1.3 不在本次范围
- 批量生成功能(后续对接生产模块时实现)
- 生产任务自动生成细胞产品
- 状态变更的审批流程
---
## 2. 数据库设计
### 2.1 新增字段
在 `biz_cell_product` 表中新增以下 4 个字段(放在业务字段区域,`tenant_no` 之前):
```sql
ALTER TABLE biz_cell_product
ADD COLUMN `passage` VARCHAR(8) DEFAULT NULL COMMENT '代次(P1~P8)' AFTER `storage_slot_id`,
ADD COLUMN `freeze_date` DATETIME DEFAULT NULL COMMENT '冻存日期' AFTER `passage`,
ADD COLUMN `expected_cell_count` DECIMAL(18,4) DEFAULT NULL COMMENT '预计细胞数量(单位:亿)' AFTER `freeze_date`,
ADD COLUMN `viability` DECIMAL(5,2) DEFAULT NULL COMMENT '活性(百分比,0~100)' AFTER `expected_cell_count`;
```
### 2.2 状态字段更新
`product_status` 字段(varchar(16))的值从 `zk`/`yc`/`ysy` 改为:
| 状态值 | 显示名 | Tag 颜色 | 说明 |
|--------|--------|---------|------|
| `frozen` | 已冻存 | `success`(绿色) | 新建时的默认状态 |
| `sold` | 已售卖 | `warning`(橙色) | 通过编辑弹窗变更 |
| `used` | 已使用 | `info`(灰色) | 通过编辑弹窗变更 |
```sql
ALTER TABLE biz_cell_product
MODIFY COLUMN `product_status` VARCHAR(16) DEFAULT 'frozen' COMMENT '状态: frozen已冻存/sold已售卖/used已使用';
```
### 2.3 完整字段清单(按顺序)
| # | DB 列名 | 类型 | 必填 | 默认值 | 说明 |
|---|---------|------|:----:|--------|------|
| 1 | `id` | varchar(64) PK | ✓ | UUID | 主键 |
| 2 | `product_code` | varchar(64) | ✓ | 自动生成 | 产品编码 CP+YYYYMMDD+6位序号 |
| 3 | `product_name` | varchar(200) | ✓ | | 产品名称 |
| 4 | `product_type_id` | varchar(64) | | | 细胞产品类型ID (FK) |
| 5 | `spec_id` | varchar(64) | | | 规格ID (FK) |
| 6 | `batch_no` | varchar(64) | ✓ | | 批次号 |
| 7 | `storage_slot_id` | varchar(64) | | | 储物格ID (FK) |
| 8 | **`passage`** | varchar(8) | | | 代次 P1~P8 |
| 9 | **`freeze_date`** | datetime | | | 冻存日期 |
| 10 | **`expected_cell_count`** | decimal(18,4) | | | 预计细胞数量(亿) |
| 11 | **`viability`** | decimal(5,2) | | | 活性(0~100%) |
| 12 | `product_status` | varchar(16) | ✓ | `frozen` | 状态 |
| 13 | `source_type` | varchar(20) | | `manual` | 来源 |
| 14 | `production_task_id` | varchar(64) | | | 来源生产任务ID |
| 15 | `quantity` | decimal(18,4) | | | 数量 |
| 16 | `unit` | varchar(64) | | | 单位 |
| 17 | `expiry_date` | datetime | | | 有效期 |
| 18 | `product_images` | varchar(4000) | | | 产品图片URL(逗号分隔) |
| — | `tenant_no` ~ `del_flag` | — | — | — | 公共字段 |
---
## 3. 状态流转
```
新建 → 已冻存 (frozen, 默认)
├─→ 已售卖 (sold) [编辑弹窗]
└─→ 已使用 (used) [编辑弹窗]
```
规则:
- 状态变更仅通过**编辑弹窗**操作
- 已售卖/已使用**不可回退**为已冻存(单向流转)
- 只有 `frozen` 状态的产品才能被编辑改为 `sold` 或 `used`
- 已售卖和已使用之间**不可互转**
---
## 4. 产品编码自动生成
### 4.1 规则
```
格式: CP + YYYYMMDD + 6位序列号(左补零)
示例: CP20260521000001
```
### 4.2 后端接口
新增 `GET /web/admin/cell-products/next-code`:
- 查询当天已有最大编码,+1 返回下一个编码
- 如果当天无记录,返回 `CPYYYYMMDD000001`
- 响应:`{ "code": "CP20260521000001" }`
### 4.3 前端交互
- 新增弹窗打开时,自动调用 next-code 接口填充产品编码
- 产品编码字段可编辑,用户可手动修改
- 编辑弹窗中产品编码只读
---
## 5. 前端界面设计
### 5.1 列表页
**页面路径**: `/production/cell-product`
**参考模板**: `src/views/system/order/index.vue`(复杂列表)
**表格列(14列)**:
| # | 列名 | 宽度 | 说明 |
|---|------|------|------|
| 1 | 序号 | 60 | type=index |
| 2 | 产品编码 | 180 | product_code |
| 3 | 产品名称 | 160 | product_name |
| 4 | 产品类型 | 120 | 关联显示类型名称 |
| 5 | 规格 | 120 | 关联显示规格名称 |
| 6 | 代次 | 80 | passage |
| 7 | 批次号 | 140 | batch_no |
| 8 | 储物格位置 | 140 | 关联显示储物格路径 |
| 9 | 预计细胞数量(亿) | 140 | expected_cell_count |
| 10 | 活性(%) | 90 | viability |
| 11 | 状态 | 100 | `<el-tag>` Tag 展示 |
| 12 | 有效期 | 120 | expiry_date, YYYY-MM-DD |
| 13 | 操作 | ≥260 | fixed="right": 查看、编辑、删除 |
**搜索筛选栏**:
- 关键词(模糊搜索,搜索产品编码/产品名称)
- 产品状态下拉(已冻存/已售卖/已使用)
- 代次下拉(P1~P8)
**工具栏**:
- 已选 N 项
- 新增按钮(`type="primary"`)
- 删除按钮(批量)
### 5.2 新增/编辑弹窗
**布局**: `label-position="top"`, 2 列网格 (`.form-grid-2`)
**字段排列(左列 → 右列)**:
| 行 | 左列 | 右列 |
|----|------|------|
| 1 | 产品编码(自动填充,可编辑;编辑时只读) | 产品名称(必填) |
| 2 | 产品类型(下拉) | 规格(下拉) |
| 3 | 代次(下拉 P1~P8) | 批次号(必填) |
| 4 | 储物格位置(下拉) | 预计细胞数量(数字输入,label 显示"预计细胞数量(亿)") |
| 5 | 活性(数字输入 0~100,label 显示"活性(%)") | 冻存日期(日期选择器) |
| 6 | 数量 | 单位 |
| 7 | 有效期(日期选择器) | 状态(编辑时可选,新增时隐藏/固定为 frozen) |
| 8 | 产品图片(跨2列,上传组件,参考采集协议单附件上传样式) | |
| 9 | 备注(跨2列,textarea) | |
**下拉数据来源**:
- 产品类型:`/web/admin/cell-product-types` 接口
- 规格:`/web/admin/cell-product-specs` 接口
- 储物格位置:库位管理接口,显示完整路径(` / `分隔)
- 代次:前端硬编码选项 P1~P8
**编辑弹窗状态字段**:
- 仅当当前状态为 `frozen` 时,显示状态下拉可改为 `sold` 或 `used`
- 状态已为 `sold` 或 `used` 时,状态字段只读
### 5.3 查看弹窗
**布局**: 参考 `SystemViewDialog` 组件模式
**展示字段**:
- 产品编码、产品名称
- 产品类型(名称)、规格(名称)
- 代次、批次号
- 储物格位置(完整路径)
- 预计细胞数量(亿)、活性(%)
- 数量、单位
- 冻存日期、有效期
- 状态(`<el-tag>` 展示)
- 产品图片(缩略图展示)
- 备注
**状态字段** 使用 `<el-tag>` 渲染:
- `frozen` → `<el-tag type="success" size="small">已冻存</el-tag>`
- `sold` → `<el-tag type="warning" size="small">已售卖</el-tag>`
- `used` → `<el-tag type="info" size="small">已使用</el-tag>`
### 5.4 产品图片上传
- 参考【采集协议单录入】模块的附件上传 UI 样式
- 上传触发器为卡片式(虚线边框),inline 排列
- 支持多图上传
- 上传后显示缩略图卡片,支持预览和删除
- 图片存储使用 MinIO 对象存储
---
## 6. 后端改动
### 6.1 Model(`backend/app/models/models.py` — BizCellProduct)
新增 4 个 Column:`passage`、`freeze_date`、`expected_cell_count`、`viability`
更新 `product_status` 默认值为 `'frozen'`
### 6.2 Schema(`backend/app/schemas/cell_product.py`)
- `CellProductCreate`: 新增 `passage`、`freeze_date`、`expected_cell_count`、`viability`(均 Optional)
- `CellProductUpdate`: 同上 + `product_status`(可选,校验只能从 frozen→sold/used)
- `CellProductListQuery`: 保持不变
### 6.3 CRUD(`backend/app/crud/cell_product.py`)
- `_row_to_dict`: 新增 4 个字段的映射
- `create` / `update`: 新增字段处理
- 新增 `get_next_code` 方法:查询当天最大编码 +1
### 6.4 API(`backend/app/api/production.py`)
- 新增 `GET /web/admin/cell-products/next-code` 端点
- `create` 端点:新增字段写入
- `update` 端点:状态变更校验(只允许 frozen→sold/used)
---
## 7. 涉及文件清单
| 层级 | 文件 | 改动类型 |
|------|------|---------|
| SQL | `docs/sql/` 新增迁移文件 | ALTER TABLE |
| Model | `backend/app/models/models.py` BizCellProduct 类 | 新增 4 个 Column + 更新默认值 |
| Schema | `backend/app/schemas/cell_product.py` | 新增字段 + 状态校验 |
| CRUD | `backend/app/crud/cell_product.py` | 字段映射 + next-code 方法 |
| API | `backend/app/api/production.py` | 新增 next-code 端点 + 状态校验 |
| 前端页面 | `web-admin/src/views/production/cell-product/index.vue` | 全面重写(表格+表单+查看弹窗) |
| 前端API | `web-admin/src/api/cell-product.ts` | 新增 next-code 接口 |
---
## 8. 约定遵循
- UI 规范: `docs/web-admin-ui-spec-fresh-mint.md`(Fresh Mint 调色板)
- 状态 Tag 颜色: `success`=已冻存, `warning`=已售卖, `info`=已使用
- 弹窗表单: `label-position="top"`, 2列网格, `.dialog-form-layout`
- 产品图片上传: 参考采集协议单附件上传卡片化布局
- 公共字段: 遵循 `database-conventions`(id → 业务字段 → tenant_no ~ del_flag)
- 编码生成: `CPYYYYMMDD` + 6位序号左补零
File diff suppressed because it is too large. Load diff
+56 -14
View File
@@ -6249,28 +6249,35 @@
"label": "fetchCellProductDetail()",
"file_type": "code",
"source_file": "api/cell-product.ts",
"source_location": "L17"
"source_location": "L19"
},
{
"id": "api_cell_product_fetchcreatecellproduct",
"label": "fetchCreateCellProduct()",
"file_type": "code",
"source_file": "api/cell-product.ts",
"source_location": "L21"
"source_location": "L23"
},
{
"id": "api_cell_product_fetchupdatecellproduct",
"label": "fetchUpdateCellProduct()",
"file_type": "code",
"source_file": "api/cell-product.ts",
"source_location": "L25"
"source_location": "L27"
},
{
"id": "api_cell_product_fetchdeletecellproduct",
"label": "fetchDeleteCellProduct()",
"file_type": "code",
"source_file": "api/cell-product.ts",
"source_location": "L29"
"source_location": "L31"
},
{
"id": "api_cell_product_fetchnextcellproductcode",
"label": "fetchNextCellProductCode()",
"file_type": "code",
"source_file": "api/cell-product.ts",
"source_location": "L35"
},
{
"id": "api_research_donation_ts",
@@ -8267,19 +8274,26 @@
"source_file": "views/production/process-route/index.vue",
"source_location": "L1"
},
{
"id": "process_route_index_if",
"label": "if()",
"file_type": "code",
"source_file": "views/production/process-route/index.vue",
"source_location": "L1024"
},
{
"id": "process_route_index_get",
"label": "get()",
"file_type": "code",
"source_file": "views/production/process-route/index.vue",
"source_location": "L880"
"source_location": "L1089"
},
{
"id": "process_route_index_set",
"label": "set()",
"file_type": "code",
"source_file": "views/production/process-route/index.vue",
"source_location": "L890"
"source_location": "L1099"
},
{
"id": "views_production_sop_index_vue",
@@ -20354,7 +20368,7 @@
"relation": "contains",
"confidence": "EXTRACTED",
"source_file": "api/cell-product.ts",
"source_location": "L17",
"source_location": "L19",
"weight": 1.0
},
{
@@ -20363,7 +20377,7 @@
"relation": "contains",
"confidence": "EXTRACTED",
"source_file": "api/cell-product.ts",
"source_location": "L21",
"source_location": "L23",
"weight": 1.0
},
{
@@ -20372,7 +20386,7 @@
"relation": "contains",
"confidence": "EXTRACTED",
"source_file": "api/cell-product.ts",
"source_location": "L25",
"source_location": "L27",
"weight": 1.0
},
{
@@ -20381,7 +20395,16 @@
"relation": "contains",
"confidence": "EXTRACTED",
"source_file": "api/cell-product.ts",
"source_location": "L29",
"source_location": "L31",
"weight": 1.0
},
{
"source": "api_cell_product_ts",
"target": "api_cell_product_fetchnextcellproductcode",
"relation": "contains",
"confidence": "EXTRACTED",
"source_file": "api/cell-product.ts",
"source_location": "L35",
"weight": 1.0
},
{
@@ -23750,13 +23773,22 @@
"source_location": "L504",
"weight": 1.0
},
{
"source": "views_production_process_route_index_vue",
"target": "process_route_index_if",
"relation": "contains",
"confidence": "EXTRACTED",
"source_file": "views/production/process-route/index.vue",
"source_location": "L1024",
"weight": 1.0
},
{
"source": "views_production_process_route_index_vue",
"target": "process_route_index_get",
"relation": "contains",
"confidence": "EXTRACTED",
"source_file": "views/production/process-route/index.vue",
"source_location": "L880",
"source_location": "L1089",
"weight": 1.0
},
{
@@ -23765,7 +23797,7 @@
"relation": "contains",
"confidence": "EXTRACTED",
"source_file": "views/production/process-route/index.vue",
"source_location": "L890",
"source_location": "L1099",
"weight": 1.0
},
{
@@ -28963,7 +28995,7 @@
"confidence": "INFERRED",
"confidence_score": 0.8,
"source_file": "api/cell-product.ts",
"source_location": "L8",
"source_location": "L10",
"weight": 1.0
},
{
@@ -28973,7 +29005,17 @@
"confidence": "INFERRED",
"confidence_score": 0.8,
"source_file": "api/cell-product.ts",
"source_location": "L18",
"source_location": "L20",
"weight": 1.0
},
{
"source": "api_cell_product_fetchnextcellproductcode",
"target": "process_route_index_get",
"relation": "calls",
"confidence": "INFERRED",
"confidence_score": 0.8,
"source_file": "api/cell-product.ts",
"source_location": "L36",
"weight": 1.0
},
{
+104 -78
View File
@@ -1,11 +1,11 @@
# Graph Report - src (2026-05-21)
## Corpus Check
- Large corpus: 577 files · ~295,435 words. Semantic extraction will be expensive (many Claude tokens). Consider running on a subfolder, or use --no-semantic to run AST-only.
- Large corpus: 577 files · ~296,820 words. Semantic extraction will be expensive (many Claude tokens). Consider running on a subfolder, or use --no-semantic to run AST-only.
## Summary
- 1401 nodes · 1751 edges · 41 communities detected
- Extraction: 78% EXTRACTED · 22% INFERRED · 0% AMBIGUOUS · INFERRED: 383 edges (avg confidence: 0.8)
- 1403 nodes · 1754 edges · 45 communities detected
- Extraction: 78% EXTRACTED · 22% INFERRED · 0% AMBIGUOUS · INFERRED: 384 edges (avg confidence: 0.8)
- Token cost: 0 input · 0 output
## Community Hubs (Navigation)
@@ -33,11 +33,11 @@
- [[_COMMUNITY_Community 21|Community 21]]
- [[_COMMUNITY_Community 22|Community 22]]
- [[_COMMUNITY_Community 23|Community 23]]
- [[_COMMUNITY_Community 25|Community 25]]
- [[_COMMUNITY_Community 24|Community 24]]
- [[_COMMUNITY_Community 26|Community 26]]
- [[_COMMUNITY_Community 27|Community 27]]
- [[_COMMUNITY_Community 28|Community 28]]
- [[_COMMUNITY_Community 29|Community 29]]
- [[_COMMUNITY_Community 30|Community 30]]
- [[_COMMUNITY_Community 31|Community 31]]
- [[_COMMUNITY_Community 32|Community 32]]
- [[_COMMUNITY_Community 33|Community 33]]
@@ -49,10 +49,14 @@
- [[_COMMUNITY_Community 39|Community 39]]
- [[_COMMUNITY_Community 40|Community 40]]
- [[_COMMUNITY_Community 41|Community 41]]
- [[_COMMUNITY_Community 42|Community 42]]
- [[_COMMUNITY_Community 43|Community 43]]
- [[_COMMUNITY_Community 45|Community 45]]
- [[_COMMUNITY_Community 53|Community 53]]
- [[_COMMUNITY_Community 54|Community 54]]
## God Nodes (most connected - your core abstractions)
1. `get()` - 152 edges
1. `get()` - 153 edges
2. `WebSocketClient` - 26 edges
3. `push()` - 18 edges
4. `withFullscreenLoading()` - 16 edges
@@ -66,14 +70,14 @@
## Surprising Connections (you probably didn't know these)
- `handleBatchDelete()` --calls--> `withFullscreenLoading()` [INFERRED]
views/system/site/index.vue → utils/fullscreen-loading.ts
- `handleBatchDelete()` --calls--> `withFullscreenLoading()` [INFERRED]
views/basic-info/sample-code/index.vue → utils/fullscreen-loading.ts
- `handleBatchRevoke()` --calls--> `withFullscreenLoading()` [INFERRED]
views/basic-info/sample-code/index.vue → utils/fullscreen-loading.ts
- `handleBatchDelete()` --calls--> `withFullscreenLoading()` [INFERRED]
views/basic-info/customer/index.vue → utils/fullscreen-loading.ts
- `handleUnauthorizedResponse()` --calls--> `fetchRefreshToken()` [INFERRED]
utils/http/index.ts → api/auth.ts
- `fetchLeaveList()` --calls--> `get()` [INFERRED]
api/leave.ts → views/production/process-route/index.vue
- `fetchLeaveDetail()` --calls--> `get()` [INFERRED]
api/leave.ts → views/production/process-route/index.vue
## Communities
@@ -83,7 +87,7 @@ Nodes (77): fetchActiveRegions(), fetchCategoryList(), fetchCategoryParameters()
### Community 1 - "Community 1"
Cohesion: 0.03
Nodes (56): fetchBatchGenerateProtocolNos(), fetchBatchGenerateSampleCodes(), fetchBatchIssueProtocolNos(), fetchBatchIssueSampleCodes(), fetchCollectionProtocolNoList(), fetchCreateCollectionProtocolNo(), fetchCreateSampleCode(), fetchDeleteCollectionProtocolNo() (+48 more)
Nodes (53): fetchBatchGenerateProtocolNos(), fetchBatchIssueProtocolNos(), fetchCollectionProtocolNoList(), fetchCollectionRegionList(), fetchCreateCollectionProtocolNo(), fetchCreateCollectionRegion(), fetchCreateHospital(), fetchDeleteCollectionProtocolNo() (+45 more)
### Community 2 - "Community 2"
Cohesion: 0.04
@@ -91,11 +95,11 @@ Nodes (31): fetchGetUserInfo(), clickTab(), handleTouchEnd(), setTransition(), I
### Community 3 - "Community 3"
Cohesion: 0.04
Nodes (18): fetchAllConsumablePacks(), fetchAllConsumables(), fetchAllOutputTypes(), fetchAllProcessCategories(), fetchAllProcesses(), fetchAllReagents(), fetchAllSopDocuments(), fetchConsumableList() (+10 more)
Nodes (33): fetchDeptTree(), fetchPostBoundUserIds(), reload(), collectIds(), formatUserLabel(), loadDeptData(), onPersonSelectChange(), RoutePermissionValidator (+25 more)
### Community 4 - "Community 4"
Cohesion: 0.05
Nodes (32): fetchDeptTree(), fetchPostBoundUserIds(), FireworkSystem, reload(), formatUserLabel(), loadDeptData(), onPersonSelectChange(), subscribeTokenRefresh() (+24 more)
Cohesion: 0.04
Nodes (18): fetchAllConsumablePacks(), fetchAllConsumables(), fetchAllOutputTypes(), fetchAllProcessCategories(), fetchAllProcesses(), fetchAllReagents(), fetchAllSopDocuments(), fetchConsumableList() (+10 more)
### Community 5 - "Community 5"
Cohesion: 0.05
@@ -106,61 +110,61 @@ Cohesion: 0.09
Nodes (5): handleReset(), handleSearch(), WebSocketClient, TableCache, emit()
### Community 7 - "Community 7"
Cohesion: 0.06
Nodes (12): collectIds(), useSettingsHandlers(), useSettingsPanel(), useSettingsState(), ComponentLoader, RoutePermissionValidator, RouteTransformer, useCeremony() (+4 more)
### Community 8 - "Community 8"
Cohesion: 0.09
Nodes (31): toLogin(), base64ToBytes(), bytesToBase64(), decryptJson(), deriveKey(), encryptJson(), fromBase64Url(), hexToBytes() (+23 more)
### Community 9 - "Community 9"
### Community 8 - "Community 8"
Cohesion: 0.09
Nodes (8): getDefaultLanguage(), checkStorageCompatibility(), StorageConfig, getSystemStorage(), getSysVersion(), StorageKeyManager, StorageCompatibilityManager, validateStorageData()
### Community 10 - "Community 10"
### Community 9 - "Community 9"
Cohesion: 0.07
Nodes (13): fetchAttachmentDownloadUrl(), fetchOrderAdjusts(), fetchOrderDetail(), fetchOrderItems(), fetchOrderList(), fetchPriceVersionDetail(), fetchSystemConfig(), handleReceptionAttachmentDownload() (+5 more)
### Community 11 - "Community 11"
### Community 10 - "Community 10"
Cohesion: 0.09
Nodes (9): fetchGetAllUpgradeLogs(), fetchGetChinaMapData(), fetchGetComments(), fetchGetCurrentVersion(), fetchGetLatestUpgradeLog(), fetchGetSystemConfig(), fetchGetUpgradeLogs(), systemUpgrade() (+1 more)
### Community 12 - "Community 12"
Cohesion: 0.07
Nodes (15): fetchResearchDonationAuditList(), fetchResearchDonationDetail(), fetchResearchDonationList(), fetchApproveTask(), fetchBusinessAllTasks(), fetchCompletedTasks(), fetchDesignerData(), fetchInstanceDetail() (+7 more)
### Community 13 - "Community 13"
### Community 11 - "Community 11"
Cohesion: 0.1
Nodes (16): fetchCreateMenu(), fetchDeleteMenu(), fetchMenuTree(), fetchUpdateMenu(), postorder(), preorder(), applyTreeExpand(), handleAdd() (+8 more)
### Community 14 - "Community 14"
Cohesion: 0.1
Nodes (8): fetchGetMenuList(), fetchGetMenuCodes(), fetchLogin(), fetchRefreshToken(), MenuProcessor, useAppMode(), useAuth(), buildWebEncryptedLoginBody()
### Community 15 - "Community 15"
### Community 12 - "Community 12"
Cohesion: 0.1
Nodes (13): fetchEthnicList(), addSample(), applyDonationBundleToForm(), confirmBatchTransportApply(), createEmptyTransport(), ensureDonationProtocolNoInOptions(), ensureEthnicOptionInList(), handleOpenForAdd() (+5 more)
### Community 16 - "Community 16"
### Community 13 - "Community 13"
Cohesion: 0.1
Nodes (18): fetchCollectionRegionList(), fetchCreateCollectionRegion(), fetchCreateHospital(), fetchDeleteCollectionRegion(), fetchDeleteHospital(), fetchHospitalList(), fetchUpdateCollectionRegion(), fetchUpdateHospital() (+10 more)
Nodes (8): fetchGetMenuList(), fetchGetMenuCodes(), fetchLogin(), fetchRefreshToken(), MenuProcessor, useAppMode(), useAuth(), buildWebEncryptedLoginBody()
### Community 17 - "Community 17"
### Community 14 - "Community 14"
Cohesion: 0.11
Nodes (21): fetchBatchGenerateSampleCodes(), fetchBatchIssueSampleCodes(), fetchCreateSampleCode(), fetchDeleteSampleCode(), fetchSampleCodeList(), fetchUpdateSampleCode(), fetchUserOptions(), handleAdd() (+13 more)
### Community 15 - "Community 15"
Cohesion: 0.11
Nodes (9): auditorDisplay(), dash(), expressName(), formatYm(), formatYmd(), sampleTypeName(), siteName(), unitName() (+1 more)
### Community 18 - "Community 18"
### Community 16 - "Community 16"
Cohesion: 0.12
Nodes (15): addCopyButton(), highlightCode(), insertLineNumbers(), isBlockProcessed(), markBlockAsProcessed(), processBlock(), setupHighlightDirective(), setupRippleDirective() (+7 more)
### Community 19 - "Community 19"
### Community 17 - "Community 17"
Cohesion: 0.14
Nodes (17): fetchCreateUser(), fetchDeleteUser(), fetchResetPassword(), fetchUpdateUser(), fetchUserDetail(), formatUserDeptDisplay(), handleAdd(), handleDelete() (+9 more)
### Community 20 - "Community 20"
### Community 18 - "Community 18"
Cohesion: 0.12
Nodes (12): fetchApproveTask(), fetchBusinessAllTasks(), fetchCompletedTasks(), fetchDesignerData(), fetchInstanceDetail(), fetchMyInstances(), fetchPendingTasks(), fetchProcessDefList() (+4 more)
### Community 19 - "Community 19"
Cohesion: 0.16
Nodes (10): useTable(), useTableImpl(), getColumnChecks(), useTableColumns(), createErrorHandler(), createSmartDebounce(), defaultResponseAdapter(), extractPagination() (+2 more)
### Community 20 - "Community 20"
Cohesion: 0.2
Nodes (2): ComponentLoader, RouteTransformer
### Community 21 - "Community 21"
Cohesion: 0.27
Nodes (14): useChart(), useChartComponent(), useChartOps(), colourBlend(), getCssVar(), getDarkColor(), getLightColor(), handleElementThemeColor() (+6 more)
@@ -171,117 +175,139 @@ Nodes (12): fetchCreateCustomer(), fetchCustomerList(), fetchDeleteCustomer(), f
### Community 23 - "Community 23"
Cohesion: 0.14
Nodes (8): useSettingsHandlers(), useSettingsPanel(), useSettingsState(), useCeremony(), useTheme(), themeAnimation(), toggleTheme(), toggleTransition()
### Community 24 - "Community 24"
Cohesion: 0.14
Nodes (2): fetchProductionTaskDetail(), fetchProductionTaskList()
### Community 25 - "Community 25"
### Community 26 - "Community 26"
Cohesion: 0.17
Nodes (3): fetchResearchDonationAuditList(), fetchResearchDonationDetail(), fetchResearchDonationList()
### Community 27 - "Community 27"
Cohesion: 0.18
Nodes (3): fetchMaterialList(), fetchMaterialOptions(), fetchMaterialStockLogList()
### Community 26 - "Community 26"
### Community 28 - "Community 28"
Cohesion: 0.2
Nodes (2): fetchQcMaterialList(), fetchQcMaterialOptions()
### Community 28 - "Community 28"
### Community 29 - "Community 29"
Cohesion: 0.28
Nodes (1): FireworkSystem
### Community 31 - "Community 31"
Cohesion: 0.39
Nodes (6): isIgnorableScriptError(), normalizeErrorMessage(), registerPromiseErrorHandler(), registerResourceErrorHandler(), scriptErrorHandler(), setupErrorHandle()
### Community 29 - "Community 29"
### Community 32 - "Community 32"
Cohesion: 0.36
Nodes (1): TableHeightCalculator
### Community 30 - "Community 30"
### Community 33 - "Community 33"
Cohesion: 0.33
Nodes (2): logApiCryptoBootInfo(), maskSecret()
### Community 31 - "Community 31"
### Community 34 - "Community 34"
Cohesion: 0.29
Nodes (2): fetchLeaveDetail(), fetchLeaveList()
### Community 32 - "Community 32"
### Community 35 - "Community 35"
Cohesion: 0.47
Nodes (3): isPaymentDueWithinWarnWindow(), parsePaymentDueYmd(), startOfLocalDay()
### Community 33 - "Community 33"
### Community 36 - "Community 36"
Cohesion: 0.33
Nodes (2): fetchSampleReceptionCheckItemList(), fetchSampleReceptionCheckItemsActive()
### Community 34 - "Community 34"
Cohesion: 0.33
Nodes (2): fetchQcThirdPartyItemList(), fetchQcThirdPartyItemsActive()
### Community 35 - "Community 35"
### Community 37 - "Community 37"
Cohesion: 0.33
Nodes (2): fetchQcThirdPartyCompanyList(), fetchQcThirdPartyCompanyOptions()
### Community 36 - "Community 36"
### Community 38 - "Community 38"
Cohesion: 0.33
Nodes (2): fetchQcLabTestItemList(), fetchQcLabTestItemsActive()
### Community 37 - "Community 37"
### Community 39 - "Community 39"
Cohesion: 0.33
Nodes (2): fetchQcTaskDetail(), fetchQcTaskList()
### Community 38 - "Community 38"
### Community 40 - "Community 40"
Cohesion: 0.53
Nodes (4): isEtaTodayOrEarlier(), parseEtaLocalDate(), sampleReceptionRowClassName(), startOfLocalDay()
### Community 39 - "Community 39"
### Community 41 - "Community 41"
Cohesion: 0.5
Nodes (2): focusInput(), handleKeydown()
### Community 40 - "Community 40"
### Community 42 - "Community 42"
Cohesion: 0.67
Nodes (2): if(), logSidebarMenu()
### Community 41 - "Community 41"
### Community 43 - "Community 43"
Cohesion: 1.0
Nodes (2): getSettingDefaults(), resetToDefaults()
### Community 43 - "Community 43"
### Community 45 - "Community 45"
Cohesion: 1.0
Nodes (2): defaultAvatarPathByRole(), resolveUserAvatarUrl()
### Community 53 - "Community 53"
Cohesion: 0.67
Nodes (1): fetchQcItemParams()
### Community 54 - "Community 54"
Cohesion: 0.67
Nodes (2): fetchDeleteProjectSystemItem(), if()
## Knowledge Gaps
- **Thin community `Community 23`** (14 nodes): `fetchAddTaskStep()`, `fetchCreateProductionTask()`, `fetchDeleteTaskStep()`, `fetchProductionTaskDetail()`, `fetchProductionTaskList()`, `fetchReplaceStepConsumables()`, `fetchReplaceStepDataCollection()`, `fetchReplaceStepReagents()`, `fetchSetTaskInputs()`, `fetchSetTaskOutputs()`, `fetchUpdateProductionTask()`, `fetchUpdateProductionTaskStatus()`, `fetchUpdateTaskStep()`, `production-task.ts`
- **Thin community `Community 20`** (17 nodes): `ComponentLoader`, `.constructor()`, `.createEmptyComponent()`, `.createErrorComponent()`, `.load()`, `.loadIframe()`, `.loadLayout()`, `.load()`, `RouteTransformer`, `.extractFirstSegment()`, `.handleFirstLevelRoute()`, `.handleIframeRoute()`, `.handleNormalRoute()`, `.isFirstLevelRoute()`, `.transform()`, `ComponentLoader.ts`, `RouteTransformer.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 26`** (10 nodes): `fetchBatchStockInQcMaterial()`, `fetchBatchStockOutQcMaterial()`, `fetchCreateQcMaterial()`, `fetchDeleteQcMaterial()`, `fetchQcMaterialList()`, `fetchQcMaterialOptions()`, `fetchStockInQcMaterial()`, `fetchStockOutQcMaterial()`, `fetchUpdateQcMaterial()`, `qc-material.ts`
- **Thin community `Community 24`** (14 nodes): `fetchAddTaskStep()`, `fetchCreateProductionTask()`, `fetchDeleteTaskStep()`, `fetchProductionTaskDetail()`, `fetchProductionTaskList()`, `fetchReplaceStepConsumables()`, `fetchReplaceStepDataCollection()`, `fetchReplaceStepReagents()`, `fetchSetTaskInputs()`, `fetchSetTaskOutputs()`, `fetchUpdateProductionTask()`, `fetchUpdateProductionTaskStatus()`, `fetchUpdateTaskStep()`, `production-task.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 29`** (8 nodes): `TableHeightCalculator`, `.calculate()`, `.calculateOffset()`, `.calculatePaginationOffset()`, `.constructor()`, `.getHeaderHeight()`, `useTableHeight()`, `useTableHeight.ts`
- **Thin community `Community 28`** (10 nodes): `fetchBatchStockInQcMaterial()`, `fetchBatchStockOutQcMaterial()`, `fetchCreateQcMaterial()`, `fetchDeleteQcMaterial()`, `fetchQcMaterialList()`, `fetchQcMaterialOptions()`, `fetchStockInQcMaterial()`, `fetchStockOutQcMaterial()`, `fetchUpdateQcMaterial()`, `qc-material.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 30`** (7 nodes): `App.vue`, `main.ts`, `refreshButtonAutoIcons()`, `refreshPanelTitleIcons()`, `logApiCryptoBootInfo()`, `maskSecret()`, `console.ts`
- **Thin community `Community 29`** (9 nodes): `FireworkSystem`, `.constructor()`, `.createFirework()`, `.createParticle()`, `.getAvailableParticle()`, `.initializePool()`, `.preloadAllImages()`, `.preloadImage()`, `index.vue`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 31`** (7 nodes): `fetchCreateLeave()`, `fetchDeleteLeave()`, `fetchLeaveDetail()`, `fetchLeaveList()`, `fetchSubmitLeave()`, `fetchUpdateLeave()`, `leave.ts`
- **Thin community `Community 32`** (8 nodes): `TableHeightCalculator`, `.calculate()`, `.calculateOffset()`, `.calculatePaginationOffset()`, `.constructor()`, `.getHeaderHeight()`, `useTableHeight()`, `useTableHeight.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 33`** (6 nodes): `fetchCreateSampleReceptionCheckItem()`, `fetchDeleteSampleReceptionCheckItem()`, `fetchSampleReceptionCheckItemList()`, `fetchSampleReceptionCheckItemsActive()`, `fetchUpdateSampleReceptionCheckItem()`, `sample-reception-check-item.ts`
- **Thin community `Community 33`** (7 nodes): `App.vue`, `main.ts`, `refreshButtonAutoIcons()`, `refreshPanelTitleIcons()`, `logApiCryptoBootInfo()`, `maskSecret()`, `console.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 34`** (6 nodes): `fetchCreateQcThirdPartyItem()`, `fetchDeleteQcThirdPartyItem()`, `fetchQcThirdPartyItemList()`, `fetchQcThirdPartyItemsActive()`, `fetchUpdateQcThirdPartyItem()`, `qc-third-party-item.ts`
- **Thin community `Community 34`** (7 nodes): `fetchCreateLeave()`, `fetchDeleteLeave()`, `fetchLeaveDetail()`, `fetchLeaveList()`, `fetchSubmitLeave()`, `fetchUpdateLeave()`, `leave.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 35`** (6 nodes): `fetchCreateQcThirdPartyCompany()`, `fetchDeleteQcThirdPartyCompany()`, `fetchQcThirdPartyCompanyList()`, `fetchQcThirdPartyCompanyOptions()`, `fetchUpdateQcThirdPartyCompany()`, `qc-third-party-company.ts`
- **Thin community `Community 36`** (6 nodes): `fetchCreateSampleReceptionCheckItem()`, `fetchDeleteSampleReceptionCheckItem()`, `fetchSampleReceptionCheckItemList()`, `fetchSampleReceptionCheckItemsActive()`, `fetchUpdateSampleReceptionCheckItem()`, `sample-reception-check-item.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 36`** (6 nodes): `fetchCreateQcLabTestItem()`, `fetchDeleteQcLabTestItem()`, `fetchQcLabTestItemList()`, `fetchQcLabTestItemsActive()`, `fetchUpdateQcLabTestItem()`, `qc-lab-test-item.ts`
- **Thin community `Community 37`** (6 nodes): `fetchCreateQcThirdPartyCompany()`, `fetchDeleteQcThirdPartyCompany()`, `fetchQcThirdPartyCompanyList()`, `fetchQcThirdPartyCompanyOptions()`, `fetchUpdateQcThirdPartyCompany()`, `qc-third-party-company.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 37`** (6 nodes): `fetchCompleteQcTask()`, `fetchQcTaskDetail()`, `fetchQcTaskList()`, `fetchStartQcTask()`, `fetchUpdateQcTaskAttachments()`, `qc-task.ts`
- **Thin community `Community 38`** (6 nodes): `fetchCreateQcLabTestItem()`, `fetchDeleteQcLabTestItem()`, `fetchQcLabTestItemList()`, `fetchQcLabTestItemsActive()`, `fetchUpdateQcLabTestItem()`, `qc-lab-test-item.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 39`** (5 nodes): `focusInput()`, `handleKeydown()`, `if()`, `search()`, `index.vue`
- **Thin community `Community 39`** (6 nodes): `fetchCompleteQcTask()`, `fetchQcTaskDetail()`, `fetchQcTaskList()`, `fetchStartQcTask()`, `fetchUpdateQcTaskAttachments()`, `qc-task.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 40`** (4 nodes): `if()`, `logSidebarMenu()`, `index.vue`, `SidebarSubmenu.vue`
- **Thin community `Community 41`** (5 nodes): `focusInput()`, `handleKeydown()`, `if()`, `search()`, `index.vue`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 41`** (3 nodes): `getSettingDefaults()`, `resetToDefaults()`, `setting.ts`
- **Thin community `Community 42`** (4 nodes): `if()`, `logSidebarMenu()`, `index.vue`, `SidebarSubmenu.vue`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 43`** (3 nodes): `defaultAvatarPathByRole()`, `resolveUserAvatarUrl()`, `user-avatar.ts`
- **Thin community `Community 43`** (3 nodes): `getSettingDefaults()`, `resetToDefaults()`, `setting.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 45`** (3 nodes): `defaultAvatarPathByRole()`, `resolveUserAvatarUrl()`, `user-avatar.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 53`** (3 nodes): `fetchQcItemParams()`, `fetchSaveQcItemParams()`, `qc-item-param.ts`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
- **Thin community `Community 54`** (3 nodes): `fetchDeleteProjectSystemItem()`, `if()`, `index.vue`
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
## Suggested Questions
_Questions this graph is uniquely positioned to answer:_
- **Why does `get()` connect `Community 0` to `Community 1`, `Community 2`, `Community 3`, `Community 4`, `Community 5`, `Community 10`, `Community 11`, `Community 12`, `Community 13`, `Community 14`, `Community 15`, `Community 16`, `Community 19`, `Community 22`, `Community 23`, `Community 25`, `Community 26`, `Community 31`, `Community 33`, `Community 34`, `Community 35`, `Community 36`, `Community 37`?**
_High betweenness centrality (0.276) - this node is a cross-community bridge._
- **Why does `push()` connect `Community 4` to `Community 0`, `Community 2`, `Community 6`, `Community 7`, `Community 11`, `Community 15`?**
_High betweenness centrality (0.085) - this node is a cross-community bridge._
- **Why does `fetchGetLatestUpgradeLog()` connect `Community 11` to `Community 0`?**
- **Why does `get()` connect `Community 0` to `Community 1`, `Community 2`, `Community 3`, `Community 4`, `Community 5`, `Community 9`, `Community 10`, `Community 11`, `Community 12`, `Community 13`, `Community 14`, `Community 17`, `Community 18`, `Community 22`, `Community 24`, `Community 26`, `Community 27`, `Community 28`, `Community 34`, `Community 36`, `Community 37`, `Community 38`, `Community 39`, `Community 53`?**
_High betweenness centrality (0.270) - this node is a cross-community bridge._
- **Why does `push()` connect `Community 3` to `Community 0`, `Community 2`, `Community 6`, `Community 10`, `Community 12`, `Community 29`?**
_High betweenness centrality (0.089) - this node is a cross-community bridge._
- **Why does `fetchGetLatestUpgradeLog()` connect `Community 10` to `Community 0`?**
_High betweenness centrality (0.042) - this node is a cross-community bridge._
- **Are the 151 inferred relationships involving `get()` (e.g. with `buildTreePathLabelFromFlat()` and `alignFqPaymentDetailsForDisplay()`) actually correct?**
_`get()` has 151 INFERRED edges - model-reasoned connections that need verification._
- **Are the 152 inferred relationships involving `get()` (e.g. with `buildTreePathLabelFromFlat()` and `alignFqPaymentDetailsForDisplay()`) actually correct?**
_`get()` has 152 INFERRED edges - model-reasoned connections that need verification._
- **Are the 17 inferred relationships involving `push()` (e.g. with `fetchAllPagedRecords()` and `calcInstallmentDueAmounts()`) actually correct?**
_`push()` has 17 INFERRED edges - model-reasoned connections that need verification._
- **Are the 15 inferred relationships involving `withFullscreenLoading()` (e.g. with `.close()` and `handleSubmit()`) actually correct?**
+4 -4
View File
@@ -57,12 +57,12 @@
</div>
<div id="legend"></div>
</div>
<div id="stats">1401 nodes &middot; 1751 edges &middot; 237 communities</div>
<div id="stats">1403 nodes &middot; 1754 edges &middot; 241 communities</div>
</div>
<script>
const RAW_NODES = [{"id": "app_vue", "label": "App.vue", "color": {"background": "#4E79A7", "border": "#4E79A7", "highlight": {"background": "#ffffff", "border": "#4E79A7"}}, "size": 10.4, "font": {"size": 0, "color": "#ffffff"}, "title": "App.vue", "community": 30, "community_name": "Community 30", "source_file": "App.vue", "file_type": "code", "degree": 3}, {"id": "src_app_refreshbuttonautoicons", "label": "refreshButtonAutoIcons()", "color": {"background": "#4E79A7", "border": "#4E79A7", "highlight": {"background": "#ffffff", "border": "#4E79A7"}}, "size": 10.1, "font": {"size": 0, "color": "#ffffff"}, "title": "refreshButtonAutoIcons()", "community": 30, "community_name": "Community 30", "source_file": "App.vue", "file_type": "code", "degree": 1}, {"id": "src_app_refreshpaneltitleicons", "label": "refreshPanelTitleIcons()", "color": {"background": "#4E79A7", "border": "#4E79A7", "highlight": {"background": "#ffffff", "border": "#4E79A7"}}, "size": 10.1, "font": {"size": 0, "color": "#ffffff"}, "title": "refreshPanelTitleIcons()", "community": 30, "community_name": "Community 30", "source_file": "App.vue", "file_type": "code", "degree": 1}, {"id": "main_ts", "label": "main.ts", "color": {"background": "#4E79A7", "border": "#4E79A7", "highlight": {"background": "#ffffff", "border": "#4E79A7"}}, "size": 10.6, "font": {"size": 0, "color": "#ffffff"}, "title": "main.ts", "community": 30, "community_name": "Community 30", "source_file": "main.ts", "file_type": "code", "degree": 4}, {"id": "src_main_masksecret", "label": "maskSecret()", "color": {"background": "#4E79A7", "border": "#4E79A7", "highlight": {"background": "#ffffff", "border": "#4E79A7"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "maskSecret()", "community": 30, "community_name": "Community 30", "source_file": "main.ts", "file_type": "code", "degree": 2}, {"id": "src_main_logapicryptobootinfo", "label": "logApiCryptoBootInfo()", "color": {"background": "#4E79A7", "border": "#4E79A7", "highlight": {"background": "#ffffff", "border": "#4E79A7"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "logApiCryptoBootInfo()", "community": 30, "community_name": "Community 30", "source_file": "main.ts", "file_type": "code", "degree": 2}, {"id": "env_d_ts", "label": "env.d.ts", "color": {"background": "#59A14F", "border": "#59A14F", "highlight": {"background": "#ffffff", "border": "#59A14F"}}, "size": 10.0, "font": {"size": 0, "color": "#ffffff"}, "title": "env.d.ts", "community": 74, "community_name": "Community 74", "source_file": "env.d.ts", "file_type": "code", "degree": 0}, {"id": "shims_loose_d_ts", "label": "shims-loose.d.ts", "color": {"background": "#EDC948", "border": "#EDC948", "highlight": {"background": "#ffffff", "border": "#EDC948"}}, "size": 10.0, "font": {"size": 0, "color": "#ffffff"}, "title": "shims-loose.d.ts", "community": 75, "community_name": "Community 75", "source_file": "shims-loose.d.ts", "file_type": "code", "degree": 0}, {"id": "directives_index_ts", "label": "index.ts", "color": {"background": "#9C755F", "border": "#9C755F", "highlight": {"background": "#ffffff", "border": "#9C755F"}}, "size": 10.1, "font": {"size": 0, "color": "#ffffff"}, "title": "index.ts", "community": 18, "community_name": "Community 18", "source_file": "directives/index.ts", "file_type": "code", "degree": 1}, {"id": "directives_index_setupglobdirectives", "label": "setupGlobDirectives()", "color": {"background": "#9C755F", "border": "#9C755F", "highlight": {"background": "#ffffff", "border": "#9C755F"}}, "size": 10.7, "font": {"size": 0, "color": "#ffffff"}, "title": "setupGlobDirectives()", "community": 18, "community_name": "Community 18", "source_file": "directives/index.ts", "file_type": "code", "degree": 5}, {"id": "directives_core_roles_ts", "label": "roles.ts", "color": {"background": "#9C755F", "border": "#9C755F", "highlight": {"background": "#ffffff", "border": "#9C755F"}}, "size": 10.4, "font": {"size": 0, "color": "#ffffff"}, "title": "roles.ts", "community": 18, "community_name": "Community 18", "source_file": "directives/core/roles.ts", "file_type": "code", "degree": 3}, {"id": "core_roles_checkrolepermission", "label": "checkRolePermission()", "color": {"background": "#9C755F", "border": "#9C755F", "highlight": {"background": "#ffffff", "border": "#9C755F"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "checkRolePermission()", "community": 18, "community_name": "Community 18", "source_file": "directives/core/roles.ts", "file_type": "code", "degree": 2}, {"id": "core_roles_removeelement", "label": "removeElement()", "color": {"background": "#9C755F", "border": "#9C755F", "highlight": {"background": "#ffffff", "border": "#9C755F"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "removeElement()", "community": 18, "community_name": "Community 18", "source_file": "directives/core/roles.ts", "file_type": "code", "degree": 2}, {"id": "core_roles_setuprolesdirective", "laLine truncated
const RAW_EDGES = [{"from": "app_vue", "to": "src_app_refreshbuttonautoicons", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_vue", "to": "src_app_refreshpaneltitleicons", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_vue", "to": "main_ts", "label": "imports_from", "title": "imports_from [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "main_ts", "to": "utils_sys_console_ts", "label": "imports_from", "title": "imports_from [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "main_ts", "to": "src_main_masksecret", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "main_ts", "to": "src_main_logapicryptobootinfo", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "src_main_masksecret", "to": "src_main_logapicryptobootinfo", "label": "calls", "title": "calls [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "directives_index_ts", "to": "directives_index_setupglobdirectives", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "directives_index_setupglobdirectives", "to": "core_auth_setupauthdirective", "label": "calls", "title": "calls [INFERRED]", "dashes": true, "width": 1, "color": {"opacity": 0.35}, "confidence": "INFERRED"}, {"from": "directives_index_setupglobdirectives", "to": "core_roles_setuprolesdirective", "label": "calls", "title": "calls [INFERRED]", "dashes": true, "width": 1, "color": {"opacity": 0.35}, "confidence": "INFERRED"}, {"from": "directives_index_setupglobdirectives", "to": "business_highlight_setuphighlightdirective", "label": "calls", "title": "calls [INFERRED]", "dashes": true, "width": 1, "color": {"opacity": 0.35}, "confidence": "INFERRED"}, {"from": "directives_index_setupglobdirectives", "to": "business_ripple_setuprippledirective", "label": "calls", "title": "calls [INFERRED]", "dashes": true, "width": 1, "color": {"opacity": 0.35}, "confidence": "INFERRED"}, {"from": "directives_core_roles_ts", "to": "core_roles_checkrolepermission", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "directives_core_roles_ts", "to": "core_roles_removeelement", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "directives_core_roles_ts", "to": "core_roles_setuprolesdirective", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_roles_checkrolepermission", "to": "core_roles_removeelement", "label": "calls", "title": "calls [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "directives_core_auth_ts", "to": "core_auth_checkauthpermission", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "directives_core_auth_ts", "to": "core_auth_removeelement", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "directives_core_auth_ts", "to": "core_auth_setupauthdirective", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_checkauthpermission", "to": "core_auth_removeelement", "label": "calls", "title": "calls [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "directives_business_ripple_ts", "to": "business_ripple_setuprippledirective", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "directives_business_highlight_ts", "to": "business_highlight_highlightcode", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "directives_business_highlight_ts", "to": "business_highlight_insertlinenumbers", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "directives_business_highlight_ts", "to": "business_highlight_addcopybutton", "label": "contains", "title": "contains [EXTRACTED]", "dLine truncated
const LEGEND = [{"cid": 0, "color": "#4E79A7", "label": "Community 0", "count": 225}, {"cid": 1, "color": "#F28E2B", "label": "Community 1", "count": 87}, {"cid": 2, "color": "#E15759", "label": "Community 2", "count": 69}, {"cid": 3, "color": "#76B7B2", "label": "Community 3", "count": 53}, {"cid": 4, "color": "#59A14F", "label": "Community 4", "count": 52}, {"cid": 5, "color": "#EDC948", "label": "Community 5", "count": 51}, {"cid": 6, "color": "#B07AA1", "label": "Community 6", "count": 48}, {"cid": 7, "color": "#FF9DA7", "label": "Community 7", "count": 47}, {"cid": 8, "color": "#9C755F", "label": "Community 8", "count": 39}, {"cid": 9, "color": "#BAB0AC", "label": "Community 9", "count": 36}, {"cid": 10, "color": "#4E79A7", "label": "Community 10", "count": 32}, {"cid": 11, "color": "#F28E2B", "label": "Community 11", "count": 31}, {"cid": 12, "color": "#E15759", "label": "Community 12", "count": 31}, {"cid": 13, "color": "#76B7B2", "label": "Community 13", "count": 28}, {"cid": 14, "color": "#59A14F", "label": "Community 14", "count": 27}, {"cid": 15, "color": "#EDC948", "label": "Community 15", "count": 27}, {"cid": 16, "color": "#B07AA1", "label": "Community 16", "count": 26}, {"cid": 17, "color": "#FF9DA7", "label": "Community 17", "count": 24}, {"cid": 18, "color": "#9C755F", "label": "Community 18", "count": 22}, {"cid": 19, "color": "#BAB0AC", "label": "Community 19", "count": 20}, {"cid": 20, "color": "#4E79A7", "label": "Community 20", "count": 17}, {"cid": 21, "color": "#F28E2B", "label": "Community 21", "count": 16}, {"cid": 22, "color": "#E15759", "label": "Community 22", "count": 16}, {"cid": 23, "color": "#76B7B2", "label": "Community 23", "count": 14}, {"cid": 24, "color": "#59A14F", "label": "Community 24", "count": 13}, {"cid": 25, "color": "#EDC948", "label": "Community 25", "count": 11}, {"cid": 26, "color": "#B07AA1", "label": "Community 26", "count": 10}, {"cid": 27, "color": "#FF9DA7", "label": "Community 27", "count": 9}, {"cid": 28, "color": "#9C755F", "label": "Community 28", "count": 8}, {"cid": 29, "color": "#BAB0AC", "label": "Community 29", "count": 8}, {"cid": 30, "color": "#4E79A7", "label": "Community 30", "count": 7}, {"cid": 31, "color": "#F28E2B", "label": "Community 31", "count": 7}, {"cid": 32, "color": "#E15759", "label": "Community 32", "count": 6}, {"cid": 33, "color": "#76B7B2", "label": "Community 33", "count": 6}, {"cid": 34, "color": "#59A14F", "label": "Community 34", "count": 6}, {"cid": 35, "color": "#EDC948", "label": "Community 35", "count": 6}, {"cid": 36, "color": "#B07AA1", "label": "Community 36", "count": 6}, {"cid": 37, "color": "#FF9DA7", "label": "Community 37", "count": 6}, {"cid": 38, "color": "#9C755F", "label": "Community 38", "count": 6}, {"cid": 39, "color": "#BAB0AC", "label": "Community 39", "count": 5}, {"cid": 40, "color": "#4E79A7", "label": "Community 40", "count": 4}, {"cid": 41, "color": "#F28E2B", "label": "Community 41", "count": 3}, {"cid": 42, "color": "#E15759", "label": "Community 42", "count": 3}, {"cid": 43, "color": "#76B7B2", "label": "Community 43", "count": 3}, {"cid": 44, "color": "#59A14F", "label": "Community 44", "count": 3}, {"cid": 45, "color": "#EDC948", "label": "Community 45", "count": 3}, {"cid": 46, "color": "#B07AA1", "label": "Community 46", "count": 3}, {"cid": 47, "color": "#FF9DA7", "label": "Community 47", "count": 3}, {"cid": 48, "color": "#9C755F", "label": "Community 48", "count": 3}, {"cid": 49, "color": "#BAB0AC", "label": "Community 49", "count": 3}, {"cid": 50, "color": "#4E79A7", "label": "Community 50", "count": 3}, {"cid": 51, "color": "#F28E2B", "label": "Community 51", "count": 2}, {"cid": 52, "color": "#E15759", "label": "Community 52", "count": 2}, {"cid": 53, "color": "#76B7B2", "label": "Community 53", "count": 2}, {"cid": 54, "color": "#59A14F", "label": "Community 54", "count": 2}, {"cid": 55, "color": "#EDC948", "label": "Community 55", "count": 2}, {"cid": 56, "color": "#B07AA1", "label": "Community 56", "count": 2}, {"cid": 57, "color": "#FF9DA7", "label": "Community 57", "count": 2}, {"cid": 58, "color": "#9C755F", "label": "Community 58", "count": 2}, {"cid": 59, "color": "#BAB0AC", "label": "Community 59", "count": 2}, {"cid": 60, "color": "#4E79A7", "label": "Community 60", "count": 2}, {"cid": 61, "color": "#F28E2B", "label": "Community 61", "count": 2}, {"cid": 62, "color": "#E15759", "label": "Community 62", "count": 2}, {"cid": 63, "color": "#76B7B2", "label": "Community 63", "count": 2}, {"cid": 64, "color": "#59A14F", "label": "Community 64", "count": 2}, {"cid": 65, "color": "#EDC948", "label": "Community 65", "count": 2}, {"cid": 66, "color": "#B07AA1", "label": "Community 66", "count": 2}, {"cid": 67, "color": "#FF9DA7", "label": "Community 67", "count": 2}, {"cid": 68, "color": "#9C755F", "label": "Community 68", "count": 2}, {"cid": 69, "color": "#BAB0AC", "label": "Community 69", "count": 2}, {"cid": 70, "color": "#4E79A7", "label": "Community 70", "count": 2}, {"cidLine truncated
const RAW_NODES = [{"id": "app_vue", "label": "App.vue", "color": {"background": "#76B7B2", "border": "#76B7B2", "highlight": {"background": "#ffffff", "border": "#76B7B2"}}, "size": 10.4, "font": {"size": 0, "color": "#ffffff"}, "title": "App.vue", "community": 33, "community_name": "Community 33", "source_file": "App.vue", "file_type": "code", "degree": 3}, {"id": "src_app_refreshbuttonautoicons", "label": "refreshButtonAutoIcons()", "color": {"background": "#76B7B2", "border": "#76B7B2", "highlight": {"background": "#ffffff", "border": "#76B7B2"}}, "size": 10.1, "font": {"size": 0, "color": "#ffffff"}, "title": "refreshButtonAutoIcons()", "community": 33, "community_name": "Community 33", "source_file": "App.vue", "file_type": "code", "degree": 1}, {"id": "src_app_refreshpaneltitleicons", "label": "refreshPanelTitleIcons()", "color": {"background": "#76B7B2", "border": "#76B7B2", "highlight": {"background": "#ffffff", "border": "#76B7B2"}}, "size": 10.1, "font": {"size": 0, "color": "#ffffff"}, "title": "refreshPanelTitleIcons()", "community": 33, "community_name": "Community 33", "source_file": "App.vue", "file_type": "code", "degree": 1}, {"id": "main_ts", "label": "main.ts", "color": {"background": "#76B7B2", "border": "#76B7B2", "highlight": {"background": "#ffffff", "border": "#76B7B2"}}, "size": 10.6, "font": {"size": 0, "color": "#ffffff"}, "title": "main.ts", "community": 33, "community_name": "Community 33", "source_file": "main.ts", "file_type": "code", "degree": 4}, {"id": "src_main_masksecret", "label": "maskSecret()", "color": {"background": "#76B7B2", "border": "#76B7B2", "highlight": {"background": "#ffffff", "border": "#76B7B2"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "maskSecret()", "community": 33, "community_name": "Community 33", "source_file": "main.ts", "file_type": "code", "degree": 2}, {"id": "src_main_logapicryptobootinfo", "label": "logApiCryptoBootInfo()", "color": {"background": "#76B7B2", "border": "#76B7B2", "highlight": {"background": "#ffffff", "border": "#76B7B2"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "logApiCryptoBootInfo()", "community": 33, "community_name": "Community 33", "source_file": "main.ts", "file_type": "code", "degree": 2}, {"id": "env_d_ts", "label": "env.d.ts", "color": {"background": "#9C755F", "border": "#9C755F", "highlight": {"background": "#ffffff", "border": "#9C755F"}}, "size": 10.0, "font": {"size": 0, "color": "#ffffff"}, "title": "env.d.ts", "community": 78, "community_name": "Community 78", "source_file": "env.d.ts", "file_type": "code", "degree": 0}, {"id": "shims_loose_d_ts", "label": "shims-loose.d.ts", "color": {"background": "#BAB0AC", "border": "#BAB0AC", "highlight": {"background": "#ffffff", "border": "#BAB0AC"}}, "size": 10.0, "font": {"size": 0, "color": "#ffffff"}, "title": "shims-loose.d.ts", "community": 79, "community_name": "Community 79", "source_file": "shims-loose.d.ts", "file_type": "code", "degree": 0}, {"id": "directives_index_ts", "label": "index.ts", "color": {"background": "#B07AA1", "border": "#B07AA1", "highlight": {"background": "#ffffff", "border": "#B07AA1"}}, "size": 10.1, "font": {"size": 0, "color": "#ffffff"}, "title": "index.ts", "community": 16, "community_name": "Community 16", "source_file": "directives/index.ts", "file_type": "code", "degree": 1}, {"id": "directives_index_setupglobdirectives", "label": "setupGlobDirectives()", "color": {"background": "#B07AA1", "border": "#B07AA1", "highlight": {"background": "#ffffff", "border": "#B07AA1"}}, "size": 10.7, "font": {"size": 0, "color": "#ffffff"}, "title": "setupGlobDirectives()", "community": 16, "community_name": "Community 16", "source_file": "directives/index.ts", "file_type": "code", "degree": 5}, {"id": "directives_core_roles_ts", "label": "roles.ts", "color": {"background": "#B07AA1", "border": "#B07AA1", "highlight": {"background": "#ffffff", "border": "#B07AA1"}}, "size": 10.4, "font": {"size": 0, "color": "#ffffff"}, "title": "roles.ts", "community": 16, "community_name": "Community 16", "source_file": "directives/core/roles.ts", "file_type": "code", "degree": 3}, {"id": "core_roles_checkrolepermission", "label": "checkRolePermission()", "color": {"background": "#B07AA1", "border": "#B07AA1", "highlight": {"background": "#ffffff", "border": "#B07AA1"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "checkRolePermission()", "community": 16, "community_name": "Community 16", "source_file": "directives/core/roles.ts", "file_type": "code", "degree": 2}, {"id": "core_roles_removeelement", "label": "removeElement()", "color": {"background": "#B07AA1", "border": "#B07AA1", "highlight": {"background": "#ffffff", "border": "#B07AA1"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "removeElement()", "community": 16, "community_name": "Community 16", "source_file": "directives/core/roles.ts", "file_type": "code", "degree": 2}, {"id": "core_roles_setuprolesdirective", "laLine truncated
const RAW_EDGES = [{"from": "app_vue", "to": "src_app_refreshbuttonautoicons", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_vue", "to": "src_app_refreshpaneltitleicons", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "app_vue", "to": "main_ts", "label": "imports_from", "title": "imports_from [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "main_ts", "to": "utils_sys_console_ts", "label": "imports_from", "title": "imports_from [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "main_ts", "to": "src_main_masksecret", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "main_ts", "to": "src_main_logapicryptobootinfo", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "src_main_masksecret", "to": "src_main_logapicryptobootinfo", "label": "calls", "title": "calls [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "directives_index_ts", "to": "directives_index_setupglobdirectives", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "directives_index_setupglobdirectives", "to": "core_auth_setupauthdirective", "label": "calls", "title": "calls [INFERRED]", "dashes": true, "width": 1, "color": {"opacity": 0.35}, "confidence": "INFERRED"}, {"from": "directives_index_setupglobdirectives", "to": "core_roles_setuprolesdirective", "label": "calls", "title": "calls [INFERRED]", "dashes": true, "width": 1, "color": {"opacity": 0.35}, "confidence": "INFERRED"}, {"from": "directives_index_setupglobdirectives", "to": "business_highlight_setuphighlightdirective", "label": "calls", "title": "calls [INFERRED]", "dashes": true, "width": 1, "color": {"opacity": 0.35}, "confidence": "INFERRED"}, {"from": "directives_index_setupglobdirectives", "to": "business_ripple_setuprippledirective", "label": "calls", "title": "calls [INFERRED]", "dashes": true, "width": 1, "color": {"opacity": 0.35}, "confidence": "INFERRED"}, {"from": "directives_core_roles_ts", "to": "core_roles_checkrolepermission", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "directives_core_roles_ts", "to": "core_roles_removeelement", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "directives_core_roles_ts", "to": "core_roles_setuprolesdirective", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_roles_checkrolepermission", "to": "core_roles_removeelement", "label": "calls", "title": "calls [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "directives_core_auth_ts", "to": "core_auth_checkauthpermission", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "directives_core_auth_ts", "to": "core_auth_removeelement", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "directives_core_auth_ts", "to": "core_auth_setupauthdirective", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "core_auth_checkauthpermission", "to": "core_auth_removeelement", "label": "calls", "title": "calls [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "directives_business_ripple_ts", "to": "business_ripple_setuprippledirective", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "directives_business_highlight_ts", "to": "business_highlight_highlightcode", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "directives_business_highlight_ts", "to": "business_highlight_insertlinenumbers", "label": "contains", "title": "contains [EXTRACTED]", "dashes": false, "width": 2, "color": {"opacity": 0.7}, "confidence": "EXTRACTED"}, {"from": "directives_business_highlight_ts", "to": "business_highlight_addcopybutton", "label": "contains", "title": "contains [EXTRACTED]", "dLine truncated
const LEGEND = [{"cid": 0, "color": "#4E79A7", "label": "Community 0", "count": 226}, {"cid": 1, "color": "#F28E2B", "label": "Community 1", "count": 88}, {"cid": 2, "color": "#E15759", "label": "Community 2", "count": 69}, {"cid": 3, "color": "#76B7B2", "label": "Community 3", "count": 59}, {"cid": 4, "color": "#59A14F", "label": "Community 4", "count": 53}, {"cid": 5, "color": "#EDC948", "label": "Community 5", "count": 51}, {"cid": 6, "color": "#B07AA1", "label": "Community 6", "count": 48}, {"cid": 7, "color": "#FF9DA7", "label": "Community 7", "count": 39}, {"cid": 8, "color": "#9C755F", "label": "Community 8", "count": 36}, {"cid": 9, "color": "#BAB0AC", "label": "Community 9", "count": 32}, {"cid": 10, "color": "#4E79A7", "label": "Community 10", "count": 31}, {"cid": 11, "color": "#F28E2B", "label": "Community 11", "count": 28}, {"cid": 12, "color": "#E15759", "label": "Community 12", "count": 27}, {"cid": 13, "color": "#76B7B2", "label": "Community 13", "count": 27}, {"cid": 14, "color": "#59A14F", "label": "Community 14", "count": 25}, {"cid": 15, "color": "#EDC948", "label": "Community 15", "count": 24}, {"cid": 16, "color": "#B07AA1", "label": "Community 16", "count": 22}, {"cid": 17, "color": "#FF9DA7", "label": "Community 17", "count": 20}, {"cid": 18, "color": "#9C755F", "label": "Community 18", "count": 19}, {"cid": 19, "color": "#BAB0AC", "label": "Community 19", "count": 17}, {"cid": 20, "color": "#4E79A7", "label": "Community 20", "count": 17}, {"cid": 21, "color": "#F28E2B", "label": "Community 21", "count": 16}, {"cid": 22, "color": "#E15759", "label": "Community 22", "count": 16}, {"cid": 23, "color": "#76B7B2", "label": "Community 23", "count": 15}, {"cid": 24, "color": "#59A14F", "label": "Community 24", "count": 14}, {"cid": 25, "color": "#EDC948", "label": "Community 25", "count": 13}, {"cid": 26, "color": "#B07AA1", "label": "Community 26", "count": 12}, {"cid": 27, "color": "#FF9DA7", "label": "Community 27", "count": 11}, {"cid": 28, "color": "#9C755F", "label": "Community 28", "count": 10}, {"cid": 29, "color": "#BAB0AC", "label": "Community 29", "count": 9}, {"cid": 30, "color": "#4E79A7", "label": "Community 30", "count": 9}, {"cid": 31, "color": "#F28E2B", "label": "Community 31", "count": 8}, {"cid": 32, "color": "#E15759", "label": "Community 32", "count": 8}, {"cid": 33, "color": "#76B7B2", "label": "Community 33", "count": 7}, {"cid": 34, "color": "#59A14F", "label": "Community 34", "count": 7}, {"cid": 35, "color": "#EDC948", "label": "Community 35", "count": 6}, {"cid": 36, "color": "#B07AA1", "label": "Community 36", "count": 6}, {"cid": 37, "color": "#FF9DA7", "label": "Community 37", "count": 6}, {"cid": 38, "color": "#9C755F", "label": "Community 38", "count": 6}, {"cid": 39, "color": "#BAB0AC", "label": "Community 39", "count": 6}, {"cid": 40, "color": "#4E79A7", "label": "Community 40", "count": 6}, {"cid": 41, "color": "#F28E2B", "label": "Community 41", "count": 5}, {"cid": 42, "color": "#E15759", "label": "Community 42", "count": 4}, {"cid": 43, "color": "#76B7B2", "label": "Community 43", "count": 3}, {"cid": 44, "color": "#59A14F", "label": "Community 44", "count": 3}, {"cid": 45, "color": "#EDC948", "label": "Community 45", "count": 3}, {"cid": 46, "color": "#B07AA1", "label": "Community 46", "count": 3}, {"cid": 47, "color": "#FF9DA7", "label": "Community 47", "count": 3}, {"cid": 48, "color": "#9C755F", "label": "Community 48", "count": 3}, {"cid": 49, "color": "#BAB0AC", "label": "Community 49", "count": 3}, {"cid": 50, "color": "#4E79A7", "label": "Community 50", "count": 3}, {"cid": 51, "color": "#F28E2B", "label": "Community 51", "count": 3}, {"cid": 52, "color": "#E15759", "label": "Community 52", "count": 3}, {"cid": 53, "color": "#76B7B2", "label": "Community 53", "count": 3}, {"cid": 54, "color": "#59A14F", "label": "Community 54", "count": 3}, {"cid": 55, "color": "#EDC948", "label": "Community 55", "count": 2}, {"cid": 56, "color": "#B07AA1", "label": "Community 56", "count": 2}, {"cid": 57, "color": "#FF9DA7", "label": "Community 57", "count": 2}, {"cid": 58, "color": "#9C755F", "label": "Community 58", "count": 2}, {"cid": 59, "color": "#BAB0AC", "label": "Community 59", "count": 2}, {"cid": 60, "color": "#4E79A7", "label": "Community 60", "count": 2}, {"cid": 61, "color": "#F28E2B", "label": "Community 61", "count": 2}, {"cid": 62, "color": "#E15759", "label": "Community 62", "count": 2}, {"cid": 63, "color": "#76B7B2", "label": "Community 63", "count": 2}, {"cid": 64, "color": "#59A14F", "label": "Community 64", "count": 2}, {"cid": 65, "color": "#EDC948", "label": "Community 65", "count": 2}, {"cid": 66, "color": "#B07AA1", "label": "Community 66", "count": 2}, {"cid": 67, "color": "#FF9DA7", "label": "Community 67", "count": 2}, {"cid": 68, "color": "#9C755F", "label": "Community 68", "count": 2}, {"cid": 69, "color": "#BAB0AC", "label": "Community 69", "count": 2}, {"cid": 70, "color": "#4E79A7", "label": "Community 70", "count": 2}, {"cLine truncated
// HTML-escape helper — prevents XSS when injecting graph data into innerHTML
function esc(s) {
+6 -6
View File
@@ -17,8 +17,8 @@
"/Users/wuzilong/work/00code/zkxw/web-admin/src/types/common/index.ts": 1778582068.0254738,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/types/directive/directive.d.ts": 1778582068.026978,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/types/api/api.d.ts": 1778582068.0246184,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/components.d.ts": 1779333640.1373396,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/auto-imports.d.ts": 1779313245.9581928,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/components.d.ts": 1779336687.830218,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/auto-imports.d.ts": 1779336630.5292668,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/types/store/index.ts": 1778582068.0293276,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/types/router/index.ts": 1778775532.895603,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/locales/index.ts": 1778582068.004279,
@@ -205,7 +205,7 @@
"/Users/wuzilong/work/00code/zkxw/web-admin/src/api/admin.ts": 1779310973.2465198,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/api/ethnic.ts": 1778929297.3919344,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-lab-test-item.ts": 1779017838.8231559,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/api/cell-product.ts": 1779207897.9650705,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/api/cell-product.ts": 1779336699.268955,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/api/research-donation.ts": 1779160004.1431847,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-task.ts": 1779182848.881475,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-item-param.ts": 1779022632.5658252,
@@ -269,8 +269,8 @@
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/dict/index.vue": 1779259163.7560437,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/leave/index.vue": 1779257191.2768743,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/output-type/index.vue": 1779307917.5932443,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/index.vue": 1779227504.3581011,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/process-route/index.vue": 1779333730.1243677,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/index.vue": 1779337180.3946729,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/process-route/index.vue": 1779337296.3522038,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/sop/index.vue": 1779309609.6630545,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-task/index.vue": 1779269660.0717294,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/reagent/index.vue": 1779307562.93293,
@@ -284,7 +284,7 @@
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/sample-reception-check-item/index.vue": 1779269589.0865154,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/lab-test-item/index.vue": 1779331328.5864854,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-material/index.vue": 1779284232.1223023,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/sample-reception/index.vue": 1779328630.8453777,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/sample-reception/index.vue": 1779333860.8573632,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-task/index.vue": 1779333720.422681,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-material-usage/index.vue": 1779285902.4727366,
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/third-party-company/index.vue": 1779330250.9402645,
+6
View File
@@ -4,6 +4,8 @@ export function fetchCellProductList(params?: {
page?: number
pageSize?: number
keyword?: string
productStatus?: string
passage?: string
}) {
return request.get<{
list: any[]
@@ -29,3 +31,7 @@ export function fetchUpdateCellProduct(id: string, data: Record<string, any>) {
export function fetchDeleteCellProduct(id: string) {
return request.del({ url: `/web/admin/cell-products/${id}` })
}
export function fetchNextCellProductCode() {
return request.get<{ code: string }>({ url: '/web/admin/cell-products/next-code' })
}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_process_route_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/process-route/index.vue", "source_location": "L1"}, {"id": "process_route_index_if", "label": "if()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/process-route/index.vue", "source_location": "L1024"}, {"id": "process_route_index_get", "label": "get()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/process-route/index.vue", "source_location": "L1089"}, {"id": "process_route_index_set", "label": "set()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/process-route/index.vue", "source_location": "L1099"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_process_route_index_vue", "target": "process_route_index_if", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/process-route/index.vue", "source_location": "L1024", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_process_route_index_vue", "target": "process_route_index_get", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/process-route/index.vue", "source_location": "L1089", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_process_route_index_vue", "target": "process_route_index_set", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/process-route/index.vue", "source_location": "L1099", "weight": 1.0}], "raw_calls": [{"caller_nid": "process_route_index_if", "callee": "mapReagentOptionLabel", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/process-route/index.vue", "source_location": "L1026"}, {"caller_nid": "process_route_index_get", "callee": "isArray", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/process-route/index.vue", "source_location": "L1093"}, {"caller_nid": "process_route_index_get", "callee": "filter", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/process-route/index.vue", "source_location": "L1093"}, {"caller_nid": "process_route_index_get", "callee": "filter", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/process-route/index.vue", "source_location": "L1094"}, {"caller_nid": "process_route_index_get", "callee": "map", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/process-route/index.vue", "source_location": "L1094"}, {"caller_nid": "process_route_index_get", "callee": "split", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/process-route/index.vue", "source_location": "L1094"}, {"caller_nid": "process_route_index_get", "callee": "String", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/process-route/index.vue", "source_location": "L1094"}, {"caller_nid": "process_route_index_set", "callee": "join", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/process-route/index.vue", "source_location": "L1101"}]}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_types_import_components_d_ts", "label": "components.d.ts", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/components.d.ts", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_api_cell_product_ts", "label": "cell-product.ts", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/cell-product.ts", "source_location": "L1"}, {"id": "api_cell_product_fetchcellproductlist", "label": "fetchCellProductList()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/cell-product.ts", "source_location": "L3"}, {"id": "api_cell_product_fetchcellproductdetail", "label": "fetchCellProductDetail()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/cell-product.ts", "source_location": "L19"}, {"id": "api_cell_product_fetchcreatecellproduct", "label": "fetchCreateCellProduct()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/cell-product.ts", "source_location": "L23"}, {"id": "api_cell_product_fetchupdatecellproduct", "label": "fetchUpdateCellProduct()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/cell-product.ts", "source_location": "L27"}, {"id": "api_cell_product_fetchdeletecellproduct", "label": "fetchDeleteCellProduct()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/cell-product.ts", "source_location": "L31"}, {"id": "api_cell_product_fetchnextcellproductcode", "label": "fetchNextCellProductCode()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/cell-product.ts", "source_location": "L35"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_cell_product_ts", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_utils_http", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/cell-product.ts", "source_location": "L1", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_cell_product_ts", "target": "api_cell_product_fetchcellproductlist", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/cell-product.ts", "source_location": "L3", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_cell_product_ts", "target": "api_cell_product_fetchcellproductdetail", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/cell-product.ts", "source_location": "L19", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_cell_product_ts", "target": "api_cell_product_fetchcreatecellproduct", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/cell-product.ts", "source_location": "L23", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_cell_product_ts", "target": "api_cell_product_fetchupdatecellproduct", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/cell-product.ts", "source_location": "L27", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_cell_product_ts", "target": "api_cell_product_fetchdeletecellproduct", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/cell-product.ts", "source_location": "L31", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_api_cell_product_ts", "target": "api_cell_product_fetchnextcellproductcode", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/cell-product.ts", "source_location": "L35", "weight": 1.0}], "raw_calls": [{"caller_nid": "api_cell_product_fetchcellproductlist", "callee": "get", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/cell-product.ts", "source_location": "L10"}, {"caller_nid": "api_cell_product_fetchcellproductdetail", "callee": "get", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/cell-product.ts", "source_location": "L20"}, {"caller_nid": "api_cell_product_fetchcreatecellproduct", "callee": "post", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/cell-product.ts", "source_location": "L24"}, {"caller_nid": "api_cell_product_fetchupdatecellproduct", "callee": "put", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/cell-product.ts", "source_location": "L28"}, {"caller_nid": "api_cell_product_fetchdeletecellproduct", "callee": "del", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/cell-product.ts", "source_location": "L32"}, {"caller_nid": "api_cell_product_fetchnextcellproductcode", "callee": "get", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/cell-product.ts", "source_location": "L36"}]}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_cell_product_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/index.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}
@@ -0,0 +1 @@
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_sample_reception_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/sample-reception/index.vue", "source_location": "L1"}, {"id": "sample_reception_index_parseetalocaldate", "label": "parseEtaLocalDate()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/sample-reception/index.vue", "source_location": "L683"}, {"id": "sample_reception_index_startoflocalday", "label": "startOfLocalDay()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/sample-reception/index.vue", "source_location": "L697"}, {"id": "sample_reception_index_isetatodayorearlier", "label": "isEtaTodayOrEarlier()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/sample-reception/index.vue", "source_location": "L701"}, {"id": "sample_reception_index_samplereceptionrowclassname", "label": "sampleReceptionRowClassName()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/sample-reception/index.vue", "source_location": "L707"}, {"id": "sample_reception_index_pad", "label": "pad()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/sample-reception/index.vue", "source_location": "L832"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_sample_reception_index_vue", "target": "sample_reception_index_parseetalocaldate", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/sample-reception/index.vue", "source_location": "L683", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_sample_reception_index_vue", "target": "sample_reception_index_startoflocalday", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/sample-reception/index.vue", "source_location": "L697", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_sample_reception_index_vue", "target": "sample_reception_index_isetatodayorearlier", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/sample-reception/index.vue", "source_location": "L701", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_sample_reception_index_vue", "target": "sample_reception_index_samplereceptionrowclassname", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/sample-reception/index.vue", "source_location": "L707", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_quality_inspection_sample_reception_index_vue", "target": "sample_reception_index_pad", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/sample-reception/index.vue", "source_location": "L832", "weight": 1.0}, {"source": "sample_reception_index_isetatodayorearlier", "target": "sample_reception_index_parseetalocaldate", "relation": "calls", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/sample-reception/index.vue", "source_location": "L702", "weight": 1.0}, {"source": "sample_reception_index_isetatodayorearlier", "target": "sample_reception_index_startoflocalday", "relation": "calls", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/sample-reception/index.vue", "source_location": "L704", "weight": 1.0}, {"source": "sample_reception_index_samplereceptionrowclassname", "target": "sample_reception_index_isetatodayorearlier", "relation": "calls", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/sample-reception/index.vue", "source_location": "L709", "weight": 1.0}], "raw_calls": [{"caller_nid": "sample_reception_index_parseetalocaldate", "callee": "trim", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/sample-reception/index.vue", "source_location": "L684"}, {"caller_nid": "sample_reception_index_parseetalocaldate", "callee": "String", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/sample-reception/index.vue", "source_location": "L684"}, {"caller_nid": "sample_reception_index_parseetalocaldate", "callee": "slice", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/srLine truncated
File diff suppressed because it is too large. Load diff
@@ -403,9 +403,28 @@
<el-input v-model="row.reagentCode" placeholder="编码" class="inner-cell-input" @change="saveReagents" />
</template>
</el-table-column>
<el-table-column label="试剂名称" min-width="120" align="center" header-align="center">
<el-table-column label="试剂名称" min-width="160" align="center" header-align="center">
<template #default="{ row }">
<el-input v-model="row.reagentName" placeholder="名称" class="inner-cell-input" @change="saveReagents" />
<el-select
v-model="row.reagentId"
placeholder="请选择试剂"
clearable
filterable
remote
reserve-keyword
class="inner-cell-select"
:loading="reagentOptionsLoading"
:remote-method="searchReagentOptions"
@visible-change="(visible) => visible && searchReagentOptions('')"
@change="(val) => onReagentRowSelect(row, val)"
>
<el-option
v-for="item in reagentOptions"
:key="item.id"
:label="item.label"
:value="item.id"
/>
</el-select>
</template>
</el-table-column>
<el-table-column label="规格" min-width="100" align="center" header-align="center">
@@ -418,9 +437,28 @@
<el-input v-model="row.standardUsage" placeholder="用量" class="inner-cell-input" @change="saveReagents" />
</template>
</el-table-column>
<el-table-column label="单位" min-width="80" align="center" header-align="center">
<el-table-column label="单位" min-width="120" align="center" header-align="center">
<template #default="{ row }">
<el-input v-model="row.unit" placeholder="单位" class="inner-cell-input" @change="saveReagents" />
<el-select
v-model="row.unit"
placeholder="请选择单位"
clearable
filterable
remote
reserve-keyword
class="inner-cell-select"
:loading="itemUnitOptionsLoading"
:remote-method="searchItemUnitOptions"
@visible-change="(visible) => visible && searchItemUnitOptions('')"
@change="saveReagents"
>
<el-option
v-for="item in itemUnitOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</template>
</el-table-column>
<el-table-column label="关键物料" width="90" align="center" header-align="center">
@@ -464,9 +502,28 @@
<el-input v-model="row.consumableCode" placeholder="编码" class="inner-cell-input" @change="saveConsumables" />
</template>
</el-table-column>
<el-table-column label="耗材名称" min-width="120" align="center" header-align="center">
<el-table-column label="耗材名称" min-width="160" align="center" header-align="center">
<template #default="{ row }">
<el-input v-model="row.consumableName" placeholder="名称" class="inner-cell-input" @change="saveConsumables" />
<el-select
v-model="row.consumableId"
placeholder="请选择耗材"
clearable
filterable
remote
reserve-keyword
class="inner-cell-select"
:loading="consumableOptionsLoading"
:remote-method="searchConsumableOptions"
@visible-change="(visible) => visible && searchConsumableOptions('')"
@change="(val) => onConsumableRowSelect(row, val)"
>
<el-option
v-for="item in consumableOptions"
:key="item.id"
:label="item.label"
:value="item.id"
/>
</el-select>
</template>
</el-table-column>
<el-table-column label="规格型号" min-width="110" align="center" header-align="center">
@@ -479,9 +536,28 @@
<el-input v-model="row.standardUsage" placeholder="用量" class="inner-cell-input" @change="saveConsumables" />
</template>
</el-table-column>
<el-table-column label="单位" min-width="80" align="center" header-align="center">
<el-table-column label="单位" min-width="120" align="center" header-align="center">
<template #default="{ row }">
<el-input v-model="row.unit" placeholder="单位" class="inner-cell-input" @change="saveConsumables" />
<el-select
v-model="row.unit"
placeholder="请选择单位"
clearable
filterable
remote
reserve-keyword
class="inner-cell-select"
:loading="itemUnitOptionsLoading"
:remote-method="searchItemUnitOptions"
@visible-change="(visible) => visible && searchItemUnitOptions('')"
@change="saveConsumables"
>
<el-option
v-for="item in itemUnitOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</template>
</el-table-column>
<el-table-column label="必需" width="80" align="center" header-align="center">
@@ -580,16 +656,33 @@
</el-table-column>
<el-table-column label="单位" min-width="120" align="center" header-align="center">
<template #default="{ row }">
<el-input v-model="row.unit" placeholder="如 °C、mL" class="inner-cell-input" @change="saveDataCollections" />
<el-select
v-model="row.unit"
placeholder="请选择单位"
clearable
filterable
remote
reserve-keyword
class="inner-cell-select"
:loading="itemUnitOptionsLoading"
:remote-method="searchItemUnitOptions"
@visible-change="(visible) => visible && searchItemUnitOptions('')"
@change="saveDataCollections"
>
<el-option
v-for="item in itemUnitOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</template>
</el-table-column>
<el-table-column label="排序号" min-width="100" align="center" header-align="center">
<template #default="{ row }">
<el-input-number
<el-input
v-model="row.orderNum"
:min="0"
:max="9999"
controls-position="right"
placeholder="排序号"
class="inner-cell-input"
@change="saveDataCollections"
/>
@@ -643,12 +736,14 @@
fetchUpdateStepConsumables,
fetchUpdateStepOutputs,
fetchUpdateStepDataCollections,
fetchReagentList,
fetchConsumableList,
fetchAllProcesses,
fetchAllProcessCategories,
fetchAllSopDocuments,
fetchAllOutputTypes
} from '@/api/production'
import { fetchCellProductTypeList } from '@/api/admin'
import { fetchCellProductTypeList, fetchItemUnitList } from '@/api/admin'
defineOptions({ name: 'ProductionProcessRoute' })
@@ -870,6 +965,234 @@
const categoryOptions = ref<Record<string, any>[]>([])
const sopOptions = ref<Record<string, any>[]>([])
const outputTypeOptions = ref<Record<string, any>[]>([])
const itemUnitOptions = ref<{ label: string; value: string }[]>([])
const itemUnitOptionsLoading = ref(false)
const reagentOptions = ref<{ id: string; label: string }[]>([])
const reagentOptionsLoading = ref(false)
const reagentMasterById = ref<Record<string, Record<string, any>>>({})
const consumableOptions = ref<{ id: string; label: string }[]>([])
const consumableOptionsLoading = ref(false)
const consumableMasterById = ref<Record<string, Record<string, any>>>({})
function mapItemUnitOption(u: { unitName?: string; unitCode?: string }) {
const name = (u.unitName || '').trim()
const code = (u.unitCode || '').trim()
return {
label: code ? `${name}(${code})` : name,
value: name
}
}
function ensureItemUnitOption(unitName: string) {
const v = unitName?.trim()
if (!v) return
if (!itemUnitOptions.value.some((o) => o.value === v)) {
itemUnitOptions.value = [{ label: v, value: v }, ...itemUnitOptions.value]
}
}
async function searchItemUnitOptions(query: string) {
itemUnitOptionsLoading.value = true
try {
const res = await fetchItemUnitList({
page: 1,
pageSize: 100,
keyword: query?.trim() || undefined
})
itemUnitOptions.value = (res.list || []).map(mapItemUnitOption).filter((o) => o.value)
} catch {
itemUnitOptions.value = []
} finally {
itemUnitOptionsLoading.value = false
}
}
function mapReagentOptionLabel(r: { reagentName?: string; reagentCode?: string }) {
const name = (r.reagentName || '').trim()
const code = (r.reagentCode || '').trim()
return code ? `${name}(${code})` : name
}
function cacheReagentMasters(list: Record<string, any>[]) {
const next = { ...reagentMasterById.value }
for (const r of list) {
if (r?.id) next[r.id] = r
}
reagentMasterById.value = next
}
async function searchReagentOptions(query: string) {
reagentOptionsLoading.value = true
try {
const res = await fetchReagentList({
page: 1,
pageSize: 100,
keyword: query?.trim() || undefined
})
const list = res.list || []
cacheReagentMasters(list)
const options = list
.filter((r) => (r.reagentName || '').trim() || (r.reagentCode || '').trim())
.map((r) => ({ id: r.id, label: mapReagentOptionLabel(r) }))
reagentOptions.value = options
ensureAllStepReagentOptions()
} catch {
reagentOptions.value = []
ensureAllStepReagentOptions()
} finally {
reagentOptionsLoading.value = false
}
}
function ensureReagentOption(row: Record<string, any>) {
if (row.reagentId && !String(row.reagentId).startsWith('legacy:')) {
if (!reagentOptions.value.some((o) => o.id === row.reagentId)) {
reagentOptions.value = [
{ id: row.reagentId, label: mapReagentOptionLabel(row) },
...reagentOptions.value
]
}
return
}
const code = (row.reagentCode || '').trim()
const name = (row.reagentName || '').trim()
if (!code && !name) return
const legacyId = `legacy:${code || name}`
row.reagentId = legacyId
if (!reagentOptions.value.some((o) => o.id === legacyId)) {
reagentOptions.value = [{ id: legacyId, label: mapReagentOptionLabel(row) }, ...reagentOptions.value]
}
}
function ensureAllStepReagentOptions() {
for (const step of stepList.value) {
;(step.reagents || []).forEach((r: Record<string, any>) => ensureReagentOption(r))
}
}
function onReagentRowSelect(row: Record<string, any>, reagentId: string) {
if (!reagentId) {
row.reagentId = ''
saveReagents()
return
}
if (String(reagentId).startsWith('legacy:')) {
saveReagents()
return
}
const master = reagentMasterById.value[reagentId]
if (!master) {
saveReagents()
return
}
row.reagentId = master.id
row.reagentCode = master.reagentCode || ''
row.reagentName = master.reagentName || ''
row.spec = master.spec || ''
row.standardUsage = master.standardUsage ?? ''
row.unit = master.unit || ''
row.remark = master.remark ?? master.description ?? ''
ensureItemUnitOption(row.unit)
ensureReagentOption(row)
saveReagents()
}
function mapConsumableOptionLabel(c: { consumableName?: string; consumableCode?: string }) {
const name = (c.consumableName || '').trim()
const code = (c.consumableCode || '').trim()
return code ? `${name}(${code})` : name
}
function cacheConsumableMasters(list: Record<string, any>[]) {
const next = { ...consumableMasterById.value }
for (const c of list) {
if (c?.id) next[c.id] = c
}
consumableMasterById.value = next
}
async function searchConsumableOptions(query: string) {
consumableOptionsLoading.value = true
try {
const res = await fetchConsumableList({
page: 1,
pageSize: 100,
keyword: query?.trim() || undefined
})
const list = res.list || []
cacheConsumableMasters(list)
consumableOptions.value = list
.filter((c) => (c.consumableName || '').trim() || (c.consumableCode || '').trim())
.map((c) => ({ id: c.id, label: mapConsumableOptionLabel(c) }))
ensureAllStepConsumableOptions()
} catch {
consumableOptions.value = []
ensureAllStepConsumableOptions()
} finally {
consumableOptionsLoading.value = false
}
}
function ensureConsumableOption(row: Record<string, any>) {
if (row.consumableId && !String(row.consumableId).startsWith('legacy:')) {
if (!consumableOptions.value.some((o) => o.id === row.consumableId)) {
consumableOptions.value = [
{ id: row.consumableId, label: mapConsumableOptionLabel(row) },
...consumableOptions.value
]
}
return
}
const code = (row.consumableCode || '').trim()
const name = (row.consumableName || '').trim()
if (!code && !name) return
const legacyId = `legacy:${code || name}`
row.consumableId = legacyId
if (!consumableOptions.value.some((o) => o.id === legacyId)) {
consumableOptions.value = [{ id: legacyId, label: mapConsumableOptionLabel(row) }, ...consumableOptions.value]
}
}
function ensureAllStepConsumableOptions() {
for (const step of stepList.value) {
;(step.consumables || []).forEach((c: Record<string, any>) => ensureConsumableOption(c))
}
}
function onConsumableRowSelect(row: Record<string, any>, consumableId: string) {
if (!consumableId) {
row.consumableId = ''
saveConsumables()
return
}
if (String(consumableId).startsWith('legacy:')) {
saveConsumables()
return
}
const master = consumableMasterById.value[consumableId]
if (!master) {
saveConsumables()
return
}
row.consumableId = master.id
row.consumableCode = master.consumableCode || ''
row.consumableName = master.consumableName || ''
row.specModel = master.specModel || ''
row.standardUsage = master.standardUsage ?? ''
row.unit = master.unit || ''
row.remark = master.remark ?? master.description ?? ''
ensureItemUnitOption(row.unit)
ensureConsumableOption(row)
saveConsumables()
}
function ensureAllStepUnitOptions() {
for (const step of stepList.value) {
;(step.reagents || []).forEach((r: Record<string, any>) => ensureItemUnitOption(r.unit))
;(step.consumables || []).forEach((c: Record<string, any>) => ensureItemUnitOption(c.unit))
;(step.dataCollections || []).forEach((d: Record<string, any>) => ensureItemUnitOption(d.unit))
}
}
const selectedStep = computed(() => {
if (!selectedStepId.value) return null
@@ -956,6 +1279,7 @@
// 展开所有分类
expandedCategories.value = Object.keys(catMap)
processLibrary.value = Object.values(catMap)
await Promise.all([searchItemUnitOptions(''), searchReagentOptions(''), searchConsumableOptions('')])
} catch {
/* ignore */
}
@@ -984,6 +1308,9 @@
try {
const detail = await fetchProcessRouteDetail(configRouteId.value)
stepList.value = (detail.steps || []).map((s: Record<string, any>) => normalizeStepSubTables(s))
ensureAllStepReagentOptions()
ensureAllStepConsumableOptions()
ensureAllStepUnitOptions()
if (stepList.value.length > 0 && !selectedStepId.value) {
selectedStepId.value = stepList.value[0].id
}
@@ -1103,6 +1430,7 @@
if (!selectedStep.value) return
if (!selectedStep.value.reagents) selectedStep.value.reagents = []
selectedStep.value.reagents.push({
reagentId: '',
reagentCode: '',
reagentName: '',
spec: '',
@@ -1144,6 +1472,7 @@
if (!selectedStep.value) return
if (!selectedStep.value.consumables) selectedStep.value.consumables = []
selectedStep.value.consumables.push({
consumableId: '',
consumableCode: '',
consumableName: '',
specModel: '',
@@ -1216,6 +1545,12 @@
}
// --- 数据采集 ---
function parseOrderNum(value: unknown): number {
const n = Number.parseInt(String(value ?? '').trim(), 10)
if (!Number.isFinite(n) || n < 0) return 0
return Math.min(n, 9999)
}
function addDataCollectionRow() {
if (!selectedStep.value) return
if (!selectedStep.value.dataCollections) selectedStep.value.dataCollections = []
@@ -1241,7 +1576,7 @@
items: (selectedStep.value.dataCollections || []).map((d: Record<string, any>) => ({
collectionName: d.collectionName,
unit: d.unit,
orderNum: d.orderNum ?? 0,
orderNum: parseOrderNum(d.orderNum),
remark: d.remark || ''
}))
})
@@ -1573,6 +1908,23 @@
justify-content: flex-end;
}
/* 步骤详情内嵌表格:单元格控件随列宽收缩 */
.inner-table {
width: 100%;
:deep(.inner-cell-input) {
width: 100%;
}
:deep(.inner-cell-select) {
width: 100%;
.el-select__wrapper {
min-height: 36px;
}
}
}
/* 路线图配置弹窗内的对话框滚动区域 */
.route-config-dialog {
:deep(.el-dialog__body) {