样品库存和出入库展示

besure_bit
ck-chenkang 3 weeks ago
parent 9a589d531a
commit ff0b41820e

@ -4,7 +4,9 @@ export interface BitWmsInboundReqVO {
productId: number
warehouseId: number
areaId?: number
locationId?: number
count: number
unitId: number
supplierId?: number
remark?: string
}

@ -1,5 +1,12 @@
<template>
<Dialog v-model="dialogVisible" :title="dialogTitle" width="92%" :fullscreen="false" top="4vh">
<Dialog
v-model="dialogVisible"
:title="dialogTitle"
width="96%"
:fullscreen="false"
:initial-fullscreen="true"
top="2vh"
>
<div class="hiprint-preview">
<div class="hiprint-toolbar">
<div class="hiprint-paper">
@ -306,6 +313,10 @@ defineExpose({ open })
<style scoped lang="scss">
.hiprint-preview {
width: 100%;
height: calc(100vh - 84px);
display: flex;
flex-direction: column;
min-height: 0;
}
.hiprint-toolbar {
@ -313,6 +324,7 @@ defineExpose({ open })
align-items: center;
gap: 12px;
margin-bottom: 12px;
flex-shrink: 0;
}
.hiprint-paper {
@ -348,7 +360,8 @@ defineExpose({ open })
}
.hiprint-body {
height: 75vh;
flex: 1;
min-height: 0;
display: grid;
grid-template-columns: 220px 1fr 300px;
gap: 12px;

@ -202,7 +202,7 @@ const clear = () => {
<template>
<div
:class="prefixCls"
class="fixed right-0 top-[45%] h-40px w-40px cursor-pointer bg-[var(--el-color-primary)] text-center leading-40px"
class="fixed right-0 top-[45%] z-10 h-40px w-40px cursor-pointer bg-[var(--el-color-primary)] text-center leading-40px"
@click="drawer = true"
>
<Icon color="#fff" icon="ep:setting" />

@ -8,35 +8,153 @@
<script setup lang="ts">
import HiprintPreviewDialog from '@/components/HiprintPreviewDialog/index.vue'
import { ConfigApi } from '@/api/mes/printconfig'
import { defaultElementTypeProvider, hiwebSocket, hiprint } from 'vue-plugin-hiprint'
const props = withDefaults(
defineProps<{
title: string
buttonText?: string
businessScene?: string
printData?: Record<string, any>
templateJson?: Record<string, any>
loadLabel?: () => Promise<{ printData: Record<string, any>; templateJson?: Record<string, any> }>
templateJson?: Record<string, any> | string
loadLabel?: () => Promise<{ printData: Record<string, any>; templateJson?: Record<string, any> | string }>
}>(),
{
buttonText: '打印'
}
)
const message = useMessage()
const printDialogRef = ref()
const loading = ref(false)
let hiprintInited = false
const clientHostStorageKey = 'hiprint-client-host'
const defaultClientHost = 'http://192.168.2.58:17521'
const getHiwebSocket = () => {
return ((hiwebSocket as any) || (window as any).hiwebSocket) as any
}
const ensureHiprintInit = () => {
if (hiprintInited) {
return
}
hiprint.init({
host: localStorage.getItem(clientHostStorageKey) || defaultClientHost,
providers: [defaultElementTypeProvider()]
})
hiprintInited = true
}
const normalizeTemplateJson = (templateJson?: Record<string, any> | string) => {
if (!templateJson) {
return undefined
}
if (typeof templateJson !== 'string') {
return templateJson
}
try {
return JSON.parse(templateJson)
} catch (error) {
message.error('打印模板格式异常')
return undefined
}
}
const getPaperSize = (templateJson?: Record<string, any>) => {
const panel = templateJson?.panels?.[0]
if (panel?.width && panel?.height) {
return {
width: Number(panel.width),
height: Number(panel.height)
}
}
return { width: 80, height: 60 }
}
const ensureClientConnected = async () => {
ensureHiprintInit()
const socket = getHiwebSocket()
if (!socket) {
message.warning('打印客户端未初始化')
return false
}
if (socket.opened) {
return true
}
if (typeof socket.start !== 'function') {
message.warning('打印客户端未提供连接方法')
return false
}
return await new Promise<boolean>((resolve) => {
socket.stop?.()
socket.start((status: boolean) => {
if (!status) {
message.warning('未连接到本地打印客户端,请先确认 electron-hiprint 已启动')
}
resolve(Boolean(status))
})
})
}
const silentPrint = async (printData: Record<string, any>, templateJson?: Record<string, any>) => {
if (!templateJson) {
message.warning('未找到可用打印模板')
return
}
const pageRes = await ConfigApi.getConfigPage({
pageNo: 1,
pageSize: 100,
businessScenario: props.businessScene
})
const configs = (pageRes?.list || []).filter((item: any) => item.isEnabled !== false)
if (!configs.length) {
message.warning('未找到匹配业务场景「' + props.businessScene + '」的打印机配置')
return
}
const printerName = configs[0].systemPrinterName
if (!printerName) {
message.warning('匹配到的打印机配置未设置系统打印机')
return
}
const connected = await ensureClientConnected()
if (!connected) {
return
}
const hiprintTmpl = new hiprint.PrintTemplate({ template: templateJson })
hiprintTmpl.on('printSuccess', () => {
message.success('打印已发送到: ' + printerName)
})
hiprintTmpl.on('printError', (error: any) => {
message.error(error?.msg || error?.message || '打印失败')
})
try {
hiprintTmpl.print2(printData, {
printer: printerName,
title: props.title
})
} catch (error: any) {
message.error(error?.message || '打印失败')
}
}
const openPrint = async () => {
loading.value = true
try {
const label = props.loadLabel ? await props.loadLabel() : undefined
const printData = label?.printData || props.printData || {}
const templateJson = label?.templateJson || props.templateJson
const templateJson = normalizeTemplateJson(label?.templateJson || props.templateJson)
if (props.businessScene) {
await silentPrint(printData, templateJson)
return
}
printDialogRef.value?.open({
title: props.title,
printData,
templateJson,
withDefaultQrcodeLayout: !templateJson,
paperSize: { width: 80, height: 60 }
paperSize: getPaperSize(templateJson)
})
} finally {
loading.value = false

@ -10,33 +10,38 @@
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="供应商" prop="supplierId">
<el-select v-model="formData.supplierId" placeholder="请选择供应商" filterable clearable class="!w-full">
<el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" />
<el-form-item label="库位" prop="locationId">
<el-select
v-model="formData.locationId"
placeholder="请选择库位"
filterable
clearable
class="!w-full"
@change="handleLocationChange"
>
<el-option
v-for="item in filteredLocationList"
:key="item.id"
:label="formatLocationLabel(item)"
:value="item.id"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="仓库" prop="warehouseId">
<el-select v-model="formData.warehouseId" placeholder="请选择仓库" filterable class="!w-full" @change="handleWarehouseChange">
<el-option v-for="item in warehouseList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
<el-col :span="12">
<el-form-item label="数量" prop="count">
<el-input-number v-model="formData.count" :min="1" :precision="0" class="!w-full" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="库区" prop="areaId">
<el-select v-model="formData.areaId" placeholder="请选择库区" filterable clearable class="!w-full">
<el-option v-for="item in filteredAreaList" :key="item.id" :label="item.areaName" :value="item.id" />
<el-col :span="12">
<el-form-item label="单位" prop="unitId">
<el-select v-model="formData.unitId" placeholder="请选择单位" filterable class="!w-full">
<el-option v-for="item in sampleUnitOptions" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="数量" prop="count">
<el-input-number v-model="formData.count" :min="1" :precision="0" class="!w-full" />
</el-form-item>
</el-col>
</el-row>
<el-form-item label="备注" prop="remark">
<el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="请输入备注" />
@ -55,48 +60,117 @@
import { BitWmsApi } from '@/api/erp/bitwms'
import { WarehouseApi } from '@/api/erp/stock/warehouse'
import { WarehouseAreaApi } from '@/api/erp/stock/warehousearea'
import { SupplierApi } from '@/api/erp/purchase/supplier'
import { WarehouseLocationApi } from '@/api/erp/stock/warehouselocation'
defineOptions({ name: 'ErpBitWmsIn' })
const DEFAULT_WAREHOUSE_NAME = '样品仓'
const DEFAULT_AREA_NAME = '必硕三楼'
const message = useMessage()
const formRef = ref()
const submitLoading = ref(false)
const sampleList = ref<any[]>([])
const warehouseList = ref<any[]>([])
const areaList = ref<any[]>([])
const supplierList = ref<any[]>([])
const locationList = ref<any[]>([])
const formData = reactive<any>({
productId: undefined,
warehouseId: undefined,
areaId: undefined,
locationId: undefined,
count: 1,
supplierId: undefined,
unitId: undefined,
remark: undefined
})
const formRules = reactive({
productId: [{ required: true, message: '请选择样品', trigger: 'change' }],
warehouseId: [{ required: true, message: '请选择仓库', trigger: 'change' }],
count: [{ required: true, message: '请输入数量', trigger: 'blur' }]
locationId: [{ required: true, message: '请选择库位', trigger: 'change' }],
count: [{ required: true, message: '请输入数量', trigger: 'blur' }],
unitId: [{ required: true, message: '请选择单位', trigger: 'change' }]
})
const filteredAreaList = computed(() => {
if (!formData.warehouseId) return areaList.value
return areaList.value.filter((item) => item.warehouseId === formData.warehouseId)
const selectedSample = computed(() => sampleList.value.find((item) => item.id === formData.productId))
const sampleUnitOptions = computed(() => {
const sample = selectedSample.value
if (!sample?.unitId) return []
return [{ id: sample.unitId, name: sample.unitName || '默认单位' }]
})
const filteredLocationList = computed(() => {
if (!formData.warehouseId || !formData.areaId) return []
return locationList.value.filter((item) => {
const matchedWarehouse = item.warehouseId === formData.warehouseId
const matchedArea = item.areaId === formData.areaId
return matchedWarehouse && matchedArea
})
})
const formatSampleLabel = (item: any) => `${item.barCode || item.code || '-'} / ${item.name || '-'}`
const handleWarehouseChange = () => {
formData.areaId = undefined
const formatLocationLabel = (item: any) => `${item.code || '-'} / ${item.name || '-'}`
const applyDefaultWarehouseArea = () => {
const warehouse = warehouseList.value.find((item) => item.name === DEFAULT_WAREHOUSE_NAME)
if (!warehouse) {
formData.warehouseId = undefined
formData.areaId = undefined
return
}
formData.warehouseId = warehouse.id
const area = areaList.value.find(
(item) => item.warehouseId === warehouse.id && item.areaName === DEFAULT_AREA_NAME
)
formData.areaId = area?.id
}
const handleLocationChange = (locationId?: number) => {
const location = locationList.value.find((item) => item.id === locationId)
if (!location) return
formData.warehouseId = location.warehouseId
formData.areaId = location.areaId
}
const handleSampleChange = () => {
formData.unitId = selectedSample.value?.unitId
}
const resetInnerFormData = () => {
formData.productId = undefined
formData.locationId = undefined
formData.count = 1
formData.unitId = undefined
formData.remark = undefined
applyDefaultWarehouseArea()
}
const resetForm = () => {
formRef.value?.resetFields()
formData.count = 1
resetInnerFormData()
}
const buildSubmitData = () => ({
productId: formData.productId,
locationId: formData.locationId,
warehouseId: formData.warehouseId,
areaId: formData.areaId,
count: formData.count,
unitId: formData.unitId,
remark: formData.remark
})
watch(
() => formData.productId,
() => handleSampleChange()
)
watch(
() => [formData.warehouseId, formData.areaId],
() => {
if (
formData.locationId &&
!filteredLocationList.value.some((item) => item.id === formData.locationId)
) {
formData.locationId = undefined
}
}
)
const submitForm = async () => {
await formRef.value.validate()
submitLoading.value = true
try {
await BitWmsApi.inbound(formData)
await BitWmsApi.inbound(buildSubmitData())
message.success('入库成功')
resetForm()
} finally {
@ -105,15 +179,16 @@ const submitForm = async () => {
}
onMounted(async () => {
const [samples, warehouses, areas, suppliers] = await Promise.all([
const [samples, warehouses, areas, locations] = await Promise.all([
BitWmsApi.getSampleSimpleList(),
WarehouseApi.getWarehouseSimpleList(),
WarehouseAreaApi.getWarehouseAreaPage({ pageNo: 1, pageSize: 100 }),
SupplierApi.getSupplierSimpleList()
WarehouseLocationApi.getWarehouseLocationPage({ pageNo: 1, pageSize: 100 })
])
sampleList.value = samples || []
warehouseList.value = warehouses || []
areaList.value = areas.list || []
supplierList.value = suppliers || []
locationList.value = locations.list || []
resetInnerFormData()
})
</script>

@ -138,7 +138,27 @@ const resetQuery = () => {
const openForm = async (type: string, id?: number) => {
formVisible.value = true
await nextTick()
formRef.value.open(type, id)
formRef.value.open(type, id, type === 'create'
? {
defaultWarehouseName: '样品仓',
defaultAreaName: '必硕三楼',
defaultData: {
isCode: false,
allowProductMix: true,
allowBatchMix: true
},
hideAutoCodeSwitch: true,
hiddenFields: [
'areaSize',
'maxLoadWeight',
'positionX',
'positionY',
'positionZ',
'allowProductMix',
'allowBatchMix'
]
}
: undefined)
}
const loadLocationLabel = async (id: number) => await BitWmsApi.getLocationLabel(id)

@ -1,23 +1,69 @@
<template>
<ContentWrap>
<el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" label-width="auto">
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="auto"
>
<el-form-item label="样品" prop="productId">
<el-select v-model="queryParams.productId" placeholder="请选择样品" clearable filterable class="!w-240px">
<el-option v-for="item in sampleList" :key="item.id" :label="formatSampleLabel(item)" :value="item.id" />
<el-select
v-model="queryParams.productId"
placeholder="请选择样品"
clearable
filterable
class="!w-240px"
>
<el-option
v-for="item in sampleList"
:key="item.id"
:label="formatSampleLabel(item)"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="仓库" prop="warehouseId">
<el-select v-model="queryParams.warehouseId" placeholder="请选择仓库" clearable filterable class="!w-220px" @change="handleWarehouseChange">
<el-option v-for="item in warehouseList" :key="item.id" :label="item.name" :value="item.id" />
<el-select
v-model="queryParams.warehouseId"
placeholder="请选择仓库"
clearable
filterable
class="!w-220px"
@change="handleWarehouseChange"
>
<el-option
v-for="item in warehouseList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="库区" prop="areaId">
<el-select v-model="queryParams.areaId" placeholder="请选择库区" clearable filterable class="!w-220px">
<el-option v-for="item in filteredAreaList" :key="item.id" :label="item.areaName" :value="item.id" />
<el-select
v-model="queryParams.areaId"
placeholder="请选择库区"
clearable
filterable
class="!w-220px"
>
<el-option
v-for="item in filteredAreaList"
:key="item.id"
:label="item.areaName"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="业务单号" prop="bizNo">
<el-input v-model="queryParams.bizNo" placeholder="请输入业务单号" clearable class="!w-220px" @keyup.enter="handleQuery" />
<el-input
v-model="queryParams.bizNo"
placeholder="请输入业务单号"
clearable
class="!w-220px"
@keyup.enter="handleQuery"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" />查询</el-button>
@ -27,30 +73,51 @@
</ContentWrap>
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" row-key="id">
<el-table
v-loading="loading"
:data="list"
:stripe="true"
:show-overflow-tooltip="true"
row-key="id"
>
<el-table-column label="业务单号" align="center" prop="bizNo" min-width="170" />
<el-table-column label="方向" align="center" prop="bizDirection" width="100">
<template #default="{ row }">
<el-tag v-if="row.bizDirection" :type="getDirectionTagType(row.bizDirection)">{{ row.bizDirection }}</el-tag>
<el-tag v-if="row.bizDirection" :type="getDirectionTagType(row.bizDirection)">{{
row.bizDirection
}}</el-tag>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="样品名称" align="left" prop="productName" min-width="180" />
<el-table-column label="分类" align="center" prop="categoryName" min-width="120" />
<!-- <el-table-column label="分类" align="center" prop="categoryName" min-width="120" /> -->
<el-table-column label="仓库" align="center" prop="warehouseName" min-width="140" />
<el-table-column label="库区" align="center" prop="areaName" min-width="120" />
<el-table-column label="变动数量" align="center" prop="count" min-width="120">
<template #default="{ row }">
<span :class="getDirectionClass(row.bizDirection)">{{ formatCount(row.count, row.unitName, true, row.bizDirection) }}</span>
<span :class="getDirectionClass(row.bizDirection)">{{
formatCount(row.count, row.unitName, true, row.bizDirection)
}}</span>
</template>
</el-table-column>
<el-table-column label="结存" align="center" prop="totalCount" min-width="120">
<template #default="{ row }">{{ formatCount(row.totalCount, row.unitName) }}</template>
</el-table-column>
<el-table-column label="操作人" align="center" prop="creatorName" min-width="110" />
<el-table-column label="操作时间" align="center" prop="recordTime" :formatter="dateFormatter" width="180" />
<el-table-column
label="操作时间"
align="center"
prop="recordTime"
:formatter="dateFormatter"
width="180"
/>
</el-table>
<Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" @pagination="getList" />
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
</template>
@ -78,7 +145,8 @@ const queryParams = reactive<any>({
areaId: undefined,
bizNo: undefined,
isSample: true,
bizUnit: 'BIT'
bizUnit: 'BIT',
bitWmsOnly: true
})
const filteredAreaList = computed(() => {
if (!queryParams.warehouseId) return areaList.value
@ -103,7 +171,12 @@ const formatNumber = (value: number | string | undefined) => {
const num = Number(value)
return Number.isFinite(num) ? num.toLocaleString() : String(value)
}
const formatCount = (value: number | string | undefined, unit?: string, withSign = false, direction?: string) => {
const formatCount = (
value: number | string | undefined,
unit?: string,
withSign = false,
direction?: string
) => {
const unitText = unit ? ` ${unit}` : ''
if (!withSign) return `${formatNumber(value)}${unitText}`
const num = Number(value)
@ -114,7 +187,11 @@ const formatCount = (value: number | string | undefined, unit?: string, withSign
return `${formatNumber(value)}${unitText}`
}
const buildQueryParams = () =>
Object.fromEntries(Object.entries(queryParams).filter(([, value]) => value !== undefined && value !== null && value !== ''))
Object.fromEntries(
Object.entries(queryParams).filter(
([, value]) => value !== undefined && value !== null && value !== ''
)
)
const getList = async () => {
loading.value = true
try {
@ -136,6 +213,7 @@ const resetQuery = () => {
queryFormRef.value.resetFields()
queryParams.isSample = true
queryParams.bizUnit = 'BIT'
queryParams.bitWmsOnly = true
handleQuery()
}

@ -1,19 +1,59 @@
<template>
<ContentWrap>
<el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" label-width="auto">
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="auto"
>
<el-form-item label="样品" prop="productId">
<el-select v-model="queryParams.productId" placeholder="请选择样品" clearable filterable class="!w-240px">
<el-option v-for="item in sampleList" :key="item.id" :label="formatSampleLabel(item)" :value="item.id" />
<el-select
v-model="queryParams.productId"
placeholder="请选择样品"
clearable
filterable
class="!w-240px"
>
<el-option
v-for="item in sampleList"
:key="item.id"
:label="formatSampleLabel(item)"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="仓库" prop="warehouseId">
<el-select v-model="queryParams.warehouseId" placeholder="请选择仓库" clearable filterable class="!w-220px" @change="handleWarehouseChange">
<el-option v-for="item in warehouseList" :key="item.id" :label="item.name" :value="item.id" />
<el-select
v-model="queryParams.warehouseId"
placeholder="请选择仓库"
clearable
filterable
class="!w-220px"
@change="handleWarehouseChange"
>
<el-option
v-for="item in warehouseList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item label="库区" prop="areaId">
<el-select v-model="queryParams.areaId" placeholder="请选择库区" clearable filterable class="!w-220px">
<el-option v-for="item in filteredAreaList" :key="item.id" :label="item.areaName" :value="item.id" />
<el-select
v-model="queryParams.areaId"
placeholder="请选择库区"
clearable
filterable
class="!w-220px"
>
<el-option
v-for="item in filteredAreaList"
:key="item.id"
:label="item.areaName"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item>
@ -24,23 +64,34 @@
</ContentWrap>
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" row-key="id">
<el-table
v-loading="loading"
:data="list"
:stripe="true"
:show-overflow-tooltip="true"
row-key="id"
>
<el-table-column label="样品码" align="center" prop="barCode" min-width="150" />
<el-table-column label="样品名称" align="left" min-width="180">
<template #default="{ row }">{{ row.name || row.productName || '-' }}</template>
</el-table-column>
<el-table-column label="样品分类" align="center" prop="categoryName" min-width="130" />
<!-- <el-table-column label="样品分类" align="center" prop="categoryName" min-width="130" /> -->
<el-table-column label="仓库" align="center" prop="warehouseName" min-width="140" />
<el-table-column label="库区" align="center" prop="areaName" min-width="120" />
<el-table-column label="库存" align="center" prop="count" min-width="120">
<template #default="{ row }">{{ formatCount(row.count) }}</template>
</el-table-column>
<el-table-column label="单位" align="center" prop="unitName" width="90" />
<el-table-column label="库存展示" align="center" prop="stockDisplay" min-width="200">
<!-- <el-table-column label="库存展示" align="center" prop="stockDisplay" min-width="200">
<template #default="{ row }">{{ row.stockDisplay || '-' }}</template>
</el-table-column>
</el-table-column> -->
</el-table>
<Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" @pagination="getList" />
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
</template>
@ -80,7 +131,11 @@ const formatCount = (value: number | string | undefined) => {
return Number.isFinite(num) ? num.toLocaleString() : String(value)
}
const buildQueryParams = () =>
Object.fromEntries(Object.entries(queryParams).filter(([, value]) => value !== undefined && value !== null && value !== ''))
Object.fromEntries(
Object.entries(queryParams).filter(
([, value]) => value !== undefined && value !== null && value !== ''
)
)
const getList = async () => {
loading.value = true
try {

@ -47,10 +47,10 @@
<el-col :span="8">
<el-form-item :label="t('ErpStock.WarehouseLocation.code')" prop="code">
<el-row :gutter="10" class="!w-full">
<el-col :span="18">
<el-col :span="hideAutoCodeSwitch ? 24 : 18">
<el-input v-model="formData.code" :placeholder="t('ErpStock.WarehouseLocation.placeholderCode')" :disabled="formData.isCode == true || formType === 'update'" />
</el-col>
<el-col :span="6">
<el-col v-if="!hideAutoCodeSwitch" :span="6">
<el-switch v-model="formData.isCode" :disabled="formType === 'update'" />
</el-col>
</el-row>
@ -63,7 +63,7 @@
<el-input v-model="formData.name" :placeholder="t('ErpStock.WarehouseLocation.placeholderName')" class="!w-full" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-col v-if="!isFieldHidden('areaSize')" :span="8">
<el-form-item :label="t('ErpStock.WarehouseLocation.areaSize')" prop="areaSize">
<el-input-number
v-model="formData.areaSize"
@ -74,7 +74,7 @@
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-col v-if="!isFieldHidden('maxLoadWeight')" :span="8">
<el-form-item :label="t('ErpStock.WarehouseLocation.maxLoadWeight')" prop="maxLoadWeight">
<el-input-number
v-model="formData.maxLoadWeight"
@ -87,7 +87,7 @@
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-col v-if="!isFieldHidden('positionX')" :span="8">
<el-form-item :label="t('ErpStock.WarehouseLocation.positionX')" prop="positionX">
<el-input-number
v-model="formData.positionX"
@ -97,7 +97,7 @@
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-col v-if="!isFieldHidden('positionY')" :span="8">
<el-form-item :label="t('ErpStock.WarehouseLocation.positionY')" prop="positionY">
<el-input-number
v-model="formData.positionY"
@ -107,7 +107,7 @@
/>
</el-form-item>
</el-col>
<el-col :span="8">
<el-col v-if="!isFieldHidden('positionZ')" :span="8">
<el-form-item :label="t('ErpStock.WarehouseLocation.positionZ')" prop="positionZ">
<el-input-number
v-model="formData.positionZ"
@ -119,12 +119,12 @@
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-col v-if="!isFieldHidden('allowProductMix')" :span="8">
<el-form-item :label="t('ErpStock.WarehouseLocation.allowProductMix')" prop="allowProductMix">
<el-switch v-model="formData.allowProductMix" />
</el-form-item>
</el-col>
<el-col :span="8">
<el-col v-if="!isFieldHidden('allowBatchMix')" :span="8">
<el-form-item :label="t('ErpStock.WarehouseLocation.allowBatchMix')" prop="allowBatchMix">
<el-switch v-model="formData.allowBatchMix" />
</el-form-item>
@ -154,6 +154,14 @@ import { CommonStatusEnum } from '@/utils/constants'
defineOptions({ name: 'WarehouseLocationForm' })
type WarehouseLocationOpenOptions = {
defaultWarehouseName?: string
defaultAreaName?: string
defaultData?: Record<string, any>
hideAutoCodeSwitch?: boolean
hiddenFields?: string[]
}
const { t } = useI18n()
const message = useMessage()
@ -176,9 +184,21 @@ const formData = ref({
status: undefined,
isCode: true
})
const validateCode = (_rule: any, value: any, callback: any) => {
if (formData.value.isCode) {
callback()
return
}
if (value === undefined || value === null || String(value).trim() === '') {
callback(new Error('请输入库位编码'))
return
}
callback()
}
const formRules = reactive({
warehouseId: [{ required: true, message: t('ErpStock.WarehouseLocation.validatorWarehouseIdRequired'), trigger: 'blur' }],
areaId: [{ required: true, message: t('ErpStock.WarehouseLocation.validatorAreaIdRequired'), trigger: 'blur' }],
code: [{ validator: validateCode, trigger: ['blur', 'change'] }],
name: [{ required: true, message: t('ErpStock.WarehouseLocation.validatorNameRequired'), trigger: 'blur' }],
allowProductMix: [{ required: true, message: t('ErpStock.WarehouseLocation.validatorAllowProductMixRequired'), trigger: 'blur' }],
allowBatchMix: [{ required: true, message: t('ErpStock.WarehouseLocation.validatorAllowBatchMixRequired'), trigger: 'blur' }],
@ -187,6 +207,8 @@ const formRules = reactive({
const formRef = ref()
const warehouseList = ref<any[]>([])
const areaList = ref<any[]>([])
const hideAutoCodeSwitch = ref(false)
const hiddenFields = ref<string[]>([])
const filteredAreaList = computed(() => {
if (!formData.value.warehouseId) return areaList.value
@ -197,9 +219,32 @@ const handleWarehouseChange = () => {
formData.value.areaId = undefined
}
const open = async (type: string, id?: number) => {
const isFieldHidden = (field: string) => hiddenFields.value.includes(field)
const applyDefaultWarehouseArea = (options?: WarehouseLocationOpenOptions) => {
if (!options) return
const defaultWarehouse = options.defaultWarehouseName
? warehouseList.value.find((item) => item.name === options.defaultWarehouseName)
: undefined
if (defaultWarehouse) {
formData.value.warehouseId = defaultWarehouse.id
}
const candidateAreaList = formData.value.warehouseId
? areaList.value.filter((item) => item.warehouseId === formData.value.warehouseId)
: areaList.value
const defaultArea = options.defaultAreaName
? candidateAreaList.find((item) => item.areaName === options.defaultAreaName)
: undefined
if (defaultArea) {
formData.value.areaId = defaultArea.id
}
}
const open = async (type: string, id?: number, options?: WarehouseLocationOpenOptions) => {
dialogTitle.value = t('action.' + type)
formType.value = type
hideAutoCodeSwitch.value = Boolean(options?.hideAutoCodeSwitch)
hiddenFields.value = options?.hiddenFields ?? []
resetForm()
warehouseList.value = await WarehouseApi.getWarehouseSimpleList()
const areaData = await WarehouseAreaApi.getWarehouseAreaPage({ pageNo: 1, pageSize: 100 })
@ -211,6 +256,12 @@ const open = async (type: string, id?: number) => {
} finally {
formLoading.value = false
}
} else {
formData.value = {
...formData.value,
...(options?.defaultData ?? {})
}
applyDefaultWarehouseArea(options)
}
}
defineExpose({ open })

@ -1,5 +1,12 @@
<template>
<Dialog v-model="dialogVisible" :title="dialogTitle" width="92%" :fullscreen="false" top="4vh">
<Dialog
v-model="dialogVisible"
:title="dialogTitle"
width="96%"
:fullscreen="false"
:initial-fullscreen="true"
top="2vh"
>
<div class="hiprint-preview">
<div class="hiprint-toolbar">
<div class="hiprint-paper">
@ -167,6 +174,11 @@ type BarcodeElementItem = {
label: string
}
type BarcodeFieldItem = BarcodeElementItem & {
field: string
type: 'image' | 'text'
}
type BarcodeElementGroup = {
value: string | number
label: string
@ -175,6 +187,37 @@ type BarcodeElementGroup = {
const barcodeGroups = ref<BarcodeElementGroup[]>([])
const barcodeDictData = ref<any[]>([])
const barcodeFieldLabelMap: Record<string, string> = {
qrcodeUrl: '二维码',
name: '名称',
code: '编码',
sampleCategory: '样品分类',
customerName: '客户名称'
}
const getBarcodeFieldLabel = (field: string, fallback: string) => barcodeFieldLabelMap[field] || fallback || field
const parseBarcodeFields = (item: any): BarcodeFieldItem[] => {
const parts = String(item.remark || '')
.split(',')
.map((s: string) => s.trim())
.filter(Boolean)
const qrcodeField = parts[0] || ''
const nameField = parts[1] || ''
const codeField = parts[2] || parts[1] || ''
const baseFields: BarcodeFieldItem[] = [
{ tid: `barcodeModule.${item.value}_qrcode`, label: '二维码', field: qrcodeField, type: 'image' },
{ tid: `barcodeModule.${item.value}_name`, label: getBarcodeFieldLabel(nameField, '名称'), field: nameField, type: 'text' },
{ tid: `barcodeModule.${item.value}_code`, label: getBarcodeFieldLabel(codeField, '编码'), field: codeField, type: 'text' }
]
const extraFields = parts.slice(3).map((field: string, index: number) => ({
tid: `barcodeModule.${item.value}_field_${index + 3}`,
label: getBarcodeFieldLabel(field, field),
field,
type: 'text' as const
}))
return [...baseFields, ...extraFields]
}
const loadBarcodeDictData = async () => {
try {
@ -184,11 +227,7 @@ const loadBarcodeDictData = async () => {
barcodeGroups.value = filtered.map((item: any) => ({
value: item.value,
label: item.label,
elements: [
{ tid: `barcodeModule.${item.value}_qrcode`, label: '二维码' },
{ tid: `barcodeModule.${item.value}_name`, label: '名称' },
{ tid: `barcodeModule.${item.value}_code`, label: '编码' }
]
elements: parseBarcodeFields(item).map(({ tid, label }) => ({ tid, label }))
}))
} catch (e) {
console.error('加载条码字典数据失败', e)
@ -201,55 +240,39 @@ const barcodeProvider = function () {
if (!barcodeDictData.value.length) return
const groups: any[] = []
barcodeDictData.value.forEach((item: any) => {
const remark = item.remark || ''
const parts = remark.split(',').map((s: string) => s.trim())
const qrcodeField = parts[0] || ''
const nameField = parts[1] || ''
const codeField = parts[2] || parts[1] || ''
const fields = parseBarcodeFields(item)
groups.push(
new hiprint.PrintElementTypeGroup(item.label, [
{
tid: `barcodeModule.${item.value}_qrcode`,
title: '二维码',
type: 'image',
options: {
field: qrcodeField,
src: qrCodeImg,
testData: qrCodeImg,
width: 80,
height: 80,
title: '二维码'
new hiprint.PrintElementTypeGroup(item.label, fields.map((fieldItem) => {
if (fieldItem.type === 'image') {
return {
tid: fieldItem.tid,
title: fieldItem.label,
type: 'image',
options: {
field: fieldItem.field,
src: qrCodeImg,
testData: qrCodeImg,
width: 80,
height: 80,
title: fieldItem.label
}
}
},
{
tid: `barcodeModule.${item.value}_name`,
title: '名称',
type: 'text',
options: {
field: nameField,
testData: '',
title: '名称',
fontSize: 10,
textAlign: 'center',
width: 80,
height: 16
}
},
{
tid: `barcodeModule.${item.value}_code`,
title: '编码',
}
return {
tid: fieldItem.tid,
title: fieldItem.label,
type: 'text',
options: {
field: codeField,
field: fieldItem.field,
testData: '',
title: '编码',
title: fieldItem.label,
fontSize: 10,
textAlign: 'center',
width: 80,
height: 16
}
}
])
}))
)
})
context.addPrintElementTypes('barcodeModule', groups)
@ -644,6 +667,10 @@ watch(selectedIconColor, () => {
<style scoped lang="scss">
.hiprint-preview {
width: 100%;
height: calc(100vh - 84px);
display: flex;
flex-direction: column;
min-height: 0;
}
.hiprint-toolbar {
@ -651,6 +678,7 @@ watch(selectedIconColor, () => {
align-items: center;
gap: 12px;
margin-bottom: 12px;
flex-shrink: 0;
}
.hiprint-paper {
@ -686,7 +714,8 @@ watch(selectedIconColor, () => {
}
.hiprint-body {
height: 75vh;
flex: 1;
min-height: 0;
display: grid;
grid-template-columns: 220px 1fr 400px;
gap: 12px;

Loading…
Cancel
Save