提交
This commit is contained in:
1 parent
a905d80623
commit
7541a167c0
33 files changed
+2715
-850
No files matched your search
+1
-1
@@ -1 +1 @@
|
||||
68778
|
||||
81932
|
||||
@@ -43,6 +43,7 @@ from app.schemas.production_task import (
|
||||
TaskStepDataCollectionItem,
|
||||
TaskInputItem,
|
||||
TaskOutputItem,
|
||||
TaskStepReorderBody,
|
||||
)
|
||||
from app.schemas.cell_product import CellProductCreate, CellProductUpdate
|
||||
|
||||
@@ -1435,6 +1436,39 @@ async def delete_production_task_step(
|
||||
return error_response(400, str(e))
|
||||
|
||||
|
||||
@router.put("/production-tasks/step/reorder")
|
||||
async def reorder_production_task_steps(
|
||||
body: TaskStepReorderBody,
|
||||
_user: dict = Depends(require_admin_or_super_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
try:
|
||||
production_task_svc.reorder_task_steps(db, body.task_id, body.step_ids)
|
||||
return success_response(None, message="排序成功")
|
||||
except ValueError as e:
|
||||
return error_response(400, str(e))
|
||||
|
||||
|
||||
@router.post("/production-tasks/{task_id}/steps/{step_id}/copy")
|
||||
async def copy_production_task_step(
|
||||
task_id: str,
|
||||
step_id: str,
|
||||
user: dict = Depends(require_admin_or_super_admin),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
try:
|
||||
tenant_no = _get_tenant_no(db, user)
|
||||
step_data = production_task_svc.copy_task_step(
|
||||
db,
|
||||
step_id=step_id,
|
||||
tenant_no=tenant_no,
|
||||
user_id=user.get("user_id"),
|
||||
)
|
||||
return success_response(step_data, message="复制成功")
|
||||
except ValueError as e:
|
||||
return error_response(400, str(e))
|
||||
|
||||
|
||||
@router.put("/production-tasks/{task_id}/steps/{step_id}/reagents")
|
||||
async def replace_task_step_reagents(
|
||||
task_id: str,
|
||||
|
||||
@@ -304,7 +304,7 @@ def update_cell_product(
|
||||
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"}}
|
||||
allowed = {"frozen": {"sold", "used", "voided"}}
|
||||
from_status = obj.product_status
|
||||
to_status = data.product_status
|
||||
if from_status not in allowed or to_status not in allowed[from_status]:
|
||||
|
||||
@@ -10,6 +10,7 @@ from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.utils import generate_uuid
|
||||
from app.models.models import (
|
||||
BizProcessRoute,
|
||||
BizProcessRouteStep,
|
||||
BizProcessRouteStepConsumable,
|
||||
BizProcessRouteStepDataCollection,
|
||||
@@ -348,7 +349,19 @@ def list_production_tasks(
|
||||
.limit(page_size)
|
||||
.all()
|
||||
)
|
||||
return [_task_to_dict(x) for x in rows], int(total)
|
||||
items = [_task_to_dict(x) for x in rows]
|
||||
# 批量补充路线名称
|
||||
route_ids = [x.route_id for x in rows if x.route_id]
|
||||
if route_ids:
|
||||
route_map = {
|
||||
r.id: r.route_name
|
||||
for r in db.query(BizProcessRoute.id, BizProcessRoute.route_name)
|
||||
.filter(BizProcessRoute.id.in_(route_ids))
|
||||
.all()
|
||||
}
|
||||
for item, row in zip(items, rows):
|
||||
item["routeName"] = route_map.get(row.route_id, "")
|
||||
return items, int(total)
|
||||
|
||||
|
||||
def get_production_task_detail(db: Session, task_id: str, tenant_no: str) -> Optional[Dict[str, Any]]:
|
||||
@@ -690,6 +703,124 @@ def set_task_outputs(
|
||||
db.commit()
|
||||
|
||||
|
||||
def reorder_task_steps(db: Session, task_id: str, step_ids: List[str]) -> None:
|
||||
now = datetime.now()
|
||||
for idx, sid in enumerate(step_ids):
|
||||
step = db.query(BizProductionTaskStep).filter(
|
||||
and_(BizProductionTaskStep.id == sid, BizProductionTaskStep.task_id == task_id,
|
||||
BizProductionTaskStep.del_flag == 0)
|
||||
).first()
|
||||
if step:
|
||||
step.step_order = idx + 1
|
||||
step.update_time = now
|
||||
db.add(step)
|
||||
db.commit()
|
||||
|
||||
|
||||
def copy_task_step(db: Session, step_id: str, *, tenant_no: str = "zkxw",
|
||||
user_id: Optional[str] = None) -> Dict[str, Any]:
|
||||
"""复制生产任务步骤,插入到原步骤之后,名称追加(复制)"""
|
||||
src = db.query(BizProductionTaskStep).filter(
|
||||
and_(BizProductionTaskStep.id == step_id, BizProductionTaskStep.del_flag == 0)
|
||||
).first()
|
||||
if not src:
|
||||
raise ValueError("步骤不存在")
|
||||
|
||||
now = datetime.now()
|
||||
new_order = src.step_order + 1
|
||||
|
||||
# 后续步骤 step_order +1
|
||||
later_steps = db.query(BizProductionTaskStep).filter(
|
||||
and_(
|
||||
BizProductionTaskStep.task_id == src.task_id,
|
||||
BizProductionTaskStep.del_flag == 0,
|
||||
BizProductionTaskStep.step_order > src.step_order,
|
||||
)
|
||||
).order_by(BizProductionTaskStep.step_order.asc()).all()
|
||||
for ls in later_steps:
|
||||
ls.step_order += 1
|
||||
ls.update_time = now
|
||||
db.add(ls)
|
||||
|
||||
# 创建新步骤
|
||||
new_name = (src.process_name or "").strip()
|
||||
if not new_name.endswith("(复制)"):
|
||||
new_name += "(复制)"
|
||||
new_step = BizProductionTaskStep(
|
||||
id=generate_uuid(), task_id=src.task_id,
|
||||
step_order=new_order,
|
||||
process_code=src.process_code, category_id=src.category_id,
|
||||
process_name=new_name, description=src.description,
|
||||
equipment_name=src.equipment_name, estimated_duration=src.estimated_duration,
|
||||
actual_duration=src.actual_duration, sop_ids=src.sop_ids,
|
||||
step_status="dzx",
|
||||
tenant_no=tenant_no, order_num=0,
|
||||
remark=src.remark, create_by=user_id, create_dept_id="",
|
||||
create_time=now, update_by=user_id, update_time=now, del_flag=0,
|
||||
)
|
||||
db.add(new_step)
|
||||
db.flush()
|
||||
|
||||
# 复制子表:试剂(从生产任务步骤试剂表复制)
|
||||
for r in db.query(BizProductionTaskStepReagent).filter(
|
||||
and_(BizProductionTaskStepReagent.step_id == step_id, BizProductionTaskStepReagent.del_flag == 0)
|
||||
).all():
|
||||
db.add(BizProductionTaskStepReagent(
|
||||
id=generate_uuid(), step_id=new_step.id, tenant_no=tenant_no,
|
||||
reagent_code=r.reagent_code, reagent_name=r.reagent_name,
|
||||
spec=r.spec, standard_usage=r.standard_usage,
|
||||
actual_usage=r.actual_usage,
|
||||
unit=r.unit, is_key_material=r.is_key_material,
|
||||
remark=r.remark,
|
||||
order_num=0, create_by=user_id, create_dept_id="",
|
||||
create_time=now, update_by=user_id, update_time=now, del_flag=0,
|
||||
))
|
||||
|
||||
# 复制子表:耗材
|
||||
for c in db.query(BizProductionTaskStepConsumable).filter(
|
||||
and_(BizProductionTaskStepConsumable.step_id == step_id, BizProductionTaskStepConsumable.del_flag == 0)
|
||||
).all():
|
||||
db.add(BizProductionTaskStepConsumable(
|
||||
id=generate_uuid(), step_id=new_step.id, tenant_no=tenant_no,
|
||||
consumable_code=c.consumable_code, consumable_name=c.consumable_name,
|
||||
spec_model=c.spec_model, standard_usage=c.standard_usage,
|
||||
actual_usage=c.actual_usage,
|
||||
unit=c.unit, is_required=c.is_required,
|
||||
remark=c.remark,
|
||||
order_num=0, create_by=user_id, create_dept_id="",
|
||||
create_time=now, update_by=user_id, update_time=now, del_flag=0,
|
||||
))
|
||||
|
||||
# 复制子表:产出
|
||||
for o in db.query(BizProductionTaskStepOutput).filter(
|
||||
and_(BizProductionTaskStepOutput.step_id == step_id, BizProductionTaskStepOutput.del_flag == 0)
|
||||
).all():
|
||||
db.add(BizProductionTaskStepOutput(
|
||||
id=generate_uuid(), step_id=new_step.id, tenant_no=tenant_no,
|
||||
output_type_id=o.output_type_id, output_name=o.output_name,
|
||||
output_barcode=o.output_barcode,
|
||||
remark=o.remark,
|
||||
order_num=0, create_by=user_id, create_dept_id="",
|
||||
create_time=now, update_by=user_id, update_time=now, del_flag=0,
|
||||
))
|
||||
|
||||
# 复制子表:数据采集
|
||||
for d in db.query(BizProductionTaskStepDataCollection).filter(
|
||||
and_(BizProductionTaskStepDataCollection.step_id == step_id, BizProductionTaskStepDataCollection.del_flag == 0)
|
||||
).all():
|
||||
db.add(BizProductionTaskStepDataCollection(
|
||||
id=generate_uuid(), step_id=new_step.id, tenant_no=tenant_no,
|
||||
collection_name=d.collection_name, unit=d.unit,
|
||||
collection_value=d.collection_value,
|
||||
remark=d.remark,
|
||||
order_num=0, create_by=user_id, create_dept_id="",
|
||||
create_time=now, update_by=user_id, update_time=now, del_flag=0,
|
||||
))
|
||||
|
||||
db.commit()
|
||||
return _step_to_dict(new_step)
|
||||
|
||||
|
||||
def total_pages(total: int, page_size: int) -> int:
|
||||
if total <= 0:
|
||||
return 0
|
||||
|
||||
@@ -2135,7 +2135,7 @@ class BizCellProduct(Base):
|
||||
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已使用")
|
||||
product_status = Column(String(16), nullable=True, default="frozen", comment="产品状态 frozen已冻存/sold已售卖/used已使用/voided已作废")
|
||||
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="数量")
|
||||
|
||||
@@ -118,8 +118,8 @@ class CellProductUpdate(BaseModel):
|
||||
@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")
|
||||
if v is not None and v not in ("frozen", "sold", "used", "voided"):
|
||||
raise ValueError("状态必须为 frozen/sold/used/voided")
|
||||
return v
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from pydantic import BaseModel
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
from typing import Optional, List
|
||||
from datetime import datetime
|
||||
|
||||
@@ -6,7 +6,7 @@ from datetime import datetime
|
||||
class ProductionTaskCreate(BaseModel):
|
||||
work_order_id: str
|
||||
task_name: Optional[str] = None
|
||||
route_id: str
|
||||
route_id: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
remark: Optional[str] = None
|
||||
|
||||
@@ -91,3 +91,9 @@ class TaskOutputItem(BaseModel):
|
||||
unit: Optional[str] = None
|
||||
remark: Optional[str] = None
|
||||
attachments: Optional[str] = None
|
||||
|
||||
|
||||
class TaskStepReorderBody(BaseModel):
|
||||
model_config = ConfigDict(populate_by_name=True)
|
||||
task_id: str = Field(..., alias="taskId")
|
||||
step_ids: List[str] = Field(..., alias="stepIds")
|
||||
@@ -36,6 +36,20 @@
|
||||
- **WHEN** 增加新表或字段
|
||||
- **THEN** 仓库 SHALL 包含可复现的 schema 片段或迁移,且与 OpenSpec `database-conventions` 中的公共字段规则一致
|
||||
|
||||
### Requirement: 单文件行数上限
|
||||
|
||||
前端(`web-admin`)与后端(`backend`)的单个源码文件 SHALL 不超过 500 行。当文件因功能增长接近或超过此限制时,SHALL 将其拆分为多个职责单一的小文件(如:前端提取子组件、composable 或工具函数;后端按领域拆分 service、crud 模块或辅助函数)。配置文件、类型声明文件、SQL 脚本与自动生成文件不在此限制范围内。
|
||||
|
||||
#### Scenario: 前端页面文件增长
|
||||
|
||||
- **WHEN** 一个 Vue 组件文件超过 500 行
|
||||
- **THEN** SHALL 将弹窗、复杂表格、可复用面板等拆分为独立子组件或 composable 函数,确保主入口文件在 500 行以内
|
||||
|
||||
#### Scenario: 后端模块文件增长
|
||||
|
||||
- **WHEN** 一个 Python 模块文件(api route、service、crud 等)超过 500 行
|
||||
- **THEN** SHALL 按领域或功能边界拆分为多个子模块(如 `crud/production_task.py` → `crud/production_task.py` + `crud/production_task_step.py`),确保单个文件在 500 行以内
|
||||
|
||||
### Requirement: 提交说明可读性
|
||||
|
||||
提交信息 SHALL 使用完整句子说明「改了什么、为何」,并符合仓库 Conventional Commits / commitlint 类型约束。
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -8324,18 +8324,53 @@
|
||||
"source_location": "L1"
|
||||
},
|
||||
{
|
||||
"id": "production_work_order_index_handleview",
|
||||
"label": "handleView()",
|
||||
"id": "production_work_order_index_layoutworkordertable",
|
||||
"label": "layoutWorkOrderTable()",
|
||||
"file_type": "code",
|
||||
"source_file": "views/production/production-work-order/index.vue",
|
||||
"source_location": "L319"
|
||||
"source_location": "L280"
|
||||
},
|
||||
{
|
||||
"id": "production_work_order_index_handlecomplete",
|
||||
"label": "handleComplete()",
|
||||
"id": "production_work_order_index_loaddata",
|
||||
"label": "loadData()",
|
||||
"file_type": "code",
|
||||
"source_file": "views/production/production-work-order/index.vue",
|
||||
"source_location": "L325"
|
||||
"source_location": "L285"
|
||||
},
|
||||
{
|
||||
"id": "production_work_order_index_handlesearch",
|
||||
"label": "handleSearch()",
|
||||
"file_type": "code",
|
||||
"source_file": "views/production/production-work-order/index.vue",
|
||||
"source_location": "L302"
|
||||
},
|
||||
{
|
||||
"id": "production_work_order_index_handlereset",
|
||||
"label": "handleReset()",
|
||||
"file_type": "code",
|
||||
"source_file": "views/production/production-work-order/index.vue",
|
||||
"source_location": "L307"
|
||||
},
|
||||
{
|
||||
"id": "production_work_order_index_handlereceptionattachmentpreview",
|
||||
"label": "handleReceptionAttachmentPreview()",
|
||||
"file_type": "code",
|
||||
"source_file": "views/production/production-work-order/index.vue",
|
||||
"source_location": "L345"
|
||||
},
|
||||
{
|
||||
"id": "production_work_order_index_handlereceptionattachmentdownload",
|
||||
"label": "handleReceptionAttachmentDownload()",
|
||||
"file_type": "code",
|
||||
"source_file": "views/production/production-work-order/index.vue",
|
||||
"source_location": "L350"
|
||||
},
|
||||
{
|
||||
"id": "production_work_order_index_formatmetric",
|
||||
"label": "formatMetric()",
|
||||
"file_type": "code",
|
||||
"source_file": "views/production/production-work-order/index.vue",
|
||||
"source_location": "L354"
|
||||
},
|
||||
{
|
||||
"id": "views_production_consumable_pack_index_vue",
|
||||
@@ -23834,20 +23869,200 @@
|
||||
},
|
||||
{
|
||||
"source": "views_production_production_work_order_index_vue",
|
||||
"target": "production_work_order_index_handleview",
|
||||
"relation": "contains",
|
||||
"target": "element_plus",
|
||||
"relation": "imports_from",
|
||||
"confidence": "EXTRACTED",
|
||||
"source_file": "views/production/production-work-order/index.vue",
|
||||
"source_location": "L319",
|
||||
"source_location": "L227",
|
||||
"weight": 1.0
|
||||
},
|
||||
{
|
||||
"source": "views_production_production_work_order_index_vue",
|
||||
"target": "production_work_order_index_handlecomplete",
|
||||
"target": "element_plus",
|
||||
"relation": "imports_from",
|
||||
"confidence": "EXTRACTED",
|
||||
"source_file": "views/production/production-work-order/index.vue",
|
||||
"source_location": "L228",
|
||||
"weight": 1.0
|
||||
},
|
||||
{
|
||||
"source": "views_production_production_work_order_index_vue",
|
||||
"target": "icons_vue",
|
||||
"relation": "imports_from",
|
||||
"confidence": "EXTRACTED",
|
||||
"source_file": "views/production/production-work-order/index.vue",
|
||||
"source_location": "L229",
|
||||
"weight": 1.0
|
||||
},
|
||||
{
|
||||
"source": "views_production_production_work_order_index_vue",
|
||||
"target": "users_wuzilong_work_00code_zkxw_web_admin_src_api_production_work_order",
|
||||
"relation": "imports_from",
|
||||
"confidence": "EXTRACTED",
|
||||
"source_file": "views/production/production-work-order/index.vue",
|
||||
"source_location": "L230",
|
||||
"weight": 1.0
|
||||
},
|
||||
{
|
||||
"source": "views_production_production_work_order_index_vue",
|
||||
"target": "users_wuzilong_work_00code_zkxw_web_admin_src_api_production_task",
|
||||
"relation": "imports_from",
|
||||
"confidence": "EXTRACTED",
|
||||
"source_file": "views/production/production-work-order/index.vue",
|
||||
"source_location": "L236",
|
||||
"weight": 1.0
|
||||
},
|
||||
{
|
||||
"source": "views_production_production_work_order_index_vue",
|
||||
"target": "users_wuzilong_work_00code_zkxw_web_admin_src_api_production",
|
||||
"relation": "imports_from",
|
||||
"confidence": "EXTRACTED",
|
||||
"source_file": "views/production/production-work-order/index.vue",
|
||||
"source_location": "L239",
|
||||
"weight": 1.0
|
||||
},
|
||||
{
|
||||
"source": "views_production_production_work_order_index_vue",
|
||||
"target": "users_wuzilong_work_00code_zkxw_web_admin_src_utils_qc_labels",
|
||||
"relation": "imports_from",
|
||||
"confidence": "EXTRACTED",
|
||||
"source_file": "views/production/production-work-order/index.vue",
|
||||
"source_location": "L243",
|
||||
"weight": 1.0
|
||||
},
|
||||
{
|
||||
"source": "views_production_production_work_order_index_vue",
|
||||
"target": "views_cell_storage_protocol_protocolattachmentreadonly_vue",
|
||||
"relation": "imports_from",
|
||||
"confidence": "EXTRACTED",
|
||||
"source_file": "views/production/production-work-order/index.vue",
|
||||
"source_location": "L248",
|
||||
"weight": 1.0
|
||||
},
|
||||
{
|
||||
"source": "views_production_production_work_order_index_vue",
|
||||
"target": "components_business_system_view_dialog_index_vue",
|
||||
"relation": "imports_from",
|
||||
"confidence": "EXTRACTED",
|
||||
"source_file": "views/production/production-work-order/index.vue",
|
||||
"source_location": "L249",
|
||||
"weight": 1.0
|
||||
},
|
||||
{
|
||||
"source": "views_production_production_work_order_index_vue",
|
||||
"target": "components_business_system_view_dialog_index_vue",
|
||||
"relation": "imports_from",
|
||||
"confidence": "EXTRACTED",
|
||||
"source_file": "views/production/production-work-order/index.vue",
|
||||
"source_location": "L250",
|
||||
"weight": 1.0
|
||||
},
|
||||
{
|
||||
"source": "views_production_production_work_order_index_vue",
|
||||
"target": "element_plus",
|
||||
"relation": "imports_from",
|
||||
"confidence": "EXTRACTED",
|
||||
"source_file": "views/production/production-work-order/index.vue",
|
||||
"source_location": "L251",
|
||||
"weight": 1.0
|
||||
},
|
||||
{
|
||||
"source": "views_production_production_work_order_index_vue",
|
||||
"target": "users_wuzilong_work_00code_zkxw_web_admin_src_utils_download_attachment",
|
||||
"relation": "imports_from",
|
||||
"confidence": "EXTRACTED",
|
||||
"source_file": "views/production/production-work-order/index.vue",
|
||||
"source_location": "L252",
|
||||
"weight": 1.0
|
||||
},
|
||||
{
|
||||
"source": "views_production_production_work_order_index_vue",
|
||||
"target": "production_work_order_index_layoutworkordertable",
|
||||
"relation": "contains",
|
||||
"confidence": "EXTRACTED",
|
||||
"source_file": "views/production/production-work-order/index.vue",
|
||||
"source_location": "L325",
|
||||
"source_location": "L280",
|
||||
"weight": 1.0
|
||||
},
|
||||
{
|
||||
"source": "views_production_production_work_order_index_vue",
|
||||
"target": "production_work_order_index_loaddata",
|
||||
"relation": "contains",
|
||||
"confidence": "EXTRACTED",
|
||||
"source_file": "views/production/production-work-order/index.vue",
|
||||
"source_location": "L285",
|
||||
"weight": 1.0
|
||||
},
|
||||
{
|
||||
"source": "views_production_production_work_order_index_vue",
|
||||
"target": "production_work_order_index_handlesearch",
|
||||
"relation": "contains",
|
||||
"confidence": "EXTRACTED",
|
||||
"source_file": "views/production/production-work-order/index.vue",
|
||||
"source_location": "L302",
|
||||
"weight": 1.0
|
||||
},
|
||||
{
|
||||
"source": "views_production_production_work_order_index_vue",
|
||||
"target": "production_work_order_index_handlereset",
|
||||
"relation": "contains",
|
||||
"confidence": "EXTRACTED",
|
||||
"source_file": "views/production/production-work-order/index.vue",
|
||||
"source_location": "L307",
|
||||
"weight": 1.0
|
||||
},
|
||||
{
|
||||
"source": "views_production_production_work_order_index_vue",
|
||||
"target": "production_work_order_index_handlereceptionattachmentpreview",
|
||||
"relation": "contains",
|
||||
"confidence": "EXTRACTED",
|
||||
"source_file": "views/production/production-work-order/index.vue",
|
||||
"source_location": "L345",
|
||||
"weight": 1.0
|
||||
},
|
||||
{
|
||||
"source": "views_production_production_work_order_index_vue",
|
||||
"target": "production_work_order_index_handlereceptionattachmentdownload",
|
||||
"relation": "contains",
|
||||
"confidence": "EXTRACTED",
|
||||
"source_file": "views/production/production-work-order/index.vue",
|
||||
"source_location": "L350",
|
||||
"weight": 1.0
|
||||
},
|
||||
{
|
||||
"source": "views_production_production_work_order_index_vue",
|
||||
"target": "production_work_order_index_formatmetric",
|
||||
"relation": "contains",
|
||||
"confidence": "EXTRACTED",
|
||||
"source_file": "views/production/production-work-order/index.vue",
|
||||
"source_location": "L354",
|
||||
"weight": 1.0
|
||||
},
|
||||
{
|
||||
"source": "production_work_order_index_loaddata",
|
||||
"target": "production_work_order_index_layoutworkordertable",
|
||||
"relation": "calls",
|
||||
"confidence": "EXTRACTED",
|
||||
"source_file": "views/production/production-work-order/index.vue",
|
||||
"source_location": "L298",
|
||||
"weight": 1.0
|
||||
},
|
||||
{
|
||||
"source": "production_work_order_index_handlesearch",
|
||||
"target": "production_work_order_index_loaddata",
|
||||
"relation": "calls",
|
||||
"confidence": "EXTRACTED",
|
||||
"source_file": "views/production/production-work-order/index.vue",
|
||||
"source_location": "L304",
|
||||
"weight": 1.0
|
||||
},
|
||||
{
|
||||
"source": "production_work_order_index_handlereset",
|
||||
"target": "production_work_order_index_loaddata",
|
||||
"relation": "calls",
|
||||
"confidence": "EXTRACTED",
|
||||
"source_file": "views/production/production-work-order/index.vue",
|
||||
"source_location": "L311",
|
||||
"weight": 1.0
|
||||
},
|
||||
{
|
||||
@@ -30159,33 +30374,23 @@
|
||||
"weight": 1.0
|
||||
},
|
||||
{
|
||||
"source": "production_work_order_index_handleview",
|
||||
"target": "api_production_work_order_fetchproductionworkorderdetail",
|
||||
"source": "production_work_order_index_loaddata",
|
||||
"target": "api_production_work_order_fetchproductionworkorderlist",
|
||||
"relation": "calls",
|
||||
"confidence": "INFERRED",
|
||||
"confidence_score": 0.8,
|
||||
"source_file": "views/production/production-work-order/index.vue",
|
||||
"source_location": "L320",
|
||||
"source_location": "L288",
|
||||
"weight": 1.0
|
||||
},
|
||||
{
|
||||
"source": "production_work_order_index_handlecomplete",
|
||||
"target": "api_production_work_order_fetchcompleteproductionworkorder",
|
||||
"source": "production_work_order_index_handlereceptionattachmentdownload",
|
||||
"target": "utils_download_attachment_downloadattachmentfile",
|
||||
"relation": "calls",
|
||||
"confidence": "INFERRED",
|
||||
"confidence_score": 0.8,
|
||||
"source_file": "views/production/production-work-order/index.vue",
|
||||
"source_location": "L327",
|
||||
"weight": 1.0
|
||||
},
|
||||
{
|
||||
"source": "production_work_order_index_handlecomplete",
|
||||
"target": "collection_region_index_loaddata",
|
||||
"relation": "calls",
|
||||
"confidence": "INFERRED",
|
||||
"confidence_score": 0.8,
|
||||
"source_file": "views/production/production-work-order/index.vue",
|
||||
"source_location": "L329",
|
||||
"source_location": "L351",
|
||||
"weight": 1.0
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# Graph Report - src (2026-05-21)
|
||||
|
||||
## Corpus Check
|
||||
- Large corpus: 577 files · ~298,996 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 · ~299,569 words. Semantic extraction will be expensive (many Claude tokens). Consider running on a subfolder, or use --no-semantic to run AST-only.
|
||||
|
||||
## Summary
|
||||
- 1405 nodes · 1759 edges · 42 communities detected
|
||||
- Extraction: 78% EXTRACTED · 22% INFERRED · 0% AMBIGUOUS · INFERRED: 387 edges (avg confidence: 0.8)
|
||||
- 1410 nodes · 1768 edges · 43 communities detected
|
||||
- Extraction: 78% EXTRACTED · 22% INFERRED · 0% AMBIGUOUS · INFERRED: 386 edges (avg confidence: 0.8)
|
||||
- Token cost: 0 input · 0 output
|
||||
|
||||
## Community Hubs (Navigation)
|
||||
@@ -50,7 +50,8 @@
|
||||
- [[_COMMUNITY_Community 40|Community 40]]
|
||||
- [[_COMMUNITY_Community 41|Community 41]]
|
||||
- [[_COMMUNITY_Community 42|Community 42]]
|
||||
- [[_COMMUNITY_Community 44|Community 44]]
|
||||
- [[_COMMUNITY_Community 43|Community 43]]
|
||||
- [[_COMMUNITY_Community 45|Community 45]]
|
||||
|
||||
## God Nodes (most connected - your core abstractions)
|
||||
1. `get()` - 153 edges
|
||||
@@ -65,8 +66,6 @@
|
||||
10. `StorageConfig` - 10 edges
|
||||
|
||||
## 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/customer/index.vue → utils/fullscreen-loading.ts
|
||||
- `handleUnauthorizedResponse()` --calls--> `fetchRefreshToken()` [INFERRED]
|
||||
@@ -75,72 +74,74 @@
|
||||
api/leave.ts → views/production/process-route/index.vue
|
||||
- `fetchLeaveDetail()` --calls--> `get()` [INFERRED]
|
||||
api/leave.ts → views/production/process-route/index.vue
|
||||
- `fetchSampleReceptionCheckItemList()` --calls--> `get()` [INFERRED]
|
||||
api/sample-reception-check-item.ts → views/production/process-route/index.vue
|
||||
|
||||
## Communities
|
||||
|
||||
### Community 0 - "Community 0"
|
||||
Cohesion: 0.01
|
||||
Nodes (83): fetchActiveRegions(), fetchCategoryList(), fetchCategoryParameters(), fetchCellProductPriceList(), fetchCellProductSpecList(), fetchCellProductTypeList(), fetchCellSampleTypeList(), fetchChannelAgreementPrices() (+75 more)
|
||||
Nodes (74): fetchActiveRegions(), fetchCategoryList(), fetchCategoryParameters(), fetchCellProductPriceList(), fetchCellProductSpecList(), fetchCellProductTypeList(), fetchCellSampleTypeList(), fetchChannelAgreementPrices() (+66 more)
|
||||
|
||||
### Community 1 - "Community 1"
|
||||
Cohesion: 0.03
|
||||
Nodes (40): fetchGetMenuList(), fetchGetMenuCodes(), fetchGetUserInfo(), fetchLogin(), fetchRefreshToken(), clickTab(), IframeRouteManager, MenuProcessor (+32 more)
|
||||
Nodes (57): fetchBatchGenerateProtocolNos(), fetchBatchGenerateSampleCodes(), fetchBatchIssueProtocolNos(), fetchBatchIssueSampleCodes(), fetchCollectionProtocolNoList(), fetchCreateCollectionProtocolNo(), fetchCreateSampleCode(), fetchDeleteCollectionProtocolNo() (+49 more)
|
||||
|
||||
### Community 2 - "Community 2"
|
||||
Cohesion: 0.03
|
||||
Nodes (56): fetchBatchGenerateProtocolNos(), fetchBatchGenerateSampleCodes(), fetchBatchIssueProtocolNos(), fetchBatchIssueSampleCodes(), fetchCollectionProtocolNoList(), fetchCreateCollectionProtocolNo(), fetchCreateSampleCode(), fetchDeleteCollectionProtocolNo() (+48 more)
|
||||
Cohesion: 0.04
|
||||
Nodes (31): fetchGetUserInfo(), clickTab(), handleTouchEnd(), setTransition(), IframeRouteManager, RouteRegistry, useCommon(), setupAfterEachGuard() (+23 more)
|
||||
|
||||
### Community 3 - "Community 3"
|
||||
Cohesion: 0.05
|
||||
Nodes (32): fetchDeptTree(), fetchPostBoundUserIds(), FireworkSystem, reload(), formatUserLabel(), loadDeptData(), onPersonSelectChange(), subscribeTokenRefresh() (+24 more)
|
||||
|
||||
### Community 4 - "Community 4"
|
||||
Cohesion: 0.04
|
||||
Nodes (18): fetchAllConsumablePacks(), fetchAllConsumables(), fetchAllOutputTypes(), fetchAllProcessCategories(), fetchAllProcesses(), fetchAllReagents(), fetchAllSopDocuments(), fetchConsumableList() (+10 more)
|
||||
|
||||
### Community 4 - "Community 4"
|
||||
Cohesion: 0.05
|
||||
Nodes (40): fetchAccessLogList(), fetchCleanRoomList(), fetchCreateAccessLog(), fetchCreateCleanRoom(), fetchCreateSite(), fetchDeleteAccessLog(), fetchDeleteCleanRoom(), fetchDeleteSite() (+32 more)
|
||||
|
||||
### Community 5 - "Community 5"
|
||||
Cohesion: 0.06
|
||||
Nodes (24): FireworkSystem, reload(), formatUserLabel(), onPersonSelectChange(), subscribeTokenRefresh(), handleMenuJump(), openExternalLink(), set() (+16 more)
|
||||
Cohesion: 0.05
|
||||
Nodes (39): fetchAccessLogList(), fetchCleanRoomList(), fetchCreateAccessLog(), fetchCreateCleanRoom(), fetchCreateSite(), fetchDeleteAccessLog(), fetchDeleteCleanRoom(), fetchDeleteSite() (+31 more)
|
||||
|
||||
### Community 6 - "Community 6"
|
||||
Cohesion: 0.06
|
||||
Nodes (11): collectIds(), useSettingsHandlers(), useSettingsPanel(), useSettingsState(), RoutePermissionValidator, useCeremony(), useTheme(), TableCache (+3 more)
|
||||
|
||||
### Community 7 - "Community 7"
|
||||
Cohesion: 0.06
|
||||
Nodes (28): fetchCreateCustomer(), fetchCreateMenu(), fetchCustomerList(), fetchDeleteCustomer(), fetchDeleteMenu(), fetchMenuTree(), fetchUpdateCustomer(), fetchUpdateMenu() (+20 more)
|
||||
|
||||
### Community 8 - "Community 8"
|
||||
Cohesion: 0.09
|
||||
Nodes (31): toLogin(), base64ToBytes(), bytesToBase64(), decryptJson(), deriveKey(), encryptJson(), fromBase64Url(), hexToBytes() (+23 more)
|
||||
|
||||
### Community 7 - "Community 7"
|
||||
### Community 9 - "Community 9"
|
||||
Cohesion: 0.09
|
||||
Nodes (8): getDefaultLanguage(), checkStorageCompatibility(), StorageConfig, getSystemStorage(), getSysVersion(), StorageKeyManager, StorageCompatibilityManager, validateStorageData()
|
||||
|
||||
### Community 8 - "Community 8"
|
||||
### Community 10 - "Community 10"
|
||||
Cohesion: 0.07
|
||||
Nodes (15): fetchProductionWorkOrderDetail(), fetchProductionWorkOrderList(), auditorDisplay(), dash(), expressName(), formatYm(), formatYmd(), sampleTypeName() (+7 more)
|
||||
|
||||
### Community 11 - "Community 11"
|
||||
Cohesion: 0.13
|
||||
Nodes (4): handleReset(), handleSearch(), WebSocketClient, emit()
|
||||
|
||||
### Community 9 - "Community 9"
|
||||
Cohesion: 0.08
|
||||
Nodes (19): fetchCreateMenu(), fetchDeleteMenu(), fetchMenuTree(), fetchUpdateMenu(), postorder(), preorder(), applyTreeExpand(), handleAdd() (+11 more)
|
||||
|
||||
### Community 10 - "Community 10"
|
||||
Cohesion: 0.08
|
||||
Nodes (23): fetchCollectionRegionList(), fetchCreateCollectionRegion(), fetchCreateHospital(), fetchDeleteCollectionRegion(), fetchDeleteHospital(), fetchHospitalList(), fetchUpdateCollectionRegion(), fetchUpdateHospital() (+15 more)
|
||||
|
||||
### Community 11 - "Community 11"
|
||||
Cohesion: 0.07
|
||||
Nodes (13): fetchAttachmentDownloadUrl(), fetchOrderAdjusts(), fetchOrderDetail(), fetchOrderItems(), fetchOrderList(), fetchPriceVersionDetail(), fetchSystemConfig(), handleReceptionAttachmentDownload() (+5 more)
|
||||
|
||||
### Community 12 - "Community 12"
|
||||
Cohesion: 0.09
|
||||
Nodes (9): fetchGetAllUpgradeLogs(), fetchGetChinaMapData(), fetchGetComments(), fetchGetCurrentVersion(), fetchGetLatestUpgradeLog(), fetchGetSystemConfig(), fetchGetUpgradeLogs(), systemUpgrade() (+1 more)
|
||||
Cohesion: 0.06
|
||||
Nodes (14): fetchAttachmentDownloadUrl(), fetchOrderAdjusts(), fetchOrderDetail(), fetchOrderItems(), fetchOrderList(), fetchPriceVersionDetail(), fetchSystemConfig(), handleReceptionAttachmentDownload() (+6 more)
|
||||
|
||||
### Community 13 - "Community 13"
|
||||
Cohesion: 0.09
|
||||
Nodes (3): collectIds(), RoutePermissionValidator, TableCache
|
||||
Cohesion: 0.1
|
||||
Nodes (8): fetchGetMenuList(), fetchGetMenuCodes(), fetchLogin(), fetchRefreshToken(), MenuProcessor, useAppMode(), useAuth(), buildWebEncryptedLoginBody()
|
||||
|
||||
### Community 14 - "Community 14"
|
||||
Cohesion: 0.1
|
||||
Nodes (13): fetchEthnicList(), addSample(), applyDonationBundleToForm(), confirmBatchTransportApply(), createEmptyTransport(), ensureDonationProtocolNoInOptions(), ensureEthnicOptionInList(), handleOpenForAdd() (+5 more)
|
||||
|
||||
### Community 15 - "Community 15"
|
||||
Cohesion: 0.11
|
||||
Nodes (9): auditorDisplay(), dash(), expressName(), formatYm(), formatYmd(), sampleTypeName(), siteName(), unitName() (+1 more)
|
||||
Cohesion: 0.1
|
||||
Nodes (18): fetchCollectionRegionList(), fetchCreateCollectionRegion(), fetchCreateHospital(), fetchDeleteCollectionRegion(), fetchDeleteHospital(), fetchHospitalList(), fetchUpdateCollectionRegion(), fetchUpdateHospital() (+10 more)
|
||||
|
||||
### Community 16 - "Community 16"
|
||||
Cohesion: 0.12
|
||||
@@ -151,29 +152,29 @@ Cohesion: 0.14
|
||||
Nodes (17): fetchCreateUser(), fetchDeleteUser(), fetchResetPassword(), fetchUpdateUser(), fetchUserDetail(), formatUserDeptDisplay(), handleAdd(), handleDelete() (+9 more)
|
||||
|
||||
### Community 18 - "Community 18"
|
||||
Cohesion: 0.1
|
||||
Nodes (7): handleTouchEnd(), setTransition(), useSettingsHandlers(), useSettingsPanel(), useSettingsState(), useCeremony(), useTheme()
|
||||
Cohesion: 0.19
|
||||
Nodes (3): fetchGetLatestUpgradeLog(), systemUpgrade(), VersionManager
|
||||
|
||||
### Community 19 - "Community 19"
|
||||
Cohesion: 0.12
|
||||
Nodes (12): fetchApproveTask(), fetchBusinessAllTasks(), fetchCompletedTasks(), fetchDesignerData(), fetchInstanceDetail(), fetchMyInstances(), fetchPendingTasks(), fetchProcessDefList() (+4 more)
|
||||
|
||||
### Community 20 - "Community 20"
|
||||
Cohesion: 0.11
|
||||
Nodes (8): fetchCollectionProtocolAuditList(), fetchCollectionProtocolDetail(), fetchCollectionProtocolList(), fetchPaymentDetailList(), fetchReceiptConfirmedList(), fetchReceiptConfirmList(), fetchRefundConfirmedList(), fetchRefundConfirmList()
|
||||
|
||||
### Community 21 - "Community 21"
|
||||
Cohesion: 0.16
|
||||
Nodes (10): useTable(), useTableImpl(), getColumnChecks(), useTableColumns(), createErrorHandler(), createSmartDebounce(), defaultResponseAdapter(), extractPagination() (+2 more)
|
||||
|
||||
### Community 21 - "Community 21"
|
||||
### Community 22 - "Community 22"
|
||||
Cohesion: 0.2
|
||||
Nodes (2): ComponentLoader, RouteTransformer
|
||||
|
||||
### Community 22 - "Community 22"
|
||||
### Community 23 - "Community 23"
|
||||
Cohesion: 0.27
|
||||
Nodes (14): useChart(), useChartComponent(), useChartOps(), colourBlend(), getCssVar(), getDarkColor(), getLightColor(), handleElementThemeColor() (+6 more)
|
||||
|
||||
### Community 23 - "Community 23"
|
||||
Cohesion: 0.17
|
||||
Nodes (12): fetchCreateCustomer(), fetchCustomerList(), fetchDeleteCustomer(), fetchUpdateCustomer(), handleAdd(), handleBatchDelete(), handleDelete(), handleReset() (+4 more)
|
||||
|
||||
### Community 24 - "Community 24"
|
||||
Cohesion: 0.14
|
||||
Nodes (2): fetchProductionTaskDetail(), fetchProductionTaskList()
|
||||
@@ -216,38 +217,42 @@ Nodes (2): fetchSampleReceptionCheckItemList(), fetchSampleReceptionCheckItemsAc
|
||||
|
||||
### Community 36 - "Community 36"
|
||||
Cohesion: 0.33
|
||||
Nodes (2): fetchQcThirdPartyCompanyList(), fetchQcThirdPartyCompanyOptions()
|
||||
Nodes (2): fetchQcThirdPartyItemList(), fetchQcThirdPartyItemsActive()
|
||||
|
||||
### Community 37 - "Community 37"
|
||||
Cohesion: 0.33
|
||||
Nodes (2): fetchQcLabTestItemList(), fetchQcLabTestItemsActive()
|
||||
Nodes (2): fetchQcThirdPartyCompanyList(), fetchQcThirdPartyCompanyOptions()
|
||||
|
||||
### Community 38 - "Community 38"
|
||||
Cohesion: 0.33
|
||||
Nodes (2): fetchQcTaskDetail(), fetchQcTaskList()
|
||||
Nodes (2): fetchQcLabTestItemList(), fetchQcLabTestItemsActive()
|
||||
|
||||
### Community 39 - "Community 39"
|
||||
Cohesion: 0.33
|
||||
Nodes (2): fetchQcTaskDetail(), fetchQcTaskList()
|
||||
|
||||
### Community 40 - "Community 40"
|
||||
Cohesion: 0.53
|
||||
Nodes (4): isEtaTodayOrEarlier(), parseEtaLocalDate(), sampleReceptionRowClassName(), startOfLocalDay()
|
||||
|
||||
### Community 40 - "Community 40"
|
||||
### Community 41 - "Community 41"
|
||||
Cohesion: 0.5
|
||||
Nodes (2): focusInput(), handleKeydown()
|
||||
|
||||
### Community 41 - "Community 41"
|
||||
### Community 42 - "Community 42"
|
||||
Cohesion: 0.67
|
||||
Nodes (2): if(), logSidebarMenu()
|
||||
|
||||
### Community 42 - "Community 42"
|
||||
### Community 43 - "Community 43"
|
||||
Cohesion: 1.0
|
||||
Nodes (2): getSettingDefaults(), resetToDefaults()
|
||||
|
||||
### Community 44 - "Community 44"
|
||||
### Community 45 - "Community 45"
|
||||
Cohesion: 1.0
|
||||
Nodes (2): defaultAvatarPathByRole(), resolveUserAvatarUrl()
|
||||
|
||||
## Knowledge Gaps
|
||||
- **Thin community `Community 21`** (17 nodes): `ComponentLoader`, `.constructor()`, `.createEmptyComponent()`, `.createErrorComponent()`, `.load()`, `.loadIframe()`, `.loadLayout()`, `.load()`, `RouteTransformer`, `.extractFirstSegment()`, `.handleFirstLevelRoute()`, `.handleIframeRoute()`, `.handleNormalRoute()`, `.isFirstLevelRoute()`, `.transform()`, `ComponentLoader.ts`, `RouteTransformer.ts`
|
||||
- **Thin community `Community 22`** (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 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.
|
||||
@@ -261,29 +266,31 @@ Nodes (2): defaultAvatarPathByRole(), resolveUserAvatarUrl()
|
||||
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
|
||||
- **Thin community `Community 35`** (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): `fetchCreateQcThirdPartyCompany()`, `fetchDeleteQcThirdPartyCompany()`, `fetchQcThirdPartyCompanyList()`, `fetchQcThirdPartyCompanyOptions()`, `fetchUpdateQcThirdPartyCompany()`, `qc-third-party-company.ts`
|
||||
- **Thin community `Community 36`** (6 nodes): `fetchCreateQcThirdPartyItem()`, `fetchDeleteQcThirdPartyItem()`, `fetchQcThirdPartyItemList()`, `fetchQcThirdPartyItemsActive()`, `fetchUpdateQcThirdPartyItem()`, `qc-third-party-item.ts`
|
||||
Too small to be a meaningful cluster - may be noise or needs more connections extracted.
|
||||
- **Thin community `Community 37`** (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 38`** (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 40`** (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 41`** (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 42`** (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 44`** (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.
|
||||
|
||||
## 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 9`, `Community 10`, `Community 11`, `Community 12`, `Community 14`, `Community 17`, `Community 19`, `Community 23`, `Community 24`, `Community 26`, `Community 27`, `Community 28`, `Community 33`, `Community 35`, `Community 36`, `Community 37`, `Community 38`?**
|
||||
_High betweenness centrality (0.265) - this node is a cross-community bridge._
|
||||
- **Why does `push()` connect `Community 5` to `Community 0`, `Community 1`, `Community 8`, `Community 9`, `Community 12`, `Community 13`, `Community 14`?**
|
||||
_High betweenness centrality (0.074) - this node is a cross-community bridge._
|
||||
- **Why does `fetchGetLatestUpgradeLog()` connect `Community 12` to `Community 0`?**
|
||||
- **Why does `get()` connect `Community 0` to `Community 1`, `Community 2`, `Community 3`, `Community 4`, `Community 5`, `Community 7`, `Community 10`, `Community 12`, `Community 13`, `Community 14`, `Community 15`, `Community 17`, `Community 18`, `Community 19`, `Community 20`, `Community 24`, `Community 26`, `Community 27`, `Community 28`, `Community 33`, `Community 35`, `Community 36`, `Community 37`, `Community 38`, `Community 39`?**
|
||||
_High betweenness centrality (0.259) - this node is a cross-community bridge._
|
||||
- **Why does `push()` connect `Community 3` to `Community 0`, `Community 2`, `Community 6`, `Community 11`, `Community 14`, `Community 18`?**
|
||||
_High betweenness centrality (0.085) - this node is a cross-community bridge._
|
||||
- **Why does `fetchGetLatestUpgradeLog()` connect `Community 18` to `Community 0`?**
|
||||
_High betweenness centrality (0.043) - this node is a cross-community bridge._
|
||||
- **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._
|
||||
|
||||
@@ -57,12 +57,12 @@
|
||||
</div>
|
||||
<div id="legend"></div>
|
||||
</div>
|
||||
<div id="stats">1405 nodes · 1759 edges · 237 communities</div>
|
||||
<div id="stats">1410 nodes · 1768 edges · 238 communities</div>
|
||||
</div>
|
||||
<script>
|
||||
const RAW_NODES = [{"id": "app_vue", "label": "App.vue", "color": {"background": "#E15759", "border": "#E15759", "highlight": {"background": "#ffffff", "border": "#E15759"}}, "size": 10.4, "font": {"size": 0, "color": "#ffffff"}, "title": "App.vue", "community": 32, "community_name": "Community 32", "source_file": "App.vue", "file_type": "code", "degree": 3}, {"id": "src_app_refreshbuttonautoicons", "label": "refreshButtonAutoIcons()", "color": {"background": "#E15759", "border": "#E15759", "highlight": {"background": "#ffffff", "border": "#E15759"}}, "size": 10.1, "font": {"size": 0, "color": "#ffffff"}, "title": "refreshButtonAutoIcons()", "community": 32, "community_name": "Community 32", "source_file": "App.vue", "file_type": "code", "degree": 1}, {"id": "src_app_refreshpaneltitleicons", "label": "refreshPanelTitleIcons()", "color": {"background": "#E15759", "border": "#E15759", "highlight": {"background": "#ffffff", "border": "#E15759"}}, "size": 10.1, "font": {"size": 0, "color": "#ffffff"}, "title": "refreshPanelTitleIcons()", "community": 32, "community_name": "Community 32", "source_file": "App.vue", "file_type": "code", "degree": 1}, {"id": "main_ts", "label": "main.ts", "color": {"background": "#E15759", "border": "#E15759", "highlight": {"background": "#ffffff", "border": "#E15759"}}, "size": 10.6, "font": {"size": 0, "color": "#ffffff"}, "title": "main.ts", "community": 32, "community_name": "Community 32", "source_file": "main.ts", "file_type": "code", "degree": 4}, {"id": "src_main_masksecret", "label": "maskSecret()", "color": {"background": "#E15759", "border": "#E15759", "highlight": {"background": "#ffffff", "border": "#E15759"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "maskSecret()", "community": 32, "community_name": "Community 32", "source_file": "main.ts", "file_type": "code", "degree": 2}, {"id": "src_main_logapicryptobootinfo", "label": "logApiCryptoBootInfo()", "color": {"background": "#E15759", "border": "#E15759", "highlight": {"background": "#ffffff", "border": "#E15759"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "logApiCryptoBootInfo()", "community": 32, "community_name": "Community 32", "source_file": "main.ts", "file_type": "code", "degree": 2}, {"id": "env_d_ts", "label": "env.d.ts", "color": {"background": "#EDC948", "border": "#EDC948", "highlight": {"background": "#ffffff", "border": "#EDC948"}}, "size": 10.0, "font": {"size": 0, "color": "#ffffff"}, "title": "env.d.ts", "community": 75, "community_name": "Community 75", "source_file": "env.d.ts", "file_type": "code", "degree": 0}, {"id": "shims_loose_d_ts", "label": "shims-loose.d.ts", "color": {"background": "#B07AA1", "border": "#B07AA1", "highlight": {"background": "#ffffff", "border": "#B07AA1"}}, "size": 10.0, "font": {"size": 0, "color": "#ffffff"}, "title": "shims-loose.d.ts", "community": 76, "community_name": "Community 76", "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": 233}, {"cid": 1, "color": "#F28E2B", "label": "Community 1", "count": 91}, {"cid": 2, "color": "#E15759", "label": "Community 2", "count": 87}, {"cid": 3, "color": "#76B7B2", "label": "Community 3", "count": 53}, {"cid": 4, "color": "#59A14F", "label": "Community 4", "count": 51}, {"cid": 5, "color": "#EDC948", "label": "Community 5", "count": 42}, {"cid": 6, "color": "#B07AA1", "label": "Community 6", "count": 39}, {"cid": 7, "color": "#FF9DA7", "label": "Community 7", "count": 36}, {"cid": 8, "color": "#9C755F", "label": "Community 8", "count": 34}, {"cid": 9, "color": "#BAB0AC", "label": "Community 9", "count": 34}, {"cid": 10, "color": "#4E79A7", "label": "Community 10", "count": 33}, {"cid": 11, "color": "#F28E2B", "label": "Community 11", "count": 32}, {"cid": 12, "color": "#E15759", "label": "Community 12", "count": 31}, {"cid": 13, "color": "#76B7B2", "label": "Community 13", "count": 29}, {"cid": 14, "color": "#59A14F", "label": "Community 14", "count": 27}, {"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": 20}, {"cid": 19, "color": "#BAB0AC", "label": "Community 19", "count": 19}, {"cid": 20, "color": "#4E79A7", "label": "Community 20", "count": 17}, {"cid": 21, "color": "#F28E2B", "label": "Community 21", "count": 17}, {"cid": 22, "color": "#E15759", "label": "Community 22", "count": 16}, {"cid": 23, "color": "#76B7B2", "label": "Community 23", "count": 16}, {"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": 8}, {"cid": 31, "color": "#F28E2B", "label": "Community 31", "count": 8}, {"cid": 32, "color": "#E15759", "label": "Community 32", "count": 7}, {"cid": 33, "color": "#76B7B2", "label": "Community 33", "count": 7}, {"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": 6}, {"cid": 40, "color": "#4E79A7", "label": "Community 40", "count": 5}, {"cid": 41, "color": "#F28E2B", "label": "Community 41", "count": 4}, {"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": 3}, {"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}, {"cLine truncated
|
||||
const RAW_NODES = [{"id": "app_vue", "label": "App.vue", "color": {"background": "#E15759", "border": "#E15759", "highlight": {"background": "#ffffff", "border": "#E15759"}}, "size": 10.4, "font": {"size": 0, "color": "#ffffff"}, "title": "App.vue", "community": 32, "community_name": "Community 32", "source_file": "App.vue", "file_type": "code", "degree": 3}, {"id": "src_app_refreshbuttonautoicons", "label": "refreshButtonAutoIcons()", "color": {"background": "#E15759", "border": "#E15759", "highlight": {"background": "#ffffff", "border": "#E15759"}}, "size": 10.1, "font": {"size": 0, "color": "#ffffff"}, "title": "refreshButtonAutoIcons()", "community": 32, "community_name": "Community 32", "source_file": "App.vue", "file_type": "code", "degree": 1}, {"id": "src_app_refreshpaneltitleicons", "label": "refreshPanelTitleIcons()", "color": {"background": "#E15759", "border": "#E15759", "highlight": {"background": "#ffffff", "border": "#E15759"}}, "size": 10.1, "font": {"size": 0, "color": "#ffffff"}, "title": "refreshPanelTitleIcons()", "community": 32, "community_name": "Community 32", "source_file": "App.vue", "file_type": "code", "degree": 1}, {"id": "main_ts", "label": "main.ts", "color": {"background": "#E15759", "border": "#E15759", "highlight": {"background": "#ffffff", "border": "#E15759"}}, "size": 10.6, "font": {"size": 0, "color": "#ffffff"}, "title": "main.ts", "community": 32, "community_name": "Community 32", "source_file": "main.ts", "file_type": "code", "degree": 4}, {"id": "src_main_masksecret", "label": "maskSecret()", "color": {"background": "#E15759", "border": "#E15759", "highlight": {"background": "#ffffff", "border": "#E15759"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "maskSecret()", "community": 32, "community_name": "Community 32", "source_file": "main.ts", "file_type": "code", "degree": 2}, {"id": "src_main_logapicryptobootinfo", "label": "logApiCryptoBootInfo()", "color": {"background": "#E15759", "border": "#E15759", "highlight": {"background": "#ffffff", "border": "#E15759"}}, "size": 10.3, "font": {"size": 0, "color": "#ffffff"}, "title": "logApiCryptoBootInfo()", "community": 32, "community_name": "Community 32", "source_file": "main.ts", "file_type": "code", "degree": 2}, {"id": "env_d_ts", "label": "env.d.ts", "color": {"background": "#B07AA1", "border": "#B07AA1", "highlight": {"background": "#ffffff", "border": "#B07AA1"}}, "size": 10.0, "font": {"size": 0, "color": "#ffffff"}, "title": "env.d.ts", "community": 76, "community_name": "Community 76", "source_file": "env.d.ts", "file_type": "code", "degree": 0}, {"id": "shims_loose_d_ts", "label": "shims-loose.d.ts", "color": {"background": "#FF9DA7", "border": "#FF9DA7", "highlight": {"background": "#ffffff", "border": "#FF9DA7"}}, "size": 10.0, "font": {"size": 0, "color": "#ffffff"}, "title": "shims-loose.d.ts", "community": 77, "community_name": "Community 77", "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": 215}, {"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": 53}, {"cid": 4, "color": "#59A14F", "label": "Community 4", "count": 53}, {"cid": 5, "color": "#EDC948", "label": "Community 5", "count": 50}, {"cid": 6, "color": "#B07AA1", "label": "Community 6", "count": 44}, {"cid": 7, "color": "#FF9DA7", "label": "Community 7", "count": 44}, {"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": 35}, {"cid": 11, "color": "#F28E2B", "label": "Community 11", "count": 34}, {"cid": 12, "color": "#E15759", "label": "Community 12", "count": 33}, {"cid": 13, "color": "#76B7B2", "label": "Community 13", "count": 27}, {"cid": 14, "color": "#59A14F", "label": "Community 14", "count": 27}, {"cid": 15, "color": "#EDC948", "label": "Community 15", "count": 26}, {"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": 19}, {"cid": 20, "color": "#4E79A7", "label": "Community 20", "count": 19}, {"cid": 21, "color": "#F28E2B", "label": "Community 21", "count": 17}, {"cid": 22, "color": "#E15759", "label": "Community 22", "count": 17}, {"cid": 23, "color": "#76B7B2", "label": "Community 23", "count": 16}, {"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": 8}, {"cid": 31, "color": "#F28E2B", "label": "Community 31", "count": 8}, {"cid": 32, "color": "#E15759", "label": "Community 32", "count": 7}, {"cid": 33, "color": "#76B7B2", "label": "Community 33", "count": 7}, {"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": 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": 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}, {"cLine truncated
|
||||
|
||||
// HTML-escape helper — prevents XSS when injecting graph data into innerHTML
|
||||
function esc(s) {
|
||||
|
||||
@@ -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": 1779340992.2511122,
|
||||
"/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/auto-imports.d.ts": 1779338103.9480238,
|
||||
"/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/components.d.ts": 1779342029.1834326,
|
||||
"/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/auto-imports.d.ts": 1779342021.1408348,
|
||||
"/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,
|
||||
@@ -269,12 +269,12 @@
|
||||
"/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": 1779340490.9725103,
|
||||
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/index.vue": 1779341967.5417912,
|
||||
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/process-route/index.vue": 1779337462.617501,
|
||||
"/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/production-task/index.vue": 1779341706.1749396,
|
||||
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/reagent/index.vue": 1779307562.93293,
|
||||
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue": 1779341270.8272316,
|
||||
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue": 1779342203.7289343,
|
||||
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/consumable-pack/index.vue": 1779307488.018346,
|
||||
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/consumable/index.vue": 1779307921.9798808,
|
||||
"/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/process/index.vue": 1779309524.0991938,
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 176 KiB |
@@ -72,3 +72,21 @@ export function fetchSetTaskInputs(taskId: string, data: Record<string, any>) {
|
||||
export function fetchSetTaskOutputs(taskId: string, data: Record<string, any>) {
|
||||
return request.post({ url: `/web/admin/production-tasks/${taskId}/outputs`, params: data })
|
||||
}
|
||||
|
||||
export function fetchReorderTaskSteps(data: { taskId: string; stepIds: string[] }) {
|
||||
return request.put({ url: '/web/admin/production-tasks/step/reorder', params: data })
|
||||
}
|
||||
|
||||
export function fetchCopyTaskStep(data: { taskId: string; stepId: string }) {
|
||||
return request.post({
|
||||
url: `/web/admin/production-tasks/${data.taskId}/steps/${data.stepId}/copy`,
|
||||
params: data
|
||||
})
|
||||
}
|
||||
|
||||
export function fetchReplaceStepOutputs(taskId: string, stepId: string, data: Record<string, any>) {
|
||||
return request.put({
|
||||
url: `/web/admin/production-tasks/${taskId}/steps/${stepId}/outputs`,
|
||||
params: data
|
||||
})
|
||||
}
|
||||
@@ -91,13 +91,7 @@
|
||||
opacity: 1
|
||||
}"
|
||||
>
|
||||
<template v-if="AppConfig.systemInfo.name === '中科兴皖'">
|
||||
<span class="system-name-main">中科</span>
|
||||
<span class="system-name-accent">兴皖</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
{{ AppConfig.systemInfo.name }}
|
||||
</template>
|
||||
{{ AppConfig.systemInfo.name }}
|
||||
</p>
|
||||
</div>
|
||||
<ElScrollbar :style="scrollbarStyle">
|
||||
|
||||
@@ -170,24 +170,21 @@
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 58px;
|
||||
right: 12px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: 10px;
|
||||
font-size: 16px;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
line-height: 1.35;
|
||||
color: #111827 !important;
|
||||
white-space: normal;
|
||||
|
||||
&.is-dual-menu-name {
|
||||
left: 25px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.system-name-main {
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.system-name-accent {
|
||||
color: #059669;
|
||||
}
|
||||
}
|
||||
|
||||
&::after {
|
||||
|
||||
@@ -38,7 +38,7 @@ import { headerBarConfig } from './modules/headerBar'
|
||||
const appConfig: SystemConfig = {
|
||||
// 系统信息
|
||||
systemInfo: {
|
||||
name: '中科兴皖' // 系统名称
|
||||
name: '生产运营一体化门户' // 系统名称
|
||||
},
|
||||
// 系统主题
|
||||
systemThemeStyles: {
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L1"}, {"id": "production_work_order_index_loaddata", "label": "loadData()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L271"}, {"id": "production_work_order_index_handlesearch", "label": "handleSearch()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L287"}, {"id": "production_work_order_index_handlereset", "label": "handleReset()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L292"}, {"id": "production_work_order_index_handlereceptionattachmentpreview", "label": "handleReceptionAttachmentPreview()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L330"}, {"id": "production_work_order_index_handlereceptionattachmentdownload", "label": "handleReceptionAttachmentDownload()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L335"}, {"id": "production_work_order_index_formatmetric", "label": "formatMetric()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L339"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "target": "element_plus", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L220", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "target": "icons_vue", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L221", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_api_production_work_order", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L222", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_api_production_task", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L228", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_api_production", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L231", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_utils_qc_labels", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L235", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_views_cell_storage_protocol_protocolattachmentreadonly_vue", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L240", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_components_business_system_view_dialog_index_vue", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L241", "weight": 1.0}, {"source": "useLine truncated
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_task_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-task/index.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}
|
||||
+1
@@ -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": []}
|
||||
+1
@@ -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": []}
|
||||
+1
@@ -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": []}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L1"}, {"id": "production_work_order_index_layoutworkordertable", "label": "layoutWorkOrderTable()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L280"}, {"id": "production_work_order_index_loaddata", "label": "loadData()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L285"}, {"id": "production_work_order_index_handlesearch", "label": "handleSearch()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L302"}, {"id": "production_work_order_index_handlereset", "label": "handleReset()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L307"}, {"id": "production_work_order_index_handlereceptionattachmentpreview", "label": "handleReceptionAttachmentPreview()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L345"}, {"id": "production_work_order_index_handlereceptionattachmentdownload", "label": "handleReceptionAttachmentDownload()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L350"}, {"id": "production_work_order_index_formatmetric", "label": "formatMetric()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L354"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "target": "element_plus", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L227", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "target": "element_plus", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L228", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "target": "icons_vue", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L229", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_api_production_work_order", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L230", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_api_production_task", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L236", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_api_production", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L239", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_utils_qc_labels", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/index.vue", "source_location": "L243", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_work_order_index_vue", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_views_cell_storage_protocol_protocolattachmentreadonly_vue", "relation": "imports_from", "confidence": "EXTRACTEDLine truncated
|
||||
+1
@@ -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,297 @@
|
||||
/**
|
||||
* 路线配置图 / 任务步骤编辑器 — 三栏布局共享样式
|
||||
* 被 process-route 和 production-work-order 的弹窗组件引用
|
||||
*/
|
||||
|
||||
.route-config-body {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
height: calc(90vh - 120px);
|
||||
min-height: 500px;
|
||||
}
|
||||
|
||||
/* 左侧面板:工艺库 */
|
||||
.route-config-left {
|
||||
width: 280px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.route-config-left-header {
|
||||
padding: 12px;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
flex-shrink: 0;
|
||||
|
||||
h3 {
|
||||
margin: 0 0 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
}
|
||||
}
|
||||
.route-config-search {
|
||||
width: 100%;
|
||||
}
|
||||
.route-config-collapse {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
border: none;
|
||||
|
||||
:deep(.el-collapse-item__header) {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
background: #ecfdf5;
|
||||
padding: 0 12px;
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
}
|
||||
:deep(.el-collapse-item__wrap) {
|
||||
border-bottom: none;
|
||||
}
|
||||
:deep(.el-collapse-item__content) {
|
||||
padding: 4px 8px 8px;
|
||||
}
|
||||
}
|
||||
.route-config-cat-bar {
|
||||
display: inline-block;
|
||||
width: 3px;
|
||||
height: 14px;
|
||||
background: #10b981;
|
||||
border-radius: 2px;
|
||||
margin-right: 8px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.route-config-process-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 8px 8px 8px 10px;
|
||||
margin-bottom: 4px;
|
||||
background: #fff;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 6px;
|
||||
cursor: grab;
|
||||
transition: all 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: #10b981;
|
||||
box-shadow: 0 2px 4px rgba(16, 185, 129, 0.12);
|
||||
}
|
||||
&:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
}
|
||||
.route-config-process-name {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #1f2937;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
.route-config-process-seq {
|
||||
color: #1f2937;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
margin-right: 2px;
|
||||
}
|
||||
.route-config-process-code {
|
||||
font-size: 11px;
|
||||
color: #9ca3af;
|
||||
}
|
||||
.route-config-process-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.route-config-process-drag {
|
||||
flex-shrink: 0;
|
||||
color: #d1d5db;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 中间面板:步骤列表 */
|
||||
.route-config-center {
|
||||
width: 640px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.route-config-center-header {
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-shrink: 0;
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
}
|
||||
}
|
||||
.route-config-step-count {
|
||||
font-size: 12px;
|
||||
color: #9ca3af;
|
||||
}
|
||||
.route-config-center-dropzone {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 12px;
|
||||
}
|
||||
.route-config-center-empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
color: #9ca3af;
|
||||
|
||||
p {
|
||||
margin-top: 8px;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
.route-config-step-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 12px;
|
||||
margin-bottom: 6px;
|
||||
background: #fff;
|
||||
border: 2px solid #e5e7eb;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: #6ee7b7;
|
||||
}
|
||||
&.is-active {
|
||||
border-color: #10b981;
|
||||
background: #ecfdf5;
|
||||
}
|
||||
}
|
||||
.route-config-step-handle {
|
||||
cursor: grab;
|
||||
color: #9ca3af;
|
||||
flex-shrink: 0;
|
||||
font-size: 16px;
|
||||
|
||||
&:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
}
|
||||
.route-config-step-order {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
background: #10b981;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.route-config-step-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.route-config-step-name {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #1f2937;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.route-config-step-desc {
|
||||
font-size: 11px;
|
||||
color: #9ca3af;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 右侧面板:步骤详情 */
|
||||
.route-config-right {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.route-config-right-header {
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
flex-shrink: 0;
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
}
|
||||
}
|
||||
.route-config-right-body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 16px;
|
||||
}
|
||||
.route-config-right-empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
padding: 16px;
|
||||
color: #9ca3af;
|
||||
|
||||
p {
|
||||
margin-top: 12px;
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
.route-config-step-form {
|
||||
margin-bottom: 12px;
|
||||
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
.route-config-tabs {
|
||||
:deep(.el-tabs__header) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
.route-config-tab-toolbar {
|
||||
margin-bottom: 8px;
|
||||
display: flex;
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -83,23 +83,6 @@
|
||||
<div class="data-item"><div class="data-label">登记日期</div><div class="data-value">{{ formatYmd(protocol?.registerDate) }}</div></div>
|
||||
<div class="data-item"><div class="data-label">采集专员</div><div class="data-value">{{ collectorDisplay }}</div></div>
|
||||
<div class="data-item"><div class="data-label">创建时间</div><div class="data-value">{{ dash(protocol?.createTime) }}</div></div>
|
||||
<template v-if="showStorageCert">
|
||||
<div class="data-item">
|
||||
<div class="data-label">制作状态</div>
|
||||
<div class="data-value">
|
||||
<el-tag
|
||||
:type="storageCertMakeStatusTagType(String(storageCertInfo?.makeStatus || '0'))"
|
||||
size="small"
|
||||
>
|
||||
{{ storageCertMakeStatusLabel(String(storageCertInfo?.makeStatus || '0')) }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">制作日期</div>
|
||||
<div class="data-value">{{ formatYmd(storageCertInfo?.makeDate) }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<!-- 协议状态变更记录(非储存证查看:仍在基本信息内) -->
|
||||
<div v-if="!showStorageCert && hasAnyStatusChange" class="status-change-section">
|
||||
@@ -123,23 +106,53 @@
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 储存证附件(储存证查看:在协议附件上方) -->
|
||||
<div v-if="showStorageCert" class="attachment-row">
|
||||
<div class="data-label">储存证附件</div>
|
||||
<ProtocolAttachmentReadonly
|
||||
:files="storageCertAttachments"
|
||||
@preview="openPreview"
|
||||
@download="downloadFile"
|
||||
/>
|
||||
<!-- 协议附件、备注同一行 -->
|
||||
<div
|
||||
v-if="protocolAttachments.length || (protocol?.remark && String(protocol.remark).trim())"
|
||||
class="data-grid data-grid--2 protocol-attach-remark-row"
|
||||
>
|
||||
<div class="data-item protocol-attachments-item">
|
||||
<div class="data-label">协议附件</div>
|
||||
<div class="data-value protocol-attachments-value">
|
||||
<ProtocolAttachmentReadonly
|
||||
:files="protocolAttachments"
|
||||
@preview="openPreview"
|
||||
@download="downloadFile"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">备注</div>
|
||||
<div class="data-value data-value--muted">{{ dash(protocol?.remark) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 协议附件 -->
|
||||
<div class="attachment-row" v-if="protocolAttachments.length">
|
||||
<div class="data-label">协议附件</div>
|
||||
<ProtocolAttachmentReadonly :files="protocolAttachments" @preview="openPreview" @download="downloadFile" />
|
||||
</div>
|
||||
<!-- 备注 -->
|
||||
<div class="remark-row" v-if="protocol?.remark">
|
||||
<div class="data-item data-item--full"><div class="data-label">备注</div><div class="data-value data-value--muted">{{ dash(protocol?.remark) }}</div></div>
|
||||
<!-- 储存证:储存证附件、制作状态、制作日期同一行(在协议附件行下方) -->
|
||||
<div v-if="showStorageCert" class="data-grid data-grid--3 storage-cert-info-row">
|
||||
<div class="data-item storage-cert-attachments-item">
|
||||
<div class="data-label">储存证附件</div>
|
||||
<div class="data-value storage-cert-attachments-value">
|
||||
<ProtocolAttachmentReadonly
|
||||
:files="storageCertAttachments"
|
||||
@preview="openPreview"
|
||||
@download="downloadFile"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">制作状态</div>
|
||||
<div class="data-value">
|
||||
<el-tag
|
||||
:type="storageCertMakeStatusTagType(String(storageCertInfo?.makeStatus || '0'))"
|
||||
size="small"
|
||||
>
|
||||
{{ storageCertMakeStatusLabel(String(storageCertInfo?.makeStatus || '0')) }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
<div class="data-item">
|
||||
<div class="data-label">制作日期</div>
|
||||
<div class="data-value">{{ formatYmd(storageCertInfo?.makeDate) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1866,6 +1879,38 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* 储存证:储存证附件 / 制作状态 / 制作日期同一行(在协议附件行下方) */
|
||||
.storage-cert-info-row {
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.storage-cert-attachments-item {
|
||||
.storage-cert-attachments-value {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
:deep(.protocol-attachment-empty) {
|
||||
font-size: 14px;
|
||||
color: #64748b;
|
||||
}
|
||||
}
|
||||
|
||||
/* 协议附件、备注同一行 */
|
||||
.protocol-attach-remark-row {
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.protocol-attachments-item {
|
||||
.protocol-attachments-value {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
:deep(.protocol-attachment-empty) {
|
||||
font-size: 14px;
|
||||
color: #64748b;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- Attachment / Remark Row ---- */
|
||||
.attachment-row {
|
||||
margin-top: 12px;
|
||||
@@ -2786,6 +2831,18 @@
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.storage-cert-info-row.data-grid--3 {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
|
||||
.storage-cert-attachments-item {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
}
|
||||
|
||||
.protocol-attach-remark-row.data-grid--2 {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.payment-summary {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
@@ -2812,6 +2869,10 @@
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.storage-cert-info-row.data-grid--3 {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.payment-summary {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
<el-option label="已冻存" value="frozen" />
|
||||
<el-option label="已售卖" value="sold" />
|
||||
<el-option label="已使用" value="used" />
|
||||
<el-option label="已作废" value="voided" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="代次">
|
||||
@@ -681,6 +682,7 @@
|
||||
frozen: { label: '已冻存', type: 'success' },
|
||||
sold: { label: '已售卖', type: 'warning' },
|
||||
used: { label: '已使用', type: 'info' },
|
||||
voided: { label: '已作废', type: 'danger' },
|
||||
}
|
||||
return map[status] || { label: status, type: 'info' }
|
||||
}
|
||||
@@ -724,6 +726,7 @@
|
||||
{ label: '已冻存', value: 'frozen' },
|
||||
{ label: '已售卖', value: 'sold' },
|
||||
{ label: '已使用', value: 'used' },
|
||||
{ label: '已作废', value: 'voided' },
|
||||
]
|
||||
|
||||
function mapUnitOption(u: { unitName?: string; unitCode?: string }) {
|
||||
|
||||
@@ -480,6 +480,8 @@
|
||||
|
||||
defineOptions({ name: 'ProductionProductionTask' })
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 状态标签映射
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -853,9 +855,15 @@
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 初始化
|
||||
// 初始化(支持从工单页面跳转带 workOrderNo 筛选)
|
||||
// ---------------------------------------------------------------------------
|
||||
onMounted(() => loadData())
|
||||
onMounted(() => {
|
||||
const qWorkOrderNo = route.query.workOrderNo as string
|
||||
if (qWorkOrderNo) {
|
||||
searchForm.workOrderNo = qWorkOrderNo
|
||||
}
|
||||
loadData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
<!-- 派发生产任务两步式弹窗:阶段一表单 → 阶段二三栏编辑 -->
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="phase === 'form' ? '派发生产任务' : `编辑生产任务 — ${createdTaskName || editTaskName || ''}`"
|
||||
:width="phase === 'form' ? '560px' : '90vw'"
|
||||
:class="phase === 'edit' ? 'dialog-form-layout dispatch-route-dialog' : 'dispatch-dialog'"
|
||||
align-center
|
||||
destroy-on-close
|
||||
:close-on-click-modal="false"
|
||||
@close="handleClose"
|
||||
>
|
||||
<!-- 阶段一:表单 -->
|
||||
<div v-if="phase === 'form'" class="dispatch-dialog-work-order-info">
|
||||
<span>工单号:<b>{{ workOrderRow?.workOrderNo }}</b></span>
|
||||
<span>协议号:{{ workOrderRow?.protocolNo || '—' }}</span>
|
||||
<span>样本编码:{{ workOrderRow?.sampleCode || '—' }}</span>
|
||||
</div>
|
||||
<el-form v-if="phase === 'form'" ref="formRef" :model="form" :rules="rules" label-position="top" class="dialog-form-layout">
|
||||
<div class="form-grid-2">
|
||||
<el-form-item label="任务名称" prop="taskName" class="form-col-span-2">
|
||||
<el-input v-model="form.taskName" placeholder="请输入任务名称" maxlength="200" />
|
||||
</el-form-item>
|
||||
<el-form-item label="工艺路线" class="form-col-span-2">
|
||||
<template #label>工艺路线<span class="form-label-tip">可选,不选则从零创建工艺过程</span></template>
|
||||
<el-select v-model="form.routeId" placeholder="请选择工艺路线(可选)" filterable clearable style="width: 100%">
|
||||
<el-option v-for="route in routeOptions" :key="route.id"
|
||||
:label="`${route.routeName}(${route.routeCode})`" :value="route.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="描述" class="form-col-span-2">
|
||||
<el-input v-model="form.description" type="textarea" :rows="3" placeholder="可选,填写任务描述" maxlength="500" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" class="form-col-span-2">
|
||||
<el-input v-model="form.remark" type="textarea" :rows="2" placeholder="可选,填写备注" maxlength="512" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
|
||||
<template #footer>
|
||||
<div v-if="phase === 'form'">
|
||||
<el-button class="btn-ghost" @click="handleClose">取消</el-button>
|
||||
<el-button class="btn-main" type="primary" :loading="submitting" @click="handleCreateTask">生成任务</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 阶段二:三栏编辑 -->
|
||||
<div v-if="phase === 'edit'" class="route-config-body">
|
||||
<div class="route-config-left">
|
||||
<div class="route-config-left-header">
|
||||
<h3>工艺库</h3>
|
||||
<el-input v-model="editor.processSearchKeyword.value" placeholder="搜索工序" clearable size="small"
|
||||
class="route-config-search" />
|
||||
</div>
|
||||
<el-collapse v-model="editor.expandedCategories.value" class="route-config-collapse">
|
||||
<el-collapse-item v-for="cat in editor.filteredProcessLibrary.value" :key="cat.categoryId"
|
||||
:name="cat.categoryId">
|
||||
<template #title>
|
||||
<span class="route-config-cat-bar"></span>
|
||||
<span>{{ cat.categoryName }}</span>
|
||||
</template>
|
||||
<div v-for="(proc, procIndex) in cat.processes" :key="proc.id" class="route-config-process-card"
|
||||
draggable="true" @dragstart="editor.onDragStart($event, proc)">
|
||||
<div class="route-config-process-info">
|
||||
<div class="route-config-process-name">
|
||||
<span class="route-config-process-seq">{{ procIndex + 1 }}</span>{{ proc.processName }}
|
||||
</div>
|
||||
<div class="route-config-process-code">{{ proc.processCode }}</div>
|
||||
</div>
|
||||
<el-icon class="route-config-process-drag"><Rank /></el-icon>
|
||||
</div>
|
||||
<el-empty v-if="!cat.processes.length" description="该分类暂无工序" :image-size="32" />
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
<el-empty v-if="!editor.filteredProcessLibrary.value.length" description="暂无工艺数据" :image-size="48" />
|
||||
</div>
|
||||
|
||||
<div class="route-config-center" @dragover.prevent @drop="editor.onDropToCenter">
|
||||
<el-skeleton v-if="editor.stepListLoading.value" animated :rows="6" />
|
||||
<template v-else>
|
||||
<div class="route-config-center-header">
|
||||
<h3>工序步骤</h3>
|
||||
<span class="route-config-step-count">共 {{ editor.stepList.value.length }} 步</span>
|
||||
</div>
|
||||
<div class="route-config-center-dropzone">
|
||||
<draggable v-model="editor.stepList.value" handle=".route-config-step-handle" :animation="200"
|
||||
@end="editor.onStepDragEnd">
|
||||
<div v-for="(element, index) in editor.stepList.value" :key="element.id"
|
||||
class="route-config-step-card" :class="{ 'is-active': editor.selectedStepId.value === element.id }"
|
||||
@click="editor.selectStep(element)">
|
||||
<div class="route-config-step-handle"><el-icon><Rank /></el-icon></div>
|
||||
<div class="route-config-step-order">{{ index + 1 }}</div>
|
||||
<div class="route-config-step-info">
|
||||
<div class="route-config-step-name">{{ element.processName || '未命名步骤' }}</div>
|
||||
<div class="route-config-step-desc">{{ element.description || '' }}</div>
|
||||
</div>
|
||||
<el-button link type="primary" size="small" title="复制步骤"
|
||||
@click.stop="editor.handleCopyStep(element)">
|
||||
<el-icon><CopyDocument /></el-icon>
|
||||
</el-button>
|
||||
<el-button link type="danger" size="small" @click.stop="editor.handleDeleteStep(element)">
|
||||
<el-icon><Delete /></el-icon>
|
||||
</el-button>
|
||||
</div>
|
||||
</draggable>
|
||||
<div v-if="!editor.stepList.value.length" class="route-config-center-empty">
|
||||
<el-icon :size="32" color="#a7f3d0"><Plus /></el-icon>
|
||||
<p>从左侧拖拽工序到此处,添加任务步骤</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<StepDetailPanel :editor="editor" />
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { Rank, Plus, CopyDocument, Delete } from '@element-plus/icons-vue'
|
||||
import { VueDraggable as draggable } from 'vue-draggable-plus'
|
||||
import { fetchCreateProductionTask } from '@/api/production-task'
|
||||
import { fetchProcessRouteList } from '@/api/production'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { useTaskStepEditor } from './useTaskStepEditor'
|
||||
import StepDetailPanel from './StepDetailPanel.vue'
|
||||
|
||||
const props = defineProps<{
|
||||
visible: boolean
|
||||
workOrderRow: Record<string, any> | null
|
||||
/** 传入已有任务 ID 时跳过阶段一,直接进入阶段二编辑 */
|
||||
editTaskId?: string
|
||||
/** 编辑模式下显示的任务名称 */
|
||||
editTaskName?: string
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:visible', val: boolean): void
|
||||
(e: 'success'): void
|
||||
}>()
|
||||
|
||||
const dialogVisible = computed({
|
||||
get: () => props.visible,
|
||||
set: (val) => emit('update:visible', val)
|
||||
})
|
||||
|
||||
const phase = ref<'form' | 'edit'>('form')
|
||||
const createdTaskName = ref('')
|
||||
const submitting = ref(false)
|
||||
|
||||
const formRef = ref<FormInstance>()
|
||||
const routeOptions = ref<any[]>([])
|
||||
const form = reactive({ taskName: '', routeId: '', description: '', remark: '' })
|
||||
const rules: FormRules = {
|
||||
taskName: [{ required: true, message: '请输入任务名称', trigger: 'blur' }]
|
||||
}
|
||||
|
||||
const editor = useTaskStepEditor()
|
||||
|
||||
async function loadRouteOptions() {
|
||||
try {
|
||||
const res = await fetchProcessRouteList({ page: 1, pageSize: 100 })
|
||||
routeOptions.value = res.list || []
|
||||
} catch { routeOptions.value = [] }
|
||||
}
|
||||
|
||||
function handleClose() {
|
||||
dialogVisible.value = false
|
||||
phase.value = 'form'
|
||||
createdTaskName.value = ''
|
||||
form.taskName = ''
|
||||
form.routeId = ''
|
||||
form.description = ''
|
||||
form.remark = ''
|
||||
}
|
||||
|
||||
async function handleCreateTask() {
|
||||
if (!formRef.value) return
|
||||
await formRef.value.validate()
|
||||
if (!props.workOrderRow) return
|
||||
|
||||
submitting.value = true
|
||||
try {
|
||||
const task = await fetchCreateProductionTask({
|
||||
work_order_id: props.workOrderRow.id,
|
||||
task_name: form.taskName,
|
||||
route_id: form.routeId,
|
||||
description: form.description || undefined,
|
||||
remark: form.remark || undefined
|
||||
})
|
||||
createdTaskName.value = form.taskName
|
||||
phase.value = 'edit'
|
||||
await editor.initEditor(task.id)
|
||||
ElMessage.success('任务已创建,可编辑步骤')
|
||||
emit('success')
|
||||
} catch { /* global error handling */ } finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => props.visible, async (val) => {
|
||||
if (val) {
|
||||
if (props.editTaskId) {
|
||||
// 编辑已有任务:跳过阶段一,直接进入阶段二
|
||||
createdTaskName.value = props.editTaskName || ''
|
||||
phase.value = 'edit'
|
||||
await editor.initEditor(props.editTaskId)
|
||||
} else {
|
||||
// 新建任务:走阶段一表单
|
||||
phase.value = 'form'
|
||||
loadRouteOptions()
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@use '../../../styles/route-config' as *;
|
||||
|
||||
.dispatch-dialog-work-order-info {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
padding: 12px 16px;
|
||||
background: #f0fdf4;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 16px;
|
||||
font-size: 13px;
|
||||
color: #475569;
|
||||
border: 1px solid #d1fae5;
|
||||
|
||||
b {
|
||||
color: #1e293b;
|
||||
}
|
||||
}
|
||||
|
||||
.dispatch-route-dialog {
|
||||
:deep(.el-dialog__body) {
|
||||
max-height: calc(90vh - 60px);
|
||||
overflow: hidden;
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,274 @@
|
||||
<!-- 步骤详情右侧面板:表单 + 试剂/耗材/产出物/数据采集 4 个 Tab -->
|
||||
<template>
|
||||
<div class="route-config-right">
|
||||
<template v-if="editor.selectedStep.value">
|
||||
<div class="route-config-right-header"><h3>步骤详情</h3></div>
|
||||
<div class="route-config-right-body">
|
||||
<el-form label-position="top" class="route-config-step-form">
|
||||
<div class="form-grid-2">
|
||||
<el-form-item label="工序名称">
|
||||
<el-input v-model="editor.selectedStep.value.processName" @change="editor.saveStepBasic" />
|
||||
</el-form-item>
|
||||
<el-form-item label="工序分类">
|
||||
<el-select v-model="editor.selectedStep.value.categoryId" placeholder="选择分类" clearable @change="editor.saveStepBasic">
|
||||
<el-option v-for="cat in editor.categoryOptions.value" :key="cat.id"
|
||||
:label="cat.categoryName || cat.name" :value="cat.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="描述" class="form-col-span-2">
|
||||
<el-input v-model="editor.selectedStep.value.description" type="textarea" :rows="2"
|
||||
placeholder="工序描述" @change="editor.saveStepBasic" />
|
||||
</el-form-item>
|
||||
<el-form-item label="设备名称">
|
||||
<el-input v-model="editor.selectedStep.value.equipmentName" @change="editor.saveStepBasic" />
|
||||
</el-form-item>
|
||||
<el-form-item label="预计时长(min)">
|
||||
<el-input v-model="editor.selectedStep.value.estimatedDuration" placeholder="如 2h" @change="editor.saveStepBasic" />
|
||||
</el-form-item>
|
||||
<el-form-item label="关联SOP" class="form-col-span-2">
|
||||
<el-select v-model="editor.sopIdsArr.value" multiple placeholder="选择SOP文档" filterable
|
||||
@change="editor.onSopIdsChange">
|
||||
<el-option v-for="sop in editor.sopOptions.value" :key="sop.id"
|
||||
:label="sop.sopName || sop.name" :value="sop.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
|
||||
<el-tabs v-model="editor.activeSubTab.value" class="route-config-tabs">
|
||||
<el-tab-pane label="试剂" name="reagent">
|
||||
<div class="route-config-tab-toolbar">
|
||||
<el-button class="btn-main" type="primary" size="small" @click="editor.addReagentRow">
|
||||
<el-icon><Plus /></el-icon>添加试剂
|
||||
</el-button>
|
||||
</div>
|
||||
<el-table :data="editor.selectedStep.value.reagents" stripe class="inner-table" max-height="300">
|
||||
<el-table-column type="index" label="序号" width="56" align="center" :index="(i: number) => i + 1" />
|
||||
<el-table-column label="试剂编码" min-width="110" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.reagentCode" placeholder="编码" class="inner-cell-input" @change="editor.saveReagents" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="试剂名称" min-width="160" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-select v-model="row.reagentId" placeholder="请选择试剂" clearable filterable remote
|
||||
reserve-keyword class="inner-cell-select" :loading="editor.reagentOptionsLoading.value"
|
||||
:remote-method="editor.searchReagentOptions"
|
||||
@visible-change="(vis: boolean) => vis && editor.searchReagentOptions('')"
|
||||
@change="(val: string) => editor.onReagentRowSelect(row, val)">
|
||||
<el-option v-for="item in editor.reagentOptions.value" :key="item.id" :label="item.label" :value="item.id" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="规格" min-width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.spec" placeholder="规格" class="inner-cell-input" @change="editor.saveReagents" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="标准用量" min-width="110" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.standardUsage" placeholder="用量" class="inner-cell-input" @change="editor.saveReagents" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单位" min-width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-select v-model="row.unit" placeholder="请选择单位" clearable filterable remote
|
||||
reserve-keyword class="inner-cell-select" :loading="editor.itemUnitOptionsLoading.value"
|
||||
:remote-method="editor.searchItemUnitOptions"
|
||||
@visible-change="(vis: boolean) => vis && editor.searchItemUnitOptions('')"
|
||||
@change="editor.saveReagents">
|
||||
<el-option v-for="item in editor.itemUnitOptions.value" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="关键物料" width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-switch v-model="row.isKeyMaterial" @change="editor.saveReagents" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" min-width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.remark" placeholder="备注" class="inner-cell-input" @change="editor.saveReagents" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="80" align="center">
|
||||
<template #default="{ $index }">
|
||||
<el-button link type="danger" size="small" @click="editor.removeReagentRow($index)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-if="!editor.selectedStep.value.reagents?.length" description="暂无试剂" :image-size="48" />
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="耗材" name="consumable">
|
||||
<div class="route-config-tab-toolbar">
|
||||
<el-button class="btn-main" type="primary" size="small" @click="editor.addConsumableRow">
|
||||
<el-icon><Plus /></el-icon>添加耗材
|
||||
</el-button>
|
||||
</div>
|
||||
<el-table :data="editor.selectedStep.value.consumables" stripe class="inner-table" max-height="300">
|
||||
<el-table-column type="index" label="序号" width="56" align="center" :index="(i: number) => i + 1" />
|
||||
<el-table-column label="耗材编码" min-width="110" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.consumableCode" placeholder="编码" class="inner-cell-input" @change="editor.saveConsumables" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="耗材名称" min-width="160" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-select v-model="row.consumableId" placeholder="请选择耗材" clearable filterable remote
|
||||
reserve-keyword class="inner-cell-select" :loading="editor.consumableOptionsLoading.value"
|
||||
:remote-method="editor.searchConsumableOptions"
|
||||
@visible-change="(vis: boolean) => vis && editor.searchConsumableOptions('')"
|
||||
@change="(val: string) => editor.onConsumableRowSelect(row, val)">
|
||||
<el-option v-for="item in editor.consumableOptions.value" :key="item.id" :label="item.label" :value="item.id" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="规格型号" min-width="110" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.specModel" placeholder="规格型号" class="inner-cell-input" @change="editor.saveConsumables" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="标准用量" min-width="110" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.standardUsage" placeholder="用量" class="inner-cell-input" @change="editor.saveConsumables" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单位" min-width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-select v-model="row.unit" placeholder="请选择单位" clearable filterable remote
|
||||
reserve-keyword class="inner-cell-select" :loading="editor.itemUnitOptionsLoading.value"
|
||||
:remote-method="editor.searchItemUnitOptions"
|
||||
@visible-change="(vis: boolean) => vis && editor.searchItemUnitOptions('')"
|
||||
@change="editor.saveConsumables">
|
||||
<el-option v-for="item in editor.itemUnitOptions.value" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="必需" width="80" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-switch v-model="row.isRequired" @change="editor.saveConsumables" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" min-width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.remark" placeholder="备注" class="inner-cell-input" @change="editor.saveConsumables" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="80" align="center">
|
||||
<template #default="{ $index }">
|
||||
<el-button link type="danger" size="small" @click="editor.removeConsumableRow($index)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-if="!editor.selectedStep.value.consumables?.length" description="暂无耗材" :image-size="48" />
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="产出物" name="output">
|
||||
<div class="route-config-tab-toolbar">
|
||||
<el-button class="btn-main" type="primary" size="small" @click="editor.addOutputRow">
|
||||
<el-icon><Plus /></el-icon>添加产出物
|
||||
</el-button>
|
||||
</div>
|
||||
<el-table :data="editor.selectedStep.value.outputs" stripe class="inner-table" max-height="300">
|
||||
<el-table-column type="index" label="序号" width="56" align="center" :index="(i: number) => i + 1" />
|
||||
<el-table-column label="产出类型" min-width="150" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-select v-model="row.outputTypeId" placeholder="选择类型" filterable class="inner-cell-select"
|
||||
@change="editor.saveOutputs">
|
||||
<el-option v-for="ot in editor.outputTypeOptions.value" :key="ot.id"
|
||||
:label="ot.typeName || ot.name" :value="ot.id" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="名称" min-width="150" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.outputName" placeholder="名称" class="inner-cell-input" @change="editor.saveOutputs" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="条码" min-width="150" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.outputBarcode" placeholder="条码" class="inner-cell-input" @change="editor.saveOutputs" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" min-width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.remark" placeholder="备注" class="inner-cell-input" @change="editor.saveOutputs" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="80" align="center">
|
||||
<template #default="{ $index }">
|
||||
<el-button link type="danger" size="small" @click="editor.removeOutputRow($index)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-if="!editor.selectedStep.value.outputs?.length" description="暂无产出物" :image-size="48" />
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="数据采集" name="dataCollection">
|
||||
<div class="route-config-tab-toolbar">
|
||||
<el-button class="btn-main" type="primary" size="small" @click="editor.addDataCollectionRow">
|
||||
<el-icon><Plus /></el-icon>添加采集项
|
||||
</el-button>
|
||||
</div>
|
||||
<el-table :data="editor.selectedStep.value.dataCollections" stripe class="inner-table" max-height="300">
|
||||
<el-table-column type="index" label="序号" width="56" align="center" :index="(i: number) => i + 1" />
|
||||
<el-table-column label="采集名称" min-width="180" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.collectionName" placeholder="采集项名称" class="inner-cell-input"
|
||||
@change="editor.saveDataCollections" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="单位" min-width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-select v-model="row.unit" placeholder="请选择单位" clearable filterable remote
|
||||
reserve-keyword class="inner-cell-select" :loading="editor.itemUnitOptionsLoading.value"
|
||||
:remote-method="editor.searchItemUnitOptions"
|
||||
@visible-change="(vis: boolean) => vis && editor.searchItemUnitOptions('')"
|
||||
@change="editor.saveDataCollections">
|
||||
<el-option v-for="item in editor.itemUnitOptions.value" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="排序号" min-width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.orderNum" placeholder="排序号" class="inner-cell-input" @change="editor.saveDataCollections" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="备注" min-width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-input v-model="row.remark" placeholder="备注" class="inner-cell-input" @change="editor.saveDataCollections" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="80" align="center">
|
||||
<template #default="{ $index }">
|
||||
<el-button link type="danger" size="small" @click="editor.removeDataCollectionRow($index)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-if="!editor.selectedStep.value.dataCollections?.length" description="暂无采集项" :image-size="48" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="route-config-right-empty">
|
||||
<el-icon :size="48" color="#d1d5db"><Rank /></el-icon>
|
||||
<p>请选择左侧步骤查看详情</p>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Plus, Rank } from '@element-plus/icons-vue'
|
||||
import { useTaskStepEditor } from './useTaskStepEditor'
|
||||
|
||||
defineProps<{
|
||||
editor: ReturnType<typeof useTaskStepEditor>
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@use '../../../styles/route-config' as *;
|
||||
</style>
|
||||
@@ -4,7 +4,7 @@
|
||||
<div class="page-container">
|
||||
<header class="page-head">
|
||||
<h1>入库生产工单</h1>
|
||||
<p class="page-desc">入库生产工单管理,查看工单详情并完成待配置工单。</p>
|
||||
<p class="page-desc">入库生产工单管理,查看工单详情、派发生产任务并完成待配置工单。</p>
|
||||
</header>
|
||||
|
||||
<section class="panel">
|
||||
@@ -35,7 +35,14 @@
|
||||
</div>
|
||||
|
||||
<div class="list-body list-wrap">
|
||||
<el-table class="list-table" :data="tableData" v-loading="loading" stripe @row-dblclick="handleView">
|
||||
<el-table
|
||||
ref="workOrderListTableRef"
|
||||
class="list-table"
|
||||
:data="tableData"
|
||||
v-loading="loading"
|
||||
stripe
|
||||
@row-dblclick="handleView"
|
||||
>
|
||||
<el-table-column type="index" label="序号" width="68" align="center" :index="(i) => (pagination.page - 1) * pagination.pageSize + i + 1" />
|
||||
<el-table-column prop="workOrderNo" label="工单号" min-width="150" align="center" />
|
||||
<el-table-column prop="protocolNo" label="协议号" min-width="140" align="center" />
|
||||
@@ -64,12 +71,20 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" min-width="170" align="center" />
|
||||
<el-table-column label="操作" min-width="280" fixed="right" align="center">
|
||||
<el-table-column label="操作" width="460" fixed="right" align="center" header-align="center">
|
||||
<template #default="{ row }">
|
||||
<el-button class="btn-op-item" @click="handleView(row)"><el-icon><View /></el-icon>查看详情</el-button>
|
||||
<el-button class="btn-op-item" @click="handleEditTask(row)"><el-icon><EditPen /></el-icon>编辑任务</el-button>
|
||||
<el-button
|
||||
v-if="row.workOrderStatus === 'dpz'"
|
||||
class="btn-op-item primary"
|
||||
@click="handleOpenDispatch(row)"
|
||||
>
|
||||
<el-icon><Position /></el-icon>派发任务
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="row.workOrderStatus === 'dpz'"
|
||||
class="btn-op-item"
|
||||
@click="handleComplete(row)"
|
||||
>
|
||||
<el-icon><Check /></el-icon>完成工单
|
||||
@@ -89,6 +104,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 查看详情弹窗 -->
|
||||
<SystemViewDialog
|
||||
v-model:visible="viewVisible"
|
||||
title="工单详情"
|
||||
@@ -111,14 +127,57 @@
|
||||
</div>
|
||||
</template>
|
||||
</SystemViewDialog>
|
||||
|
||||
<!-- 派发任务弹窗 -->
|
||||
<DispatchTaskDialog
|
||||
v-model:visible="dispatchVisible"
|
||||
:work-order-row="dispatchRow"
|
||||
@success="loadData"
|
||||
/>
|
||||
|
||||
<!-- 编辑已有任务弹窗 -->
|
||||
<DispatchTaskDialog
|
||||
v-model:visible="editTaskVisible"
|
||||
:work-order-row="editTaskRow"
|
||||
:edit-task-id="editTaskId"
|
||||
:edit-task-name="editTaskName"
|
||||
@success="loadData"
|
||||
/>
|
||||
|
||||
<!-- 任务列表弹窗 -->
|
||||
<el-dialog v-model="taskSelectVisible" title="生产任务列表" width="780px" destroy-on-close align-center>
|
||||
<div v-loading="taskSelectLoading" style="max-height:60vh;overflow-y:auto">
|
||||
<el-table :data="taskSelectList" stripe style="width:100%">
|
||||
<el-table-column prop="taskName" label="任务名称" min-width="140" />
|
||||
<el-table-column prop="routeName" label="工艺路线" min-width="140">
|
||||
<template #default="{ row }">{{ row.routeName || '—' }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="taskStatus" label="任务状态" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="taskStatusLabel(row.taskStatus).type" size="small">
|
||||
{{ taskStatusLabel(row.taskStatus).label }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="createTime" label="创建时间" width="170" align="center" />
|
||||
<el-table-column label="操作" width="80" align="center" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button link type="primary" size="small" @click="openTaskEditor(row)">编辑</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-empty v-if="!taskSelectLoading && !taskSelectList.length" description="暂无任务,请先派发任务" :image-size="48" />
|
||||
</div>
|
||||
</el-dialog>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { ref, reactive, computed, onMounted, nextTick } from 'vue'
|
||||
import type { TableInstance } from 'element-plus'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Search, RefreshLeft, View, Check, Document } from '@element-plus/icons-vue'
|
||||
import { Search, RefreshLeft, View, Check, Document, Position, EditPen } from '@element-plus/icons-vue'
|
||||
import {
|
||||
fetchProductionWorkOrderList,
|
||||
fetchProductionWorkOrderDetail,
|
||||
@@ -134,10 +193,11 @@
|
||||
import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
|
||||
import type { ViewSection } from '@/components/business/system-view-dialog/index.vue'
|
||||
import { downloadAttachmentFile } from '@/utils/download-attachment'
|
||||
import DispatchTaskDialog from './DispatchTaskDialog.vue'
|
||||
import { fetchProductionTaskList } from '@/api/production-task'
|
||||
|
||||
defineOptions({ name: 'ProductionProductionWorkOrder' })
|
||||
|
||||
/* ---- 标签映射 ---- */
|
||||
const woStatusTag = (status: string) => {
|
||||
const map: Record<string, { label: string; type: string }> = {
|
||||
dpz: { label: '待配置', type: 'info' },
|
||||
@@ -145,7 +205,14 @@
|
||||
}
|
||||
return map[status] || { label: status, type: 'info' }
|
||||
}
|
||||
|
||||
const taskStatusLabel = (status: string) => {
|
||||
const map: Record<string, { label: string; type: string }> = {
|
||||
dpz: { label: '待配置', type: 'info' },
|
||||
jxz: { label: '进行中', type: 'warning' },
|
||||
ywc: { label: '已完成', type: 'success' }
|
||||
}
|
||||
return map[status] || { label: status, type: 'info' }
|
||||
}
|
||||
const sourceTag = (source: string) => {
|
||||
const map: Record<string, { label: string; type: string }> = {
|
||||
cell_storage: { label: '细胞存储', type: 'success' },
|
||||
@@ -154,12 +221,16 @@
|
||||
return map[source] || { label: source, type: 'info' }
|
||||
}
|
||||
|
||||
/* ---- 搜索 & 列表 ---- */
|
||||
const searchForm = reactive({ workOrderNo: '', workOrderStatus: '' })
|
||||
const tableData = ref<any[]>([])
|
||||
const workOrderListTableRef = ref<TableInstance>()
|
||||
const loading = ref(false)
|
||||
const pagination = reactive({ page: 1, pageSize: 10, total: 0 })
|
||||
|
||||
async function layoutWorkOrderTable() {
|
||||
await nextTick()
|
||||
workOrderListTableRef.value?.doLayout?.()
|
||||
}
|
||||
async function loadData() {
|
||||
loading.value = true
|
||||
try {
|
||||
@@ -173,22 +244,12 @@
|
||||
pagination.total = res.total ?? 0
|
||||
} finally {
|
||||
loading.value = false
|
||||
await layoutWorkOrderTable()
|
||||
}
|
||||
}
|
||||
function handleSearch() { pagination.page = 1; loadData() }
|
||||
function handleReset() { searchForm.workOrderNo = ''; searchForm.workOrderStatus = ''; pagination.page = 1; loadData() }
|
||||
|
||||
function handleSearch() {
|
||||
pagination.page = 1
|
||||
loadData()
|
||||
}
|
||||
|
||||
function handleReset() {
|
||||
searchForm.workOrderNo = ''
|
||||
searchForm.workOrderStatus = ''
|
||||
pagination.page = 1
|
||||
loadData()
|
||||
}
|
||||
|
||||
/* ---- 查看详情 ---- */
|
||||
const viewVisible = ref(false)
|
||||
const detail = ref<ProductionWorkOrderDetail | null>(null)
|
||||
const minioBaseUrl = (import.meta.env.VITE_MINIO_BASE_URL || '').replace(/\/$/, '')
|
||||
@@ -196,46 +257,35 @@
|
||||
const viewReceptionAttachmentFiles = computed(() => {
|
||||
const r = detail.value?.reception
|
||||
if (!r) return []
|
||||
if (Array.isArray(r.receiveAttachmentFiles) && r.receiveAttachmentFiles.length) {
|
||||
if (Array.isArray(r.receiveAttachmentFiles) && r.receiveAttachmentFiles.length)
|
||||
return r.receiveAttachmentFiles as Array<Record<string, unknown>>
|
||||
}
|
||||
const urls = String(r.receiveAttachments || '')
|
||||
if (!urls.trim()) return []
|
||||
return urls
|
||||
.split(',')
|
||||
.map((u, i) => {
|
||||
const url = u.trim()
|
||||
if (!url) return null
|
||||
const name = url.includes('/') ? url.split('/').pop() || `附件${i + 1}` : url
|
||||
return { id: `url_${i}`, fileUrl: url, fileName: name }
|
||||
})
|
||||
.filter(Boolean) as Array<Record<string, unknown>>
|
||||
return urls.split(',').map((u, i) => {
|
||||
const url = u.trim()
|
||||
if (!url) return null
|
||||
const name = url.includes('/') ? url.split('/').pop() || `附件${i + 1}` : url
|
||||
return { id: `url_${i}`, fileUrl: url, fileName: name }
|
||||
}).filter(Boolean) as Array<Record<string, unknown>>
|
||||
})
|
||||
|
||||
function receptionAttachmentFullUrl(url: string) {
|
||||
const u = String(url || '').trim()
|
||||
if (!u) return ''
|
||||
if (/^https?:\/\//i.test(u)) return u
|
||||
return minioBaseUrl ? `${minioBaseUrl}/${u.replace(/^\//, '')}` : u
|
||||
}
|
||||
|
||||
function handleReceptionAttachmentPreview(file: Record<string, unknown>) {
|
||||
const url = receptionAttachmentFullUrl(String(file.fileUrl || ''))
|
||||
if (url) window.open(url, '_blank')
|
||||
}
|
||||
|
||||
function handleReceptionAttachmentDownload(file: Record<string, unknown>) {
|
||||
downloadAttachmentFile(file, { fullFileUrl: receptionAttachmentFullUrl })
|
||||
}
|
||||
|
||||
function formatMetric(v: unknown) {
|
||||
if (v === null || v === undefined || v === '') return '—'
|
||||
return String(v)
|
||||
}
|
||||
|
||||
function formatCheckResults(
|
||||
items?: Array<{ itemName?: string; checked?: boolean }>
|
||||
) {
|
||||
function formatCheckResults(items?: Array<{ itemName?: string; checked?: boolean }>) {
|
||||
if (!items?.length) return '—'
|
||||
return items.map((x) => `${x.itemName || ''}${x.checked ? '(✓)' : '(×)'}`).join('、')
|
||||
}
|
||||
@@ -245,10 +295,7 @@
|
||||
const d = detail.value
|
||||
const r = d.reception || {}
|
||||
const transportFields: ViewSection['fields'] = [
|
||||
{
|
||||
label: '运输方式',
|
||||
value: transportTypeLabel(String(r.transportType || ''))
|
||||
}
|
||||
{ label: '运输方式', value: transportTypeLabel(String(r.transportType || '')) }
|
||||
]
|
||||
if (r.transportType === 'kdys') {
|
||||
transportFields.push(
|
||||
@@ -261,14 +308,10 @@
|
||||
{ label: '携带人电话', value: r.carrierPhone || '—' }
|
||||
)
|
||||
}
|
||||
if (r.etaTime) {
|
||||
transportFields.push({ label: '预计到达', value: r.etaTime })
|
||||
}
|
||||
const transportSection =
|
||||
transportFields.length > 1 || r.transportType
|
||||
? [{ title: '运输信息', fields: transportFields }]
|
||||
: []
|
||||
|
||||
if (r.etaTime) transportFields.push({ label: '预计到达', value: r.etaTime })
|
||||
const transportSection = transportFields.length > 1 || r.transportType
|
||||
? [{ title: '运输信息', fields: transportFields }]
|
||||
: []
|
||||
return [
|
||||
{
|
||||
title: '基本信息',
|
||||
@@ -276,17 +319,9 @@
|
||||
{ label: '工单号', value: d.workOrderNo },
|
||||
{ label: '协议号', value: d.protocolNo },
|
||||
{ label: '样本编码', value: d.sampleCode },
|
||||
{
|
||||
label: '来源类型',
|
||||
value: r.sourceTypeLabel || d.receptionSourceLabel || receptionSourceLabel(d.receptionSource || ''),
|
||||
tagType: 'primary'
|
||||
},
|
||||
{ label: '来源类型', value: r.sourceTypeLabel || d.receptionSourceLabel || receptionSourceLabel(d.receptionSource || ''), tagType: 'primary' },
|
||||
{ label: '样本类型', value: r.sampleTypeName || d.sampleTypeName || '—' },
|
||||
{
|
||||
label: '工单状态',
|
||||
value: woStatusTag(d.workOrderStatus).label,
|
||||
tagType: woStatusTag(d.workOrderStatus).type as any
|
||||
},
|
||||
{ label: '工单状态', value: woStatusTag(d.workOrderStatus).label, tagType: woStatusTag(d.workOrderStatus).type as any },
|
||||
{ label: '创建时间', value: d.createTime || '—' },
|
||||
{ label: '备注', value: d.remark || '—', span: true }
|
||||
]
|
||||
@@ -298,17 +333,10 @@
|
||||
fields: [
|
||||
{ label: '接收时间', value: r.receptionTime || d.receptionTime || '—' },
|
||||
{ label: '接收经手人', value: r.handlerName || d.handlerName || '—' },
|
||||
{
|
||||
label: '接收温度(℃)',
|
||||
value: r.receiveTemperature ? `${r.receiveTemperature}℃` : '—'
|
||||
},
|
||||
{ label: '接收温度(℃)', value: r.receiveTemperature ? `${r.receiveTemperature}℃` : '—' },
|
||||
{ label: '体积(ml)', value: formatMetric(r.volumeMl) },
|
||||
{ label: '净重(g)', value: formatMetric(r.netWeightG) },
|
||||
{
|
||||
label: '结论',
|
||||
value: r.acceptConclusionLabel || acceptConclusionLabel(String(r.acceptConclusion || '')),
|
||||
tagType: r.acceptConclusion === 'js' ? 'success' : r.acceptConclusion === 'jj' ? 'danger' : 'info'
|
||||
},
|
||||
{ label: '结论', value: r.acceptConclusionLabel || acceptConclusionLabel(String(r.acceptConclusion || '')), tagType: r.acceptConclusion === 'js' ? 'success' : r.acceptConclusion === 'jj' ? 'danger' : 'info' },
|
||||
{ label: '检查项', value: formatCheckResults(r.checkResults), span: true },
|
||||
{ label: '备注', value: r.remark || '—', span: true }
|
||||
]
|
||||
@@ -320,8 +348,6 @@
|
||||
detail.value = await fetchProductionWorkOrderDetail(row.id)
|
||||
viewVisible.value = true
|
||||
}
|
||||
|
||||
/* ---- 完成工单 ---- */
|
||||
async function handleComplete(row: any) {
|
||||
await ElMessageBox.confirm('确定要完成该工单吗?', '提示', { type: 'warning' })
|
||||
await fetchCompleteProductionWorkOrder(row.id, {})
|
||||
@@ -329,6 +355,47 @@
|
||||
loadData()
|
||||
}
|
||||
|
||||
// ── 派发任务 ──────────────────────────────────────────
|
||||
const dispatchVisible = ref(false)
|
||||
const dispatchRow = ref<any>(null)
|
||||
function handleOpenDispatch(row: any) {
|
||||
dispatchRow.value = row
|
||||
dispatchVisible.value = true
|
||||
}
|
||||
|
||||
// ── 编辑已有任务 ──────────────────────────────────────
|
||||
const editTaskVisible = ref(false)
|
||||
const editTaskRow = ref<any>(null)
|
||||
const editTaskId = ref('')
|
||||
const editTaskName = ref('')
|
||||
|
||||
// 任务选择弹窗
|
||||
const taskSelectVisible = ref(false)
|
||||
const taskSelectList = ref<any[]>([])
|
||||
const taskSelectLoading = ref(false)
|
||||
|
||||
async function handleEditTask(row: any) {
|
||||
taskSelectLoading.value = true
|
||||
taskSelectVisible.value = true
|
||||
taskSelectList.value = []
|
||||
try {
|
||||
const res = await fetchProductionTaskList({ workOrderId: row.id, pageSize: 100 })
|
||||
taskSelectList.value = res.list || []
|
||||
} catch {
|
||||
taskSelectList.value = []
|
||||
} finally {
|
||||
taskSelectLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function openTaskEditor(task: any) {
|
||||
editTaskId.value = task.id
|
||||
editTaskName.value = task.taskName || ''
|
||||
editTaskRow.value = null
|
||||
editTaskVisible.value = true
|
||||
taskSelectVisible.value = false
|
||||
}
|
||||
|
||||
onMounted(() => loadData())
|
||||
</script>
|
||||
|
||||
@@ -343,16 +410,8 @@
|
||||
font-size: 13px;
|
||||
align-items: start;
|
||||
|
||||
dt {
|
||||
margin: 0;
|
||||
color: #64748b;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin: 0;
|
||||
color: #1e293b;
|
||||
}
|
||||
dt { margin: 0; color: #64748b; font-weight: 500; }
|
||||
dd { margin: 0; color: #1e293b; }
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,496 @@
|
||||
import {
|
||||
fetchProductionTaskDetail,
|
||||
fetchAddTaskStep,
|
||||
fetchUpdateTaskStep,
|
||||
fetchDeleteTaskStep,
|
||||
fetchReorderTaskSteps,
|
||||
fetchCopyTaskStep,
|
||||
fetchReplaceStepReagents,
|
||||
fetchReplaceStepConsumables,
|
||||
fetchReplaceStepDataCollection,
|
||||
fetchReplaceStepOutputs
|
||||
} from '@/api/production-task'
|
||||
import {
|
||||
fetchAllProcesses,
|
||||
fetchAllProcessCategories,
|
||||
fetchAllSopDocuments,
|
||||
fetchAllOutputTypes,
|
||||
fetchReagentList,
|
||||
fetchConsumableList
|
||||
} from '@/api/production'
|
||||
import { fetchItemUnitList } from '@/api/admin'
|
||||
|
||||
// ── helpers ──────────────────────────────────────────────────────────
|
||||
function toApiFlag(val: boolean | number | string | undefined): string {
|
||||
if (val === true || val === 1 || val === '1') return '1'
|
||||
return '0'
|
||||
}
|
||||
function fromApiFlag(val: boolean | number | string | undefined): boolean {
|
||||
return val === true || val === 1 || val === '1'
|
||||
}
|
||||
function normalizeStepSubTables(step: Record<string, any>) {
|
||||
return {
|
||||
...step,
|
||||
reagents: (step.reagents || []).map((r: Record<string, any>) => ({
|
||||
...r,
|
||||
isKeyMaterial: fromApiFlag(r.isKeyMaterial)
|
||||
})),
|
||||
consumables: (step.consumables || []).map((c: Record<string, any>) => ({
|
||||
...c,
|
||||
isRequired: fromApiFlag(c.isRequired)
|
||||
})),
|
||||
outputs: step.outputs || [],
|
||||
dataCollections: step.dataCollections || []
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
// ── composable ───────────────────────────────────────────────────────
|
||||
export function useTaskStepEditor() {
|
||||
const taskId = ref('')
|
||||
const stepList = ref<Record<string, any>[]>([])
|
||||
const selectedStepId = ref('')
|
||||
const stepListLoading = ref(false)
|
||||
const activeSubTab = ref('reagent')
|
||||
const processSearchKeyword = ref('')
|
||||
const configSuppressSave = ref(false)
|
||||
|
||||
const processLibrary = ref<Record<string, any>[]>([])
|
||||
const expandedCategories = ref<string[]>([])
|
||||
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)
|
||||
reagentOptions.value = list
|
||||
.filter((r) => (r.reagentName || '').trim() || (r.reagentCode || '').trim())
|
||||
.map((r) => ({ id: r.id, label: mapReagentOptionLabel(r) }))
|
||||
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 }
|
||||
Object.assign(row, {
|
||||
reagentId: master.id, reagentCode: master.reagentCode || '',
|
||||
reagentName: master.reagentName || '', spec: master.spec || '',
|
||||
standardUsage: master.standardUsage ?? '', unit: master.unit || '',
|
||||
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 }
|
||||
Object.assign(row, {
|
||||
consumableId: master.id, consumableCode: master.consumableCode || '',
|
||||
consumableName: master.consumableName || '', specModel: master.specModel || '',
|
||||
standardUsage: master.standardUsage ?? '', unit: master.unit || '',
|
||||
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
|
||||
return stepList.value.find((s) => s.id === selectedStepId.value) || null
|
||||
})
|
||||
const sopIdsArr = computed({
|
||||
get() {
|
||||
if (!selectedStep.value) return []
|
||||
const raw = selectedStep.value.sopIds
|
||||
if (!raw) return []
|
||||
if (Array.isArray(raw)) return raw.filter(Boolean)
|
||||
return String(raw).split(',').map((s: string) => s.trim()).filter(Boolean)
|
||||
},
|
||||
set(val: string[]) {
|
||||
if (!selectedStep.value) return
|
||||
selectedStep.value.sopIds = val.join(',')
|
||||
}
|
||||
})
|
||||
const filteredProcessLibrary = computed(() => {
|
||||
const keyword = processSearchKeyword.value?.trim().toLowerCase() || ''
|
||||
return processLibrary.value
|
||||
.map((group) => {
|
||||
const filtered = keyword
|
||||
? group.processes.filter(
|
||||
(p: Record<string, any>) =>
|
||||
(p.processName || '').toLowerCase().includes(keyword) ||
|
||||
(p.processCode || '').toLowerCase().includes(keyword)
|
||||
)
|
||||
: group.processes
|
||||
return { categoryId: group.categoryId, categoryName: group.categoryName, processes: filtered }
|
||||
})
|
||||
.filter((group) => group.processes.length > 0)
|
||||
})
|
||||
|
||||
async function loadConfigReferenceData() {
|
||||
const [processes, categories, sops, outputTypes] = await Promise.all([
|
||||
fetchAllProcesses(), fetchAllProcessCategories(),
|
||||
fetchAllSopDocuments(), fetchAllOutputTypes()
|
||||
])
|
||||
categoryOptions.value = categories || []
|
||||
sopOptions.value = sops || []
|
||||
outputTypeOptions.value = outputTypes || []
|
||||
|
||||
const catMap: Record<string, { categoryId: string; categoryName: string; processes: Record<string, any>[] }> = {}
|
||||
const uKey = '__uncategorized__'
|
||||
catMap[uKey] = { categoryId: uKey, categoryName: '未分类', processes: [] }
|
||||
;(categories || []).forEach((cat: Record<string, any>) => {
|
||||
catMap[cat.id] = { categoryId: cat.id, categoryName: cat.categoryName || cat.name || '未命名分类', processes: [] }
|
||||
})
|
||||
;(processes || []).forEach((proc: Record<string, any>) => {
|
||||
const catId = proc.categoryId || uKey
|
||||
if (!catMap[catId]) catMap[catId] = { categoryId: catId, categoryName: '未知分类', processes: [] }
|
||||
catMap[catId].processes.push(proc)
|
||||
})
|
||||
expandedCategories.value = Object.keys(catMap)
|
||||
processLibrary.value = Object.values(catMap)
|
||||
await Promise.all([searchItemUnitOptions(''), searchReagentOptions(''), searchConsumableOptions('')])
|
||||
}
|
||||
async function loadTaskDetail() {
|
||||
if (!taskId.value) return
|
||||
try {
|
||||
const detail = await fetchProductionTaskDetail(taskId.value)
|
||||
stepList.value = (detail.steps || []).map(normalizeStepSubTables)
|
||||
ensureAllStepReagentOptions()
|
||||
ensureAllStepConsumableOptions()
|
||||
ensureAllStepUnitOptions()
|
||||
if (stepList.value.length > 0 && !selectedStepId.value)
|
||||
selectedStepId.value = stepList.value[0].id
|
||||
} catch {
|
||||
stepList.value = []
|
||||
}
|
||||
}
|
||||
|
||||
function onDragStart(event: DragEvent, proc: Record<string, any>) {
|
||||
event.dataTransfer?.setData('processId', proc.id)
|
||||
event.dataTransfer!.effectAllowed = 'copy'
|
||||
}
|
||||
async function onDropToCenter(event: DragEvent) {
|
||||
const processId = event.dataTransfer?.getData('processId')
|
||||
if (!processId || !taskId.value) return
|
||||
stepListLoading.value = true
|
||||
try {
|
||||
const newStep = await fetchAddTaskStep(taskId.value, { processId })
|
||||
await loadTaskDetail()
|
||||
if (newStep?.id) selectedStepId.value = newStep.id
|
||||
ElMessage.success('步骤添加成功')
|
||||
} catch { /* ignore */ } finally {
|
||||
stepListLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function onStepDragEnd() {
|
||||
if (!taskId.value) return
|
||||
stepListLoading.value = true
|
||||
try {
|
||||
await fetchReorderTaskSteps({ taskId: taskId.value, stepIds: stepList.value.map((s) => s.id) })
|
||||
} catch { /* ignore */ } finally {
|
||||
stepListLoading.value = false
|
||||
}
|
||||
}
|
||||
function selectStep(step: Record<string, any>) { selectedStepId.value = step.id }
|
||||
async function handleCopyStep(step: Record<string, any>) {
|
||||
stepListLoading.value = true
|
||||
try {
|
||||
const ns = await fetchCopyTaskStep({ taskId: taskId.value, stepId: step.id })
|
||||
await loadTaskDetail()
|
||||
if (ns?.id) selectedStepId.value = ns.id
|
||||
ElMessage.success('步骤已复制')
|
||||
} catch (e: any) { ElMessage.error(e?.message || '复制失败') } finally {
|
||||
stepListLoading.value = false
|
||||
}
|
||||
}
|
||||
async function handleDeleteStep(step: Record<string, any>) {
|
||||
await ElMessageBox.confirm('确定删除该步骤吗?', '提示', { type: 'warning' })
|
||||
stepListLoading.value = true
|
||||
try {
|
||||
await fetchDeleteTaskStep(taskId.value, step.id)
|
||||
await loadTaskDetail()
|
||||
if (selectedStepId.value === step.id)
|
||||
selectedStepId.value = stepList.value.length > 0 ? stepList.value[0].id : ''
|
||||
ElMessage.success('步骤已删除')
|
||||
} catch { /* ignore */ } finally {
|
||||
stepListLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function saveStepBasic() {
|
||||
if (!selectedStep.value) return
|
||||
try {
|
||||
await fetchUpdateTaskStep(taskId.value, selectedStep.value.id, {
|
||||
processName: selectedStep.value.processName,
|
||||
categoryId: selectedStep.value.categoryId,
|
||||
description: selectedStep.value.description,
|
||||
equipmentName: selectedStep.value.equipmentName,
|
||||
estimatedDuration: selectedStep.value.estimatedDuration,
|
||||
sopIds: selectedStep.value.sopIds
|
||||
})
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
function onSopIdsChange(val: string[]) {
|
||||
if (!selectedStep.value) return
|
||||
selectedStep.value.sopIds = val.join(',')
|
||||
saveStepBasic()
|
||||
}
|
||||
|
||||
function addReagentRow() {
|
||||
if (!selectedStep.value) return
|
||||
if (!selectedStep.value.reagents) selectedStep.value.reagents = []
|
||||
selectedStep.value.reagents.push({
|
||||
reagentId: '', reagentCode: '', reagentName: '', spec: '',
|
||||
standardUsage: '', unit: '', isKeyMaterial: false, remark: ''
|
||||
})
|
||||
saveReagents()
|
||||
}
|
||||
function removeReagentRow(i: number) { selectedStep.value?.reagents?.splice(i, 1); saveReagents() }
|
||||
async function saveReagents() {
|
||||
if (!selectedStep.value || configSuppressSave.value) return
|
||||
try {
|
||||
await fetchReplaceStepReagents(taskId.value, selectedStep.value.id, {
|
||||
items: (selectedStep.value.reagents || []).map((r: Record<string, any>) => ({
|
||||
reagentCode: r.reagentCode, reagentName: r.reagentName, spec: r.spec,
|
||||
standardUsage: r.standardUsage, unit: r.unit,
|
||||
isKeyMaterial: toApiFlag(r.isKeyMaterial), remark: r.remark || ''
|
||||
}))
|
||||
})
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
function addConsumableRow() {
|
||||
if (!selectedStep.value) return
|
||||
if (!selectedStep.value.consumables) selectedStep.value.consumables = []
|
||||
selectedStep.value.consumables.push({
|
||||
consumableId: '', consumableCode: '', consumableName: '', specModel: '',
|
||||
standardUsage: '', unit: '', isRequired: false, remark: ''
|
||||
})
|
||||
saveConsumables()
|
||||
}
|
||||
function removeConsumableRow(i: number) { selectedStep.value?.consumables?.splice(i, 1); saveConsumables() }
|
||||
async function saveConsumables() {
|
||||
if (!selectedStep.value || configSuppressSave.value) return
|
||||
try {
|
||||
await fetchReplaceStepConsumables(taskId.value, selectedStep.value.id, {
|
||||
items: (selectedStep.value.consumables || []).map((c: Record<string, any>) => ({
|
||||
consumableCode: c.consumableCode, consumableName: c.consumableName, specModel: c.specModel,
|
||||
standardUsage: c.standardUsage, unit: c.unit,
|
||||
isRequired: toApiFlag(c.isRequired), remark: c.remark || ''
|
||||
}))
|
||||
})
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
function addOutputRow() {
|
||||
if (!selectedStep.value) return
|
||||
if (!selectedStep.value.outputs) selectedStep.value.outputs = []
|
||||
selectedStep.value.outputs.push({ outputTypeId: '', outputName: '', outputBarcode: '', remark: '' })
|
||||
saveOutputs()
|
||||
}
|
||||
function removeOutputRow(i: number) { selectedStep.value?.outputs?.splice(i, 1); saveOutputs() }
|
||||
async function saveOutputs() {
|
||||
if (!selectedStep.value || configSuppressSave.value) return
|
||||
try {
|
||||
await fetchReplaceStepOutputs(taskId.value, selectedStep.value.id, {
|
||||
items: (selectedStep.value.outputs || []).map((o: Record<string, any>) => ({
|
||||
outputTypeId: o.outputTypeId, outputName: o.outputName,
|
||||
outputBarcode: o.outputBarcode, remark: o.remark || ''
|
||||
}))
|
||||
})
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
function addDataCollectionRow() {
|
||||
if (!selectedStep.value) return
|
||||
if (!selectedStep.value.dataCollections) selectedStep.value.dataCollections = []
|
||||
selectedStep.value.dataCollections.push({ collectionName: '', unit: '', orderNum: 0, remark: '' })
|
||||
saveDataCollections()
|
||||
}
|
||||
function removeDataCollectionRow(i: number) { selectedStep.value?.dataCollections?.splice(i, 1); saveDataCollections() }
|
||||
async function saveDataCollections() {
|
||||
if (!selectedStep.value || configSuppressSave.value) return
|
||||
try {
|
||||
await fetchReplaceStepDataCollection(taskId.value, selectedStep.value.id, {
|
||||
items: (selectedStep.value.dataCollections || []).map((d: Record<string, any>) => ({
|
||||
collectionName: d.collectionName, unit: d.unit,
|
||||
orderNum: parseOrderNum(d.orderNum), remark: d.remark || ''
|
||||
}))
|
||||
})
|
||||
} catch { /* ignore */ }
|
||||
}
|
||||
|
||||
async function initEditor(id: string) {
|
||||
taskId.value = id
|
||||
selectedStepId.value = ''
|
||||
activeSubTab.value = 'reagent'
|
||||
processSearchKeyword.value = ''
|
||||
configSuppressSave.value = true
|
||||
try {
|
||||
await loadConfigReferenceData()
|
||||
await loadTaskDetail()
|
||||
} finally {
|
||||
await nextTick()
|
||||
configSuppressSave.value = false
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
taskId, stepList, selectedStepId, selectedStep, stepListLoading,
|
||||
activeSubTab, processSearchKeyword, configSuppressSave,
|
||||
processLibrary, filteredProcessLibrary, expandedCategories,
|
||||
categoryOptions, sopOptions, outputTypeOptions,
|
||||
itemUnitOptions, itemUnitOptionsLoading,
|
||||
reagentOptions, reagentOptionsLoading,
|
||||
consumableOptions, consumableOptionsLoading,
|
||||
sopIdsArr,
|
||||
onDragStart, onDropToCenter, onStepDragEnd, selectStep,
|
||||
handleCopyStep, handleDeleteStep,
|
||||
saveStepBasic, onSopIdsChange,
|
||||
searchItemUnitOptions, searchReagentOptions, searchConsumableOptions,
|
||||
onReagentRowSelect, onConsumableRowSelect,
|
||||
addReagentRow, removeReagentRow, saveReagents,
|
||||
addConsumableRow, removeConsumableRow, saveConsumables,
|
||||
addOutputRow, removeOutputRow, saveOutputs,
|
||||
addDataCollectionRow, removeDataCollectionRow, saveDataCollections,
|
||||
initEditor
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user