feat(mes): 添加物料选择弹窗组件,优化物料编码生成逻辑

新增物料选择弹窗组件,支持物料分类树和物料列表的搜索与选择功能。优化物料编码生成方法,使用自动编码规则生成物料编码,提升用户体验。
pull/871/MERGE
YunaiV 2026-03-28 09:14:55 +08:00
parent 856e8f6a76
commit 3860970490
5 changed files with 217 additions and 17 deletions

View File

@ -37,6 +37,7 @@
</el-col>
<el-col :span="8">
<el-form-item label="物料分类" prop="itemTypeId">
<!-- TODO @AI /Users/yunai/Java/yudao-all-in-one/yudao-ui-admin-vue3/src/views/mes/md/item/type 增加一个物料的 select注意只允许选择子节点 -->
<el-tree-select
v-model="formData.itemTypeId"
:data="itemTypeTree"
@ -48,6 +49,7 @@
/>
</el-form-item>
</el-col>
<!-- TODO @AI1新建时默认为禁用后端设置2这里只负责展示后端的 save vo 不要接收该参数 -->
<el-col :span="8">
<el-form-item label="状态" prop="status">
<el-radio-group v-model="formData.status">
@ -82,7 +84,7 @@
v-model="formData.minStock"
placeholder="请输入最低库存量"
:min="0"
:precision="4"
:precision="2"
class="!w-1/1"
/>
</el-form-item>
@ -93,7 +95,7 @@
v-model="formData.maxStock"
placeholder="请输入最高库存量"
:min="0"
:precision="4"
:precision="2"
class="!w-1/1"
/>
</el-form-item>
@ -113,7 +115,7 @@
<el-tab-pane label="批次属性" name="batch" lazy v-if="formData.batchFlag">
<MdItemBatchConfigForm :itemId="formData.id!" :itemOrProduct="currentItemOrProduct" />
</el-tab-pane>
<!-- TODO @芋艿替代品等替代品模块实现后对接 -->
<!-- TODO @芋艿对齐替代品目前没这个可忽略 -->
<el-tab-pane label="替代品" name="substitute" lazy>
<el-empty description="替代品(待实现)" />
</el-tab-pane>
@ -132,9 +134,9 @@
</template>
<script setup lang="ts">
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
import { generateRandomStr } from '@/utils'
import { MdItemApi, MdItemVO } from '@/api/mes/md/item'
import { MdItemTypeApi, MdItemTypeVO } from '@/api/mes/md/item/type'
import { AutoCodeRecordApi } from '@/api/mes/md/autocode/record'
import MdItemBatchConfigForm from './MdItemBatchConfigForm.vue'
import MdProductBomForm from './MdProductBomForm.vue'
import MdProductSopForm from './MdProductSopForm.vue'
@ -142,6 +144,7 @@ import MdProductSipForm from './MdProductSipForm.vue'
import MdUnitMeasureSelect from '@/views/mes/md/unitmeasure/components/MdUnitMeasureSelect.vue'
import { CommonStatusEnum } from '@/utils/constants'
import { defaultProps, handleTree } from '@/utils/tree'
import { MesAutoCodeRuleCode } from '@/views/mes/utils/constants'
/** MES 物料产品 表单 */
defineOptions({ name: 'MdItemForm' })
@ -150,6 +153,7 @@ const { t } = useI18n() // 国际化
const message = useMessage() //
const dialogVisible = ref(false) //
// TODO @AI使 compute ///
const dialogTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
@ -161,7 +165,7 @@ const formData = ref({
specification: undefined,
unitMeasureId: undefined,
itemTypeId: undefined,
status: CommonStatusEnum.ENABLE,
status: CommonStatusEnum.DISABLE,
safeStockFlag: false,
minStock: 0,
maxStock: 0,
@ -190,9 +194,8 @@ const currentItemOrProduct = computed(() => {
})
/** 生成物料编码 */
const generateCode = () => {
// TODO @
formData.value.code = 'IF' + generateRandomStr(12)
const generateCode = async () => {
formData.value.code = await AutoCodeRecordApi.generateAutoCode(MesAutoCodeRuleCode.ITEM_CODE)
}
/** 打开弹窗 */
@ -233,6 +236,7 @@ const submitForm = async () => {
await MdItemApi.updateItem(data)
message.success(t('common.updateSuccess'))
}
// TODO @AI
dialogVisible.value = false
//
emit('success')
@ -250,7 +254,7 @@ const resetForm = () => {
specification: undefined,
unitMeasureId: undefined,
itemTypeId: undefined,
status: CommonStatusEnum.ENABLE,
status: CommonStatusEnum.DISABLE,
safeStockFlag: false,
minStock: 0,
maxStock: 0,

View File

@ -66,7 +66,7 @@
</Dialog>
<!-- 物料选择弹窗 -->
<ItemProductSelect ref="itemSelectRef" @selected="handleItemSelected" />
<MdItemSelectDialog ref="itemSelectRef" @selected="handleItemSelected" />
</div>
</template>
@ -74,7 +74,7 @@
import { MdProductBomApi, MdProductBomVO } from '@/api/mes/md/item/productBom'
import { MdItemVO } from '@/api/mes/md/item'
import { getItemOrProductLabel } from '@/views/mes/utils/constants'
import ItemProductSelect from '@/views/mes/md/components/ItemProductSelect.vue'
import MdItemSelectDialog from '@/views/mes/md/item/components/MdItemSelectDialog.vue'
defineOptions({ name: 'MdProductBomForm' })

View File

@ -0,0 +1,197 @@
<!-- MES 物料产品 弹窗选择器 -->
<template>
<Dialog title="物料产品选择" v-model="dialogVisible" width="80%">
<el-row :gutter="20">
<!-- 左侧分类树 -->
<el-col :span="5">
<el-input
v-model="filterText"
placeholder="搜索分类"
clearable
class="mb-12px"
:prefix-icon="iconSearch"
/>
<el-tree
ref="treeRef"
:data="itemTypeTree"
:props="treeProps"
:expand-on-click-node="false"
:filter-node-method="filterNode"
default-expand-all
highlight-current
@node-click="handleNodeClick"
/>
</el-col>
<!-- 右侧物料表格 -->
<el-col :span="19">
<!-- 搜索表单 -->
<el-form :inline="true" :model="queryParams" class="mb-10px" label-width="80px">
<el-form-item label="物料编码">
<el-input
v-model="queryParams.code"
placeholder="请输入物料编码"
clearable
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item label="物料名称">
<el-input
v-model="queryParams.name"
placeholder="请输入物料名称"
clearable
@keyup.enter="handleQuery"
/>
</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-form-item>
</el-form>
<!-- 数据表格 -->
<el-table
v-loading="loading"
:data="list"
:stripe="true"
:show-overflow-tooltip="true"
border
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="50" align="center" />
<el-table-column label="物料编码" align="center" prop="code" width="120" />
<el-table-column label="物料名称" align="center" prop="name" min-width="120" />
<el-table-column label="规格型号" align="center" prop="specification" />
<el-table-column label="单位" align="center" prop="unitMeasureName" width="80" />
<el-table-column label="物料/产品" align="center" prop="itemOrProduct" width="100">
<template #default="scope">
{{ getItemOrProductLabel(scope.row.itemOrProduct) }}
</template>
</el-table-column>
<el-table-column label="所属分类" align="center" prop="itemTypeName" width="120" />
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</el-col>
</el-row>
<template #footer>
<el-button type="primary" @click="confirmSelect"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { MdItemApi, MdItemVO } from '@/api/mes/md/item'
import { MdItemTypeApi, MdItemTypeVO } from '@/api/mes/md/item/type'
import { handleTree } from '@/utils/tree'
import { getItemOrProductLabel } from '@/views/mes/utils/constants'
import { Search as iconSearch } from '@element-plus/icons-vue'
defineOptions({ name: 'MdItemSelectDialog' })
const message = useMessage() //
const emit = defineEmits<{
selected: [rows: MdItemVO[]] //
}>()
const dialogVisible = ref(false) //
const loading = ref(false) //
const list = ref<MdItemVO[]>([]) //
const total = ref(0) //
const selectedRows = ref<MdItemVO[]>([]) //
// ==================== ====================
const treeRef = ref() // Ref
const filterText = ref('') //
const itemTypeTree = ref<MdItemTypeVO[]>([]) //
const treeProps = { children: 'children', label: 'name' } //
/** 过滤树节点 */
const filterNode = (value: string, data: MdItemTypeVO) => {
if (!value) return true
return data.name?.includes(value)
}
/** 监听筛选文本变化 */
watch(filterText, (val) => {
treeRef.value?.filter(val)
})
/** 点击树节点 */
const handleNodeClick = (data: MdItemTypeVO) => {
queryParams.itemTypeId = data.id
handleQuery()
}
// ==================== ====================
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
code: undefined as string | undefined,
name: undefined as string | undefined,
itemTypeId: undefined as number | undefined
})
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await MdItemApi.getItemPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置 */
const resetQuery = () => {
queryParams.code = undefined
queryParams.name = undefined
queryParams.itemTypeId = undefined
handleQuery()
}
/** 选中变化 */
const handleSelectionChange = (rows: MdItemVO[]) => {
selectedRows.value = rows
}
/** 确认选择 */
const confirmSelect = () => {
if (selectedRows.value.length === 0) {
message.warning('请至少选择一条数据')
return
}
emit('selected', selectedRows.value)
dialogVisible.value = false
}
// ==================== ====================
/** 打开弹窗 */
const open = async () => {
dialogVisible.value = true
selectedRows.value = []
//
const typeList = await MdItemTypeApi.getItemTypeSimpleList()
itemTypeTree.value = handleTree(typeList)
//
await getList()
}
defineExpose({ open }) // open
</script>

View File

@ -89,6 +89,7 @@
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<!-- TODO @AI增加点击进入详情formType = detail 这种 -->
<el-table-column label="物料编码" align="center" prop="code" />
<el-table-column label="物料名称" align="center" prop="name" />
<el-table-column label="规格型号" align="center" prop="specification" />
@ -104,6 +105,7 @@
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.safeStockFlag" />
</template>
</el-table-column>
<!-- TODO @AIstatus 改成 switch 单独一个开关 -->
<el-table-column label="状态" align="center" prop="status">
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
@ -134,6 +136,7 @@
>
删除
</el-button>
<!-- TODO @芋艿HiPrint标签打印 -->
<el-button
link
type="primary"
@ -253,12 +256,7 @@ const handleImport = () => {
/** 查看物料条码 */
const barcodeDetailRef = ref()
const handleBarcode = async (row: MdItemVO) => {
await barcodeDetailRef.value.openByBusiness(
row.id,
BarcodeBizTypeEnum.ITEM,
row.code,
row.name
)
await barcodeDetailRef.value.openByBusiness(row.id, BarcodeBizTypeEnum.ITEM, row.code, row.name)
}
/** 导出按钮操作 */

View File

@ -413,6 +413,7 @@ export const MesAutoCodePaddedMethodEnum = {
/** MES 自动编码规则 Code 枚举 */
export const MesAutoCodeRuleCode = {
ITEM_CODE: 'MD_ITEM_CODE', // 物料编码
SN_CODE: 'WM_SN_CODE', // SN 码
PACKAGE_CODE: 'WM_PACKAGE_CODE', // 装箱单编码
BATCH_CODE: 'WM_BATCH_CODE', // 批次编码