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 @@
+
+
+
+
+
+
+
+
+
设备编号
+
{{ selectedCl && selectedCl.equipment_code || '—' }}
+
使用手机/PDA 扫描上方二维码进入巡检填报
+
+
+
+
+
@@ -381,6 +402,7 @@ import {
getChecklistItems, createChecklistItem,
getInspectionRecords, createInspectionRecord, getInspectionRecordDetails,
} from '@/api'
+import QRCode from 'qrcode'
const PERIOD_MAP = {
daily: { label: '每日', badge: 'badge-blue' },
@@ -418,6 +440,9 @@ export default {
itemDialogVisible: false,
itemForm: { item_name: '', item_standard: '', sort_order: 0 },
+ // 二维码弹窗
+ qrDialogVisible: false,
+
// 开始巡检
inspectDialogVisible: false,
inspectForm: { inspector: '', inspect_time: '', status: 'ok', overall_result: '', remark: '' },
@@ -447,6 +472,17 @@ export default {
this.selectedCl = cl
await Promise.all([this.fetchClItems(), this.fetchRecords()])
},
+ openQrDialog() {
+ if (!this.selectedCl) return
+ this.qrDialogVisible = true
+ this.$nextTick(() => {
+ const canvas = this.$refs.qrCanvas
+ if (!canvas) return
+ const text = this.selectedCl.equipment_code || this.selectedCl.name || ''
+ if (!text) return
+ QRCode.toCanvas(canvas, text, { width: 220, margin: 1, color: { dark: '#0a0a0a', light: '#ffffff' } }).catch(() => {})
+ })
+ },
async fetchClItems() {
if (!this.selectedCl) return
try {
@@ -688,6 +724,9 @@ export default {
}
.sec-title { font-size: 11px; color: $text-muted; font-weight: 600; letter-spacing: .5px; text-transform: uppercase; }
+
+.qr-box { padding: 10px; background: #fff; border-radius: 4px; display: inline-flex; }
+.qr-box canvas { display: block; }
.form-field { display: flex; flex-direction: column; gap: 5px; }
.action-link { color: $sms-highlight; cursor: pointer; font-size: 12px; margin-right: 8px; &:hover { text-decoration: underline; } }
.modal-mask { position: fixed; inset: 0; background: rgba(0,0,0,.6); display: flex; align-items: center; justify-content: center; z-index: 9999; }
diff --git a/frontend/src/views/Layout.vue b/frontend/src/views/Layout.vue
index ad3fa1b..dab3806 100644
--- a/frontend/src/views/Layout.vue
+++ b/frontend/src/views/Layout.vue
@@ -71,15 +71,13 @@ const IC = {
const MENU = [
{ path: '/dashboard', title: '生产看板', icon: IC.dashboard },
+ { path: '/plan', title: '计划管理', icon: IC.plan },
{ path: '/material', title: '物料跟踪', icon: IC.material },
{ path: '/production', title: '实绩管理', icon: IC.production },
- { path: '/plan', title: '计划管理', icon: IC.plan },
- { path: '/downtime', title: '停机管理', icon: IC.downtime },
- { path: '/equipment', title: '设备管理', icon: IC.equipment },
- { path: '/inspection', title: '设备巡检', icon: IC.inspection },
- { path: '/message', title: '报文监控', icon: IC.message },
{ path: '/process-model', title: '工艺段模型', icon: IC.process },
- { path: '/tension-model', title: '张力设定', icon: IC.tension },
+ { path: '/tension-model', title: '张力模型', icon: IC.tension },
+ { path: '/downtime', title: '停机管理', icon: IC.downtime },
+ { path: '/inspection', title: '设备巡检', icon: IC.inspection },
{ path: '/quality', title: '质量管理', icon: IC.quality },
{ path: '/capacity', title: '产能分析', icon: IC.capacity },
]
diff --git a/frontend/src/views/Material.vue b/frontend/src/views/Material.vue
index 46b4109..ac53c69 100644
--- a/frontend/src/views/Material.vue
+++ b/frontend/src/views/Material.vue
@@ -1,154 +1,286 @@
-
-
-
-
-
-
- 卷号
-
-
-
- 状态
-
-
-
-
-
-
-
- 共 {{ total }} 条
-
-
+
+
+
+
+ 当前卷号
+ {{ current.coil_no || '—' }}
+
+
+ 工艺段速度
+ {{ current.speed.toFixed(1) }} m/min
+
+
+ 焊缝位置
+ {{ (weld.position * 100).toFixed(1) }} %
+
+
+ 当前设备
+ {{ currentEquipment.label }}
+
+
+ 开卷张力
+ {{ uncoiler.tension.toFixed(1) }} kN
+
+
+ 收卷张力
+ {{ recoiler.tension.toFixed(1) }} kN
+
+
+ {{ l1Online ? 'L1 在线' : '模拟数据' }}
+ {{ rtItems.length }} 测点
-
-
-
-
-
-
-
-
- 第 {{ query.page }} 页 / 共 {{ Math.ceil(total/query.page_size) }} 页
-
-
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
-
-
-
-
- | 时间 | 位置 | 事件类型 | 描述 | 实测厚度 | 速度 | 操作员 |
-
-
-
- | {{ fmtTime(t.event_time) }} |
- {{ t.position || '—' }} |
- {{ t.event_type }} |
- {{ t.event_desc || '—' }} |
- {{ t.actual_thickness || '—' }} |
- {{ t.speed || '—' }} |
- {{ t.operator || '—' }} |
-
-
- | 暂无跟踪记录 |
-
-
-
-
-
@@ -156,116 +288,328 @@
diff --git a/frontend/src/views/Plan.vue b/frontend/src/views/Plan.vue
index 1d8c220..b27e7cb 100644
--- a/frontend/src/views/Plan.vue
+++ b/frontend/src/views/Plan.vue
@@ -10,6 +10,10 @@
+
+ 冷卷号
+
+
日期
@@ -33,42 +37,50 @@
- | 计划号 | 计划日期 | 班次 |
- 计划(卷) | 计划重量(kg) |
- 实际(卷) | 实际重量(kg) |
- 完成率 | 优先级 | 状态 | 创建人 | 操作 |
+ 序号 |
+ 冷卷号 |
+ 热卷号 |
+ 钢种 |
+ 来料厚度 |
+ 产品厚度 |
+ 偏差上限 |
+ 偏差下限 |
+ 来料宽度 |
+ 产品宽度 |
+ 包装要求 |
+ 卷径 |
+ 分卷数 |
+ 下工序 |
+ 计划时间 |
+ 状态 |
+ 操作 |
-
- | {{ row.plan_no }} |
- {{ fmtDate(row.plan_date) }} |
- {{ row.shift ? row.shift + '班' : '—' }} |
- {{ row.plan_quantity }} |
- {{ row.plan_weight }} |
- {{ row.actual_quantity }} |
- {{ row.actual_weight }} |
-
-
-
- {{ completionRate(row) }}%
-
- —
- |
-
- P{{ row.priority }}
- |
+
+ | {{ idx + 1 }} |
+ {{ row.cold_coil_no || row.plan_no || '—' }} |
+ {{ row.hot_coil_no || '—' }} |
+ {{ row.steel_grade || '—' }} |
+ {{ fmtNum(row.incoming_thickness) }} |
+ {{ fmtNum(row.product_thickness) }} |
+ {{ fmtNum(row.deviation_upper, 3) }} |
+ {{ fmtNum(row.deviation_lower, 3) }} |
+ {{ fmtNum(row.incoming_width, 0) }} |
+ {{ fmtNum(row.product_width, 0) }} |
+ {{ row.packaging_req || '—' }} |
+ {{ fmtNum(row.coil_diameter, 0) }} |
+ {{ row.split_count != null ? row.split_count : 1 }} |
+ {{ row.next_process != null ? row.next_process : '—' }} |
+ {{ fmtTime(row.plan_date) }} |
{{ statusLabel(row.status) }} |
- {{ row.created_by || '—' }} |
编辑
- 确认
+ 上线
|
- | 暂无数据 |
+ 暂无数据 |
@@ -76,47 +88,82 @@
-
+
-
@@ -133,11 +180,10 @@
import { getPlans, createPlan, updatePlan, confirmPlan as apiConfirm } from '@/api'
const STATUS_MAP = {
- draft: { label: '草稿', badge: 'badge-gray' },
- confirmed: { label: '已确认', badge: 'badge-blue' },
- in_progress: { label: '执行中', badge: 'badge-green' },
- completed: { label: '完成', badge: 'badge-gray' },
- cancelled: { label: '取消', badge: 'badge-red' },
+ ready: { label: '准备好', badge: 'badge-green' },
+ online: { label: '在线', badge: 'badge-yellow' },
+ producing: { label: '生产中', badge: 'badge-yellow' },
+ produced: { label: '产出', badge: 'badge-blue' },
}
export default {
@@ -146,41 +192,62 @@ export default {
return {
loading: false, saving: false,
tableData: [], total: 0,
- query: { page: 1, page_size: 20, status: '', start_date: '', end_date: '' },
+ query: { page: 1, page_size: 50, status: '', cold_coil_no: '', start_date: '', end_date: '' },
statusOptions: Object.entries(STATUS_MAP).map(([value, { label }]) => ({ value, label })),
- dialogVisible: false, editRow: null, form: { priority: 5 },
+ dialogVisible: false, editRow: null, form: { split_count: 1, status: 'ready' },
}
},
created() { this.fetchData() },
methods: {
async fetchData() {
this.loading = true
- const params = { ...this.query }
- if (params.start_date) params.start_date += 'T00:00:00'
- if (params.end_date) params.end_date += 'T23:59:59'
- try { const res = await getPlans(params); this.tableData = res.data.items; this.total = res.data.total } finally { this.loading = false }
+ const params = { page: this.query.page, page_size: this.query.page_size }
+ if (this.query.status) params.status = this.query.status
+ if (this.query.start_date) params.start_date = this.query.start_date + 'T00:00:00'
+ if (this.query.end_date) params.end_date = this.query.end_date + 'T23:59:59'
+ try {
+ const res = await getPlans(params)
+ let items = res.data.items
+ if (this.query.cold_coil_no) {
+ items = items.filter(x => (x.cold_coil_no || '').includes(this.query.cold_coil_no))
+ }
+ this.tableData = items
+ this.total = res.data.total
+ } finally { this.loading = false }
},
- fmtDate(t) { return t ? t.slice(0, 10) : '—' },
- statusLabel(s) { return STATUS_MAP[s]?.label || s },
+ fmtTime(t) { return t ? t.slice(0, 16).replace('T', ' ') : '—' },
+ fmtNum(v, n = 2) { return v != null ? Number(v).toFixed(n) : '—' },
+ statusLabel(s) { return STATUS_MAP[s]?.label || s || '—' },
statusBadge(s) { return STATUS_MAP[s]?.badge || 'badge-gray' },
- completionRate(row) { return row.plan_quantity > 0 ? Math.min(100, Math.round(row.actual_quantity / row.plan_quantity * 100)) : 0 },
- rateColor(row) {
- const r = this.completionRate(row)
- return r >= 90 ? 'var(--accent-green)' : r >= 70 ? 'var(--accent-yellow)' : 'var(--accent-red)'
+ openDialog(row = null) {
+ this.editRow = row
+ if (row) {
+ const r = { ...row }
+ if (r.plan_date) r.plan_date = r.plan_date.slice(0, 16)
+ this.form = r
+ } else {
+ this.form = { plan_no: '', split_count: 1, status: 'ready', plan_date: this.nowDT() }
+ }
+ this.dialogVisible = true
+ },
+ nowDT() {
+ const d = new Date(); const p = n => String(n).padStart(2, '0')
+ return `${d.getFullYear()}-${p(d.getMonth()+1)}-${p(d.getDate())}T${p(d.getHours())}:${p(d.getMinutes())}`
},
- openDialog(row = null) { this.editRow = row; this.form = row ? { ...row } : { priority: 5, plan_quantity: 0, plan_weight: 0 }; this.dialogVisible = true },
async confirmPlan(row) {
- if (!confirm(`确认计划 ${row.plan_no}?`)) return
+ if (!confirm(`将计划 ${row.plan_no} 上线?`)) return
await apiConfirm(row.id)
- this.$message.success('已确认')
+ this.$message.success('已上线')
this.fetchData()
},
async save() {
if (!this.form.plan_no) { this.$message.error('计划号不能为空'); return }
+ if (!this.form.plan_date) { this.$message.error('计划时间不能为空'); return }
this.saving = true
try {
const d = { ...this.form }
- if (d.plan_date && !d.plan_date.includes('T')) d.plan_date += 'T00:00:00'
+ if (d.plan_date && !d.plan_date.includes(':')) d.plan_date += 'T00:00:00'
+ else if (d.plan_date && d.plan_date.length === 16) d.plan_date += ':00'
if (this.editRow) await updatePlan(this.editRow.id, d)
else await createPlan(d)
this.$message.success('保存成功')
@@ -195,6 +262,7 @@ export default {
@import '@/assets/styles/variables';
.action-link { color: $sms-highlight; cursor: pointer; font-size: 12px; margin-right: 12px; &:hover { text-decoration: underline; } }
.form-field { display: flex; flex-direction: column; gap: 5px; }
+.grid-3 { display: grid; grid-template-columns: 1fr 1fr 1fr; }
.modal-mask { position: fixed; inset: 0; background: rgba(0,0,0,.6); display: flex; align-items: center; justify-content: center; z-index: 9999; }
.modal-box { background: $bg-card; border: 1px solid $border; border-radius: 6px; max-width: 95vw; max-height: 90vh; display: flex; flex-direction: column; }
.modal-header { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; background: $bg-panel; border-bottom: 1px solid $border; font-size: 13px; font-weight: 600; color: $sms-highlight; .modal-close { cursor: pointer; color: $text-muted; &:hover { color: $text-primary; } } }
diff --git a/frontend/src/views/ProcessModel.vue b/frontend/src/views/ProcessModel.vue
index f029798..b7d316c 100644
--- a/frontend/src/views/ProcessModel.vue
+++ b/frontend/src/views/ProcessModel.vue
@@ -43,10 +43,10 @@
-
-
酸槽 4#–6#
+
+
酸槽 4#–5#
-
+
-
-
+
@@ -326,7 +326,7 @@ export default {
return {
lastRefresh: '--:--:--',
l1Online: false,
- tanks: Array.from({ length: 6 }, () => ({ conc: null, temp: null, fe2: null, rt: null, eff: null })),
+ tanks: Array.from({ length: 5 }, () => ({ conc: null, temp: null, fe2: null, rt: null, eff: null })),
rinse: Array.from({ length: 5 }, () => ({ ph: null, temp: null, flow: null, conductivity: null })),
current: { speed: null, tension_inlet: null, tension_outlet: null, acid_temp: null, coil_no: null },
steelGrades: STEEL_GRADES,
@@ -336,8 +336,8 @@ export default {
steel_grade: 'Q235',
target_pi: 95,
scale_weight: 8.5,
- acid_conc_list: [200, 188, 175, 162, 148, 135],
- acid_temp_list: [80, 78, 76, 75, 74, 72],
+ acid_conc_list: [200, 185, 170, 155, 140],
+ acid_temp_list: [80, 78, 76, 75, 74],
},
calculating: false,
calcResult: null,
diff --git a/frontend/src/views/Production.vue b/frontend/src/views/Production.vue
index 2877802..9b2ae99 100644
--- a/frontend/src/views/Production.vue
+++ b/frontend/src/views/Production.vue
@@ -4,25 +4,33 @@
@@ -34,99 +42,158 @@
共 {{ total }} 条