feat:产品管理-产品物料信息-产品分类时添加关联设备、关联模具字段

pull/1/head
黄伟杰 2 months ago
parent b8dc35bc23
commit 4863831a0c

@ -20,6 +20,12 @@ export interface ProductVO {
purchasePrice: number // 采购价格,单位:元
salePrice: number // 销售价格,单位:元
minPrice: number // 最低价格,单位:元
deviceIds?: string // 关联设备ID列表
moldIds?: string // 关联模具ID列表
devices?: { id: number; name: string }[] // 关联设备列表
molds?: { id: number; name: string }[] // 关联模具列表
deviceList?: any[]
moldList?: any[]
}
// ERP 产品 API

@ -0,0 +1,146 @@
<template>
<Dialog :title="title" v-model="dialogVisible" :appendToBody="true" width="1080">
<ContentWrap>
<el-table
ref="tableRef"
v-loading="loading"
:data="list"
:row-key="resolveRowKey"
:show-overflow-tooltip="true"
:stripe="true"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" reserve-selection />
<el-table-column
v-for="column in columns"
:key="column.prop"
:label="column.label"
:prop="column.prop"
:width="column.width"
:min-width="column.minWidth"
:align="column.align || 'center'"
/>
</el-table>
<Pagination
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
:total="total"
@pagination="getList"
/>
</ContentWrap>
<template #footer>
<el-button type="primary" @click="submitSelection"> </el-button>
<el-button @click="dialogVisible = false"> </el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import type { ElTable } from 'element-plus'
defineOptions({ name: 'TableSelectDialog' })
type TableColumn = {
label: string
prop: string
width?: string | number
minWidth?: string | number
align?: 'left' | 'center' | 'right'
}
const props = withDefaults(
defineProps<{
title: string
columns: TableColumn[]
fetchApi: (params: Record<string, any>) => Promise<{ list: any[]; total: number }>
rowKey?: string
pageSize?: number
initialRows?: any[]
}>(),
{
rowKey: 'id',
pageSize: 10,
initialRows: () => []
}
)
const emit = defineEmits<{
(e: 'confirm', value: { ids: (number | string)[]; rows: any[] }): void
}>()
const dialogVisible = ref(false)
const loading = ref(false)
const list = ref<any[]>([])
const total = ref(0)
const tableRef = ref<InstanceType<typeof ElTable>>()
const queryParams = reactive({
pageNo: 1,
pageSize: props.pageSize
})
const selectedMap = ref(new Map<number | string, any>())
const syncingSelection = ref(false)
const resolveRowKey = (row: Record<string, any>) => row[props.rowKey]
const refreshSelectionOnTable = async () => {
await nextTick()
const table = tableRef.value
if (!table) return
syncingSelection.value = true
try {
table.clearSelection()
const selectedIds = selectedMap.value
list.value.forEach((row) => {
const rowId = resolveRowKey(row)
if (selectedIds.has(rowId)) {
table.toggleRowSelection(row, true)
}
})
} finally {
syncingSelection.value = false
}
}
const getList = async () => {
loading.value = true
try {
const data = await props.fetchApi(queryParams)
list.value = data.list || []
total.value = data.total || 0
await refreshSelectionOnTable()
} finally {
loading.value = false
}
}
const handleSelectionChange = (rows: any[]) => {
if (syncingSelection.value) return
const pageIdSet = new Set(list.value.map((item) => resolveRowKey(item)))
pageIdSet.forEach((id) => {
selectedMap.value.delete(id)
})
rows.forEach((row) => {
selectedMap.value.set(resolveRowKey(row), row)
})
}
const open = async (rows?: any[]) => {
selectedMap.value.clear()
;(rows || props.initialRows).forEach((row) => {
selectedMap.value.set(resolveRowKey(row), row)
})
queryParams.pageNo = 1
queryParams.pageSize = props.pageSize
dialogVisible.value = true
await getList()
}
const submitSelection = () => {
const rows = Array.from(selectedMap.value.values())
const ids = rows.map((row) => resolveRowKey(row))
emit('confirm', { ids, rows })
dialogVisible.value = false
}
defineExpose({ open })
</script>

@ -133,6 +133,26 @@
</el-radio-group>
</el-form-item>
</el-col>
<el-col v-if="isProductCategory" :span="12">
<el-form-item label="关联设备" prop="devices">
<el-input
:model-value="deviceDisplayText"
placeholder="点击选择设备"
readonly
@click="openDeviceSelectDialog"
/>
</el-form-item>
</el-col>
<el-col v-if="isProductCategory" :span="12">
<el-form-item label="关联模具" prop="molds">
<el-input
:model-value="moldDisplayText"
placeholder="点击选择模具"
readonly
@click="openMoldSelectDialog"
/>
</el-form-item>
</el-col>
<el-col v-if="formType === 'update'" :span="24">
<el-form-item :label="t('FactoryModeling.ProductInformation.qrcode')" prop="qrcodeUrl">
<QrcodeActionCard
@ -159,12 +179,31 @@
<el-button @click="dialogVisible = false">{{ t('common.cancel') }}</el-button>
</template>
</Dialog>
<TableSelectDialog
ref="deviceSelectDialogRef"
title="选择设备"
:columns="deviceColumns"
:fetch-api="DeviceLedgerApi.getDeviceLedgerPage"
row-key="id"
@confirm="handleDeviceSelectConfirm"
/>
<TableSelectDialog
ref="moldSelectDialogRef"
title="选择模具"
:columns="moldColumns"
:fetch-api="MoldBrandApi.getMoldPage"
row-key="id"
@confirm="handleMoldSelectConfirm"
/>
</template>
<script setup lang="ts">
import { ProductApi, ProductVO } from '@/api/erp/product/product'
import { ProductCategoryApi, ProductCategoryVO } from '@/api/erp/product/category'
import { ProductUnitApi, ProductUnitVO } from '@/api/erp/product/unit'
import QrcodeActionCard from '@/components/QrcodeActionCard/index.vue'
import TableSelectDialog from '@/components/TableSelectDialog/TableSelectDialog.vue'
import { DeviceLedgerApi } from '@/api/mes/deviceledger'
import { MoldBrandApi } from '@/api/erp/mold'
import { CommonStatusEnum } from '@/utils/constants'
import { defaultProps, handleTree } from '@/utils/tree'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
@ -179,6 +218,8 @@ const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const deviceSelectDialogRef = ref()
const moldSelectDialogRef = ref()
const formData = ref({
id: undefined,
name: undefined,
@ -195,8 +236,156 @@ const formData = ref({
purchasePrice: undefined,
salePrice: undefined,
minPrice: undefined,
safetyNumber: undefined
safetyNumber: undefined,
devices: [] as { id: number; name: string }[],
molds: [] as { id: number; name: string }[]
})
const selectedDeviceRows = ref<any[]>([])
const selectedMoldRows = ref<any[]>([])
const findCategoryNameById = (id: number | undefined, tree: any[]): string => {
if (!id || !Array.isArray(tree) || !tree.length) return ''
const queue = [...tree]
while (queue.length) {
const node = queue.shift()
if (!node) continue
if (Number(node.id) === Number(id)) {
return String(node.name || node.label || '')
}
if (Array.isArray(node.children) && node.children.length) {
queue.push(...node.children)
}
}
return ''
}
const selectedCategoryName = computed(() => findCategoryNameById(formData.value.categoryId as any, categoryList.value))
const isProductCategory = computed(() => selectedCategoryName.value === '产品')
const getRelationName = (item: Record<string, any>, nameKeys: string[], fallbackPrefix: string, id: number) => {
for (const key of nameKeys) {
const value = item[key]
if (value !== undefined && value !== null && String(value).trim() !== '') {
return String(value)
}
}
return `${fallbackPrefix}ID:${id}`
}
const normalizeRelationList = (
value: unknown,
fallbackRows: any[] | undefined,
nameKeys: string[],
fallbackPrefix: string
): { id: number; name: string }[] => {
const fallbackMap = new Map<number, string>()
;(fallbackRows || []).forEach((row) => {
const id = Number(row?.id)
if (!Number.isFinite(id)) return
fallbackMap.set(id, getRelationName(row, nameKeys, fallbackPrefix, id))
})
const parseArray = (source: unknown): any[] => {
if (Array.isArray(source)) return source
if (typeof source === 'string') {
const content = source.trim()
if (!content) return []
try {
const parsed = JSON.parse(content.startsWith('[') ? content : `[${content}]`)
return Array.isArray(parsed) ? parsed : []
} catch {
return content.split(',').map((item) => item.trim()).filter(Boolean)
}
}
return []
}
return parseArray(value)
.map((item) => {
if (typeof item === 'object' && item !== null) {
const id = Number((item as any).id)
if (!Number.isFinite(id)) return undefined
return {
id,
name: getRelationName(item as any, nameKeys, fallbackPrefix, id)
}
}
const id = Number(item)
if (!Number.isFinite(id)) return undefined
return {
id,
name: fallbackMap.get(id) || `${fallbackPrefix}ID:${id}`
}
})
.filter((item): item is { id: number; name: string } => Boolean(item))
}
const toDeviceRows = (devices: { id: number; name: string }[]) => {
return devices.map((item) => ({
id: item.id,
deviceName: item.name,
name: item.name
}))
}
const toMoldRows = (molds: { id: number; name: string }[]) => {
return molds.map((item) => ({
id: item.id,
name: item.name
}))
}
const deviceDisplayText = computed(() => {
if (!formData.value.devices.length) return ''
if (!selectedDeviceRows.value.length) return formData.value.devices.map((item) => item.name).join('、')
return selectedDeviceRows.value.map((item) => item.deviceName || item.name || item.code || `ID:${item.id}`).join('、')
})
const moldDisplayText = computed(() => {
if (!formData.value.molds.length) return ''
if (!selectedMoldRows.value.length) return formData.value.molds.map((item) => item.name).join('、')
return selectedMoldRows.value.map((item) => item.name || item.code || `ID:${item.id}`).join('、')
})
const deviceColumns = [
{ label: '设备编号', prop: 'deviceCode', minWidth: 140 },
{ label: '设备名称', prop: 'deviceName', minWidth: 160 },
{ label: '设备型号', prop: 'deviceModel', minWidth: 140 },
{ label: '所属车间', prop: 'workshop', minWidth: 140 }
]
const moldColumns = [
{ label: '模具编码', prop: 'code', minWidth: 140 },
{ label: '模具名称', prop: 'name', minWidth: 160 },
{ label: '模具型号', prop: 'brandName', minWidth: 140 },
{ label: '状态', prop: 'status', minWidth: 100 }
]
const openDeviceSelectDialog = () => {
const rows = selectedDeviceRows.value.length
? selectedDeviceRows.value.map((item) => ({ ...item, id: Number(item.id) }))
: toDeviceRows(formData.value.devices)
deviceSelectDialogRef.value?.open(rows)
}
const openMoldSelectDialog = () => {
const rows = selectedMoldRows.value.length
? selectedMoldRows.value.map((item) => ({ ...item, id: Number(item.id) }))
: toMoldRows(formData.value.molds)
moldSelectDialogRef.value?.open(rows)
}
const handleDeviceSelectConfirm = (payload: { ids: (number | string)[]; rows: any[] }) => {
formData.value.devices = payload.rows
.map((item) => {
const id = Number(item.id)
if (!Number.isFinite(id)) return undefined
return {
id,
name: item.deviceName || item.name || item.code || `设备ID:${id}`
}
})
.filter((item): item is { id: number; name: string } => Boolean(item))
selectedDeviceRows.value = payload.rows
}
const handleMoldSelectConfirm = (payload: { ids: (number | string)[]; rows: any[] }) => {
formData.value.molds = payload.rows
.map((item) => {
const id = Number(item.id)
if (!Number.isFinite(id)) return undefined
return {
id,
name: item.name || item.code || `模具ID:${id}`
}
})
.filter((item): item is { id: number; name: string } => Boolean(item))
selectedMoldRows.value = payload.rows
}
const validateBarCode = (_rule, value, callback) => {
if (Boolean(formData.value.isCode)) {
callback()
@ -225,20 +414,38 @@ const open = async (type: string, id?: number) => {
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
const categoryData = await ProductCategoryApi.getProductCategorySimpleList()
categoryList.value = handleTree(categoryData, 'id', 'parentId')
unitList.value = await ProductUnitApi.getProductUnitSimpleList()
//
if (id) {
formLoading.value = true
try {
formData.value = await ProductApi.getProduct(id)
const productData = await ProductApi.getProduct(id)
const devices = normalizeRelationList(
(productData as any).devices ?? (productData as any).deviceIds,
(productData as any).deviceList,
['name', 'deviceName', 'code'],
'设备'
)
const molds = normalizeRelationList(
(productData as any).molds ?? (productData as any).moldIds,
(productData as any).moldList,
['name', 'code'],
'模具'
)
formData.value = {
...formData.value,
...productData,
devices,
molds
}
selectedDeviceRows.value = toDeviceRows(devices)
selectedMoldRows.value = toMoldRows(molds)
} finally {
formLoading.value = false
}
}
//
const categoryData = await ProductCategoryApi.getProductCategorySimpleList()
categoryList.value = handleTree(categoryData, 'id', 'parentId')
//
unitList.value = await ProductUnitApi.getProductUnitSimpleList()
}
defineExpose({ open }) // open
@ -265,6 +472,10 @@ const handleQrcodeRefreshSuccess = async (data: any) => {
formData.value.barCode = productData?.barCode ?? formData.value.barCode
}
const buildRelationIdListString = (list: { id: number; name: string }[]) => {
return list.map((item) => Number(item.id)).filter((id) => Number.isFinite(id))
}
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
@ -273,7 +484,15 @@ const submitForm = async () => {
//
formLoading.value = true
try {
const data = formData.value as unknown as ProductVO
const relationDevices = isProductCategory.value ? formData.value.devices : []
const relationMolds = isProductCategory.value ? formData.value.molds : []
const data = {
...formData.value,
deviceIds: buildRelationIdListString(relationDevices),
moldIds: buildRelationIdListString(relationMolds)
} as unknown as ProductVO
delete (data as any).devices
delete (data as any).molds
if (formType.value === 'create') {
await ProductApi.createProduct(data)
message.success(t('common.createSuccess'))
@ -306,9 +525,25 @@ const resetForm = () => {
weight: undefined,
purchasePrice: undefined,
salePrice: undefined,
minPrice: undefined
minPrice: undefined,
devices: [],
molds: []
}
selectedDeviceRows.value = []
selectedMoldRows.value = []
formRef.value?.resetFields()
}
watch(
() => [formData.value.categoryId, selectedCategoryName.value, categoryList.value.length],
() => {
if (!formData.value.categoryId || !categoryList.value.length) return
if (selectedCategoryName.value === '产品') return
formData.value.devices = []
formData.value.molds = []
selectedDeviceRows.value = []
selectedMoldRows.value = []
}
)
</script>
<style scoped lang="scss"></style>

Loading…
Cancel
Save