生产领料、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> <template>
<el-dialog <el-drawer
:title="dialogTitle"
v-model="dialogVisible" v-model="dialogVisible"
width="1000px" :title="dialogTitle"
@close="handleClose" direction="rtl"
:close-on-click-modal="false" size="100%"
:close-on-press-escape="true"
:destroy-on-close="true"
:append-to-body="true"
class="mobile-form-drawer"
> >
<el-form <div class="mobile-form" v-loading="formLoading">
ref="formRef" <el-form
:model="formData" ref="formRef"
:rules="formRules" :model="formData"
label-width="100px" :rules="formRules"
v-loading="formLoading" label-position="top"
> :disabled="isViewMode"
<el-row :gutter="20"> >
<el-col :span="12"> <!-- 基本信息 -->
<div class="mobile-form__section">
<div class="mobile-form__section-title">基本信息</div>
<el-form-item label="产品" prop="productId"> <el-form-item label="产品" prop="productId">
<el-select <el-select
v-model="formData.productId" v-model="formData.productId"
placeholder="请选择产品" placeholder="请选择产品"
filterable filterable
class="!w-100%" style="width: 100%"
:disabled="isViewMode" :disabled="isViewMode"
@change="handleProductChange" @change="handleProductChange"
> >
@@ -32,154 +37,147 @@
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="BOM名称" prop="bomName"> <el-form-item label="BOM名称" prop="bomName">
<el-input v-model="formData.bomName" placeholder="请输入BOM名称" :disabled="isViewMode" /> <el-input v-model="formData.bomName" placeholder="请输入BOM名称" :disabled="isViewMode" />
</el-form-item> </el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="BOM编码" prop="bomCode"> <el-form-item label="BOM编码" prop="bomCode">
<el-input v-model="formData.bomCode" placeholder="自动生成" :disabled="true" /> <el-input v-model="formData.bomCode" placeholder="自动生成" :disabled="true" />
</el-form-item> </el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="版本号" prop="version"> <el-form-item label="版本号" prop="version">
<el-input v-model="formData.version" placeholder="如V1.0" :disabled="isViewMode" /> <el-input v-model="formData.version" placeholder="如V1.0" :disabled="isViewMode" />
</el-form-item> </el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="默认版本" prop="isDefault"> <el-form-item label="默认版本" prop="isDefault">
<el-radio-group v-model="formData.isDefault" :disabled="isViewMode"> <el-radio-group v-model="formData.isDefault" :disabled="isViewMode">
<el-radio :label="1"></el-radio> <el-radio :label="1"></el-radio>
<el-radio :label="0"></el-radio> <el-radio :label="0"></el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="状态" prop="status"> <el-form-item label="状态" prop="status">
<el-radio-group v-model="formData.status" :disabled="isViewMode"> <el-radio-group v-model="formData.status" :disabled="isViewMode">
<el-radio :label="1">启用</el-radio> <el-radio :label="1">启用</el-radio>
<el-radio :label="0">禁用</el-radio> <el-radio :label="0">禁用</el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="24">
<el-form-item label="备注" prop="remark"> <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-form-item>
</el-col> </div>
</el-row>
<!-- BOM明细 --> <!-- BOM明细 -->
<el-divider content-position="left">BOM明细</el-divider> <div class="mobile-form__section">
<el-form-item v-if="!isViewMode"> <div class="mobile-form__section-title">BOM明细</div>
<el-button type="primary" @click="addItem"> <el-button v-if="!isViewMode" type="primary" plain style="width: 100%; margin-bottom: 12px" @click="addItem">
<Icon icon="ep:plus" class="mr-5px" /> 添加物料 <Icon icon="ep:plus" class="mr-5px" /> 添加物料
</el-button> </el-button>
</el-form-item>
<el-table :data="formData.items" border style="width: 100%"> <div class="mobile-item-list">
<el-table-column label="物料" min-width="200"> <div
<template #default="{ row, $index }"> v-for="(row, $index) in formData.items"
<el-select :key="$index"
v-if="!isViewMode" class="mobile-item-card"
v-model="row.materialId"
placeholder="请选择物料"
filterable
class="!w-100%"
@change="(val: number) => handleMaterialChange(val, $index)"
> >
<el-option <div class="mobile-item-card__header">
v-for="item in materialList" <span class="mobile-item-card__index">#{{ $index + 1 }}</span>
:key="item.id" <span class="mobile-item-card__name">{{ row.materialName || '未选择物料' }}</span>
:label="`${item.name} (${item.barCode || '-'})`" <el-button
:value="item.id" v-if="!isViewMode"
/> link
</el-select> type="danger"
<span v-else>{{ row.materialName }} ({{ row.materialCode || '-' }})</span> size="small"
</template> @click="removeItem($index)"
</el-table-column> >删除</el-button>
<el-table-column label="物料类型" width="120"> </div>
<template #default="{ row }"> <div class="mobile-item-card__body">
<el-input v-if="!isViewMode" v-model="row.materialType" placeholder="类型" :disabled="true" /> <el-form-item v-if="!isViewMode" label="物料" :prop="`items.${$index}.materialId`">
<span v-else>{{ row.materialType || '-' }}</span> <el-select
</template> v-model="row.materialId"
</el-table-column> placeholder="请选择物料"
<el-table-column label="单位" width="80"> filterable
<template #default="{ row }"> style="width: 100%"
<el-input v-if="!isViewMode" v-model="row.unit" placeholder="单位" /> @change="(val: number) => handleMaterialChange(val, $index)"
<span v-else>{{ row.unit || '-' }}</span> >
</template> <el-option
</el-table-column> v-for="item in materialList"
<el-table-column label="单位用量" width="120"> :key="item.id"
<template #default="{ row }"> :label="`${item.name} (${item.barCode || '-'})`"
<el-input-number :value="item.id"
v-if="!isViewMode" />
v-model="row.unitQuantity" </el-select>
:min="0" </el-form-item>
:precision="4" <div class="mobile-item-card__info-row">
:step="0.1" <span class="mobile-item-card__info-label">物料编码</span>
:controls="false" <span class="mobile-item-card__info-value">{{ row.materialCode || '-' }}</span>
class="!w-100%" </div>
/> <div class="mobile-item-card__info-row">
<span v-else>{{ row.unitQuantity }}</span> <span class="mobile-item-card__info-label">物料类型</span>
</template> <span class="mobile-item-card__info-value">{{ row.materialType || '-' }}</span>
</el-table-column> </div>
<el-table-column label="损耗率(%)" width="100"> <div class="mobile-item-card__info-row">
<template #default="{ row }"> <span class="mobile-item-card__info-label">单位</span>
<el-input-number <span class="mobile-item-card__info-value" v-if="isViewMode">{{ row.unit || '-' }}</span>
v-if="!isViewMode" <el-input v-else v-model="row.unit" placeholder="单位" style="width: 120px" />
v-model="row.lossRate" </div>
:min="0" <div class="mobile-item-card__input-group">
:max="100" <el-form-item label="单位用量" :prop="`items.${$index}.unitQuantity`">
:precision="2" <el-input-number
:controls="false" v-if="!isViewMode"
class="!w-100%" v-model="row.unitQuantity"
/> :min="0"
<span v-else>{{ row.lossRate || 0 }}</span> :precision="4"
</template> :step="0.1"
</el-table-column> controls-position="right"
<el-table-column label="单价" width="100"> style="width: 100%"
<template #default="{ row }"> />
<el-input-number <span v-else>{{ row.unitQuantity }}</span>
v-if="!isViewMode" </el-form-item>
v-model="row.unitPrice" <el-form-item label="损耗率(%)" :prop="`items.${$index}.lossRate`">
:min="0" <el-input-number
:precision="2" v-if="!isViewMode"
:controls="false" v-model="row.lossRate"
class="!w-100%" :min="0"
/> :max="100"
<span v-else>{{ row.unitPrice ? '¥' + row.unitPrice : '-' }}</span> :precision="2"
</template> controls-position="right"
</el-table-column> style="width: 100%"
<el-table-column label="必需" width="80"> />
<template #default="{ row }"> <span v-else>{{ row.lossRate || 0 }}</span>
<el-checkbox v-if="!isViewMode" v-model="row.required" :true-label="1" :false-label="0" /> </el-form-item>
<el-tag v-else :type="row.required === 1 ? 'success' : 'info'" size="small"> </div>
{{ row.required === 1 ? '是' : '否' }} <div class="mobile-item-card__input-group">
</el-tag> <el-form-item label="单价" :prop="`items.${$index}.unitPrice`">
</template> <el-input-number
</el-table-column> v-if="!isViewMode"
<el-table-column v-if="!isViewMode" label="操作" width="80" fixed="right"> v-model="row.unitPrice"
<template #default="{ $index }"> :min="0"
<el-button link type="danger" @click="removeItem($index)">删除</el-button> :precision="2"
</template> controls-position="right"
</el-table-column> style="width: 100%"
</el-table> />
</el-form> <span v-else>{{ row.unitPrice ? '¥' + row.unitPrice : '-' }}</span>
<template #footer> </el-form-item>
<el-button @click="dialogVisible = false">{{ isViewMode ? '关闭' : '取消' }}</el-button> <el-form-item label="必需">
<el-button v-if="!isViewMode" type="primary" @click="submitForm" :loading="formLoading">确定</el-button> <el-checkbox v-if="!isViewMode" v-model="row.required" :true-label="1" :false-label="0" />
</template> <el-tag v-else :type="row.required === 1 ? 'success' : 'info'" size="small">
</el-dialog> {{ 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> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -374,3 +372,109 @@ const handleClose = () => {
defineExpose({ open }) defineExpose({ open })
</script> </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> <template>
<ContentWrap> <div class="mobile-bom-list">
<!-- 搜索工作栏 --> <!-- 顶部操作栏 -->
<el-form <div class="mobile-header">
ref="queryFormRef" <div class="mobile-header__search">
: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">
<el-input <el-input
v-model="queryParams.bomName" v-model="queryParams.bomName"
placeholder="请输入BOM名称" placeholder="搜索BOM名称"
clearable clearable
@keyup.enter="handleQuery" @keyup.enter="handleQuery"
class="!w-200px" :prefix-icon="Search"
/> />
</el-form-item> </div>
<el-form-item label="状态" prop="status"> <div class="mobile-header__actions">
<el-select <el-button :icon="Filter" circle @click="filterVisible = true" />
v-model="queryParams.status" <el-button type="primary" :icon="Plus" circle @click="openForm('create')" v-hasPermi="['mes:product-bom:create']" />
placeholder="请选择状态" </div>
clearable </div>
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>
<el-button type="primary" <div class="mobile-header__quick-filter">
@click="openForm('create')" <div
v-hasPermi="['mes:product-bom:create']" class="quick-filter-item"
> :class="{ active: queryParams.status === undefined }"
<Icon icon="ep:plus" class="mr-5px" /> 新增 @click="handleQuickFilter(undefined)"
</el-button> >
</el-form-item> 全部
</el-form> </div>
</ContentWrap> <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> <!-- 卡片列表 -->
<div class="mobile-list" v-loading="loading">
<!-- 列表 --> <div v-if="list.length === 0 && !loading" class="mobile-empty">
<el-table v-loading="loading" :data="list"> <el-empty description="暂无BOM记录" />
<el-table-column label="BOM编码" align="center" prop="bomCode" width="180" /> </div>
<el-table-column label="BOM名称" align="center" prop="bomName" min-width="150" /> <div
<el-table-column label="产品编码" align="center" prop="productCode" width="120" /> v-for="item in list"
<el-table-column label="产品名称" align="center" prop="productName" min-width="150" /> :key="item.id"
<el-table-column label="版本" align="center" prop="version" width="80" /> class="mobile-card"
<el-table-column label="默认版本" align="center" prop="isDefault" width="100"> @click="openForm('view', item.id)"
<template #default="{ row }"> >
<el-tag :type="row.isDefault === 1 ? 'success' : 'info'"> <div class="mobile-card__header">
{{ row.isDefault === 1 ? '是' : '否' }} <span class="mobile-card__no">{{ item.bomName }}</span>
<el-tag :type="item.status === 1 ? 'success' : 'danger'" size="small">
{{ item.status === 1 ? '启用' : '禁用' }}
</el-tag> </el-tag>
</template> </div>
</el-table-column> <div class="mobile-card__body">
<el-table-column label="状态" align="center" prop="status" width="80"> <div class="mobile-card__row">
<template #default="{ row }"> <span class="mobile-card__label">BOM编码</span>
<el-tag :type="row.status === 1 ? 'success' : 'danger'"> <span class="mobile-card__value">{{ item.bomCode || '-' }}</span>
{{ row.status === 1 ? '启用' : '禁用' }} </div>
</el-tag> <div class="mobile-card__row">
</template> <span class="mobile-card__label">产品名称</span>
</el-table-column> <span class="mobile-card__value">{{ item.productName || '-' }}</span>
<el-table-column label="创建时间" align="center" prop="createTime" width="180" /> </div>
<el-table-column label="操作" align="center" fixed="right" width="200"> <div class="mobile-card__row">
<template #default="scope"> <span class="mobile-card__label">产品编码</span>
<el-button <span class="mobile-card__value">{{ item.productCode || '-' }}</span>
link </div>
type="primary" <div class="mobile-card__row">
@click="openForm('view', scope.row.id)" <span class="mobile-card__label">版本</span>
v-hasPermi="['mes:product-bom:query']" <span class="mobile-card__value">
> {{ item.version || '-' }}
查看 <el-tag v-if="item.isDefault === 1" type="success" size="small" style="margin-left: 4px">默认</el-tag>
</el-button> </span>
<el-button </div>
link <div class="mobile-card__row">
type="primary" <span class="mobile-card__label">创建时间</span>
@click="openForm('update', scope.row.id)" <span class="mobile-card__value">{{ item.createTime || '-' }}</span>
v-hasPermi="['mes:product-bom:update']" </div>
> </div>
编辑 <div class="mobile-card__footer">
</el-button> <el-button size="small" @click.stop="openForm('view', item.id)" v-hasPermi="['mes:product-bom:query']">查看</el-button>
<el-button <el-button size="small" type="primary" @click.stop="openForm('update', item.id)" v-hasPermi="['mes:product-bom:update']">编辑</el-button>
link <el-button size="small" type="danger" @click.stop="handleDelete(item.id)" v-hasPermi="['mes:product-bom:delete']">删除</el-button>
type="danger" </div>
@click="handleDelete(scope.row.id)" </div>
v-hasPermi="['mes:product-bom:delete']" </div>
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 --> <!-- 分页 -->
<Pagination <div class="mobile-pagination" v-if="total > 0">
:total="total" <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" />
v-model:page="queryParams.pageNo" </div>
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 表单弹窗添加/修改 --> <!-- 筛选抽屉 -->
<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> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive, onMounted } from 'vue' import { ref, reactive, onMounted } from 'vue'
import { Search, Filter, Plus } from '@element-plus/icons-vue'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import { Icon } from '@/components/Icon' import { Icon } from '@/components/Icon'
import ProductBomForm from './ProductBomForm.vue' import ProductBomForm from './ProductBomForm.vue'
@@ -140,6 +136,8 @@ import { getProductBomPage, deleteProductBom } from '@/api/mes/product/bom'
defineOptions({ name: 'MesProductBom' }) defineOptions({ name: 'MesProductBom' })
const filterVisible = ref(false)
const loading = ref(true) const loading = ref(true)
const total = ref(0) const total = ref(0)
const list = ref<any[]>([]) const list = ref<any[]>([])
@@ -194,4 +192,90 @@ const handleDelete = async (id: number) => {
onMounted(() => { onMounted(() => {
getList() getList()
}) })
/** 快捷分类筛选 */
const handleQuickFilter = (status: number | undefined) => {
queryParams.status = status
queryParams.pageNo = 1
getList()
}
/** 筛选确认 */
const handleFilterConfirm = () => {
filterVisible.value = false
handleQuery()
}
</script> </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> <template>
<Dialog title="查看领料单" v-model="dialogVisible" width="80%"> <el-drawer
<Descriptions :schema="schema" :data="detailData" /> 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"> <div class="mobile-form__section">
<el-table-column type="index" label="序号" width="60" align="center" /> <div class="mobile-form__section-title">领料明细</div>
<el-table-column label="物料信息" min-width="240"> <div class="mobile-item-list">
<template #default="{ row }"> <div
<div class="text-13px text-gray-600">编码{{ row.materialCode }}</div> v-for="(row, index) in detailData.items"
<div class="text-13px">名称{{ row.materialName }}</div> :key="index"
</template> class="mobile-item-card"
</el-table-column> >
<el-table-column label="单位" prop="unit" width="80" /> <div class="mobile-item-card__header">
<el-table-column label="计划数量" prop="planQuantity" width="120" /> <span class="mobile-item-card__index">#{{ index + 1 }}</span>
<el-table-column label="实际数量" prop="actualQuantity" width="120" /> <span class="mobile-item-card__name">{{ row.materialName || '未知物料' }}</span>
<el-table-column label="仓库" prop="warehouseName" width="140" /> </div>
<el-table-column label="过磅单号" prop="no" width="160" /> <div class="mobile-item-card__body">
<el-table-column label="备注" prop="remark" min-width="160" /> <div class="mobile-item-card__info-row">
<el-table-column label="操作" width="100" align="center" fixed="right"> <span class="mobile-item-card__info-label">物料编码</span>
<template #default="{ row }"> <span class="mobile-item-card__info-value">{{ row.materialCode || '-' }}</span>
<el-tooltip content="过磅详情" placement="top" v-if="row.purchaseId"> </div>
<el-button link type="primary" @click="openWeighDetail(row.purchaseId)"> <div class="mobile-item-card__info-row">
<Icon icon="ep:document" /> <span class="mobile-item-card__info-label">单位</span>
</el-button> <span class="mobile-item-card__info-value">{{ row.unit || '-' }}</span>
</el-tooltip> </div>
</template> <div class="mobile-item-card__info-row">
</el-table-column> <span class="mobile-item-card__info-label">计划数量</span>
</el-table> <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> <div class="mobile-form__footer">
</template> <el-button @click="dialogVisible = false"> </el-button>
</Dialog> </div>
</div>
</el-drawer>
<!-- 过磅单详情弹窗 --> <!-- 过磅单详情弹窗 -->
<Dialog title="过磅单详情" v-model="weighDialogVisible" width="50%"> <el-drawer
<el-descriptions :column="2" border> v-model="weighDialogVisible"
<el-descriptions-item label="过磅单号">{{ weighDetail?.no || '-' }}</el-descriptions-item> title="过磅单详情"
<el-descriptions-item label="入库状态">{{ direction="rtl"
weighDetail?.inStatus === 1 ? '已入库' : '未入库' size="100%"
}}</el-descriptions-item> :close-on-press-escape="true"
<el-descriptions-item label="产品">{{ :destroy-on-close="true"
weighDetail?.productName || '-' :append-to-body="true"
}}</el-descriptions-item> class="mobile-form-drawer"
<el-descriptions-item label="车牌">{{ >
weighDetail?.vehicleNumber || '-' <div class="mobile-form">
}}</el-descriptions-item> <!-- 基本信息 -->
<el-descriptions-item label="毛重">{{ <div class="mobile-form__section">
weighDetail?.grossWeight != null ? weighDetail.grossWeight + ' kg' : '-' <div class="mobile-form__section-title">基本信息</div>
}}</el-descriptions-item> <div class="mobile-info-list">
<el-descriptions-item label="皮重">{{ <div class="mobile-info-row">
weighDetail?.tareWeight != null ? weighDetail.tareWeight + ' kg' : '-' <span class="mobile-info-row__label">过磅单号</span>
}}</el-descriptions-item> <span class="mobile-info-row__value">{{ weighDetail?.no || '-' }}</span>
<el-descriptions-item label="净重">{{ </div>
weighDetail?.netWeight != null ? weighDetail.netWeight + ' kg' : '-' <div class="mobile-info-row">
}}</el-descriptions-item> <span class="mobile-info-row__label">入库状态</span>
<el-descriptions-item label="计划重量">{{ <span class="mobile-info-row__value">
weighDetail?.plannedWeight != null ? weighDetail.plannedWeight + ' kg' : '-' <el-tag :type="weighDetail?.inStatus === 1 ? 'success' : 'warning'" size="small">
}}</el-descriptions-item> {{ weighDetail?.inStatus === 1 ? '已入库' : '未入库' }}
<el-descriptions-item label="杂质率">{{ </el-tag>
weighDetail?.impurityRate != null ? (weighDetail.impurityRate * 100).toFixed(2) + '%' : '-' </span>
}}</el-descriptions-item> </div>
<el-descriptions-item label="供应商">{{ <div class="mobile-info-row">
weighDetail?.supplierName || '-' <span class="mobile-info-row__label">产品</span>
}}</el-descriptions-item> <span class="mobile-info-row__value">{{ weighDetail?.productName || '-' }}</span>
<el-descriptions-item label="供应商类型">{{ </div>
weighDetail?.supplierName2 || '-' <div class="mobile-info-row">
}}</el-descriptions-item> <span class="mobile-info-row__label">车牌</span>
<el-descriptions-item label="身份证号">{{ <span class="mobile-info-row__value">{{ weighDetail?.vehicleNumber || '-' }}</span>
weighDetail?.idCardNumber || '-' </div>
}}</el-descriptions-item> <div class="mobile-info-row">
<el-descriptions-item label="司机">{{ weighDetail?.driver || '-' }}</el-descriptions-item> <span class="mobile-info-row__label">毛重</span>
<el-descriptions-item label="管理员">{{ <span class="mobile-info-row__value">{{ weighDetail?.grossWeight != null ? weighDetail.grossWeight + ' kg' : '-' }}</span>
weighDetail?.administrator || '-' </div>
}}</el-descriptions-item> <div class="mobile-info-row">
<el-descriptions-item label="过磅员">{{ weighDetail?.weigher || '-' }}</el-descriptions-item> <span class="mobile-info-row__label">皮重</span>
<el-descriptions-item label="产地证编号">{{ <span class="mobile-info-row__value">{{ weighDetail?.tareWeight != null ? weighDetail.tareWeight + ' kg' : '-' }}</span>
weighDetail?.originCertificateNumber || '-' </div>
}}</el-descriptions-item> <div class="mobile-info-row">
<el-descriptions-item label="附件"> <span class="mobile-info-row__label">净重</span>
<template v-if="weighDetail?.fileUrl"> <span class="mobile-info-row__value">{{ weighDetail?.netWeight != null ? weighDetail.netWeight + ' kg' : '-' }}</span>
<a :href="weighDetail.fileUrl" target="_blank">查看附件</a> </div>
</template> <div class="mobile-info-row">
<template v-else>-</template> <span class="mobile-info-row__label">计划重量</span>
</el-descriptions-item> <span class="mobile-info-row__value">{{ weighDetail?.plannedWeight != null ? weighDetail.plannedWeight + ' kg' : '-' }}</span>
<el-descriptions-item label="备注" :span="2">{{ </div>
weighDetail?.remark || '-' <div class="mobile-info-row">
}}</el-descriptions-item> <span class="mobile-info-row__label">杂质率</span>
<el-descriptions-item label="创建时间" :span="2">{{ <span class="mobile-info-row__value">{{ weighDetail?.impurityRate != null ? (weighDetail.impurityRate * 100).toFixed(2) + '%' : '-' }}</span>
weighDetail?.createTime ? formatDate(weighDetail.createTime) : '-' </div>
}}</el-descriptions-item> <div class="mobile-info-row">
</el-descriptions> <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"> <div class="mobile-form__section" v-if="hasInspectionData">
<el-divider content-position="left">检验信息</el-divider> <div class="mobile-form__section-title">检验信息</div>
<el-descriptions :column="2" border> <div class="mobile-info-list">
<el-descriptions-item label="取样日期">{{ <div class="mobile-info-row">
weighDetail?.sampleDate ? formatDate(weighDetail.sampleDate) : '-' <span class="mobile-info-row__label">取样日期</span>
}}</el-descriptions-item> <span class="mobile-info-row__value">{{ weighDetail?.sampleDate ? formatDate(weighDetail.sampleDate) : '-' }}</span>
<el-descriptions-item label="取样地点">{{ </div>
weighDetail?.sampleLocation || '-' <div class="mobile-info-row">
}}</el-descriptions-item> <span class="mobile-info-row__label">取样地点</span>
<el-descriptions-item label="代表重量">{{ <span class="mobile-info-row__value">{{ weighDetail?.sampleLocation || '-' }}</span>
weighDetail?.representativeWeight != null ? weighDetail.representativeWeight + ' kg' : '-' </div>
}}</el-descriptions-item> <div class="mobile-info-row">
<el-descriptions-item label="样品重量">{{ <span class="mobile-info-row__label">代表重量</span>
weighDetail?.sampleWeight != null ? weighDetail.sampleWeight + ' kg' : '-' <span class="mobile-info-row__value">{{ weighDetail?.representativeWeight != null ? weighDetail.representativeWeight + ' kg' : '-' }}</span>
}}</el-descriptions-item> </div>
<el-descriptions-item label="杂质重量">{{ <div class="mobile-info-row">
weighDetail?.impurityWeight != null ? weighDetail.impurityWeight + ' kg' : '-' <span class="mobile-info-row__label">样品重量</span>
}}</el-descriptions-item> <span class="mobile-info-row__value">{{ weighDetail?.sampleWeight != null ? weighDetail.sampleWeight + ' kg' : '-' }}</span>
<el-descriptions-item label="沙土重量">{{ </div>
weighDetail?.sandWeight != null ? weighDetail.sandWeight + ' kg' : '-' <div class="mobile-info-row">
}}</el-descriptions-item> <span class="mobile-info-row__label">杂质重量</span>
<el-descriptions-item label="感官">{{ <span class="mobile-info-row__value">{{ weighDetail?.impurityWeight != null ? weighDetail.impurityWeight + ' kg' : '-' }}</span>
weighDetail?.sensoryEvaluation || '-' </div>
}}</el-descriptions-item> <div class="mobile-info-row">
<el-descriptions-item label="合格率">{{ <span class="mobile-info-row__label">沙土重量</span>
weighDetail?.qualificationRate ? weighDetail.qualificationRate + '%' : '-' <span class="mobile-info-row__value">{{ weighDetail?.sandWeight != null ? weighDetail.sandWeight + ' kg' : '-' }}</span>
}}</el-descriptions-item> </div>
<el-descriptions-item label="水分">{{ <div class="mobile-info-row">
weighDetail?.moistureContent ? weighDetail.moistureContent + '%' : '-' <span class="mobile-info-row__label">感官</span>
}}</el-descriptions-item> <span class="mobile-info-row__value">{{ weighDetail?.sensoryEvaluation || '-' }}</span>
<el-descriptions-item label="含糖">{{ </div>
weighDetail?.sugarContent ? weighDetail.sugarContent + '%' : '-' <div class="mobile-info-row">
}}</el-descriptions-item> <span class="mobile-info-row__label">合格率</span>
<el-descriptions-item label="RA值">{{ <span class="mobile-info-row__value">{{ weighDetail?.qualificationRate ? weighDetail.qualificationRate + '%' : '-' }}</span>
weighDetail?.raValue || '-' </div>
}}</el-descriptions-item> <div class="mobile-info-row">
<el-descriptions-item label="STV值">{{ <span class="mobile-info-row__label">水分</span>
weighDetail?.stvValue || '-' <span class="mobile-info-row__value">{{ weighDetail?.moistureContent ? weighDetail.moistureContent + '%' : '-' }}</span>
}}</el-descriptions-item> </div>
<el-descriptions-item label="化验员">{{ <div class="mobile-info-row">
weighDetail?.labTechnician || '-' <span class="mobile-info-row__label">含糖</span>
}}</el-descriptions-item> <span class="mobile-info-row__value">{{ weighDetail?.sugarContent ? weighDetail.sugarContent + '%' : '-' }}</span>
<el-descriptions-item label="检验备注" :span="2">{{ </div>
weighDetail?.inspectionRemark || '-' <div class="mobile-info-row">
}}</el-descriptions-item> <span class="mobile-info-row__label">RA值</span>
</el-descriptions> <span class="mobile-info-row__value">{{ weighDetail?.raValue || '-' }}</span>
</template> </div>
</Dialog> <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> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -246,3 +343,115 @@ defineExpose({
open open
}) })
</script> </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> <template>
<el-dialog <el-drawer
:title="formType === 'create' ? '新增工单' : '编辑工单'"
v-model="visible" v-model="visible"
:width="isMobile ? '100%' : '900px'" :title="formType === 'create' ? '新增工单' : '编辑工单'"
:fullscreen="isMobile" direction="rtl"
@close="handleClose" size="100%"
:close-on-click-modal="false" :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"> <div class="mobile-form" v-loading="formLoading">
<el-tabs v-model="activeTab"> <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-form-item label="产品" prop="productId">
<el-select <el-select
v-model="form.productId" v-model="form.productId"
placeholder="请选择产品" placeholder="请选择产品"
:class="isMobile ? '!w-full' : '!w-240px'" style="width: 100%"
@change="handleProductChange" @change="handleProductChange"
> >
<el-option <el-option
@@ -42,7 +50,7 @@
<el-select <el-select
v-model="form.routeId" v-model="form.routeId"
placeholder="请选择工序路线" placeholder="请选择工序路线"
:class="isMobile ? '!w-full' : '!w-240px'" style="width: 100%"
@change="handleRouteChange" @change="handleRouteChange"
> >
<el-option <el-option
@@ -63,7 +71,8 @@
:precision="2" :precision="2"
:step="0.1" :step="0.1"
placeholder="请输入计划数量" placeholder="请输入计划数量"
:class="isMobile ? '!w-full' : ''" controls-position="right"
style="width: 100%"
/> />
</el-form-item> </el-form-item>
<!-- <el-form-item label="优先级" prop="priority">--> <!-- <el-form-item label="优先级" prop="priority">-->
@@ -78,7 +87,7 @@
<el-form-item label="计划时间" prop="planTime"> <el-form-item label="计划时间" prop="planTime">
<el-date-picker <el-date-picker
v-model="form.planTime" v-model="form.planTime"
:type="isMobile ? 'datetimerange' : 'datetimerange'" type="datetimerange"
start-placeholder="开始时间" start-placeholder="开始时间"
end-placeholder="结束时间" end-placeholder="结束时间"
:default-time="[ :default-time="[
@@ -97,35 +106,52 @@
) )
) )
]" ]"
:class="isMobile ? '!w-full' : '!w-380px'" style="width: 100%"
/> />
</el-form-item> </el-form-item>
<el-form-item label="备注" prop="remark"> <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-form-item>
</el-tab-pane> </div>
<!-- 工序信息 --> <!-- 工序信息 -->
<el-tab-pane label="工序信息" name="process"> <div class="mobile-form__section">
<el-table :data="form.operations" border> <div class="mobile-form__section-title">工序信息</div>
<el-table-column type="index" label="序号" width="60" align="center" /> <div class="mobile-item-list">
<el-table-column label="工序编码" prop="operationCode" min-width="120" align="center" /> <div
<el-table-column label="工序名称" prop="operationName" min-width="120" align="center" /> v-for="(op, index) in form.operations"
<el-table-column :key="index"
label="工时(分钟)" class="mobile-item-card"
prop="requiredTime" >
min-width="120" <div class="mobile-item-card__header">
align="center" <span class="mobile-item-card__index">#{{ index + 1 }}</span>
/> <span class="mobile-item-card__name">{{ op.operationName || '未知工序' }}</span>
</el-table> </div>
</el-tab-pane> <div class="mobile-item-card__body">
</el-tabs> <div class="mobile-item-card__info-row">
</el-form> <span class="mobile-item-card__info-label">工序编码</span>
<template #footer> <span class="mobile-item-card__info-value">{{ op.operationCode || '-' }}</span>
<el-button @click="visible = false">取消</el-button> </div>
<el-button type="primary" :loading="loading" @click="submitForm">确定</el-button> <div class="mobile-item-card__info-row">
</template> <span class="mobile-item-card__info-label">工时(分钟)</span>
</el-dialog> <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> </template>
<script setup lang="ts"> <script setup lang="ts">
@@ -408,8 +434,98 @@ const handleClose = () => {
defineExpose({ open }) defineExpose({ open })
</script> </script>
<style scoped> <style lang="scss" scoped>
.el-tabs :deep(.el-tabs__content) { .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; padding: 20px 0;
font-size: 14px;
} }
</style> </style>

View File

@@ -1,128 +1,126 @@
<template> <template>
<el-dialog title="工序执行情况" v-model="visible" width="1200px" v-loading="loading"> <el-drawer
<!-- 工序进度统计 --> v-model="visible"
<OperationProgressCard :progressData="progressData" /> 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"> <div class="mobile-form__section">
<el-table-column type="index" label="序号" width="60" align="center" /> <div class="mobile-form__section-title">工序列表</div>
<el-table-column label="工序信息" min-width="180"> <div class="mobile-item-list" v-loading="tableLoading">
<template #default="{ row }"> <div
<div>{{ row.operationName }}</div> v-for="(row, index) in operationList"
<div class="text-gray-400 text-sm">{{ row.operationCode }}</div> :key="row.id"
</template> class="mobile-item-card"
</el-table-column> >
<el-table-column label="生产数量" min-width="180"> <div class="mobile-item-card__header">
<template #default="{ row }"> <span class="mobile-item-card__index">#{{ index + 1 }}</span>
<div>计划{{ row.planQuantity }}</div> <span class="mobile-item-card__name">{{ row.operationName || '未知工序' }}</span>
<div class="mt-1">完成{{ row.completedQuantity }}</div> <el-tag :type="getStatusType(row.status)" size="small">{{ getStatusText(row.status) }}</el-tag>
<div class="mt-1"> </div>
<span class="text-success">合格{{ row.qualifiedQuantity }}</span> <div class="mobile-item-card__body">
<span class="text-danger ml-2">不合格{{ row.unqualifiedQuantity }}</span> <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> </div>
</template> <div v-if="operationList.length === 0 && !tableLoading" class="mobile-empty-tip">
</el-table-column> 暂无工序数据
<el-table-column label="操作人" min-width="120"> </div>
<template #default="{ row }"> </div>
<div>{{ getOperationExecution(row.id)?.workerName || '-' }}</div> </div>
</template> </div>
</el-table-column> </el-drawer>
<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>
<!-- 开始工序弹窗 --> <!-- 开始工序弹窗 -->
<OperationStartDialog <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"> <el-form ref="completeFormRef" :model="completeForm" :rules="completeRules" label-width="120px">
<!-- 动态工序字段 --> <!-- 动态工序字段 -->
<template v-if="Object.keys(processFields).length > 0"> <template v-if="Object.keys(processFields).length > 0">
@@ -1097,7 +1095,88 @@ onMounted(() => {
defineExpose({ open }) defineExpose({ open })
</script> </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 { .text-lg {
font-size: 16px; font-size: 16px;
} }