diff --git a/.DS_Store b/.DS_Store index c4f49fd..2c1b064 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/PPLZJHF2501.00-01 酸洗机组布置总图.pdf b/PPLZJHF2501.00-01 酸洗机组布置总图.pdf deleted file mode 100644 index 856bb2c..0000000 Binary files a/PPLZJHF2501.00-01 酸洗机组布置总图.pdf and /dev/null differ diff --git a/README.md b/README.md deleted file mode 100644 index 4396938..0000000 --- a/README.md +++ /dev/null @@ -1,188 +0,0 @@ -# 推拉酸洗线 L2 过程控制系统 - -基于 FastAPI + Vue 2 的推拉酸洗线二级过程控制系统,实现物料跟踪、实绩管理、计划管理、停机管理、设备管理、工艺预测等功能。 - ---- - -## 技术栈 - -| 层 | 技术 | -|----|------| -| 后端 | Python 3.11+,FastAPI,SQLAlchemy 2(async),asyncpg | -| 数据库 | PostgreSQL 16 | -| 缓存 | Redis 7 | -| 前端 | Vue 2.7,Element UI,Axios | -| L1通信 | UDP(asyncio DatagramProtocol),监听 9000 端口 | - ---- - -## 目录结构 - -``` -pickling-mes/ -├── backend/ -│ ├── app/ -│ │ ├── api/ # 路由(各模块接口) -│ │ ├── models/ # SQLAlchemy 数据模型 -│ │ ├── services/ # 业务逻辑(UDP解析、预测模型) -│ │ ├── config.py # 配置(环境变量) -│ │ ├── database.py # 数据库连接 -│ │ └── main.py # 入口 -│ ├── tests/ -│ │ └── test_udp_sender.py # UDP报文模拟测试工具 -│ └── requirements.txt -├── frontend/ -│ ├── src/ -│ │ ├── views/ # 页面组件 -│ │ ├── api/ # 接口调用 -│ │ ├── store/ # Vuex(认证) -│ │ └── router/ # 路由 -│ └── package.json -├── docs/ # 接口文档 -└── docker-compose.yml -``` - ---- - -## 启动方式 - -### 方式一:Docker Compose(推荐) - -**前置条件**:已安装 Docker Desktop - -```bash -cd pickling-mes -docker-compose up -d -``` - -启动后访问: -- 前端:http://localhost:8080 -- 后端 API 文档:http://localhost:8000/docs -- 默认账号:`admin` / `admin123` - -停止: -```bash -docker-compose down -``` - ---- - -### 方式二:本地开发启动 - -**前置条件**:Python 3.11+,Node.js 18+,PostgreSQL 16,Redis 7 - -#### 1. 准备数据库 - -```bash -# 启动 PostgreSQL 和 Redis(也可用 Docker 只起这两个服务) -docker-compose up -d postgres redis -``` - -#### 2. 启动后端 - -```bash -cd backend - -# 安装依赖 -pip install -r requirements.txt - -# 配置环境变量(可选,默认值已可用于本地开发) -cp .env.example .env # 如有需要修改数据库连接 - -# 启动 -uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 -``` - -后端启动时会自动: -- 建表(init_db) -- 创建默认 admin 账号(admin / admin123) -- 启动 UDP 监听服务(0.0.0.0:9000) - -#### 3. 启动前端 - -```bash -cd frontend - -# 安装依赖 -npm install - -# 开发模式启动 -npm run serve -``` - -访问 http://localhost:8080 - ---- - -## 环境变量说明 - -在 `backend/.env` 中配置(不存在则使用下列默认值): - -| 变量 | 默认值 | 说明 | -|------|--------|------| -| `DATABASE_URL` | `postgresql+asyncpg://postgres:password@localhost:5432/pickling_mes` | 数据库连接(async) | -| `DATABASE_SYNC_URL` | `postgresql://postgres:password@localhost:5432/pickling_mes` | 数据库连接(同步,Alembic用)| -| `REDIS_URL` | `redis://localhost:6379/0` | Redis 连接 | -| `SECRET_KEY` | `dev-secret-key` | JWT 签名密钥,**生产环境必须修改** | -| `L1_HOST` | `0.0.0.0` | UDP 监听地址 | -| `L1_PORT` | `9000` | UDP 监听端口(L1 PLC 向此端口发送报文)| -| `ACCESS_TOKEN_EXPIRE_MINUTES` | `480` | Token 有效期(分钟)| - ---- - -## L1 通信(UDP) - -系统启动后自动监听 UDP `0.0.0.0:9000`,接收 L1 PLC 推送的报文。 - -**已实现的报文类型**(Body 格式待 PLC 方协议文档确认后适配): - -| 报文 ID | 含义 | 触发时机 | -|---------|------|---------| -| PC01 | 卷材入口 | 带钢上线时 | -| PC02 | 卷材出口 | 带钢下线时 | -| PC03 | 过程数据 | 周期推送(2s)| -| PC04 | 质量缺陷 | 缺陷检出时 | -| PC05 | 设备状态 | 状态变化时 | -| PC20 | 心跳 | 每 10s | - -**模拟测试**(无 PLC 时验证 UDP 通信): - -```bash -cd backend -python tests/test_udp_sender.py -# 默认向 127.0.0.1:9000 发送 PC20/PC01/PC03/PC02 测试帧 -``` - ---- - -## 预测模型 - -后端内置 4 个基于物理公式的工艺预测模型,无需训练数据即可运行: - -| 模型 | 接口 | 说明 | -|------|------|------| -| 酸洗速度 | `POST /prediction/acid-speed` | 基于 Arrhenius 动力学,输出最大允许速度 | -| 张力设定 | `POST /prediction/tension` | 基于截面积×屈服强度,输出各区张力 | -| 质量预测 | `POST /prediction/quality` | 输出质量等级(A1~C)及改进建议 | -| 消耗预测 | `POST /prediction/consumption` | 输出单卷酸、蒸汽、电、水消耗量 | - -接口参数详见:http://localhost:8000/docs - ---- - -## 主要功能模块 - -| 路径 | 模块 | -|------|------| -| `/dashboard` | 生产看板(实时指标、趋势图)| -| `/material` | 物料跟踪(卷材全流程跟踪)| -| `/production` | 实绩管理 | -| `/plan` | 计划管理 | -| `/downtime` | 停机管理 | -| `/equipment` | 设备管理 | -| `/message` | 报文监控(UDP收发日志)| -| `/process-model` | 工艺段模型(酸洗速度预测)| -| `/tension-model` | 张力设定 | -| `/pdi` | PDI 管理(L3 下发→L2 确认)| -| `/quality` | 质量管理 | -| `/capacity` | 产能分析 | diff --git a/docs/.DS_Store b/docs/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/docs/.DS_Store differ diff --git a/docs/L1-L2接口需求说明书_共同评估.pdf b/docs/L1-L2接口需求说明书_共同评估.pdf deleted file mode 100644 index 90b7cc4..0000000 Binary files a/docs/L1-L2接口需求说明书_共同评估.pdf and /dev/null differ diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js index 0504580..1b041cf 100644 --- a/frontend/src/router/index.js +++ b/frontend/src/router/index.js @@ -14,14 +14,8 @@ const routes = [ path: '/', component: () => import('@/views/Layout.vue'), meta: { requiresAuth: true }, - redirect: '/dashboard', + redirect: '/plan', children: [ - { - path: 'dashboard', - name: 'Dashboard', - component: () => import('@/views/Dashboard.vue'), - meta: { title: '生产看板', icon: 'el-icon-monitor', requiresAuth: true } - }, { path: 'material', name: 'Material', @@ -70,12 +64,6 @@ const routes = [ component: () => import('@/views/Quality.vue'), meta: { title: '质量管理', icon: 'el-icon-medal', requiresAuth: true } }, - { - path: 'capacity', - name: 'Capacity', - component: () => import('@/views/Capacity.vue'), - meta: { title: '产能分析', icon: 'el-icon-s-data', requiresAuth: true } - }, ] }, { path: '*', redirect: '/' } diff --git a/frontend/src/views/Layout.vue b/frontend/src/views/Layout.vue index dab3806..94bfa24 100644 --- a/frontend/src/views/Layout.vue +++ b/frontend/src/views/Layout.vue @@ -70,7 +70,6 @@ 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 }, @@ -79,7 +78,6 @@ const MENU = [ { path: '/downtime', title: '停机管理', icon: IC.downtime }, { path: '/inspection', title: '设备巡检', icon: IC.inspection }, { path: '/quality', title: '质量管理', icon: IC.quality }, - { path: '/capacity', title: '产能分析', icon: IC.capacity }, ] export default { diff --git a/frontend/src/views/Material.vue b/frontend/src/views/Material.vue index f0905df..6fffb63 100644 --- a/frontend/src/views/Material.vue +++ b/frontend/src/views/Material.vue @@ -36,8 +36,16 @@
推拉酸洗线 - 物料跟踪总图
- - + + + + + + + + {{ s.name }} + @@ -232,19 +240,12 @@
- -
-
-
- {{ t.label }} - {{ t.count }} -
-
- {{ rtItems.length }} 项实时数据 + +
+
入口段 + 在线 {{ onlinePlans.length }} / 生产中 {{ producingPlan ? 1 : 0 }}
- - -
+
@@ -291,8 +292,13 @@
- -
+
+
+ + +
+
酸洗段(5 槽)
+
{{ i+1 }}# 酸洗槽
@@ -323,8 +329,13 @@
- -
+
+
+ + +
+
漂洗段(5 级)+ 烘干
+
{{ i+1 }}# 漂洗
@@ -358,8 +369,13 @@
- -
+
+
+ + +
+
出口段
+
三辊张力装置
@@ -393,12 +409,18 @@
- -
+
+
+ + +
+
物料跟踪表 {{ equipments.length }} 台设备
+
+ @@ -411,6 +433,11 @@ +
# 设备 状态 当前钢卷
{{ i + 1 }} + + {{ eq.section }} + + {{ eq.label }} 加工中 @@ -425,7 +452,7 @@
-
+
@@ -435,27 +462,35 @@ function rnd(base, amp) { return base + (Math.random() - 0.5) * amp } function fix(v, n = 1) { return Number(v).toFixed(n) } const EQUIPMENTS = [ - { k:'uncoiler', label:'开卷机', type:'coiler', code:'DC-1' }, - { k:'straightener', label:'九辊矫直机', type:'rolls9', code:'STR-9' }, - { k:'crop_shear', label:'切头剪', type:'shear', code:'CRP' }, - { k:'acid1', label:'酸洗槽1', type:'acid', idx:0 }, - { k:'acid2', label:'酸洗槽2', type:'acid', idx:1 }, - { k:'acid3', label:'酸洗槽3', type:'acid', idx:2 }, - { k:'acid4', label:'酸洗槽4', type:'acid', idx:3 }, - { k:'acid5', label:'酸洗槽5', type:'acid', idx:4 }, - { k:'rinse', label:'漂洗段', type:'rinse' }, - { k:'dryer', label:'热风烘干段', type:'dryer' }, - { k:'br1', label:'1号夹送辊', type:'pinch', code:'BR-1' }, - { k:'loop', label:'活套坑', type:'loop' }, - { k:'br2', label:'2号夹送辊', type:'pinch', code:'BR-2' }, - { k:'br3', label:'3号夹送辊', type:'pinch', code:'BR-3' }, - { k:'tension', label:'三辊张力装置', type:'tension3', code:'TEN-3' }, - { k:'leveler', label:'平整机', type:'leveler', code:'SPM' }, - { k:'tail_shear', label:'切尾剪', type:'shear', code:'TLS' }, - { k:'oiler', label:'静电涂油机', type:'oiler', code:'EOL' }, - { k:'recoiler', label:'卷取机', type:'recoiler', code:'REC-1' }, + { k:'uncoiler', label:'开卷机', type:'coiler', code:'DC-1', section:'入口段' }, + { k:'straightener', label:'九辊矫直机', type:'rolls9', code:'STR-9', section:'入口段' }, + { k:'crop_shear', label:'切头剪', type:'shear', code:'CRP', section:'入口段' }, + { k:'acid1', label:'酸洗槽1', type:'acid', idx:0, section:'酸洗段' }, + { k:'acid2', label:'酸洗槽2', type:'acid', idx:1, section:'酸洗段' }, + { k:'acid3', label:'酸洗槽3', type:'acid', idx:2, section:'酸洗段' }, + { k:'acid4', label:'酸洗槽4', type:'acid', idx:3, section:'酸洗段' }, + { k:'acid5', label:'酸洗槽5', type:'acid', idx:4, section:'酸洗段' }, + { k:'rinse', label:'漂洗段', type:'rinse', section:'清洗段' }, + { k:'dryer', label:'热风烘干段', type:'dryer', section:'烘干段' }, + { k:'br1', label:'1号夹送辊', type:'pinch', code:'BR-1', section:'出口段' }, + { k:'loop', label:'活套坑', type:'loop', section:'出口段' }, + { k:'br2', label:'2号夹送辊', type:'pinch', code:'BR-2', section:'出口段' }, + { k:'br3', label:'3号夹送辊', type:'pinch', code:'BR-3', section:'出口段' }, + { k:'tension', label:'三辊张力装置', type:'tension3', code:'TEN-3', section:'出口段' }, + { k:'leveler', label:'平整机', type:'leveler', code:'SPM', section:'出口段' }, + { k:'tail_shear', label:'切尾剪', type:'shear', code:'TLS', section:'出口段' }, + { k:'oiler', label:'静电涂油机', type:'oiler', code:'EOL', section:'出口段' }, + { k:'recoiler', label:'卷取机', type:'recoiler', code:'REC-1', section:'出口段' }, ] +const SECTION_COLORS = { + '入口段': '#5a8fc8', + '酸洗段': '#ffaa44', + '清洗段': '#3aa0c8', + '烘干段': '#e87a3a', + '出口段': '#88c070', +} + // 默认辊缝值 (mm) const DEFAULT_GAP = { straightener: 4.20, @@ -514,19 +549,9 @@ export default { _plansTimer: null, plans: [], moving: false, - tab: 'entry', } }, computed: { - tabs() { - return [ - { k: 'entry', label: '入口段', count: this.onlinePlans.length }, - { k: 'acid', label: '酸洗段', count: 5 }, - { k: 'rinse', label: '漂洗段', count: 5 }, - { k: 'exit', label: '出口段', count: 3 }, - { k: 'track', label: '物料跟踪表', count: this.equipments.length }, - ] - }, entryItems() { const f = (v, n=1) => Number(v).toFixed(n) return [ @@ -554,6 +579,29 @@ export default { const step = (xEnd - xStart) / (n - 1) return EQUIPMENTS.map((e, i) => ({ ...e, x: xStart + step * i })) }, + sections() { + const eqs = this.equipments + const groups = [] + let cur = null + eqs.forEach((e, i) => { + if (!cur || cur.name !== e.section) { + if (cur) groups.push(cur) + cur = { name: e.section, color: SECTION_COLORS[e.section] || '#9aa8b6', + startIdx: i, endIdx: i, x0: e.x, x1: e.x } + } else { + cur.endIdx = i + cur.x1 = e.x + } + }) + if (cur) groups.push(cur) + const half = (eqs[1].x - eqs[0].x) / 2 + return groups.map(g => ({ + ...g, + bandX: g.x0 - half + 4, + bandW: (g.x1 - g.x0) + half * 2 - 8, + labelX: (g.x0 + g.x1) / 2, + })) + }, weldX() { const p = Math.max(0, Math.min(1, this.weld.position)) return 50 + (1850 - 50) * p @@ -635,6 +683,7 @@ export default { methods: { fmt(v, n = 2) { return v != null && v !== '' ? Number(v).toFixed(n) : '—' }, fix(v, n = 1) { return Number(v).toFixed(n) }, + sectionColor(s) { return SECTION_COLORS[s] || '#9aa8b6' }, fmtTime(t) { return t ? t.slice(0, 16).replace('T', ' ') : '—' }, async loadPlans() { try { @@ -777,7 +826,12 @@ export default {