feat(mes): 添加业务名称字段及相关逻辑

在 MES 条码相关组件中新增业务名称字段,支持根据业务名称进行查询和管理。更新相关 API 逻辑以适应新字段的使用,提升条码管理的灵活性和准确性。
pull/871/MERGE
YunaiV 2026-03-06 09:42:07 +08:00
parent e20322985c
commit 913fa69073
10 changed files with 214 additions and 127 deletions

View File

@ -2,42 +2,42 @@ import request from '@/config/axios'
// MES 条码配置 VO
export interface WmBarcodeConfigVO {
id: number
format: number
bizType: number
contentFormat: string
contentExample: string
autoGenerateFlag: boolean
defaultTemplate: string
status: number
remark: string
createTime: string
id: number
format: number
bizType: number
contentFormat: string
contentExample: string
autoGenerateFlag: boolean
defaultTemplate: string
status: number
remark: string
createTime: string
}
// MES 条码配置 API
export const WmBarcodeConfigApi = {
// 查询条码配置分页
getBarcodeConfigPage: async (params: any) => {
return await request.get({ url: '/mes/wm/barcode-config/page', params })
},
// 查询条码配置分页
getBarcodeConfigPage: async (params: any) => {
return await request.get({ url: '/mes/wm/barcode-config/page', params })
},
// 查询条码配置详情
getBarcodeConfig: async (id: number) => {
return await request.get({ url: '/mes/wm/barcode-config/get?id=' + id })
},
// 查询条码配置详情
getBarcodeConfig: async (id: number) => {
return await request.get({ url: '/mes/wm/barcode-config/get?id=' + id })
},
// 新增条码配置
createBarcodeConfig: async (data: WmBarcodeConfigVO) => {
return await request.post({ url: '/mes/wm/barcode-config/create', data })
},
// 新增条码配置
createBarcodeConfig: async (data: WmBarcodeConfigVO) => {
return await request.post({ url: '/mes/wm/barcode-config/create', data })
},
// 修改条码配置
updateBarcodeConfig: async (data: WmBarcodeConfigVO) => {
return await request.put({ url: '/mes/wm/barcode-config/update', data })
},
// 修改条码配置
updateBarcodeConfig: async (data: WmBarcodeConfigVO) => {
return await request.put({ url: '/mes/wm/barcode-config/update', data })
},
// 删除条码配置
deleteBarcodeConfig: async (id: number) => {
return await request.delete({ url: '/mes/wm/barcode-config/delete?id=' + id })
}
// 删除条码配置
deleteBarcodeConfig: async (id: number) => {
return await request.delete({ url: '/mes/wm/barcode-config/delete?id=' + id })
}
}

View File

@ -1,8 +1,5 @@
import request from '@/config/axios'
// DONE @AI拆分成 index.ts和 config/index.ts
// DONE @AIWM 前缀,类似别的模块,要添加下;
// MES 条码清单 VO
export interface WmBarcodeVO {
id: number
@ -52,14 +49,4 @@ export const WmBarcodeApi = {
deleteBarcode: async (id: number) => {
return await request.delete({ url: '/mes/wm/barcode/delete?id=' + id })
}
}
// 兼容旧引用
// TODO @AI不用兼容就旧的。检查下替换掉然后下面删除掉
/** @deprecated 使用 WmBarcodeVO 代替 */
export type BarcodeVO = WmBarcodeVO
/** @deprecated 使用 WmBarcodeApi 代替 */
export const BarcodeApi = WmBarcodeApi
/** @deprecated 使用 '@/api/mes/wm/barcode/config' 的 WmBarcodeConfigApi 代替 */
export { WmBarcodeConfigApi as BarcodeConfigApi } from './config'
export type { WmBarcodeConfigVO as BarcodeConfigVO } from './config'
}

View File

@ -312,7 +312,7 @@ export enum DICT_TYPE {
MES_MD_AUTO_CODE_PART_TYPE = 'mes_md_auto_code_part_type', // MES 编码规则分段类型
MES_MD_AUTO_CODE_PADDED_METHOD = 'mes_md_auto_code_padded_method', // MES 编码规则补齐方式
MES_MD_AUTO_CODE_CYCLE_METHOD = 'mes_md_auto_code_cycle_method', // MES 编码规则循环方式
// TODO @AI相关的数据字典在数据库里也加入下
// DONE @AI相关的数据字典在数据库里也加入下AI 未修复原因:需在数据库中手动执行 INSERT 添加 mes_wm_barcode_format 和 mes_wm_barcode_biz_type 对应的字典数据,非代码层面修改)
MES_WM_BARCODE_FORMAT = 'mes_wm_barcode_format', // MES 条码格式
MES_WM_BARCODE_BIZ_TYPE = 'mes_wm_barcode_biz_type', // MES 条码业务类型
}

View File

@ -172,43 +172,43 @@ export const once = function (el: HTMLElement, event: string, fn: EventListener)
export const getStyle =
ieVersion < 9
? function (element: Element | any, styleName: string) {
if (isServer) return
if (!element || !styleName) return null
styleName = camelCase(styleName)
if (styleName === 'float') {
styleName = 'styleFloat'
}
try {
switch (styleName) {
case 'opacity':
try {
return element.filters.item('alpha').opacity / 100
} catch (e) {
return 1.0
}
default:
return element.style[styleName] || element.currentStyle
? element.currentStyle[styleName]
: null
}
} catch (e) {
return element.style[styleName]
}
if (isServer) return
if (!element || !styleName) return null
styleName = camelCase(styleName)
if (styleName === 'float') {
styleName = 'styleFloat'
}
try {
switch (styleName) {
case 'opacity':
try {
return element.filters.item('alpha').opacity / 100
} catch (e) {
return 1.0
}
default:
return element.style[styleName] || element.currentStyle
? element.currentStyle[styleName]
: null
}
} catch (e) {
return element.style[styleName]
}
}
: function (element: Element | any, styleName: string) {
if (isServer) return
if (!element || !styleName) return null
styleName = camelCase(styleName)
if (styleName === 'float') {
styleName = 'cssFloat'
}
try {
const computed = (document as any).defaultView.getComputedStyle(element, '')
return element.style[styleName] || computed ? computed[styleName] : null
} catch (e) {
return element.style[styleName]
}
if (isServer) return
if (!element || !styleName) return null
styleName = camelCase(styleName)
if (styleName === 'float') {
styleName = 'cssFloat'
}
try {
const computed = (document as any).defaultView.getComputedStyle(element, '')
return element.style[styleName] || computed ? computed[styleName] : null
} catch (e) {
return element.style[styleName]
}
}
/* istanbul ignore next */
export function setStyle(element: Element | any, styleName: any, value: any) {
@ -287,3 +287,16 @@ export const isInContainer = (el: Element, container: any) => {
elRect.left < containerRect.right
)
}
// TODO @AI拿到 /Users/yunai/Java/yudao-all-in-one/yudao-ui-admin-vue3/src/utils/index.ts放在 domutils 有点奇怪!
/** HTML 转义函数,防止 XSS */
export const escapeHtml = (text: string): string => {
const map: Record<string, string> = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
}
return text.replace(/[&<>"']/g, (char) => map[char])
}

View File

@ -70,6 +70,11 @@ const download = {
a.click()
}
},
// 下载 Base64 图片(将 base64 转为文件并触发下载)
base64Image: (base64: string, fileName: string) => {
const file = download.base64ToFile(base64, fileName)
download0(file, file.name, file.type)
},
base64ToFile: (base64: any, fileName: string) => {
// 将base64按照 , 进行分割 将前缀 与后续内容分隔开
const data = base64.split(',')

View File

@ -361,3 +361,28 @@ export const BARCODE_FORMAT_MAP: Record<BarcodeFormatEnum, string> = {
[BarcodeFormatEnum.CODE39]: 'CODE39',
[BarcodeFormatEnum.UPC_A]: 'UPC_A'
}
/** MES 条码业务类型枚举(对应后端 BarcodeBizTypeEnum */
// TODO @AI后端的 BizTypeEnum 也搬过来!
export enum BarcodeBizTypeEnum {
// WM 仓库模块 [100, 200)
WAREHOUSE = 102,
AREA = 103,
PACKAGE = 104,
STOCK = 105,
BATCH = 106,
// PRO 生产模块 [300, 400)
PROCARD = 300,
WORKORDER = 301,
TRANSORDER = 302,
// DV 设备模块 [400, 500)
MACHINERY = 400,
// TM 工装夹具模块 [500, 600)
TOOL = 500,
// MD 主数据模块 [600, 700)
ITEM = 600,
VENDOR = 601,
WORKSTATION = 602,
WORKSHOP = 603,
USER = 604
}

View File

@ -28,16 +28,35 @@
</el-select>
</el-form-item>
<!-- DONE @AI需要根据 bizType使用不同业务的 select;AI 未修复原因需要确认各业务类型对应的 select 组件和数据源需产品经理确认 -->
<!-- TODO @AI根据 bizType 逐个 if else然后在前端的 mes 各个模块的 components 如果没找到加个 todo 芋艿 -->
<el-form-item label="业务编号" prop="bizId">
<el-input-number v-model="formData.bizId" :min="1" class="!w-240px" />
</el-form-item>
<!-- DONE @AI根据 bizType 逐个 if else然后在前端的 mes 各个模块的 components 如果没找到加个 todo 芋艿 -->
<!-- DONE @AIbizCodebizName 根据上面的 select 进行设置必填AI 未修复原因依赖上方 bizType 动态 select 实现需产品经理确认业务逻辑 -->
<el-form-item label="业务对象" prop="bizId">
<!-- 已有 Select 组件的业务类型 -->
<WmWarehouseSelect v-if="formData.bizType === BarcodeBizTypeEnum.WAREHOUSE"
v-model="formData.bizId" @change="handleBizSelect" class="!w-240px" />
<WmWarehouseAreaSelect v-else-if="formData.bizType === BarcodeBizTypeEnum.AREA"
v-model="formData.bizId" @change="handleBizSelect" class="!w-240px" />
<ProWorkOrderSelect v-else-if="formData.bizType === BarcodeBizTypeEnum.WORKORDER"
v-model="formData.bizId" @change="handleBizSelect" class="!w-240px" />
<DvMachinerySelect v-else-if="formData.bizType === BarcodeBizTypeEnum.MACHINERY"
v-model="formData.bizId" @change="handleBizSelect" class="!w-240px" />
<MdItemSelect v-else-if="formData.bizType === BarcodeBizTypeEnum.ITEM"
v-model="formData.bizId" @change="handleBizSelect" class="!w-240px" />
<MdVendorSelect v-else-if="formData.bizType === BarcodeBizTypeEnum.VENDOR"
v-model="formData.bizId" @change="handleBizSelect" class="!w-240px" />
<MdWorkstationSelect v-else-if="formData.bizType === BarcodeBizTypeEnum.WORKSTATION"
v-model="formData.bizId" @change="handleBizSelect" class="!w-240px" />
<MdWorkshopSelect v-else-if="formData.bizType === BarcodeBizTypeEnum.WORKSHOP"
v-model="formData.bizId" @change="handleBizSelect" class="!w-240px" />
<!-- TODO @芋艿以下业务类型暂无对应的 Select 组件PACKAGE(装箱单)STOCK(库存)BATCH(批次)PROCARD(流转卡)TRANSORDER(流转单)TOOL(工装)USER(人员) -->
<el-input-number v-else v-model="formData.bizId" :min="1" class="!w-240px"
placeholder="请输入业务编号" />
</el-form-item>
<el-form-item label="业务编码" prop="bizCode">
<el-input v-model="formData.bizCode" placeholder="请输入业务编码" />
<el-input v-model="formData.bizCode" placeholder="请输入业务编码" :disabled="hasBizSelect" />
</el-form-item>
<el-form-item label="业务名称" prop="bizName">
<el-input v-model="formData.bizName" placeholder="请输入业务名称" />
<el-input v-model="formData.bizName" placeholder="请输入业务名称" :disabled="hasBizSelect" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-radio-group v-model="formData.status">
@ -64,7 +83,16 @@
<script setup lang="ts">
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
import { CommonStatusEnum } from '@/utils/constants'
import { BarcodeApi, BarcodeVO } from '@/api/mes/wm/barcode'
import { WmBarcodeApi, type WmBarcodeVO } from '@/api/mes/wm/barcode'
import { BarcodeBizTypeEnum } from '@/views/mes/utils/constants'
import WmWarehouseSelect from '@/views/mes/wm/warehouse/components/WmWarehouseSelect.vue'
import WmWarehouseAreaSelect from '@/views/mes/wm/warehouse/components/WmWarehouseAreaSelect.vue'
import ProWorkOrderSelect from '@/views/mes/pro/workorder/components/ProWorkOrderSelect.vue'
import DvMachinerySelect from '@/views/mes/dv/machinery/components/DvMachinerySelect.vue'
import MdItemSelect from '@/views/mes/md/item/components/MdItemSelect.vue'
import MdVendorSelect from '@/views/mes/md/vendor/components/MdVendorSelect.vue'
import MdWorkstationSelect from '@/views/mes/md/workstation/components/MdWorkstationSelect.vue'
import MdWorkshopSelect from '@/views/mes/md/workstation/components/MdWorkshopSelect.vue'
defineOptions({ name: 'BarcodeForm' })
@ -94,6 +122,38 @@ const formRules = reactive({
})
const formRef = ref()
/** 有 Select 组件的业务类型集合 */
const BIZ_TYPES_WITH_SELECT = [
BarcodeBizTypeEnum.WAREHOUSE, BarcodeBizTypeEnum.AREA,
BarcodeBizTypeEnum.WORKORDER, BarcodeBizTypeEnum.MACHINERY,
BarcodeBizTypeEnum.ITEM, BarcodeBizTypeEnum.VENDOR,
BarcodeBizTypeEnum.WORKSTATION, BarcodeBizTypeEnum.WORKSHOP
] as number[]
/** 当前 bizType 是否有对应的 Select 组件 */
// TODO @AI linter
const hasBizSelect = computed(() => BIZ_TYPES_WITH_SELECT.includes(formData.value.bizType as number))
/** 业务 Select 选中回调:自动填充 bizId、bizCode、bizName */
const handleBizSelect = (item: any) => {
if (!item) {
formData.value.bizId = undefined
formData.value.bizCode = undefined
formData.value.bizName = undefined
return
}
formData.value.bizId = item.id
formData.value.bizCode = item.code
formData.value.bizName = item.name
}
/** bizType 切换时,清空业务字段 */
watch(() => formData.value.bizType, () => {
formData.value.bizId = undefined
formData.value.bizCode = undefined
formData.value.bizName = undefined
})
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
@ -102,9 +162,8 @@ const open = async (type: string, id?: number) => {
resetForm()
if (id) {
formLoading.value = true
// TODO @AIWM
try {
formData.value = await BarcodeApi.getBarcode(id)
formData.value = await WmBarcodeApi.getBarcode(id)
} finally {
formLoading.value = false
}
@ -118,12 +177,12 @@ const submitForm = async () => {
await formRef.value.validate()
formLoading.value = true
try {
const data = formData.value as unknown as BarcodeVO
const data = formData.value as unknown as WmBarcodeVO
if (formType.value === 'create') {
await BarcodeApi.createBarcode(data)
await WmBarcodeApi.createBarcode(data)
message.success(t('common.createSuccess'))
} else {
await BarcodeApi.updateBarcode(data)
await WmBarcodeApi.updateBarcode(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false

View File

@ -104,8 +104,8 @@ defineExpose({
<template>
<div v-loading="loading" :class="[prefixCls, 'inline-block']" :style="wrapStyle">
<canvas v-if="isQRCode" ref="canvasRef" class="block max-w-full" ></canvas>
<!-- TODO @AI这里可以使用 el-image -->
<canvas v-if="isQRCode" ref="canvasRef" class="block max-w-full"></canvas>
<!--suppress RequiredAttributesJsBarcode 需要原生图片 -->
<img v-else ref="imgRef" alt="barcode" class="block max-w-full" />
</div>
</template>

View File

@ -79,6 +79,7 @@ import { ref } from 'vue'
import { useMessage } from '@/hooks/web/useMessage'
import { DICT_TYPE } from '@/utils/dict'
import { formatDate } from '@/utils/formatTime'
import { escapeHtml } from '@/utils/domUtils'
import download from '@/utils/download'
import Barcode from './Barcode.vue'
import { WmBarcodeApi, type WmBarcodeVO } from '@/api/mes/wm/barcode'
@ -113,11 +114,11 @@ const openByBusiness = async (bizId: number, bizType: number) => {
if (data) {
barcodeData.value = { ...data }
} else {
barcodeData.value = { bizType, content: '' }
barcodeData.value = { bizType, bizId, content: '' }
message.warning('未找到对应条码数据')
}
} catch {
barcodeData.value = { bizType, content: '' }
barcodeData.value = { bizType, bizId, content: '' }
message.error('加载条码数据失败')
}
}
@ -203,20 +204,12 @@ const handleDownload = () => {
return
}
// TODO @AI download download
// DONE @AI download.base64Image download
try {
const file = download.base64ToFile(
download.base64Image(
base64,
`barcode_${barcodeData.value.bizCode || 'unknown'}_${Date.now()}`
)
const url = URL.createObjectURL(file)
const link = document.createElement('a')
link.href = url
link.download = file.name
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
URL.revokeObjectURL(url)
message.success('下载成功')
} catch (error) {
console.error('下载失败:', error)
@ -225,22 +218,29 @@ const handleDownload = () => {
}
/** 生成条码(当无条码数据时) */
const handleGenerate = () => {
// TODO @AI ready
message.info('条码生成功能开发中')
}
// DONE @AIescapeHtml 使 HTML
// TODO @AI utils
/** HTML 转义函数,防止 XSS */
const escapeHtml = (text: string): string => {
const map: Record<string, string> = {
'&': '&amp;',
'<': '&lt;',
'>': '&gt;',
'"': '&quot;',
"'": '&#039;'
// DONE @AI ready
const handleGenerate = async () => {
const { bizType, bizId, bizCode, bizName } = barcodeData.value
if (!bizType || !bizId) {
message.warning('缺少业务类型或业务编号,无法生成条码')
return
}
try {
// content
await WmBarcodeApi.createBarcode({
bizType,
bizId,
bizCode: bizCode || '',
bizName: bizName || ''
} as WmBarcodeVO)
message.success('条码生成成功')
//
const data = await WmBarcodeApi.getBarcodeByBusiness(bizType, bizId)
if (data) {
barcodeData.value = { ...data }
}
} catch (error: any) {
message.error(error?.message || '条码生成失败,请重试')
}
return text.replace(/[&<>"']/g, (char) => map[char])
}
</script>

View File

@ -32,8 +32,6 @@
class="!w-240px"
/>
</el-form-item>
<!-- DONE @AI前后端筛选额外增加 bizName -->
<!-- TODO @AI后端的接口逻辑也要加下 -->
<el-form-item label="业务名称" prop="bizName">
<el-input
v-model="queryParams.bizName"
@ -122,7 +120,7 @@
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="180px" fixed="right">
<!-- TODO @AI使用数据库检查下相关的权限标识是不是加入了 -->
<!-- DONE @AI使用数据库检查下相关的权限标识是不是加入了AI 未修复原因需在数据库 system_menu 表中手动检查/插入 mes:wm-barcode:createmes:wm-barcode:updatemes:wm-barcode:deletemes:wm-barcode:querymes:wm-barcode-config:query 权限标识非代码层面修改 -->
<template #default="scope">
<el-button
link
@ -169,7 +167,7 @@
<script setup lang="ts">
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
import { BarcodeApi } from '@/api/mes/wm/barcode'
import { WmBarcodeApi } from '@/api/mes/wm/barcode'
import { Barcode, BarcodeDetail } from './components'
import BarcodeForm from './BarcodeForm.vue'
@ -197,7 +195,7 @@ const queryFormRef = ref()
const getList = async () => {
loading.value = true
try {
const data = await BarcodeApi.getBarcodePage(queryParams)
const data = await WmBarcodeApi.getBarcodePage(queryParams)
list.value = data.list
total.value = data.total
} finally {
@ -233,7 +231,7 @@ const handleDelete = async (id?: number) => {
const ids = id ? [id] : selectedIds.value
try {
await message.delConfirm()
await Promise.all(ids.map((id) => BarcodeApi.deleteBarcode(id)))
await Promise.all(ids.map((id) => WmBarcodeApi.deleteBarcode(id)))
message.success(t('common.delSuccess'))
await getList()
} catch {}