生产领料、BOM管理、生产工单适配手机端

This commit is contained in:
2026-03-12 13:58:58 +08:00
parent 52f1a1cda2
commit 0432e2430a
6 changed files with 1863 additions and 1120 deletions

View File

@@ -1,26 +1,31 @@
<template>
<el-dialog
:title="dialogTitle"
<el-drawer
v-model="dialogVisible"
width="1000px"
@close="handleClose"
:close-on-click-modal="false"
:title="dialogTitle"
direction="rtl"
size="100%"
:close-on-press-escape="true"
:destroy-on-close="true"
:append-to-body="true"
class="mobile-form-drawer"
>
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="100px"
v-loading="formLoading"
>
<el-row :gutter="20">
<el-col :span="12">
<div class="mobile-form" v-loading="formLoading">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-position="top"
:disabled="isViewMode"
>
<!-- 基本信息 -->
<div class="mobile-form__section">
<div class="mobile-form__section-title">基本信息</div>
<el-form-item label="产品" prop="productId">
<el-select
v-model="formData.productId"
placeholder="请选择产品"
filterable
class="!w-100%"
style="width: 100%"
:disabled="isViewMode"
@change="handleProductChange"
>
@@ -32,154 +37,147 @@
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="BOM名称" prop="bomName">
<el-input v-model="formData.bomName" placeholder="请输入BOM名称" :disabled="isViewMode" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="BOM编码" prop="bomCode">
<el-input v-model="formData.bomCode" placeholder="自动生成" :disabled="true" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="版本号" prop="version">
<el-input v-model="formData.version" placeholder="如V1.0" :disabled="isViewMode" />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="默认版本" prop="isDefault">
<el-radio-group v-model="formData.isDefault" :disabled="isViewMode">
<el-radio :label="1"></el-radio>
<el-radio :label="0"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="状态" prop="status">
<el-radio-group v-model="formData.status" :disabled="isViewMode">
<el-radio :label="1">启用</el-radio>
<el-radio :label="0">禁用</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="备注" prop="remark">
<el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="请输入备注" :disabled="isViewMode" />
<el-input v-model="formData.remark" type="textarea" :rows="2" placeholder="请输入备注" :disabled="isViewMode" />
</el-form-item>
</el-col>
</el-row>
</div>
<!-- BOM明细 -->
<el-divider content-position="left">BOM明细</el-divider>
<el-form-item v-if="!isViewMode">
<el-button type="primary" @click="addItem">
<Icon icon="ep:plus" class="mr-5px" /> 添加物料
</el-button>
</el-form-item>
<!-- BOM明细 -->
<div class="mobile-form__section">
<div class="mobile-form__section-title">BOM明细</div>
<el-button v-if="!isViewMode" type="primary" plain style="width: 100%; margin-bottom: 12px" @click="addItem">
<Icon icon="ep:plus" class="mr-5px" /> 添加物料
</el-button>
<el-table :data="formData.items" border style="width: 100%">
<el-table-column label="物料" min-width="200">
<template #default="{ row, $index }">
<el-select
v-if="!isViewMode"
v-model="row.materialId"
placeholder="请选择物料"
filterable
class="!w-100%"
@change="(val: number) => handleMaterialChange(val, $index)"
<div class="mobile-item-list">
<div
v-for="(row, $index) in formData.items"
:key="$index"
class="mobile-item-card"
>
<el-option
v-for="item in materialList"
:key="item.id"
:label="`${item.name} (${item.barCode || '-'})`"
:value="item.id"
/>
</el-select>
<span v-else>{{ row.materialName }} ({{ row.materialCode || '-' }})</span>
</template>
</el-table-column>
<el-table-column label="物料类型" width="120">
<template #default="{ row }">
<el-input v-if="!isViewMode" v-model="row.materialType" placeholder="类型" :disabled="true" />
<span v-else>{{ row.materialType || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="单位" width="80">
<template #default="{ row }">
<el-input v-if="!isViewMode" v-model="row.unit" placeholder="单位" />
<span v-else>{{ row.unit || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="单位用量" width="120">
<template #default="{ row }">
<el-input-number
v-if="!isViewMode"
v-model="row.unitQuantity"
:min="0"
:precision="4"
:step="0.1"
:controls="false"
class="!w-100%"
/>
<span v-else>{{ row.unitQuantity }}</span>
</template>
</el-table-column>
<el-table-column label="损耗率(%)" width="100">
<template #default="{ row }">
<el-input-number
v-if="!isViewMode"
v-model="row.lossRate"
:min="0"
:max="100"
:precision="2"
:controls="false"
class="!w-100%"
/>
<span v-else>{{ row.lossRate || 0 }}</span>
</template>
</el-table-column>
<el-table-column label="单价" width="100">
<template #default="{ row }">
<el-input-number
v-if="!isViewMode"
v-model="row.unitPrice"
:min="0"
:precision="2"
:controls="false"
class="!w-100%"
/>
<span v-else>{{ row.unitPrice ? '¥' + row.unitPrice : '-' }}</span>
</template>
</el-table-column>
<el-table-column label="必需" width="80">
<template #default="{ row }">
<el-checkbox v-if="!isViewMode" v-model="row.required" :true-label="1" :false-label="0" />
<el-tag v-else :type="row.required === 1 ? 'success' : 'info'" size="small">
{{ row.required === 1 ? '是' : '否' }}
</el-tag>
</template>
</el-table-column>
<el-table-column v-if="!isViewMode" label="操作" width="80" fixed="right">
<template #default="{ $index }">
<el-button link type="danger" @click="removeItem($index)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">{{ isViewMode ? '关闭' : '取消' }}</el-button>
<el-button v-if="!isViewMode" type="primary" @click="submitForm" :loading="formLoading">确定</el-button>
</template>
</el-dialog>
<div class="mobile-item-card__header">
<span class="mobile-item-card__index">#{{ $index + 1 }}</span>
<span class="mobile-item-card__name">{{ row.materialName || '未选择物料' }}</span>
<el-button
v-if="!isViewMode"
link
type="danger"
size="small"
@click="removeItem($index)"
>删除</el-button>
</div>
<div class="mobile-item-card__body">
<el-form-item v-if="!isViewMode" label="物料" :prop="`items.${$index}.materialId`">
<el-select
v-model="row.materialId"
placeholder="请选择物料"
filterable
style="width: 100%"
@change="(val: number) => handleMaterialChange(val, $index)"
>
<el-option
v-for="item in materialList"
:key="item.id"
:label="`${item.name} (${item.barCode || '-'})`"
:value="item.id"
/>
</el-select>
</el-form-item>
<div class="mobile-item-card__info-row">
<span class="mobile-item-card__info-label">物料编码</span>
<span class="mobile-item-card__info-value">{{ row.materialCode || '-' }}</span>
</div>
<div class="mobile-item-card__info-row">
<span class="mobile-item-card__info-label">物料类型</span>
<span class="mobile-item-card__info-value">{{ row.materialType || '-' }}</span>
</div>
<div class="mobile-item-card__info-row">
<span class="mobile-item-card__info-label">单位</span>
<span class="mobile-item-card__info-value" v-if="isViewMode">{{ row.unit || '-' }}</span>
<el-input v-else v-model="row.unit" placeholder="单位" style="width: 120px" />
</div>
<div class="mobile-item-card__input-group">
<el-form-item label="单位用量" :prop="`items.${$index}.unitQuantity`">
<el-input-number
v-if="!isViewMode"
v-model="row.unitQuantity"
:min="0"
:precision="4"
:step="0.1"
controls-position="right"
style="width: 100%"
/>
<span v-else>{{ row.unitQuantity }}</span>
</el-form-item>
<el-form-item label="损耗率(%)" :prop="`items.${$index}.lossRate`">
<el-input-number
v-if="!isViewMode"
v-model="row.lossRate"
:min="0"
:max="100"
:precision="2"
controls-position="right"
style="width: 100%"
/>
<span v-else>{{ row.lossRate || 0 }}</span>
</el-form-item>
</div>
<div class="mobile-item-card__input-group">
<el-form-item label="单价" :prop="`items.${$index}.unitPrice`">
<el-input-number
v-if="!isViewMode"
v-model="row.unitPrice"
:min="0"
:precision="2"
controls-position="right"
style="width: 100%"
/>
<span v-else>{{ row.unitPrice ? '¥' + row.unitPrice : '-' }}</span>
</el-form-item>
<el-form-item label="必需">
<el-checkbox v-if="!isViewMode" v-model="row.required" :true-label="1" :false-label="0" />
<el-tag v-else :type="row.required === 1 ? 'success' : 'info'" size="small">
{{ row.required === 1 ? '是' : '否' }}
</el-tag>
</el-form-item>
</div>
</div>
</div>
<div v-if="formData.items.length === 0" class="mobile-empty-tip">
暂无物料明细请点击上方按钮添加
</div>
</div>
</div>
</el-form>
<!-- 底部操作按钮 -->
<div class="mobile-form__footer">
<el-button @click="dialogVisible = false">{{ isViewMode ? '关闭' : '取消' }}</el-button>
<el-button v-if="!isViewMode" type="primary" @click="submitForm" :loading="formLoading"> </el-button>
</div>
</div>
</el-drawer>
</template>
<script setup lang="ts">
@@ -374,3 +372,109 @@ const handleClose = () => {
defineExpose({ open })
</script>
<style lang="scss" scoped>
.mobile-form {
padding: 0 4px;
}
.mobile-form__section {
background: #fff;
border-radius: 10px;
padding: 14px;
margin-bottom: 12px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
}
.mobile-form__section-title {
font-size: 15px;
font-weight: 600;
color: #303133;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 1px solid #f0f0f0;
}
.mobile-form__footer {
position: sticky;
bottom: 0;
background: #fff;
padding: 12px 16px;
padding-bottom: calc(12px + constant(safe-area-inset-bottom));
padding-bottom: calc(12px + env(safe-area-inset-bottom));
border-top: 1px solid #eee;
display: flex;
justify-content: flex-end;
gap: 12px;
z-index: 10;
margin: 0 -4px;
.el-button {
flex: 1;
height: 40px;
font-size: 15px;
}
}
.mobile-item-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.mobile-item-card {
background: #f9f9fb;
border-radius: 8px;
padding: 12px;
border: 1px solid #ebeef5;
&__header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 10px;
padding-bottom: 8px;
border-bottom: 1px solid #f0f0f0;
}
&__index {
font-size: 12px;
color: #909399;
font-weight: 600;
}
&__name {
flex: 1;
font-size: 14px;
font-weight: 600;
color: #303133;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&__body {
font-size: 13px;
}
&__info-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 4px 0;
}
&__info-label {
color: #909399;
flex-shrink: 0;
}
&__info-value {
color: #606266;
text-align: right;
}
&__input-group {
display: flex;
gap: 10px;
margin-top: 6px;
:deep(.el-form-item) {
flex: 1;
margin-bottom: 10px;
}
}
}
.mobile-empty-tip {
text-align: center;
color: #909399;
padding: 20px 0;
font-size: 14px;
}
</style>

View File

@@ -1,138 +1,134 @@
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
ref="queryFormRef"
:model="queryParams"
:inline="true"
label-width="80px"
class="-mb-15px"
>
<el-form-item label="产品名称" prop="productName">
<el-input
v-model="queryParams.productName"
placeholder="请输入产品名称"
clearable
@keyup.enter="handleQuery"
class="!w-200px"
/>
</el-form-item>
<el-form-item label="BOM编码" prop="bomCode">
<el-input
v-model="queryParams.bomCode"
placeholder="请输入BOM编码"
clearable
@keyup.enter="handleQuery"
class="!w-200px"
/>
</el-form-item>
<el-form-item label="BOM名称" prop="bomName">
<div class="mobile-bom-list">
<!-- 顶部操作栏 -->
<div class="mobile-header">
<div class="mobile-header__search">
<el-input
v-model="queryParams.bomName"
placeholder="请输入BOM名称"
placeholder="搜索BOM名称"
clearable
@keyup.enter="handleQuery"
class="!w-200px"
:prefix-icon="Search"
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select
v-model="queryParams.status"
placeholder="请选择状态"
clearable
class="!w-200px"
>
<el-option label="启用" :value="1" />
<el-option label="禁用" :value="0" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleQuery">
<Icon icon="ep:search" class="mr-5px" /> 搜索
</el-button>
<el-button @click="resetQuery">
<Icon icon="ep:refresh" class="mr-5px" /> 重置
</el-button>
</div>
<div class="mobile-header__actions">
<el-button :icon="Filter" circle @click="filterVisible = true" />
<el-button type="primary" :icon="Plus" circle @click="openForm('create')" v-hasPermi="['mes:product-bom:create']" />
</div>
</div>
<el-button type="primary"
@click="openForm('create')"
v-hasPermi="['mes:product-bom:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<div class="mobile-header__quick-filter">
<div
class="quick-filter-item"
:class="{ active: queryParams.status === undefined }"
@click="handleQuickFilter(undefined)"
>
全部
</div>
<div
class="quick-filter-item"
:class="{ active: queryParams.status === 1 }"
@click="handleQuickFilter(1)"
>
启用
</div>
<div
class="quick-filter-item"
:class="{ active: queryParams.status === 0 }"
@click="handleQuickFilter(0)"
>
禁用
</div>
</div>
<ContentWrap>
<!-- 列表 -->
<el-table v-loading="loading" :data="list">
<el-table-column label="BOM编码" align="center" prop="bomCode" width="180" />
<el-table-column label="BOM名称" align="center" prop="bomName" min-width="150" />
<el-table-column label="产品编码" align="center" prop="productCode" width="120" />
<el-table-column label="产品名称" align="center" prop="productName" min-width="150" />
<el-table-column label="版本" align="center" prop="version" width="80" />
<el-table-column label="默认版本" align="center" prop="isDefault" width="100">
<template #default="{ row }">
<el-tag :type="row.isDefault === 1 ? 'success' : 'info'">
{{ row.isDefault === 1 ? '是' : '否' }}
<!-- 卡片列表 -->
<div class="mobile-list" v-loading="loading">
<div v-if="list.length === 0 && !loading" class="mobile-empty">
<el-empty description="暂无BOM记录" />
</div>
<div
v-for="item in list"
:key="item.id"
class="mobile-card"
@click="openForm('view', item.id)"
>
<div class="mobile-card__header">
<span class="mobile-card__no">{{ item.bomName }}</span>
<el-tag :type="item.status === 1 ? 'success' : 'danger'" size="small">
{{ item.status === 1 ? '启用' : '禁用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="状态" align="center" prop="status" width="80">
<template #default="{ row }">
<el-tag :type="row.status === 1 ? 'success' : 'danger'">
{{ row.status === 1 ? '启用' : '禁用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180" />
<el-table-column label="操作" align="center" fixed="right" width="200">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('view', scope.row.id)"
v-hasPermi="['mes:product-bom:query']"
>
查看
</el-button>
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['mes:product-bom:update']"
>
编辑
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['mes:product-bom:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
<div class="mobile-card__body">
<div class="mobile-card__row">
<span class="mobile-card__label">BOM编码</span>
<span class="mobile-card__value">{{ item.bomCode || '-' }}</span>
</div>
<div class="mobile-card__row">
<span class="mobile-card__label">产品名称</span>
<span class="mobile-card__value">{{ item.productName || '-' }}</span>
</div>
<div class="mobile-card__row">
<span class="mobile-card__label">产品编码</span>
<span class="mobile-card__value">{{ item.productCode || '-' }}</span>
</div>
<div class="mobile-card__row">
<span class="mobile-card__label">版本</span>
<span class="mobile-card__value">
{{ item.version || '-' }}
<el-tag v-if="item.isDefault === 1" type="success" size="small" style="margin-left: 4px">默认</el-tag>
</span>
</div>
<div class="mobile-card__row">
<span class="mobile-card__label">创建时间</span>
<span class="mobile-card__value">{{ item.createTime || '-' }}</span>
</div>
</div>
<div class="mobile-card__footer">
<el-button size="small" @click.stop="openForm('view', item.id)" v-hasPermi="['mes:product-bom:query']">查看</el-button>
<el-button size="small" type="primary" @click.stop="openForm('update', item.id)" v-hasPermi="['mes:product-bom:update']">编辑</el-button>
<el-button size="small" type="danger" @click.stop="handleDelete(item.id)" v-hasPermi="['mes:product-bom:delete']">删除</el-button>
</div>
</div>
</div>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<div class="mobile-pagination" v-if="total > 0">
<Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" :page-sizes="[10, 20]" layout="total, prev, pager, next" :pager-count="5" @pagination="getList" />
</div>
<!-- 表单弹窗添加/修改 -->
<ProductBomForm ref="formRef" @success="getList" />
<!-- 筛选抽屉 -->
<el-drawer v-model="filterVisible" title="筛选条件" direction="btt" size="50%">
<el-form :model="queryParams" ref="queryFormRef" label-position="top">
<el-form-item label="产品名称" prop="productName">
<el-input v-model="queryParams.productName" placeholder="请输入产品名称" clearable style="width:100%" />
</el-form-item>
<el-form-item label="BOM编码" prop="bomCode">
<el-input v-model="queryParams.bomCode" placeholder="请输入BOM编码" clearable style="width:100%" />
</el-form-item>
<el-form-item label="BOM名称" prop="bomName">
<el-input v-model="queryParams.bomName" placeholder="请输入BOM名称" clearable style="width:100%" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable style="width:100%">
<el-option label="启用" :value="1" />
<el-option label="禁用" :value="0" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="resetQuery">重置</el-button>
<el-button type="primary" @click="handleFilterConfirm">确认筛选</el-button>
</template>
</el-drawer>
<!-- 表单弹窗添加/修改 -->
<ProductBomForm ref="formRef" @success="getList" />
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { Search, Filter, Plus } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Icon } from '@/components/Icon'
import ProductBomForm from './ProductBomForm.vue'
@@ -140,6 +136,8 @@ import { getProductBomPage, deleteProductBom } from '@/api/mes/product/bom'
defineOptions({ name: 'MesProductBom' })
const filterVisible = ref(false)
const loading = ref(true)
const total = ref(0)
const list = ref<any[]>([])
@@ -194,4 +192,90 @@ const handleDelete = async (id: number) => {
onMounted(() => {
getList()
})
/** 快捷分类筛选 */
const handleQuickFilter = (status: number | undefined) => {
queryParams.status = status
queryParams.pageNo = 1
getList()
}
/** 筛选确认 */
const handleFilterConfirm = () => {
filterVisible.value = false
handleQuery()
}
</script>
<style lang="scss" scoped>
.mobile-bom-list {
padding: 12px;
background: #f5f5f5;
min-height: 100vh;
}
.mobile-header {
display: flex;
gap: 8px;
align-items: center;
margin-bottom: 12px;
&__search { flex: 1; }
&__actions { display: flex; gap: 4px; flex-shrink: 0; }
}
.mobile-header__quick-filter {
display: flex;
gap: 12px;
margin: 8px 0;
justify-content: flex-start;
.quick-filter-item {
padding: 4px 12px;
font-size: 14px;
border-radius: 20px;
cursor: pointer;
color: #909399;
background: transparent;
transition: all 0.2s;
&.active {
color: #fff;
background: #409eff;
}
}
}
.mobile-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.mobile-empty { padding: 40px 0; }
.mobile-card {
background: #fff;
border-radius: 10px;
padding: 14px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
&__header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
&__no { font-weight: 600; font-size: 15px; color: #303133; }
&__body { font-size: 13px; }
&__row { display: flex; justify-content: space-between; padding: 3px 0; }
&__label { color: #909399; flex-shrink: 0; margin-right: 12px; }
&__value {
color: #606266;
text-align: right;
display: flex;
align-items: center;
justify-content: flex-end;
}
&__footer { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; padding-top: 10px; border-top: 1px solid #f0f0f0; }
}
.mobile-pagination {
margin-top: 12px;
display: flex;
justify-content: center;
:deep(.el-pagination) { flex-wrap: wrap; justify-content: center; }
}
</style>

View File

@@ -1,146 +1,243 @@
<template>
<Dialog title="查看领料单" v-model="dialogVisible" width="80%">
<Descriptions :schema="schema" :data="detailData" />
<el-drawer
v-model="dialogVisible"
title="查看领料单"
direction="rtl"
size="100%"
:close-on-press-escape="true"
:destroy-on-close="true"
:append-to-body="true"
class="mobile-form-drawer"
>
<div class="mobile-form">
<!-- 基本信息 -->
<div class="mobile-form__section">
<div class="mobile-form__section-title">基本信息</div>
<div class="mobile-info-list">
<div class="mobile-info-row" v-for="item in schema" :key="item.field">
<span class="mobile-info-row__label">{{ item.label }}</span>
<span class="mobile-info-row__value">
{{ item.formatter ? item.formatter(detailData[item.field]) : (detailData[item.field] || '-') }}
</span>
</div>
</div>
</div>
<el-divider content-position="left">领料明细</el-divider>
<el-table :data="detailData.items" border style="width: 100%" max-height="480">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column label="物料信息" min-width="240">
<template #default="{ row }">
<div class="text-13px text-gray-600">编码{{ row.materialCode }}</div>
<div class="text-13px">名称{{ row.materialName }}</div>
</template>
</el-table-column>
<el-table-column label="单位" prop="unit" width="80" />
<el-table-column label="计划数量" prop="planQuantity" width="120" />
<el-table-column label="实际数量" prop="actualQuantity" width="120" />
<el-table-column label="仓库" prop="warehouseName" width="140" />
<el-table-column label="过磅单号" prop="no" width="160" />
<el-table-column label="备注" prop="remark" min-width="160" />
<el-table-column label="操作" width="100" align="center" fixed="right">
<template #default="{ row }">
<el-tooltip content="过磅详情" placement="top" v-if="row.purchaseId">
<el-button link type="primary" @click="openWeighDetail(row.purchaseId)">
<Icon icon="ep:document" />
</el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<!-- 领料明细 -->
<div class="mobile-form__section">
<div class="mobile-form__section-title">领料明细</div>
<div class="mobile-item-list">
<div
v-for="(row, index) in detailData.items"
:key="index"
class="mobile-item-card"
>
<div class="mobile-item-card__header">
<span class="mobile-item-card__index">#{{ index + 1 }}</span>
<span class="mobile-item-card__name">{{ row.materialName || '未知物料' }}</span>
</div>
<div class="mobile-item-card__body">
<div class="mobile-item-card__info-row">
<span class="mobile-item-card__info-label">物料编码</span>
<span class="mobile-item-card__info-value">{{ row.materialCode || '-' }}</span>
</div>
<div class="mobile-item-card__info-row">
<span class="mobile-item-card__info-label">单位</span>
<span class="mobile-item-card__info-value">{{ row.unit || '-' }}</span>
</div>
<div class="mobile-item-card__info-row">
<span class="mobile-item-card__info-label">计划数量</span>
<span class="mobile-item-card__info-value">{{ row.planQuantity }}</span>
</div>
<div class="mobile-item-card__info-row">
<span class="mobile-item-card__info-label">实际数量</span>
<span class="mobile-item-card__info-value">{{ row.actualQuantity || '-' }}</span>
</div>
<div class="mobile-item-card__info-row">
<span class="mobile-item-card__info-label">仓库</span>
<span class="mobile-item-card__info-value">{{ row.warehouseName || '-' }}</span>
</div>
<div class="mobile-item-card__info-row">
<span class="mobile-item-card__info-label">过磅单号</span>
<span class="mobile-item-card__info-value">{{ row.no || '-' }}</span>
</div>
<div class="mobile-item-card__info-row" v-if="row.remark">
<span class="mobile-item-card__info-label">备注</span>
<span class="mobile-item-card__info-value">{{ row.remark }}</span>
</div>
</div>
<div class="mobile-item-card__footer" v-if="row.purchaseId">
<el-button size="small" @click="openWeighDetail(row.purchaseId)">查看过磅详情</el-button>
</div>
</div>
<div v-if="detailData.items.length === 0" class="mobile-empty-tip">
暂无领料明细
</div>
</div>
</div>
<template #footer>
<el-button @click="dialogVisible = false">关闭</el-button>
</template>
</Dialog>
<!-- 底部操作按钮 -->
<div class="mobile-form__footer">
<el-button @click="dialogVisible = false"> </el-button>
</div>
</div>
</el-drawer>
<!-- 过磅单详情弹窗 -->
<Dialog title="过磅单详情" v-model="weighDialogVisible" width="50%">
<el-descriptions :column="2" border>
<el-descriptions-item label="过磅单号">{{ weighDetail?.no || '-' }}</el-descriptions-item>
<el-descriptions-item label="入库状态">{{
weighDetail?.inStatus === 1 ? '已入库' : '未入库'
}}</el-descriptions-item>
<el-descriptions-item label="产品">{{
weighDetail?.productName || '-'
}}</el-descriptions-item>
<el-descriptions-item label="车牌">{{
weighDetail?.vehicleNumber || '-'
}}</el-descriptions-item>
<el-descriptions-item label="毛重">{{
weighDetail?.grossWeight != null ? weighDetail.grossWeight + ' kg' : '-'
}}</el-descriptions-item>
<el-descriptions-item label="皮重">{{
weighDetail?.tareWeight != null ? weighDetail.tareWeight + ' kg' : '-'
}}</el-descriptions-item>
<el-descriptions-item label="净重">{{
weighDetail?.netWeight != null ? weighDetail.netWeight + ' kg' : '-'
}}</el-descriptions-item>
<el-descriptions-item label="计划重量">{{
weighDetail?.plannedWeight != null ? weighDetail.plannedWeight + ' kg' : '-'
}}</el-descriptions-item>
<el-descriptions-item label="杂质率">{{
weighDetail?.impurityRate != null ? (weighDetail.impurityRate * 100).toFixed(2) + '%' : '-'
}}</el-descriptions-item>
<el-descriptions-item label="供应商">{{
weighDetail?.supplierName || '-'
}}</el-descriptions-item>
<el-descriptions-item label="供应商类型">{{
weighDetail?.supplierName2 || '-'
}}</el-descriptions-item>
<el-descriptions-item label="身份证号">{{
weighDetail?.idCardNumber || '-'
}}</el-descriptions-item>
<el-descriptions-item label="司机">{{ weighDetail?.driver || '-' }}</el-descriptions-item>
<el-descriptions-item label="管理员">{{
weighDetail?.administrator || '-'
}}</el-descriptions-item>
<el-descriptions-item label="过磅员">{{ weighDetail?.weigher || '-' }}</el-descriptions-item>
<el-descriptions-item label="产地证编号">{{
weighDetail?.originCertificateNumber || '-'
}}</el-descriptions-item>
<el-descriptions-item label="附件">
<template v-if="weighDetail?.fileUrl">
<a :href="weighDetail.fileUrl" target="_blank">查看附件</a>
</template>
<template v-else>-</template>
</el-descriptions-item>
<el-descriptions-item label="备注" :span="2">{{
weighDetail?.remark || '-'
}}</el-descriptions-item>
<el-descriptions-item label="创建时间" :span="2">{{
weighDetail?.createTime ? formatDate(weighDetail.createTime) : '-'
}}</el-descriptions-item>
</el-descriptions>
<el-drawer
v-model="weighDialogVisible"
title="过磅单详情"
direction="rtl"
size="100%"
:close-on-press-escape="true"
:destroy-on-close="true"
:append-to-body="true"
class="mobile-form-drawer"
>
<div class="mobile-form">
<!-- 基本信息 -->
<div class="mobile-form__section">
<div class="mobile-form__section-title">基本信息</div>
<div class="mobile-info-list">
<div class="mobile-info-row">
<span class="mobile-info-row__label">过磅单号</span>
<span class="mobile-info-row__value">{{ weighDetail?.no || '-' }}</span>
</div>
<div class="mobile-info-row">
<span class="mobile-info-row__label">入库状态</span>
<span class="mobile-info-row__value">
<el-tag :type="weighDetail?.inStatus === 1 ? 'success' : 'warning'" size="small">
{{ weighDetail?.inStatus === 1 ? '已入库' : '未入库' }}
</el-tag>
</span>
</div>
<div class="mobile-info-row">
<span class="mobile-info-row__label">产品</span>
<span class="mobile-info-row__value">{{ weighDetail?.productName || '-' }}</span>
</div>
<div class="mobile-info-row">
<span class="mobile-info-row__label">车牌</span>
<span class="mobile-info-row__value">{{ weighDetail?.vehicleNumber || '-' }}</span>
</div>
<div class="mobile-info-row">
<span class="mobile-info-row__label">毛重</span>
<span class="mobile-info-row__value">{{ weighDetail?.grossWeight != null ? weighDetail.grossWeight + ' kg' : '-' }}</span>
</div>
<div class="mobile-info-row">
<span class="mobile-info-row__label">皮重</span>
<span class="mobile-info-row__value">{{ weighDetail?.tareWeight != null ? weighDetail.tareWeight + ' kg' : '-' }}</span>
</div>
<div class="mobile-info-row">
<span class="mobile-info-row__label">净重</span>
<span class="mobile-info-row__value">{{ weighDetail?.netWeight != null ? weighDetail.netWeight + ' kg' : '-' }}</span>
</div>
<div class="mobile-info-row">
<span class="mobile-info-row__label">计划重量</span>
<span class="mobile-info-row__value">{{ weighDetail?.plannedWeight != null ? weighDetail.plannedWeight + ' kg' : '-' }}</span>
</div>
<div class="mobile-info-row">
<span class="mobile-info-row__label">杂质率</span>
<span class="mobile-info-row__value">{{ weighDetail?.impurityRate != null ? (weighDetail.impurityRate * 100).toFixed(2) + '%' : '-' }}</span>
</div>
<div class="mobile-info-row">
<span class="mobile-info-row__label">供应商</span>
<span class="mobile-info-row__value">{{ weighDetail?.supplierName || '-' }}</span>
</div>
<div class="mobile-info-row">
<span class="mobile-info-row__label">司机</span>
<span class="mobile-info-row__value">{{ weighDetail?.driver || '-' }}</span>
</div>
<div class="mobile-info-row">
<span class="mobile-info-row__label">管理员</span>
<span class="mobile-info-row__value">{{ weighDetail?.administrator || '-' }}</span>
</div>
<div class="mobile-info-row">
<span class="mobile-info-row__label">过磅员</span>
<span class="mobile-info-row__value">{{ weighDetail?.weigher || '-' }}</span>
</div>
<div class="mobile-info-row" v-if="weighDetail?.fileUrl">
<span class="mobile-info-row__label">附件</span>
<span class="mobile-info-row__value"><a :href="weighDetail.fileUrl" target="_blank">查看附件</a></span>
</div>
<div class="mobile-info-row" v-if="weighDetail?.remark">
<span class="mobile-info-row__label">备注</span>
<span class="mobile-info-row__value">{{ weighDetail.remark }}</span>
</div>
<div class="mobile-info-row">
<span class="mobile-info-row__label">创建时间</span>
<span class="mobile-info-row__value">{{ weighDetail?.createTime ? formatDate(weighDetail.createTime) : '-' }}</span>
</div>
</div>
</div>
<!-- 检验信息部分 - 仅当有检验数据时显示 -->
<template v-if="hasInspectionData">
<el-divider content-position="left">检验信息</el-divider>
<el-descriptions :column="2" border>
<el-descriptions-item label="取样日期">{{
weighDetail?.sampleDate ? formatDate(weighDetail.sampleDate) : '-'
}}</el-descriptions-item>
<el-descriptions-item label="取样地点">{{
weighDetail?.sampleLocation || '-'
}}</el-descriptions-item>
<el-descriptions-item label="代表重量">{{
weighDetail?.representativeWeight != null ? weighDetail.representativeWeight + ' kg' : '-'
}}</el-descriptions-item>
<el-descriptions-item label="样品重量">{{
weighDetail?.sampleWeight != null ? weighDetail.sampleWeight + ' kg' : '-'
}}</el-descriptions-item>
<el-descriptions-item label="杂质重量">{{
weighDetail?.impurityWeight != null ? weighDetail.impurityWeight + ' kg' : '-'
}}</el-descriptions-item>
<el-descriptions-item label="沙土重量">{{
weighDetail?.sandWeight != null ? weighDetail.sandWeight + ' kg' : '-'
}}</el-descriptions-item>
<el-descriptions-item label="感官">{{
weighDetail?.sensoryEvaluation || '-'
}}</el-descriptions-item>
<el-descriptions-item label="合格率">{{
weighDetail?.qualificationRate ? weighDetail.qualificationRate + '%' : '-'
}}</el-descriptions-item>
<el-descriptions-item label="水分">{{
weighDetail?.moistureContent ? weighDetail.moistureContent + '%' : '-'
}}</el-descriptions-item>
<el-descriptions-item label="含糖">{{
weighDetail?.sugarContent ? weighDetail.sugarContent + '%' : '-'
}}</el-descriptions-item>
<el-descriptions-item label="RA值">{{
weighDetail?.raValue || '-'
}}</el-descriptions-item>
<el-descriptions-item label="STV值">{{
weighDetail?.stvValue || '-'
}}</el-descriptions-item>
<el-descriptions-item label="化验员">{{
weighDetail?.labTechnician || '-'
}}</el-descriptions-item>
<el-descriptions-item label="检验备注" :span="2">{{
weighDetail?.inspectionRemark || '-'
}}</el-descriptions-item>
</el-descriptions>
</template>
</Dialog>
<!-- 检验信息部分 - 仅当有检验数据时显示 -->
<div class="mobile-form__section" v-if="hasInspectionData">
<div class="mobile-form__section-title">检验信息</div>
<div class="mobile-info-list">
<div class="mobile-info-row">
<span class="mobile-info-row__label">取样日期</span>
<span class="mobile-info-row__value">{{ weighDetail?.sampleDate ? formatDate(weighDetail.sampleDate) : '-' }}</span>
</div>
<div class="mobile-info-row">
<span class="mobile-info-row__label">取样地点</span>
<span class="mobile-info-row__value">{{ weighDetail?.sampleLocation || '-' }}</span>
</div>
<div class="mobile-info-row">
<span class="mobile-info-row__label">代表重量</span>
<span class="mobile-info-row__value">{{ weighDetail?.representativeWeight != null ? weighDetail.representativeWeight + ' kg' : '-' }}</span>
</div>
<div class="mobile-info-row">
<span class="mobile-info-row__label">样品重量</span>
<span class="mobile-info-row__value">{{ weighDetail?.sampleWeight != null ? weighDetail.sampleWeight + ' kg' : '-' }}</span>
</div>
<div class="mobile-info-row">
<span class="mobile-info-row__label">杂质重量</span>
<span class="mobile-info-row__value">{{ weighDetail?.impurityWeight != null ? weighDetail.impurityWeight + ' kg' : '-' }}</span>
</div>
<div class="mobile-info-row">
<span class="mobile-info-row__label">沙土重量</span>
<span class="mobile-info-row__value">{{ weighDetail?.sandWeight != null ? weighDetail.sandWeight + ' kg' : '-' }}</span>
</div>
<div class="mobile-info-row">
<span class="mobile-info-row__label">感官</span>
<span class="mobile-info-row__value">{{ weighDetail?.sensoryEvaluation || '-' }}</span>
</div>
<div class="mobile-info-row">
<span class="mobile-info-row__label">合格率</span>
<span class="mobile-info-row__value">{{ weighDetail?.qualificationRate ? weighDetail.qualificationRate + '%' : '-' }}</span>
</div>
<div class="mobile-info-row">
<span class="mobile-info-row__label">水分</span>
<span class="mobile-info-row__value">{{ weighDetail?.moistureContent ? weighDetail.moistureContent + '%' : '-' }}</span>
</div>
<div class="mobile-info-row">
<span class="mobile-info-row__label">含糖</span>
<span class="mobile-info-row__value">{{ weighDetail?.sugarContent ? weighDetail.sugarContent + '%' : '-' }}</span>
</div>
<div class="mobile-info-row">
<span class="mobile-info-row__label">RA值</span>
<span class="mobile-info-row__value">{{ weighDetail?.raValue || '-' }}</span>
</div>
<div class="mobile-info-row">
<span class="mobile-info-row__label">STV值</span>
<span class="mobile-info-row__value">{{ weighDetail?.stvValue || '-' }}</span>
</div>
<div class="mobile-info-row">
<span class="mobile-info-row__label">化验员</span>
<span class="mobile-info-row__value">{{ weighDetail?.labTechnician || '-' }}</span>
</div>
<div class="mobile-info-row" v-if="weighDetail?.inspectionRemark">
<span class="mobile-info-row__label">检验备注</span>
<span class="mobile-info-row__value">{{ weighDetail.inspectionRemark }}</span>
</div>
</div>
</div>
<div class="mobile-form__footer">
<el-button @click="weighDialogVisible = false" style="width: 100%"> </el-button>
</div>
</div>
</el-drawer>
</template>
<script setup lang="ts">
@@ -246,3 +343,115 @@ defineExpose({
open
})
</script>
<style lang="scss" scoped>
.mobile-form {
padding: 0 4px;
}
.mobile-form__section {
background: #fff;
border-radius: 10px;
padding: 14px;
margin-bottom: 12px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
}
.mobile-form__section-title {
font-size: 15px;
font-weight: 600;
color: #303133;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 1px solid #f0f0f0;
}
.mobile-form__footer {
position: sticky;
bottom: 0;
background: #fff;
padding: 12px 16px;
padding-bottom: calc(12px + constant(safe-area-inset-bottom));
padding-bottom: calc(12px + env(safe-area-inset-bottom));
border-top: 1px solid #eee;
display: flex;
justify-content: center;
z-index: 10;
margin: 0 -4px;
.el-button {
width: 100%;
height: 40px;
font-size: 15px;
}
}
.mobile-info-list {
font-size: 13px;
}
.mobile-info-row {
display: flex;
justify-content: space-between;
padding: 6px 0;
border-bottom: 1px solid #f5f5f5;
&:last-child { border-bottom: none; }
&__label { color: #909399; flex-shrink: 0; margin-right: 12px; }
&__value { color: #303133; text-align: right; }
}
.mobile-item-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.mobile-item-card {
background: #f9f9fb;
border-radius: 8px;
padding: 12px;
border: 1px solid #ebeef5;
&__header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 10px;
padding-bottom: 8px;
border-bottom: 1px solid #f0f0f0;
}
&__index {
font-size: 12px;
color: #909399;
font-weight: 600;
}
&__name {
flex: 1;
font-size: 14px;
font-weight: 600;
color: #303133;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&__body {
font-size: 13px;
}
&__info-row {
display: flex;
justify-content: space-between;
padding: 4px 0;
}
&__info-label {
color: #909399;
flex-shrink: 0;
}
&__info-value {
color: #606266;
text-align: right;
}
&__footer {
margin-top: 10px;
padding-top: 10px;
border-top: 1px solid #f0f0f0;
}
}
.mobile-empty-tip {
text-align: center;
color: #909399;
padding: 20px 0;
font-size: 14px;
}
</style>

View File

@@ -1,21 +1,29 @@
<template>
<el-dialog
:title="formType === 'create' ? '新增工单' : '编辑工单'"
<el-drawer
v-model="visible"
:width="isMobile ? '100%' : '900px'"
:fullscreen="isMobile"
@close="handleClose"
:close-on-click-modal="false"
:title="formType === 'create' ? '新增工单' : '编辑工单'"
direction="rtl"
size="100%"
:close-on-press-escape="true"
:destroy-on-close="true"
:append-to-body="true"
class="mobile-form-drawer"
>
<el-form :model="form" :rules="rules" ref="formRef" :label-width="isMobile ? '80px' : '120px'" v-loading="formLoading">
<el-tabs v-model="activeTab">
<div class="mobile-form" v-loading="formLoading">
<el-form
ref="formRef"
:model="form"
:rules="rules"
label-position="top"
>
<!-- 基本信息 -->
<el-tab-pane label="基本信息" name="basic">
<div class="mobile-form__section">
<div class="mobile-form__section-title">基本信息</div>
<el-form-item label="产品" prop="productId">
<el-select
v-model="form.productId"
placeholder="请选择产品"
:class="isMobile ? '!w-full' : '!w-240px'"
style="width: 100%"
@change="handleProductChange"
>
<el-option
@@ -42,7 +50,7 @@
<el-select
v-model="form.routeId"
placeholder="请选择工序路线"
:class="isMobile ? '!w-full' : '!w-240px'"
style="width: 100%"
@change="handleRouteChange"
>
<el-option
@@ -63,7 +71,8 @@
:precision="2"
:step="0.1"
placeholder="请输入计划数量"
:class="isMobile ? '!w-full' : ''"
controls-position="right"
style="width: 100%"
/>
</el-form-item>
<!-- <el-form-item label="优先级" prop="priority">-->
@@ -78,7 +87,7 @@
<el-form-item label="计划时间" prop="planTime">
<el-date-picker
v-model="form.planTime"
:type="isMobile ? 'datetimerange' : 'datetimerange'"
type="datetimerange"
start-placeholder="开始时间"
end-placeholder="结束时间"
:default-time="[
@@ -97,35 +106,52 @@
)
)
]"
:class="isMobile ? '!w-full' : '!w-380px'"
style="width: 100%"
/>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" type="textarea" placeholder="请输入备注" />
<el-input v-model="form.remark" type="textarea" :rows="2" placeholder="请输入备注" />
</el-form-item>
</el-tab-pane>
</div>
<!-- 工序信息 -->
<el-tab-pane label="工序信息" name="process">
<el-table :data="form.operations" border>
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column label="工序编码" prop="operationCode" min-width="120" align="center" />
<el-table-column label="工序名称" prop="operationName" min-width="120" align="center" />
<el-table-column
label="工时(分钟)"
prop="requiredTime"
min-width="120"
align="center"
/>
</el-table>
</el-tab-pane>
</el-tabs>
</el-form>
<template #footer>
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" :loading="loading" @click="submitForm">确定</el-button>
</template>
</el-dialog>
<div class="mobile-form__section">
<div class="mobile-form__section-title">工序信息</div>
<div class="mobile-item-list">
<div
v-for="(op, index) in form.operations"
:key="index"
class="mobile-item-card"
>
<div class="mobile-item-card__header">
<span class="mobile-item-card__index">#{{ index + 1 }}</span>
<span class="mobile-item-card__name">{{ op.operationName || '未知工序' }}</span>
</div>
<div class="mobile-item-card__body">
<div class="mobile-item-card__info-row">
<span class="mobile-item-card__info-label">工序编码</span>
<span class="mobile-item-card__info-value">{{ op.operationCode || '-' }}</span>
</div>
<div class="mobile-item-card__info-row">
<span class="mobile-item-card__info-label">工时(分钟)</span>
<span class="mobile-item-card__info-value">{{ op.requiredTime || '-' }}</span>
</div>
</div>
</div>
<div v-if="form.operations.length === 0" class="mobile-empty-tip">
请先选择工序路线
</div>
</div>
</div>
</el-form>
<!-- 底部操作按钮 -->
<div class="mobile-form__footer">
<el-button @click="visible = false"> </el-button>
<el-button type="primary" :loading="loading" @click="submitForm"> </el-button>
</div>
</div>
</el-drawer>
</template>
<script setup lang="ts">
@@ -408,8 +434,98 @@ const handleClose = () => {
defineExpose({ open })
</script>
<style scoped>
.el-tabs :deep(.el-tabs__content) {
<style lang="scss" scoped>
.mobile-form {
padding: 0 4px;
}
.mobile-form__section {
background: #fff;
border-radius: 10px;
padding: 14px;
margin-bottom: 12px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
}
.mobile-form__section-title {
font-size: 15px;
font-weight: 600;
color: #303133;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 1px solid #f0f0f0;
}
.mobile-form__footer {
position: sticky;
bottom: 0;
background: #fff;
padding: 12px 16px;
padding-bottom: calc(12px + constant(safe-area-inset-bottom));
padding-bottom: calc(12px + env(safe-area-inset-bottom));
border-top: 1px solid #eee;
display: flex;
justify-content: flex-end;
gap: 12px;
z-index: 10;
margin: 0 -4px;
.el-button {
flex: 1;
height: 40px;
font-size: 15px;
}
}
.mobile-item-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.mobile-item-card {
background: #f9f9fb;
border-radius: 8px;
padding: 12px;
border: 1px solid #ebeef5;
&__header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 10px;
padding-bottom: 8px;
border-bottom: 1px solid #f0f0f0;
}
&__index {
font-size: 12px;
color: #909399;
font-weight: 600;
}
&__name {
flex: 1;
font-size: 14px;
font-weight: 600;
color: #303133;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&__body {
font-size: 13px;
}
&__info-row {
display: flex;
justify-content: space-between;
padding: 4px 0;
}
&__info-label {
color: #909399;
flex-shrink: 0;
}
&__info-value {
color: #606266;
text-align: right;
}
}
.mobile-empty-tip {
text-align: center;
color: #909399;
padding: 20px 0;
font-size: 14px;
}
</style>

View File

@@ -1,128 +1,126 @@
<template>
<el-dialog title="工序执行情况" v-model="visible" width="1200px" v-loading="loading">
<!-- 工序进度统计 -->
<OperationProgressCard :progressData="progressData" />
<el-drawer
v-model="visible"
title="工序执行情况"
direction="rtl"
size="100%"
:close-on-press-escape="true"
:destroy-on-close="true"
:append-to-body="true"
class="mobile-form-drawer"
>
<div class="mobile-form" v-loading="loading">
<!-- 工序进度统计 -->
<div class="mobile-form__section">
<div class="mobile-form__section-title">工序进度</div>
<OperationProgressCard :progressData="progressData" />
</div>
<!-- 工序列表 -->
<el-table :data="operationList" border stripe v-loading="tableLoading">
<el-table-column type="index" label="序号" width="60" align="center" />
<el-table-column label="工序信息" min-width="180">
<template #default="{ row }">
<div>{{ row.operationName }}</div>
<div class="text-gray-400 text-sm">{{ row.operationCode }}</div>
</template>
</el-table-column>
<el-table-column label="生产数量" min-width="180">
<template #default="{ row }">
<div>计划{{ row.planQuantity }}</div>
<div class="mt-1">完成{{ row.completedQuantity }}</div>
<div class="mt-1">
<span class="text-success">合格{{ row.qualifiedQuantity }}</span>
<span class="text-danger ml-2">不合格{{ row.unqualifiedQuantity }}</span>
<!-- 工序列表 -->
<div class="mobile-form__section">
<div class="mobile-form__section-title">工序列表</div>
<div class="mobile-item-list" v-loading="tableLoading">
<div
v-for="(row, index) in operationList"
:key="row.id"
class="mobile-item-card"
>
<div class="mobile-item-card__header">
<span class="mobile-item-card__index">#{{ index + 1 }}</span>
<span class="mobile-item-card__name">{{ row.operationName || '未知工序' }}</span>
<el-tag :type="getStatusType(row.status)" size="small">{{ getStatusText(row.status) }}</el-tag>
</div>
<div class="mobile-item-card__body">
<div class="mobile-item-card__info-row">
<span class="mobile-item-card__info-label">工序编码</span>
<span class="mobile-item-card__info-value">{{ row.operationCode || '-' }}</span>
</div>
<div class="mobile-item-card__info-row">
<span class="mobile-item-card__info-label">计划数量</span>
<span class="mobile-item-card__info-value">{{ row.planQuantity }}</span>
</div>
<div class="mobile-item-card__info-row">
<span class="mobile-item-card__info-label">完成数量</span>
<span class="mobile-item-card__info-value">{{ row.completedQuantity }}</span>
</div>
<div class="mobile-item-card__info-row">
<span class="mobile-item-card__info-label">合格/不合格</span>
<span class="mobile-item-card__info-value">
<span style="color: #67c23a">{{ row.qualifiedQuantity }}</span>
<span style="color: #909399"> / </span>
<span style="color: #f56c6c">{{ row.unqualifiedQuantity }}</span>
</span>
</div>
<div class="mobile-item-card__info-row">
<span class="mobile-item-card__info-label">操作人</span>
<span class="mobile-item-card__info-value">{{ getOperationExecution(row.id)?.workerName || '-' }}</span>
</div>
<div class="mobile-item-card__info-row">
<span class="mobile-item-card__info-label">开始时间</span>
<span class="mobile-item-card__info-value">{{ getOperationExecution(row.id)?.startTime || '未开始' }}</span>
</div>
<div class="mobile-item-card__info-row">
<span class="mobile-item-card__info-label">结束时间</span>
<span class="mobile-item-card__info-value">{{ getOperationExecution(row.id)?.endTime || '未结束' }}</span>
</div>
</div>
<div class="mobile-item-card__footer">
<el-button
size="small"
type="primary"
@click="handleStart(row)"
v-if="row.status === 0 && [2, 3].includes(workOrderStatus)"
v-hasPermi="['mes:production-order:operations']"
>开始</el-button>
<el-button
size="small"
type="success"
@click="handleComplete(row)"
v-if="row.status === 1"
v-hasPermi="['mes:production-order:operations']"
>完成</el-button>
<el-button
size="small"
type="warning"
@click="handlePause(row)"
v-if="row.status === 1"
v-hasPermi="['mes:production-order:operations']"
>暂停</el-button>
<el-button
size="small"
type="primary"
@click="handleResume(row)"
v-if="row.status === 3"
v-hasPermi="['mes:production-order:operations']"
>恢复</el-button>
<el-button
size="small"
@click="handleViewDetail(row)"
v-if="row.status === 2"
v-hasPermi="['mes:production-order:operations']"
>详情</el-button>
<el-button
size="small"
type="primary"
@click="handleRecordProgress(row)"
v-if="row.status === 1"
v-hasPermi="['mes:production-order:operations']"
>记录进度</el-button>
<el-button
size="small"
@click="handleViewProgressHistory(row)"
v-if="row.status === 1 || row.status === 2"
v-hasPermi="['mes:production-order:operations']"
>查看记录</el-button>
</div>
</div>
</template>
</el-table-column>
<el-table-column label="操作人" min-width="120">
<template #default="{ row }">
<div>{{ getOperationExecution(row.id)?.workerName || '-' }}</div>
</template>
</el-table-column>
<el-table-column label="实际时间" min-width="180">
<template #default="{ row }">
<div>开始{{ getOperationExecution(row.id)?.startTime || '未开始' }}</div>
<div class="mt-1">结束{{ getOperationExecution(row.id)?.endTime || '未结束' }}</div>
</template>
</el-table-column>
<el-table-column label="状态" width="100" align="center">
<template #default="{ row }">
<el-tag :type="getStatusType(row.status)">{{ getStatusText(row.status) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="200" align="center">
<template #default="{ row }">
<el-tooltip
content="开始"
placement="top"
v-if="row.status === 0 && [2, 3].includes(workOrderStatus)"
>
<el-button
link
type="primary"
@click="handleStart(row)"
v-hasPermi="['mes:production-order:operations']"
>
<Icon icon="ep:video-play" />
</el-button>
</el-tooltip>
<el-tooltip content="完成" placement="top" v-if="row.status === 1">
<el-button
link
type="success"
@click="handleComplete(row)"
v-hasPermi="['mes:production-order:operations']"
>
<Icon icon="ep:check" />
</el-button>
</el-tooltip>
<el-tooltip content="暂停" placement="top" v-if="row.status === 1">
<el-button
link
type="warning"
@click="handlePause(row)"
v-hasPermi="['mes:production-order:operations']"
>
<Icon icon="ep:video-pause" />
</el-button>
</el-tooltip>
<el-tooltip content="恢复" placement="top" v-if="row.status === 3">
<el-button
link
type="primary"
@click="handleResume(row)"
v-hasPermi="['mes:production-order:operations']"
>
<Icon icon="ep:video-play" />
</el-button>
</el-tooltip>
<!-- 查看详情按钮 - 修改显示条件 -->
<el-tooltip content="查看详情" placement="top" v-if="row.status === 2">
<el-button
link
type="info"
@click="handleViewDetail(row)"
v-hasPermi="['mes:production-order:operations']"
>
<Icon icon="ep:document-copy" />
</el-button>
</el-tooltip>
<el-tooltip content="记录进度" placement="top" v-if="row.status === 1">
<el-button
link
type="primary"
@click="handleRecordProgress(row)"
v-hasPermi="['mes:production-order:operations']"
>
<Icon icon="ep:edit" />
</el-button>
</el-tooltip>
<el-tooltip
content="查看记录"
placement="top"
v-if="row.status === 1 || row.status === 2"
>
<el-button
link
type="info"
@click="handleViewProgressHistory(row)"
v-hasPermi="['mes:production-order:operations']"
>
<Icon icon="ep:list" />
</el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
</el-dialog>
<div v-if="operationList.length === 0 && !tableLoading" class="mobile-empty-tip">
暂无工序数据
</div>
</div>
</div>
</div>
</el-drawer>
<!-- 开始工序弹窗 -->
<OperationStartDialog
@@ -135,7 +133,7 @@
/>
<!-- 完成工序弹窗 -->
<el-dialog v-model="completeDialogVisible" title="完成工序" width="600px" append-to-body>
<el-dialog v-model="completeDialogVisible" title="完成工序" width="400px" append-to-body>
<el-form ref="completeFormRef" :model="completeForm" :rules="completeRules" label-width="120px">
<!-- 动态工序字段 -->
<template v-if="Object.keys(processFields).length > 0">
@@ -1097,7 +1095,88 @@ onMounted(() => {
defineExpose({ open })
</script>
<style scoped>
<style lang="scss" scoped>
.mobile-form {
padding: 0 4px;
}
.mobile-form__section {
background: #fff;
border-radius: 10px;
padding: 14px;
margin-bottom: 12px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
}
.mobile-form__section-title {
font-size: 15px;
font-weight: 600;
color: #303133;
margin-bottom: 12px;
padding-bottom: 8px;
border-bottom: 1px solid #f0f0f0;
}
.mobile-item-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.mobile-item-card {
background: #f9f9fb;
border-radius: 8px;
padding: 12px;
border: 1px solid #ebeef5;
&__header {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 10px;
padding-bottom: 8px;
border-bottom: 1px solid #f0f0f0;
}
&__index {
font-size: 12px;
color: #909399;
font-weight: 600;
}
&__name {
flex: 1;
font-size: 14px;
font-weight: 600;
color: #303133;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&__body {
font-size: 13px;
}
&__info-row {
display: flex;
justify-content: space-between;
padding: 4px 0;
}
&__info-label {
color: #909399;
flex-shrink: 0;
}
&__info-value {
color: #606266;
text-align: right;
}
&__footer {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-top: 10px;
padding-top: 10px;
border-top: 1px solid #f0f0f0;
}
}
.mobile-empty-tip {
text-align: center;
color: #909399;
padding: 20px 0;
font-size: 14px;
}
.text-lg {
font-size: 16px;
}