From db3945c26356df2a4e384938bc7f72ff3602f64f Mon Sep 17 00:00:00 2001 From: wangyu <823267011@qq.com> Date: Sat, 20 Jun 2026 18:19:06 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=90=8C=E6=AD=A5=E6=9C=AC=E5=9C=B0?= =?UTF-8?q?=E6=9C=AA=E6=8F=90=E4=BA=A4=E7=9A=84=E5=89=8D=E5=90=8E=E7=AB=AF?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=EF=BC=88plan/quality/material/inspection/pro?= =?UTF-8?q?duction=20=E7=AD=89=E6=A8=A1=E5=9D=97=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/api/plan.py | 9 +- backend/app/api/quality.py | 37 +- backend/app/database.py | 66 +++ backend/app/models/plan.py | 47 +- backend/app/models/production.py | 49 +- backend/app/models/quality.py | 16 +- backend/app/schemas/plan.py | 72 ++- backend/app/schemas/production.py | 94 ++-- backend/app/schemas/quality.py | 102 ++-- frontend/package.json | 3 +- frontend/src/api/index.js | 2 + frontend/src/router/index.js | 14 +- frontend/src/views/Inspection.vue | 41 +- frontend/src/views/Layout.vue | 10 +- frontend/src/views/Material.vue | 806 ++++++++++++++++++++-------- frontend/src/views/Plan.vue | 200 ++++--- frontend/src/views/ProcessModel.vue | 16 +- frontend/src/views/Production.vue | 288 +++++++--- frontend/src/views/Quality.vue | 770 ++++++++++++-------------- 19 files changed, 1681 insertions(+), 961 deletions(-) diff --git a/backend/app/api/plan.py b/backend/app/api/plan.py index 56da118..9d824bd 100644 --- a/backend/app/api/plan.py +++ b/backend/app/api/plan.py @@ -5,7 +5,7 @@ from typing import Optional from datetime import datetime from app.database import get_db -from app.models.plan import ProductionPlan, PlanStatus +from app.models.plan import ProductionPlan from app.schemas.plan import PlanCreate, PlanUpdate, PlanOut from app.schemas.common import Response, PageResponse from app.services.auth_service import get_current_user @@ -34,10 +34,7 @@ async def list_plans( ): query = select(ProductionPlan).order_by(desc(ProductionPlan.plan_date)) if status: - try: - query = query.where(ProductionPlan.status == PlanStatus(status)) - except ValueError: - pass + query = query.where(ProductionPlan.status == status) _sd = _parse_dt(start_date) if _sd: query = query.where(ProductionPlan.plan_date >= _sd) @@ -98,6 +95,6 @@ async def confirm_plan(plan_id: int, db: AsyncSession = Depends(get_db), _ = Dep plan = result.scalar_one_or_none() if not plan: raise HTTPException(status_code=404, detail="计划不存在") - plan.status = PlanStatus.CONFIRMED + plan.status = "online" await db.flush() return Response.ok(PlanOut.model_validate(plan)) diff --git a/backend/app/api/quality.py b/backend/app/api/quality.py index 256be56..a6ac419 100644 --- a/backend/app/api/quality.py +++ b/backend/app/api/quality.py @@ -9,7 +9,7 @@ from app.models.quality import QcTask, QcTaskItem, QcDefect from app.schemas.quality import ( QcTaskCreate, QcTaskUpdate, QcTaskOut, QcTaskItemCreate, QcTaskItemUpdate, QcTaskItemOut, - QcDefectCreate, QcDefectUpdate, QcDefectOut, + QcDefectCreate, QcDefectUpdate, QcDefectOut, QcDefectBulkSave, ) from app.schemas.common import Response, PageResponse from app.services.auth_service import get_current_user @@ -158,6 +158,41 @@ async def create_defect(body: QcDefectCreate, db: AsyncSession = Depends(get_db) return Response.ok(QcDefectOut.model_validate(defect)) +@router.get("/defects/by-coil/{coil_no}", response_model=Response[list[QcDefectOut]]) +async def list_defects_by_coil(coil_no: str, db: AsyncSession = Depends(get_db), _=Depends(get_current_user)): + r = await db.execute( + select(QcDefect) + .where(QcDefect.coil_no == coil_no, QcDefect.del_flag == 0) + .order_by(QcDefect.seq_no.asc().nulls_last(), QcDefect.id.asc()) + ) + return Response.ok([QcDefectOut.model_validate(d) for d in r.scalars()]) + + +@router.post("/defects/bulk-save", response_model=Response[list[QcDefectOut]]) +async def bulk_save_defects(body: QcDefectBulkSave, db: AsyncSession = Depends(get_db), _=Depends(get_current_user)): + """按卷号替换全部缺陷记录(软删旧记录后批量插入)""" + coil_no = body.coil_no + # 软删旧记录 + old = await db.execute(select(QcDefect).where(QcDefect.coil_no == coil_no, QcDefect.del_flag == 0)) + for d in old.scalars(): + d.del_flag = 1 + # 插入新记录 + saved: list[QcDefect] = [] + for idx, item in enumerate(body.defects, start=1): + data = item.model_dump() + data["coil_no"] = coil_no + if not data.get("seq_no"): + data["seq_no"] = idx + # 自动算长度 + if data.get("start_position") is not None and data.get("end_position") is not None and not data.get("length_val"): + data["length_val"] = round(float(data["end_position"]) - float(data["start_position"]), 3) + d = QcDefect(**data) + db.add(d) + saved.append(d) + await db.flush() + return Response.ok([QcDefectOut.model_validate(d) for d in saved]) + + @router.put("/defects/{defect_id}", response_model=Response[QcDefectOut]) async def update_defect(defect_id: int, body: QcDefectUpdate, db: AsyncSession = Depends(get_db), _=Depends(get_current_user)): r = await db.execute(select(QcDefect).where(QcDefect.id == defect_id, QcDefect.del_flag == 0)) diff --git a/backend/app/database.py b/backend/app/database.py index 14e2dd9..e214b75 100644 --- a/backend/app/database.py +++ b/backend/app/database.py @@ -31,3 +31,69 @@ async def get_db(): async def init_db(): async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) + await _run_migrations(conn) + + +async def _run_migrations(conn): + """Postgres 专用:为已存在表追加新列(幂等)""" + from sqlalchemy import text + is_pg = engine.dialect.name == "postgresql" + if not is_pg: + return + statements = [ + # production_plans 新字段 + "ALTER TABLE production_plans ADD COLUMN IF NOT EXISTS cold_coil_no VARCHAR(30)", + "ALTER TABLE production_plans ADD COLUMN IF NOT EXISTS hot_coil_no VARCHAR(30)", + "ALTER TABLE production_plans ADD COLUMN IF NOT EXISTS incoming_thickness DOUBLE PRECISION", + "ALTER TABLE production_plans ADD COLUMN IF NOT EXISTS product_thickness DOUBLE PRECISION", + "ALTER TABLE production_plans ADD COLUMN IF NOT EXISTS deviation_upper DOUBLE PRECISION", + "ALTER TABLE production_plans ADD COLUMN IF NOT EXISTS deviation_lower DOUBLE PRECISION", + "ALTER TABLE production_plans ADD COLUMN IF NOT EXISTS incoming_width DOUBLE PRECISION", + "ALTER TABLE production_plans ADD COLUMN IF NOT EXISTS product_width DOUBLE PRECISION", + "ALTER TABLE production_plans ADD COLUMN IF NOT EXISTS packaging_req VARCHAR(30)", + "ALTER TABLE production_plans ADD COLUMN IF NOT EXISTS trim_req VARCHAR(30)", + "ALTER TABLE production_plans ADD COLUMN IF NOT EXISTS rolling_mode VARCHAR(30)", + "ALTER TABLE production_plans ADD COLUMN IF NOT EXISTS split_count INTEGER DEFAULT 1", + "ALTER TABLE production_plans ADD COLUMN IF NOT EXISTS coil_diameter DOUBLE PRECISION", + "ALTER TABLE production_plans ADD COLUMN IF NOT EXISTS next_process VARCHAR(30)", + # 状态列改为 VARCHAR 以适配新值 + "ALTER TABLE production_plans ALTER COLUMN status TYPE VARCHAR(20) USING status::text", + # production_records 新字段 + "ALTER TABLE production_records ADD COLUMN IF NOT EXISTS sub_coil_no VARCHAR(30)", + "ALTER TABLE production_records ADD COLUMN IF NOT EXISTS hot_coil_no VARCHAR(30)", + "ALTER TABLE production_records ADD COLUMN IF NOT EXISTS team VARCHAR(10)", + "ALTER TABLE production_records ADD COLUMN IF NOT EXISTS steel_grade VARCHAR(30)", + "ALTER TABLE production_records ADD COLUMN IF NOT EXISTS incoming_thickness DOUBLE PRECISION", + "ALTER TABLE production_records ADD COLUMN IF NOT EXISTS deviation_upper DOUBLE PRECISION", + "ALTER TABLE production_records ADD COLUMN IF NOT EXISTS deviation_lower DOUBLE PRECISION", + "ALTER TABLE production_records ADD COLUMN IF NOT EXISTS incoming_width DOUBLE PRECISION", + "ALTER TABLE production_records ADD COLUMN IF NOT EXISTS outlet_width DOUBLE PRECISION", + "ALTER TABLE production_records ADD COLUMN IF NOT EXISTS incoming_weight DOUBLE PRECISION", + "ALTER TABLE production_records ADD COLUMN IF NOT EXISTS weighed_weight DOUBLE PRECISION", + "ALTER TABLE production_records ADD COLUMN IF NOT EXISTS packaging_req VARCHAR(30)", + "ALTER TABLE production_records ADD COLUMN IF NOT EXISTS trim_req VARCHAR(30)", + "ALTER TABLE production_records ADD COLUMN IF NOT EXISTS surface_quality VARCHAR(30)", + "ALTER TABLE production_records ADD COLUMN IF NOT EXISTS product_quality DOUBLE PRECISION", + "ALTER TABLE production_records ADD COLUMN IF NOT EXISTS product_length DOUBLE PRECISION", + "ALTER TABLE production_records ADD COLUMN IF NOT EXISTS length_per_ton DOUBLE PRECISION", + "ALTER TABLE production_records ADD COLUMN IF NOT EXISTS offline_time TIMESTAMP", + "ALTER TABLE production_records ADD COLUMN IF NOT EXISTS status VARCHAR(20) DEFAULT 'UNWEIGH'", + # qc_defect 新字段 + "ALTER TABLE qc_defect ADD COLUMN IF NOT EXISTS seq_no INTEGER", + "ALTER TABLE qc_defect ADD COLUMN IF NOT EXISTS defect_desc VARCHAR(200)", + "ALTER TABLE qc_defect ADD COLUMN IF NOT EXISTS start_position DOUBLE PRECISION", + "ALTER TABLE qc_defect ADD COLUMN IF NOT EXISTS end_position DOUBLE PRECISION", + "ALTER TABLE qc_defect ADD COLUMN IF NOT EXISTS length_val DOUBLE PRECISION", + "ALTER TABLE qc_defect ADD COLUMN IF NOT EXISTS upper_surface BOOLEAN DEFAULT FALSE", + "ALTER TABLE qc_defect ADD COLUMN IF NOT EXISTS lower_surface BOOLEAN DEFAULT FALSE", + "ALTER TABLE qc_defect ADD COLUMN IF NOT EXISTS side_op BOOLEAN DEFAULT FALSE", + "ALTER TABLE qc_defect ADD COLUMN IF NOT EXISTS side_middle BOOLEAN DEFAULT FALSE", + "ALTER TABLE qc_defect ADD COLUMN IF NOT EXISTS side_drive BOOLEAN DEFAULT FALSE", + "ALTER TABLE qc_defect ADD COLUMN IF NOT EXISTS is_main BOOLEAN DEFAULT FALSE", + "ALTER TABLE qc_defect ADD COLUMN IF NOT EXISTS image_url VARCHAR(255)", + ] + for s in statements: + try: + await conn.execute(text(s)) + except Exception: + pass diff --git a/backend/app/models/plan.py b/backend/app/models/plan.py index 5975a1d..82ef45b 100644 --- a/backend/app/models/plan.py +++ b/backend/app/models/plan.py @@ -1,14 +1,9 @@ -from sqlalchemy import Column, Integer, String, Float, DateTime, Enum, Text, func +from sqlalchemy import Column, Integer, String, Float, DateTime, Text, func from app.database import Base -import enum -class PlanStatus(str, enum.Enum): - DRAFT = "draft" # 草稿 - CONFIRMED = "confirmed" # 已确认 - IN_PROGRESS = "in_progress" # 执行中 - COMPLETED = "completed" # 完成 - CANCELLED = "cancelled" # 取消 +# 计划状态:准备好/在线/生产中/产出 +PLAN_STATUS = ("ready", "online", "producing", "produced") class ProductionPlan(Base): @@ -17,16 +12,34 @@ class ProductionPlan(Base): id = Column(Integer, primary_key=True, index=True) plan_no = Column(String(30), unique=True, nullable=False, index=True, comment="计划号") - plan_date = Column(DateTime, nullable=False, comment="计划日期") + plan_date = Column(DateTime, nullable=False, comment="计划时间") + status = Column(String(20), default="ready", comment="状态: ready/online/producing/produced") + + # 新结构:卷号 / 钢种 / 厚宽 / 偏差 / 工艺 + cold_coil_no = Column(String(30), index=True, comment="冷卷号") + hot_coil_no = Column(String(30), index=True, comment="热卷号") + steel_grade = Column(String(30), comment="钢种") + incoming_thickness = Column(Float, comment="来料厚度 mm") + product_thickness = Column(Float, comment="产品厚度 mm") + deviation_upper = Column(Float, comment="偏差上限 mm") + deviation_lower = Column(Float, comment="偏差下限 mm") + incoming_width = Column(Float, comment="来料宽度 mm") + product_width = Column(Float, comment="产品宽度 mm") + packaging_req = Column(String(30), comment="包装要求") + trim_req = Column(String(30), comment="切边要求") + rolling_mode = Column(String(30), comment="轧制模式") + coil_diameter = Column(Float, comment="卷径 mm") + split_count = Column(Integer, default=1, comment="分卷数") + next_process = Column(String(30), comment="下工序") + + # 兼容历史字段 shift = Column(String(10), comment="班次") - plan_quantity = Column(Integer, default=0, comment="计划数量(卷)") - plan_weight = Column(Float, default=0, comment="计划重量kg") - actual_quantity = Column(Integer, default=0, comment="实际数量(卷)") - actual_weight = Column(Float, default=0, comment="实际重量kg") - status = Column(Enum(PlanStatus), default=PlanStatus.DRAFT) - steel_grade = Column(String(30), comment="主要钢种") - spec_range = Column(String(50), comment="规格范围") - priority = Column(Integer, default=5, comment="优先级1-10") + plan_quantity = Column(Integer, default=0) + plan_weight = Column(Float, default=0) + actual_quantity = Column(Integer, default=0) + actual_weight = Column(Float, default=0) + spec_range = Column(String(50)) + priority = Column(Integer, default=5) remark = Column(Text) created_by = Column(String(50)) created_at = Column(DateTime, server_default=func.now()) diff --git a/backend/app/models/production.py b/backend/app/models/production.py index 686a556..44816e5 100644 --- a/backend/app/models/production.py +++ b/backend/app/models/production.py @@ -7,21 +7,42 @@ class ProductionRecord(Base): __tablename__ = "production_records" id = Column(Integer, primary_key=True, index=True) - coil_no = Column(String(30), nullable=False, index=True) + coil_no = Column(String(30), nullable=False, index=True) # 兼容旧字段,等同于 sub_coil_no + sub_coil_no = Column(String(30), index=True, comment="子卷号") + hot_coil_no = Column(String(30), index=True, comment="热卷号") plan_id = Column(Integer, ForeignKey("production_plans.id"), nullable=True) - shift = Column(String(10), comment="班次: 甲/乙/丙/丁") - shift_date = Column(DateTime, comment="班期") - start_time = Column(DateTime, comment="开始时间") - end_time = Column(DateTime, comment="结束时间") - process_length = Column(Float, comment="处理长度m") - process_weight = Column(Float, comment="处理重量kg") - avg_speed = Column(Float, comment="平均速度m/min") - max_speed = Column(Float, comment="最大速度m/min") - acid_consumption = Column(Float, comment="酸耗量L") - inlet_thickness = Column(Float, comment="入口厚度mm") - outlet_thickness = Column(Float, comment="出口厚度mm") - inlet_width = Column(Float, comment="入口宽度mm") - quality_grade = Column(String(10), comment="质量等级") + shift = Column(String(10), comment="班") + team = Column(String(10), comment="组") + steel_grade = Column(String(30), comment="钢种") + incoming_thickness = Column(Float, comment="来料厚度 mm") + outlet_thickness = Column(Float, comment="出口厚度 mm") + deviation_upper = Column(Float, comment="偏差上限") + deviation_lower = Column(Float, comment="偏差下限") + incoming_width = Column(Float, comment="来料宽度 mm") + outlet_width = Column(Float, comment="出口宽度 mm") + incoming_weight = Column(Float, comment="来料重量 t") + weighed_weight = Column(Float, comment="称重重量 t") + packaging_req = Column(String(30), comment="包装要求") + trim_req = Column(String(30), comment="切边要求") + surface_quality = Column(String(30), comment="表面质量") + product_quality = Column(Float, comment="成品质量 %") + product_length = Column(Float, comment="成品长度 m") + length_per_ton = Column(Float, comment="吨钢长度 m/t") + offline_time = Column(DateTime, comment="下线时间") + status = Column(String(20), default="UNWEIGH", comment="状态: UNWEIGH/PRODUCT") + + # 兼容历史字段 + shift_date = Column(DateTime) + start_time = Column(DateTime) + end_time = Column(DateTime) + process_length = Column(Float) + process_weight = Column(Float) + avg_speed = Column(Float) + max_speed = Column(Float) + acid_consumption = Column(Float) + inlet_thickness = Column(Float) + inlet_width = Column(Float) + quality_grade = Column(String(10)) operator = Column(String(50)) remark = Column(Text) created_at = Column(DateTime, server_default=func.now()) diff --git a/backend/app/models/quality.py b/backend/app/models/quality.py index e63a267..3d7dfff 100644 --- a/backend/app/models/quality.py +++ b/backend/app/models/quality.py @@ -46,6 +46,20 @@ class QcDefect(Base): __tablename__ = "qc_defect" id = Column(Integer, primary_key=True, index=True) coil_no = Column(String(30), nullable=True, index=True) + seq_no = Column(Integer, nullable=True, comment="序号") + defect_desc = Column(String(200), nullable=True, comment="缺陷描述") + start_position = Column(Float, nullable=True, comment="开始位置") + end_position = Column(Float, nullable=True, comment="结束位置") + length_val = Column(Float, nullable=True, comment="长度") + upper_surface = Column(Boolean, default=False, comment="上板面") + lower_surface = Column(Boolean, default=False, comment="下板面") + side_op = Column(Boolean, default=False, comment="操作侧") + side_middle = Column(Boolean, default=False, comment="中间") + side_drive = Column(Boolean, default=False, comment="驱动侧") + is_main = Column(Boolean, default=False, comment="主缺陷") + image_url = Column(String(255), nullable=True, comment="缺陷图片URL") + + # 兼容旧字段 production_line = Column(String(50), nullable=True) position = Column(String(50), nullable=True) plate_surface = Column(String(20), nullable=True) @@ -53,7 +67,7 @@ class QcDefect(Base): defect_type = Column(String(50), nullable=True, index=True) defect_rate = Column(Float, nullable=True) defect_weight = Column(Float, nullable=True) - degree = Column(String(20), nullable=True) # light/normal/serious + degree = Column(String(20), nullable=True) # light/medium/serious judge_level = Column(String(20), nullable=True) judge_by = Column(String(50), nullable=True) judge_time = Column(DateTime, nullable=True) diff --git a/backend/app/schemas/plan.py b/backend/app/schemas/plan.py index 1cfced9..8eebe47 100644 --- a/backend/app/schemas/plan.py +++ b/backend/app/schemas/plan.py @@ -1,30 +1,48 @@ from pydantic import BaseModel from typing import Optional from datetime import datetime -from app.models.plan import PlanStatus class PlanCreate(BaseModel): plan_no: str plan_date: datetime - shift: Optional[str] = None - plan_quantity: int = 0 - plan_weight: float = 0 + cold_coil_no: Optional[str] = None + hot_coil_no: Optional[str] = None steel_grade: Optional[str] = None - spec_range: Optional[str] = None - priority: int = 5 + incoming_thickness: Optional[float] = None + product_thickness: Optional[float] = None + deviation_upper: Optional[float] = None + deviation_lower: Optional[float] = None + incoming_width: Optional[float] = None + product_width: Optional[float] = None + packaging_req: Optional[str] = None + trim_req: Optional[str] = None + rolling_mode: Optional[str] = None + coil_diameter: Optional[float] = None + split_count: Optional[int] = 1 + next_process: Optional[str] = None + status: Optional[str] = "ready" remark: Optional[str] = None class PlanUpdate(BaseModel): plan_date: Optional[datetime] = None - shift: Optional[str] = None - plan_quantity: Optional[int] = None - plan_weight: Optional[float] = None - actual_quantity: Optional[int] = None - actual_weight: Optional[float] = None - status: Optional[PlanStatus] = None - priority: Optional[int] = None + cold_coil_no: Optional[str] = None + hot_coil_no: Optional[str] = None + steel_grade: Optional[str] = None + incoming_thickness: Optional[float] = None + product_thickness: Optional[float] = None + deviation_upper: Optional[float] = None + deviation_lower: Optional[float] = None + incoming_width: Optional[float] = None + product_width: Optional[float] = None + packaging_req: Optional[str] = None + trim_req: Optional[str] = None + rolling_mode: Optional[str] = None + coil_diameter: Optional[float] = None + split_count: Optional[int] = None + next_process: Optional[str] = None + status: Optional[str] = None remark: Optional[str] = None @@ -32,16 +50,24 @@ class PlanOut(BaseModel): id: int plan_no: str plan_date: datetime - shift: Optional[str] - plan_quantity: int - plan_weight: float - actual_quantity: int - actual_weight: float - status: PlanStatus - steel_grade: Optional[str] - spec_range: Optional[str] - priority: int - created_by: Optional[str] + status: Optional[str] = None + cold_coil_no: Optional[str] = None + hot_coil_no: Optional[str] = None + steel_grade: Optional[str] = None + incoming_thickness: Optional[float] = None + product_thickness: Optional[float] = None + deviation_upper: Optional[float] = None + deviation_lower: Optional[float] = None + incoming_width: Optional[float] = None + product_width: Optional[float] = None + packaging_req: Optional[str] = None + trim_req: Optional[str] = None + rolling_mode: Optional[str] = None + coil_diameter: Optional[float] = None + split_count: Optional[int] = 1 + next_process: Optional[str] = None + remark: Optional[str] = None + created_by: Optional[str] = None created_at: datetime class Config: diff --git a/backend/app/schemas/production.py b/backend/app/schemas/production.py index 1ff2c81..d468435 100644 --- a/backend/app/schemas/production.py +++ b/backend/app/schemas/production.py @@ -5,52 +5,82 @@ from datetime import datetime class ProductionRecordCreate(BaseModel): coil_no: str + sub_coil_no: Optional[str] = None + hot_coil_no: Optional[str] = None plan_id: Optional[int] = None shift: Optional[str] = None - shift_date: Optional[datetime] = None - start_time: Optional[datetime] = None - end_time: Optional[datetime] = None - process_length: Optional[float] = None - process_weight: Optional[float] = None - avg_speed: Optional[float] = None - max_speed: Optional[float] = None - acid_consumption: Optional[float] = None - inlet_thickness: Optional[float] = None + team: Optional[str] = None + steel_grade: Optional[str] = None + incoming_thickness: Optional[float] = None outlet_thickness: Optional[float] = None - inlet_width: Optional[float] = None - quality_grade: Optional[str] = None - operator: Optional[str] = None + deviation_upper: Optional[float] = None + deviation_lower: Optional[float] = None + incoming_width: Optional[float] = None + outlet_width: Optional[float] = None + incoming_weight: Optional[float] = None + weighed_weight: Optional[float] = None + packaging_req: Optional[str] = None + trim_req: Optional[str] = None + surface_quality: Optional[str] = None + product_quality: Optional[float] = None + product_length: Optional[float] = None + length_per_ton: Optional[float] = None + offline_time: Optional[datetime] = None + status: Optional[str] = "UNWEIGH" remark: Optional[str] = None class ProductionRecordUpdate(BaseModel): + sub_coil_no: Optional[str] = None + hot_coil_no: Optional[str] = None shift: Optional[str] = None - end_time: Optional[datetime] = None - process_length: Optional[float] = None - process_weight: Optional[float] = None - avg_speed: Optional[float] = None - acid_consumption: Optional[float] = None - quality_grade: Optional[str] = None + team: Optional[str] = None + steel_grade: Optional[str] = None + incoming_thickness: Optional[float] = None + outlet_thickness: Optional[float] = None + deviation_upper: Optional[float] = None + deviation_lower: Optional[float] = None + incoming_width: Optional[float] = None + outlet_width: Optional[float] = None + incoming_weight: Optional[float] = None + weighed_weight: Optional[float] = None + packaging_req: Optional[str] = None + trim_req: Optional[str] = None + surface_quality: Optional[str] = None + product_quality: Optional[float] = None + product_length: Optional[float] = None + length_per_ton: Optional[float] = None + offline_time: Optional[datetime] = None + status: Optional[str] = None remark: Optional[str] = None class ProductionRecordOut(BaseModel): id: int coil_no: str - plan_id: Optional[int] - shift: Optional[str] - shift_date: Optional[datetime] - start_time: Optional[datetime] - end_time: Optional[datetime] - process_length: Optional[float] - process_weight: Optional[float] - avg_speed: Optional[float] - max_speed: Optional[float] - acid_consumption: Optional[float] - inlet_thickness: Optional[float] - outlet_thickness: Optional[float] - quality_grade: Optional[str] - operator: Optional[str] + sub_coil_no: Optional[str] = None + hot_coil_no: Optional[str] = None + plan_id: Optional[int] = None + shift: Optional[str] = None + team: Optional[str] = None + steel_grade: Optional[str] = None + incoming_thickness: Optional[float] = None + outlet_thickness: Optional[float] = None + deviation_upper: Optional[float] = None + deviation_lower: Optional[float] = None + incoming_width: Optional[float] = None + outlet_width: Optional[float] = None + incoming_weight: Optional[float] = None + weighed_weight: Optional[float] = None + packaging_req: Optional[str] = None + trim_req: Optional[str] = None + surface_quality: Optional[str] = None + product_quality: Optional[float] = None + product_length: Optional[float] = None + length_per_ton: Optional[float] = None + offline_time: Optional[datetime] = None + status: Optional[str] = None + remark: Optional[str] = None created_at: datetime class Config: diff --git a/backend/app/schemas/quality.py b/backend/app/schemas/quality.py index 2e3e49c..7136e06 100644 --- a/backend/app/schemas/quality.py +++ b/backend/app/schemas/quality.py @@ -85,8 +85,78 @@ class QcTaskOut(BaseModel): from_attributes = True -class QcDefectCreate(BaseModel): +class QcDefectBase(BaseModel): coil_no: Optional[str] = None + seq_no: Optional[int] = None + defect_desc: Optional[str] = None + start_position: Optional[float] = None + end_position: Optional[float] = None + length_val: Optional[float] = None + upper_surface: Optional[bool] = False + lower_surface: Optional[bool] = False + side_op: Optional[bool] = False + side_middle: Optional[bool] = False + side_drive: Optional[bool] = False + is_main: Optional[bool] = False + image_url: Optional[str] = None + defect_code: Optional[str] = None + defect_type: Optional[str] = None + degree: Optional[str] = None + remark: Optional[str] = None + + +class QcDefectCreate(QcDefectBase): + production_line: Optional[str] = None + position: Optional[str] = None + plate_surface: Optional[str] = None + defect_rate: Optional[float] = None + defect_weight: Optional[float] = None + judge_level: Optional[str] = None + judge_by: Optional[str] = None + judge_time: Optional[datetime] = None + main_mark: Optional[int] = None + whole_coil_mark: Optional[int] = None + + +class QcDefectUpdate(BaseModel): + seq_no: Optional[int] = None + defect_desc: Optional[str] = None + start_position: Optional[float] = None + end_position: Optional[float] = None + length_val: Optional[float] = None + upper_surface: Optional[bool] = None + lower_surface: Optional[bool] = None + side_op: Optional[bool] = None + side_middle: Optional[bool] = None + side_drive: Optional[bool] = None + is_main: Optional[bool] = None + image_url: Optional[str] = None + defect_code: Optional[str] = None + defect_type: Optional[str] = None + degree: Optional[str] = None + remark: Optional[str] = None + + +class QcDefectBulkSave(BaseModel): + coil_no: str + defects: List[QcDefectCreate] + + +class QcDefectOut(BaseModel): + id: int + coil_no: Optional[str] + seq_no: Optional[int] = None + defect_desc: Optional[str] = None + start_position: Optional[float] = None + end_position: Optional[float] = None + length_val: Optional[float] = None + upper_surface: Optional[bool] = None + lower_surface: Optional[bool] = None + side_op: Optional[bool] = None + side_middle: Optional[bool] = None + side_drive: Optional[bool] = None + is_main: Optional[bool] = None + image_url: Optional[str] = None production_line: Optional[str] = None position: Optional[str] = None plate_surface: Optional[str] = None @@ -101,36 +171,6 @@ class QcDefectCreate(BaseModel): main_mark: Optional[int] = None whole_coil_mark: Optional[int] = None remark: Optional[str] = None - - -class QcDefectUpdate(BaseModel): - defect_type: Optional[str] = None - defect_rate: Optional[float] = None - defect_weight: Optional[float] = None - degree: Optional[str] = None - judge_level: Optional[str] = None - judge_by: Optional[str] = None - judge_time: Optional[datetime] = None - remark: Optional[str] = None - - -class QcDefectOut(BaseModel): - id: int - coil_no: Optional[str] - production_line: Optional[str] - position: Optional[str] - plate_surface: Optional[str] - defect_code: Optional[str] - defect_type: Optional[str] - defect_rate: Optional[float] - defect_weight: Optional[float] - degree: Optional[str] - judge_level: Optional[str] - judge_by: Optional[str] - judge_time: Optional[datetime] - main_mark: Optional[int] - whole_coil_mark: Optional[int] - remark: Optional[str] created_at: datetime class Config: from_attributes = True diff --git a/frontend/package.json b/frontend/package.json index f59eee8..d0bfb26 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -16,7 +16,8 @@ "echarts": "^5.5.0", "vue-echarts": "^6.7.3", "dayjs": "^1.11.11", - "nprogress": "^0.2.0" + "nprogress": "^0.2.0", + "qrcode": "^1.5.3" }, "devDependencies": { "@vue/cli-plugin-babel": "^5.0.8", diff --git a/frontend/src/api/index.js b/frontend/src/api/index.js index 95d0aa6..cd9eeef 100644 --- a/frontend/src/api/index.js +++ b/frontend/src/api/index.js @@ -73,3 +73,5 @@ export const getQcDefects = params => request.get('/quality/defects', { params } export const createQcDefect = data => request.post('/quality/defects', data) export const updateQcDefect = (id, data) => request.put(`/quality/defects/${id}`, data) export const deleteQcDefect = id => request.delete(`/quality/defects/${id}`) +export const getQcDefectsByCoil = coilNo => request.get(`/quality/defects/by-coil/${encodeURIComponent(coilNo)}`) +export const bulkSaveQcDefects = data => request.post('/quality/defects/bulk-save', data) diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js index adfe943..0504580 100644 --- a/frontend/src/router/index.js +++ b/frontend/src/router/index.js @@ -46,18 +46,6 @@ const routes = [ component: () => import('@/views/Downtime.vue'), meta: { title: '停机管理', icon: 'el-icon-warning-outline', requiresAuth: true } }, - { - path: 'equipment', - name: 'Equipment', - component: () => import('@/views/Equipment.vue'), - meta: { title: '设备管理', icon: 'el-icon-set-up', requiresAuth: true } - }, - { - path: 'message', - name: 'Message', - component: () => import('@/views/Message.vue'), - meta: { title: '报文监控', icon: 'el-icon-connection', requiresAuth: true } - }, { path: 'process-model', name: 'ProcessModel', @@ -68,7 +56,7 @@ const routes = [ path: 'tension-model', name: 'TensionModel', component: () => import('@/views/TensionModel.vue'), - meta: { title: '张力设定', icon: 'el-icon-odometer', requiresAuth: true } + meta: { title: '张力模型', icon: 'el-icon-odometer', requiresAuth: true } }, { path: 'inspection', diff --git a/frontend/src/views/Inspection.vue b/frontend/src/views/Inspection.vue index cc6b9bc..d7e8943 100644 --- a/frontend/src/views/Inspection.vue +++ b/frontend/src/views/Inspection.vue @@ -36,7 +36,7 @@
{{ selectedCl.is_active ? '启用中' : '已停用' }} - +
@@ -269,6 +269,27 @@
+ + +