refactor: 重构产线工位与备料流程,移除冗余字段

1. 移除工单、工件、日排产中的冗余工位组合/接驳台字段
2. 新增工位接驳台编码字段与唯一约束,关联WMS接驳台主数据
3. 重构日排产为工位产量分配模式,替代原接驳台列表
4. 新增备料单工位级字段与分批出库支持
5. 移除定时同步可生产数量,改为WMS实时拉取接口
6. 过滤操作日志,排除登录/退出相关日志
7. 调整仪表盘工序展示逻辑为今日派工路线
8. 新增接驳台维护菜单与基础数据
This commit is contained in:
SunYF
2026-09-19 07:50:21 +08:00
parent 543655d441
commit 37f10d3a13
156 changed files with 4489 additions and 1285 deletions
+14
View File
@@ -213,6 +213,20 @@ export const helpZone = {
]
}
export const helpDock = {
title: '接驳台维护',
overview:
'维护全厂接驳台(AGV 取送货的物理停靠位)主数据。接驳台是独立主数据,不依附工位:除了产线各工位旁,库房收货口、线边缓存区等其他位置也有接驳台位。\n\n三种类型:\n· 产线:位于产线工位旁,必须绑定一个工位号,且与工位一一对应(一个工位只能有一个接驳台、一个接驳台只能给一个工位);\n· 库房:库房收货/发货口(如 DOCK21),不绑定工位;\n· 其他:缓存区、暂存位等,不绑定工位。\n\n接驳台编码是产线与 AGV 任务的引用锚点,创建后不可修改;MES「关联工位」页绑定接驳台时从本页数据实时拉取。有托盘占用的接驳台不允许删除,需先处理托盘。',
fields: [
{ name: '接驳台编码', source: '手工填写,创建后不可改。', purpose: '接驳台唯一编号,产线/AGV 任务据此定位', fill: '如 DOCK21、DOCK22;建议 DOCK+两位数字' },
{ name: '接驳台名称', source: '手工填写。', purpose: '接驳台中文名,便于识别', fill: '如 库房收货接驳台、三号缓存接驳台' },
{ name: '类型', source: '选择:产线 / 库房 / 其他。', purpose: '区分接驳台所处位置与用途', fill: '产线接驳台需填工位号;库房/其他不填' },
{ name: '绑定工位号', source: '仅产线类型填写。', purpose: '该接驳台服务哪个工位,与工位 1:1', fill: '如 3 表示 3 号工位;该工位已绑定其他接驳台时会提示' },
{ name: '托盘状态', source: '系统自动维护(PLC 托盘传感器 / AGV 到位卸货)。', purpose: '看该接驳台当前是否被占用', fill: '不可手改;有托盘的接驳台不允许删除' },
{ name: '状态', source: '选择:启用 / 停用。', purpose: '停用的接驳台不再参与配送与下拉选择', fill: '默认启用' }
]
}
export const helpMaterial = {
title: '物料档案',
overview:
@@ -4,7 +4,7 @@ import { useRoute, useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import {
HomeFilled, Download, Upload, Search, CircleCheck, List,
Box, Tickets, Monitor, EditPen, Fold, Expand, Location, Goods, Key, Operation, Van, Clock, RefreshLeft
Box, Tickets, Monitor, EditPen, Fold, Expand, Location, Goods, Key, Operation, Van, Clock, RefreshLeft, Aim
} from '@element-plus/icons-vue'
import { getUser, getToken, logout, setSession } from '../utils/auth'
import request from '../utils/request'
@@ -93,7 +93,8 @@ const menuGroups = [
{
title: '基础资料', icon: Goods, children: [
{ path: '/material', title: '物料档案', icon: Goods, code: 'material:view' },
{ path: '/zone', title: '区域维护', icon: Location, code: 'zone:view' }
{ path: '/zone', title: '区域维护', icon: Location, code: 'zone:view' },
{ path: '/dock', title: '接驳台维护', icon: Aim, code: 'dock:manage' }
]
},
{
+208
View File
@@ -0,0 +1,208 @@
<template>
<div class="dock-page">
<el-card shadow="never">
<el-alert type="info" :closable="false" show-icon style="margin-bottom:12px"
title="接驳台是独立主数据:除产线工位旁,库房、缓存区等其他位置也有接驳台位。类型分 产线 / 库房 / 其他;只有产线接驳台绑定工位号,且与工位一一对应(一个工位只能有一个接驳台)。库房/其他接驳台不绑定工位。" />
<el-form inline :model="filter">
<el-form-item label="类型">
<el-select v-model="filter.dockType" clearable style="width:130px" placeholder="全部">
<el-option label="产线" value="line" />
<el-option label="库房" value="store" />
<el-option label="其他" value="other" />
</el-select>
</el-form-item>
<el-form-item label="关键字">
<el-input v-model="filter.keyword" clearable style="width:200px" placeholder="接驳台编码/名称" @keyup.enter="search" />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="search">查询</el-button>
<el-button @click="reset">重置</el-button>
</el-form-item>
</el-form>
<div style="margin-bottom:12px">
<el-button v-if="can('dock:add')" type="primary" @click="openAdd">新增接驳台</el-button>
</div>
<el-table :data="list" border size="small" v-loading="loading">
<el-table-column prop="dockCode" label="接驳台编码" width="140" />
<el-table-column prop="name" label="接驳台名称" min-width="200" show-overflow-tooltip />
<el-table-column label="类型" width="100" align="center">
<template #default="{ row }">
<el-tag size="small" :type="tagType(row.dockType)">{{ typeLabel(row.dockType) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="绑定工位" width="110" align="center">
<template #default="{ row }">{{ row.stationNo > 0 ? '工位' + row.stationNo : '—' }}</template>
</el-table-column>
<el-table-column label="托盘状态" width="110" align="center">
<template #default="{ row }">
<el-tag size="small" :type="row.hasPallet ? 'warning' : 'info'">{{ row.hasPallet ? '有托盘' : '空闲' }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="palletRef" label="托盘关联" min-width="160" show-overflow-tooltip>
<template #default="{ row }">{{ row.palletRef || '—' }}</template>
</el-table-column>
<el-table-column label="状态" width="90" align="center">
<template #default="{ row }">
<el-tag size="small" :type="row.status === 'ENABLED' ? 'success' : 'info'">
{{ row.status === 'ENABLED' ? '启用' : '停用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="创建时间" width="170">
<template #default="{ row }">{{ fmtTime(row.createdAt) }}</template>
</el-table-column>
<el-table-column label="操作" width="150" align="center" fixed="right">
<template #default="{ row }">
<el-button v-if="can('dock:edit')" size="small" @click="openEdit(row)">编辑</el-button>
<el-button v-if="can('dock:delete')" size="small" type="danger" @click="remove(row)">删除</el-button>
</template>
</el-table-column>
<template #empty>
<el-empty description="暂无接驳台数据" />
</template>
</el-table>
<el-pagination v-model:current-page="page.current" v-model:page-size="page.size" :page-sizes="[20, 50, 100]"
:total="total" background layout="total, sizes, prev, pager, next, jumper" style="margin-top:12px"
@size-change="search" @current-change="load" />
</el-card>
<el-dialog v-model="dialog.show" :title="dialog.isAdd ? '新增接驳台' : '编辑接驳台'" width="480px">
<el-form :model="form" label-width="110px">
<el-form-item label="接驳台编码" required>
<el-input v-model="form.dockCode" :disabled="!dialog.isAdd" placeholder="如 DOCK22" />
<div v-if="!dialog.isAdd" style="color:#909399;font-size:12px;margin-top:4px">编码是产线/AGV 任务的引用锚点创建后不可修改</div>
</el-form-item>
<el-form-item label="接驳台名称" required>
<el-input v-model="form.name" placeholder="如 三号缓存接驳台" />
</el-form-item>
<el-form-item label="类型" required>
<el-radio-group v-model="form.dockType">
<el-radio label="line">产线</el-radio>
<el-radio label="store">库房</el-radio>
<el-radio label="other">其他</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-if="form.dockType === 'line'" label="绑定工位号" required>
<el-input-number v-model="form.stationNo" :min="1" :max="999" style="width:100%" />
<div style="color:#909399;font-size:12px;margin-top:4px">产线接驳台与工位一一对应该工位已绑定其他接驳台时会提示</div>
</el-form-item>
<el-form-item label="状态">
<el-radio-group v-model="form.status">
<el-radio label="ENABLED">启用</el-radio>
<el-radio label="DISABLED">停用</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialog.show = false">取消</el-button>
<el-button type="primary" :loading="saving" @click="save">保存</el-button>
</template>
</el-dialog>
</div>
<PageHelp :data="helpDock" page="Dock" />
</template>
<script setup>
defineOptions({ name: 'Dock' })
import { reactive, ref, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import request from '../utils/request'
import { fmtTime } from '../utils/format'
import { can } from '../utils/perm'
import PageHelp from '../components/PageHelp.vue'
import { helpDock } from '../help'
const list = ref([])
const total = ref(0)
const loading = ref(false)
const saving = ref(false)
const page = reactive({ current: 1, size: 20 })
const filter = reactive({ dockType: '', keyword: '' })
const dialog = reactive({ show: false, isAdd: true })
const form = reactive({ id: 0, dockCode: '', name: '', dockType: 'line', stationNo: 1, status: 'ENABLED' })
const TYPE_LABEL = { line: '产线', store: '库房', other: '其他' }
function typeLabel(t) { return TYPE_LABEL[t] || '其他' }
function tagType(t) { return t === 'line' ? 'success' : (t === 'store' ? 'warning' : 'info') }
async function load() {
loading.value = true
try {
const params = { page: page.current, pageSize: page.size }
if (filter.dockType) params.dockType = filter.dockType
if (filter.keyword) params.keyword = filter.keyword
const data = await request.get('/docks', { params })
list.value = data?.list || []
total.value = data?.total || 0
} finally {
loading.value = false
}
}
function search() {
page.current = 1
load()
}
function reset() {
filter.dockType = ''
filter.keyword = ''
search()
}
function openAdd() {
dialog.isAdd = true
Object.assign(form, { id: 0, dockCode: '', name: '', dockType: 'line', stationNo: 1, status: 'ENABLED' })
dialog.show = true
}
function openEdit(row) {
dialog.isAdd = false
Object.assign(form, {
id: row.id, dockCode: row.dockCode, name: row.name,
dockType: row.dockType || 'line', stationNo: row.stationNo > 0 ? row.stationNo : 1, status: row.status || 'ENABLED'
})
dialog.show = true
}
async function save() {
if (!form.dockCode.trim()) return ElMessage.warning('请填写接驳台编码')
if (!form.name.trim()) return ElMessage.warning('请填写接驳台名称')
saving.value = true
try {
const body = {
dockCode: form.dockCode.trim(), name: form.name.trim(),
dockType: form.dockType,
stationNo: form.dockType === 'line' ? form.stationNo : 0,
status: form.status
}
if (dialog.isAdd) {
await request.post('/docks', body)
ElMessage.success('接驳台已新增')
} else {
await request.post('/docks/update', { ...body, id: form.id })
ElMessage.success('接驳台已保存')
}
dialog.show = false
load()
} finally {
saving.value = false
}
}
async function remove(row) {
try {
await ElMessageBox.confirm(`确认删除接驳台「${row.dockCode} ${row.name}」?`, '删除接驳台', { type: 'warning' })
} catch {
return
}
await request.post('/docks/delete', { id: row.id })
ElMessage.success('接驳台已删除')
load()
}
onMounted(load)
</script>
+3 -3
View File
@@ -61,8 +61,8 @@ import PageHelp from '../components/PageHelp.vue'
import { helpEventLog } from '../help'
// 事件类型按"族前缀"归类(写点处 eventType 形如 user.create / inbound.create …)
// 登录/退出(auth.*)不属于操作日志,后端已排除、本页也不提供该筛选项
const EVENT_FAMILIES = [
{ label: '登录/退出', value: 'auth.' },
{ label: '账号管理', value: 'user.' },
{ label: '角色管理', value: 'role.' },
{ label: '权限初始化', value: 'rbac.' },
@@ -76,12 +76,12 @@ const EVENT_FAMILIES = [
]
const FAMILY_LABEL = Object.fromEntries(EVENT_FAMILIES.map((f) => [f.value, f.label]))
const FAMILY_TAG = {
'auth.': 'danger', 'user.': 'primary', 'role.': 'warning', 'rbac.': 'danger',
'user.': 'primary', 'role.': 'warning', 'rbac.': 'danger',
'material.': 'success', 'zone.': 'success', 'inbound.': 'success',
'outbound.': 'warning', 'inspection.': 'info', 'stocktake.': 'info', 'semi.': 'info'
}
const ENTITY_LABEL = {
auth: '登录', user: '账号', role: '角色', rbac: '权限', material: '物料', zone: '区域',
user: '账号', role: '角色', rbac: '权限', material: '物料', zone: '区域',
inbound: '入库', outbound: '出库', inspection: '检验', stocktake: '盘点', semi: '半成品'
}
@@ -23,6 +23,7 @@ const routes = [
{ path: 'agv', name: 'Agv', component: () => import('../pages/Agv.vue'), meta: { title: 'AGV配送', keepAlive: true, componentName: 'Agv' } },
{ path: 'zone', name: 'Zone', component: () => import('../pages/BaseData.vue'), props: { mode: 'zone' }, meta: { title: '区域维护', keepAlive: true, componentName: 'BaseData' } },
{ path: 'material', name: 'Material', component: () => import('../pages/BaseData.vue'), props: { mode: 'material' }, meta: { title: '物料档案', keepAlive: true, componentName: 'BaseData' } },
{ path: 'dock', name: 'Dock', component: () => import('../pages/Dock.vue'), meta: { title: '接驳台维护', keepAlive: true, componentName: 'Dock' } },
{ path: 'change-password', name: 'ChangePassword', component: () => import('../pages/ChangePassword.vue'), meta: { title: '修改密码' } },
{ path: 'users', name: 'UserManage', component: () => import('../pages/UserManage.vue'), meta: { title: '账号管理', keepAlive: true, componentName: 'UserManage' } },
{ path: 'roles', name: 'RoleManage', component: () => import('../pages/RoleManage.vue'), meta: { title: '角色管理', keepAlive: true, componentName: 'RoleManage' } },