feat:设备台账、关键件、备件、物料、模具添加二维码功能

pull/1/head
黄伟杰 1 month ago
parent 3708e99cf8
commit bbee7c8cd2

@ -26,6 +26,7 @@ export interface MoldBrandTreeVO extends MoldBrandVO {
export interface MoldVO { export interface MoldVO {
id: number // ID id: number // ID
code: string // 模具编码 code: string // 模具编码
isCode?: boolean
name: string // 模具名称 name: string // 模具名称
unitId: number // 单位ID unitId: number // 单位ID
machineId: number // 机台ID machineId: number // 机台ID
@ -125,6 +126,9 @@ export const MoldBrandApi = {
// 获得模具 // 获得模具
getMold: async (id: number) => { getMold: async (id: number) => {
return await request.get({ url: `/erp/mold-brand/mold/get?id=` + id }) return await request.get({ url: `/erp/mold-brand/mold/get?id=` + id })
},
regenerateCode: async (id: number, code: string) => {
return await request.post({ url: `/erp/mold-brand/regenerate-code?id=${id}&code=${encodeURIComponent(code)}` })
}, },
// 根据状态获得模具 // 根据状态获得模具
getMoldListByStatus: async (status: number) => { getMoldListByStatus: async (status: number) => {

@ -5,6 +5,8 @@ export interface ProductVO {
id: number // 产品编号 id: number // 产品编号
name: string // 产品名称 name: string // 产品名称
barCode: string // 产品条码 barCode: string // 产品条码
isCode?: boolean
qrcodeUrl?: string
categoryId: number // 产品类型编号 categoryId: number // 产品类型编号
subCategoryId: number // 产品类型子类编号 subCategoryId: number // 产品类型子类编号
subCategoryName: string // 产品类型子类名称 subCategoryName: string // 产品类型子类名称
@ -63,6 +65,13 @@ export const ProductApi = {
return await request.put({ url: `/erp/product/update`, data }) return await request.put({ url: `/erp/product/update`, data })
}, },
// 刷新产品二维码
regenerateCode: async (id: number, code: string) => {
return await request.post({
url: `/erp/product/regenerate-code?id=${id}&code=${encodeURIComponent(code)}`
})
},
// 删除产品 // 删除产品
deleteProduct: async (id: number) => { deleteProduct: async (id: number) => {
return await request.delete({ url: `/erp/product/delete?id=` + id }) return await request.delete({ url: `/erp/product/delete?id=` + id })

@ -3,10 +3,12 @@ import request from '@/config/axios'
export interface CriticalComponentVO { export interface CriticalComponentVO {
id: number id: number
code: string code: string
isCode?: boolean
name: string name: string
description?: string description?: string
count?: number count?: number
remark?: string remark?: string
qrcodeUrl?: string
createTime?: string createTime?: string
} }
@ -17,6 +19,9 @@ export const CriticalComponentApi = {
getCriticalComponentList: async () => { getCriticalComponentList: async () => {
return await request.get({ url: `/mes/critical-component/list`, }) return await request.get({ url: `/mes/critical-component/list`, })
}, },
getCriticalComponent: async (id: number) => {
return await request.get({ url: `/mes/critical-component/get?id=` + id })
},
createCriticalComponent: async (data: Partial<CriticalComponentVO>) => { createCriticalComponent: async (data: Partial<CriticalComponentVO>) => {
return await request.post({ url: `/mes/critical-component/create`, data }) return await request.post({ url: `/mes/critical-component/create`, data })
}, },
@ -25,6 +30,12 @@ export const CriticalComponentApi = {
return await request.put({ url: `/mes/critical-component/update`, data }) return await request.put({ url: `/mes/critical-component/update`, data })
}, },
regenerateCode: async (id: number, code: string) => {
return await request.post({
url: `/mes/critical-component/regenerate-code?id=${id}&code=${encodeURIComponent(code)}`
})
},
deleteCriticalComponent: async (ids: string) => { deleteCriticalComponent: async (ids: string) => {
return await request.delete({ url: `/mes/critical-component/delete?ids=` + ids }) return await request.delete({ url: `/mes/critical-component/delete?ids=` + ids })
}, },

@ -6,6 +6,7 @@ import { MoldVO } from '@/api/erp/mold'
export interface DeviceLedgerVO { export interface DeviceLedgerVO {
id: number // id id: number // id
deviceCode: string // 设备编号 deviceCode: string // 设备编号
isCode?: boolean
deviceName: string // 设备名称 deviceName: string // 设备名称
deviceStatus: number // 设备状态 (0-正常, 1-停用, 2-维修, 3-报废) deviceStatus: number // 设备状态 (0-正常, 1-停用, 2-维修, 3-报废)
deviceBrand: string // 设备品牌 deviceBrand: string // 设备品牌
@ -24,6 +25,7 @@ export interface DeviceLedgerVO {
deviceRemark: string // 设备备注 deviceRemark: string // 设备备注
remark: string // 备注 remark: string // 备注
fileUrl?: string // 附件下载 fileUrl?: string // 附件下载
qrcodeUrl?: string
componentId?: string // 关键件ids集合 componentId?: string // 关键件ids集合
componentList?: CriticalComponentVO[] componentList?: CriticalComponentVO[]
beijianId?: string // 备件ids集合 beijianId?: string // 备件ids集合
@ -73,6 +75,10 @@ export const DeviceLedgerApi = {
return await request.put({ url: `/mes/device-ledger/update`, data }) return await request.put({ url: `/mes/device-ledger/update`, data })
}, },
regenerateCode: async (id: number, code: string) => {
return await request.post({ url: `/mes/device-ledger/regenerate-code?id=${id}&code=${encodeURIComponent(code)}` })
},
// 删除设备类型 // 删除设备类型
deleteDeviceLedger: async (ids: string) => { deleteDeviceLedger: async (ids: string) => {
return await request.delete({ url: `/mes/device-ledger/delete?ids=` + ids }) return await request.delete({ url: `/mes/device-ledger/delete?ids=` + ids })

@ -1192,6 +1192,7 @@ export default {
// Equipment Ledger // Equipment Ledger
EquipmentLedger: { EquipmentLedger: {
deviceCode: 'Code', deviceCode: 'Code',
qrcode: 'QR Code/Barcode',
deviceName: 'Name', deviceName: 'Name',
deviceStatus: 'Status', deviceStatus: 'Status',
deviceType: 'Type', deviceType: 'Type',
@ -1261,11 +1262,15 @@ export default {
fileUrl: 'File', fileUrl: 'File',
serialNumber: 'No.', serialNumber: 'No.',
dvName: 'Device Name', dvName: 'Device Name',
dvId: 'Please select device' dvId: 'Please select device',
qrcodeLoadError: 'Failed to load QR code',
qrcodeEmpty: 'No QR code',
validatorDeviceCodeRequired: 'Code can not be empty'
}, },
// Critical Component // Critical Component
EquipmentKeyItems: { EquipmentKeyItems: {
code: 'Code', code: 'Code',
qrcode: 'QR Code/Barcode',
name: 'Name', name: 'Name',
batchDelete: 'Batch Delete', batchDelete: 'Batch Delete',
description: 'Description', description: 'Description',
@ -1277,7 +1282,11 @@ export default {
placeholderCode: 'Please input code', placeholderCode: 'Please input code',
placeholderName: 'Please input name', placeholderName: 'Please input name',
placeholderDescription: 'Please input description', placeholderDescription: 'Please input description',
placeholderRemark: 'Please input remark' placeholderRemark: 'Please input remark',
qrcodeLoadError: 'Failed to load QR code',
qrcodeEmpty: 'No QR code',
validatorCodeRequired: 'Code can not be empty',
validatorNameRequired: 'Name can not be empty'
}, },
// Maintenance Project (Project Maintenance) // Maintenance Project (Project Maintenance)
DvSubject: { DvSubject: {
@ -1536,6 +1545,7 @@ export default {
SpareInfo: { SpareInfo: {
name: 'Spare Part Name', name: 'Spare Part Name',
code: 'Spare Part Code', code: 'Spare Part Code',
qrcode: 'QR Code/Barcode',
standard: 'Specification', standard: 'Specification',
expiryDay: 'Shelf Life (days)', expiryDay: 'Shelf Life (days)',
category: 'Category', category: 'Category',
@ -1553,6 +1563,8 @@ export default {
placeholderRemark: 'Please input remark', placeholderRemark: 'Please input remark',
placeholderStandard: 'Please input specification', placeholderStandard: 'Please input specification',
placeholderExpiryDay: 'Please input shelf life (days)', placeholderExpiryDay: 'Please input shelf life (days)',
qrcodeLoadError: 'Failed to load QR code',
qrcodeEmpty: 'No QR code',
validatorNameRequired: 'Spare part name can not be empty', validatorNameRequired: 'Spare part name can not be empty',
validatorCodeRequired: 'Spare part code can not be empty', validatorCodeRequired: 'Spare part code can not be empty',
validatorCategoryRequired: 'Category can not be empty', validatorCategoryRequired: 'Category can not be empty',
@ -1837,6 +1849,7 @@ export default {
tableOperateColumn: 'Operation', tableOperateColumn: 'Operation',
tableEditAction: 'Edit', tableEditAction: 'Edit',
tableDeleteAction: 'Delete', tableDeleteAction: 'Delete',
qrcode: 'QR Code/Barcode',
dialogBarCodeLabel: 'Code', dialogBarCodeLabel: 'Code',
dialogBarCodePlaceholder: 'Please enter code', dialogBarCodePlaceholder: 'Please enter code',
dialogNameLabel: 'Name', dialogNameLabel: 'Name',
@ -1862,6 +1875,8 @@ export default {
dialogRemarkPlaceholder: 'Please enter remark', dialogRemarkPlaceholder: 'Please enter remark',
dialogCancelButton: 'Cancel', dialogCancelButton: 'Cancel',
dialogSubmitButton: 'Confirm', dialogSubmitButton: 'Confirm',
qrcodeLoadError: 'Failed to load QR code',
qrcodeEmpty: 'No QR code',
validatorNameRequired: 'Product name can not be empty', validatorNameRequired: 'Product name can not be empty',
validatorBarCodeRequired: 'Product barcode can not be empty', validatorBarCodeRequired: 'Product barcode can not be empty',
validatorCategoryRequired: 'Product category id can not be empty', validatorCategoryRequired: 'Product category id can not be empty',
@ -2214,6 +2229,7 @@ export default {
unit: 'Unit', unit: 'Unit',
inTime: 'Stock In Time', inTime: 'Stock In Time',
status: 'Status', status: 'Status',
qrcode: 'QR / Barcode',
images: 'Mold Images', images: 'Mold Images',
remark: 'Remark', remark: 'Remark',
isEnable: 'Is Enable', isEnable: 'Is Enable',
@ -2240,7 +2256,9 @@ export default {
validatorIsEnableRequired: 'Is enable can not be empty', validatorIsEnableRequired: 'Is enable can not be empty',
validatorBrandRequired: 'Model can not be empty', validatorBrandRequired: 'Model can not be empty',
selectBrandTip: 'Please select a mold model', selectBrandTip: 'Please select a mold model',
statusTooltip: 'Source: Data dictionary - Mold status' statusTooltip: 'Source: Data dictionary - Mold status',
qrcodeLoadError: 'QR code load failed',
qrcodeEmpty: 'No QR code'
}, },
MoldReturn: { MoldReturn: {

@ -1184,6 +1184,7 @@ export default {
// 设备台账 // 设备台账
EquipmentLedger: { EquipmentLedger: {
deviceCode: '编码', deviceCode: '编码',
qrcode: '二维码/条形码',
deviceName: '名称', deviceName: '名称',
deviceStatus: '状态', deviceStatus: '状态',
deviceType: '类型', deviceType: '类型',
@ -1253,12 +1254,16 @@ export default {
fileUrl: '资料', fileUrl: '资料',
serialNumber: '序号', serialNumber: '序号',
dvName: '关联采集设备', dvName: '关联采集设备',
dvId: '请选择设备' dvId: '请选择设备',
qrcodeLoadError: '二维码加载失败',
qrcodeEmpty: '暂无二维码',
validatorDeviceCodeRequired: '编码不能为空'
}, },
// 设备关键件 // 设备关键件
EquipmentKeyItems: { EquipmentKeyItems: {
count: '数量', count: '数量',
code: '编码', code: '编码',
qrcode: '二维码/条形码',
name: '名称', name: '名称',
batchDelete: '批量删除', batchDelete: '批量删除',
description: '描述', description: '描述',
@ -1271,7 +1276,11 @@ export default {
placeholderName: '请输入名称', placeholderName: '请输入名称',
placeholderDescription: '请输入描述', placeholderDescription: '请输入描述',
placeholderRemark: '请输入备注', placeholderRemark: '请输入备注',
placeholderCount: '请输入数量' placeholderCount: '请输入数量',
qrcodeLoadError: '二维码加载失败',
qrcodeEmpty: '暂无二维码',
validatorCodeRequired: '编码不能为空',
validatorNameRequired: '名称不能为空'
}, },
// 项目维护 // 项目维护
DvSubject: { DvSubject: {
@ -1531,6 +1540,7 @@ export default {
SpareInfo: { SpareInfo: {
name: '备件名称', name: '备件名称',
code: '备件编码', code: '备件编码',
qrcode: '二维码/条形码',
standard: '规格', standard: '规格',
expiryDay: '保质期天数', expiryDay: '保质期天数',
category: '分类', category: '分类',
@ -1548,6 +1558,8 @@ export default {
placeholderRemark: '请输入备注', placeholderRemark: '请输入备注',
placeholderStandard: '请输入规格', placeholderStandard: '请输入规格',
placeholderExpiryDay: '请输入保质期天数', placeholderExpiryDay: '请输入保质期天数',
qrcodeLoadError: '二维码加载失败',
qrcodeEmpty: '暂无二维码',
validatorNameRequired: '备件名称不能为空', validatorNameRequired: '备件名称不能为空',
validatorCodeRequired: '备件编码不能为空', validatorCodeRequired: '备件编码不能为空',
validatorCategoryRequired: '分类不能为空', validatorCategoryRequired: '分类不能为空',
@ -1731,6 +1743,7 @@ export default {
unit: '单位', unit: '单位',
inTime: '入库时间', inTime: '入库时间',
status: '状态', status: '状态',
qrcode: '二维码/条形码',
images: '模具图片', images: '模具图片',
remark: '备注', remark: '备注',
isEnable: '是否启用', isEnable: '是否启用',
@ -1757,7 +1770,9 @@ export default {
validatorIsEnableRequired: '是否启用不能为空', validatorIsEnableRequired: '是否启用不能为空',
validatorBrandRequired: '型号不能为空', validatorBrandRequired: '型号不能为空',
selectBrandTip: '请选择一个模具型号', selectBrandTip: '请选择一个模具型号',
statusTooltip: '来源:数据字典-模具状态' statusTooltip: '来源:数据字典-模具状态',
qrcodeLoadError: '二维码加载失败',
qrcodeEmpty: '暂无二维码'
}, },
MoldReturn: { MoldReturn: {
@ -2642,6 +2657,7 @@ export default {
tableOperateColumn: '操作', tableOperateColumn: '操作',
tableEditAction: '编辑', tableEditAction: '编辑',
tableDeleteAction: '删除', tableDeleteAction: '删除',
qrcode: '二维码/条形码',
dialogBarCodeLabel: '编码', dialogBarCodeLabel: '编码',
dialogBarCodePlaceholder: '请输入编码', dialogBarCodePlaceholder: '请输入编码',
dialogNameLabel: '名称', dialogNameLabel: '名称',
@ -2667,6 +2683,8 @@ export default {
dialogRemarkPlaceholder: '请输入备注', dialogRemarkPlaceholder: '请输入备注',
dialogCancelButton: '取 消', dialogCancelButton: '取 消',
dialogSubmitButton: '确 定', dialogSubmitButton: '确 定',
qrcodeLoadError: '二维码加载失败',
qrcodeEmpty: '暂无二维码',
validatorNameRequired: '产品名称不能为空', validatorNameRequired: '产品名称不能为空',
validatorBarCodeRequired: '产品条码不能为空', validatorBarCodeRequired: '产品条码不能为空',
validatorCategoryRequired: '产品分类编号不能为空', validatorCategoryRequired: '产品分类编号不能为空',

@ -11,7 +11,24 @@
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="12"> <el-col :span="12">
<el-form-item :label="t('SparePartsManagement.SpareInfo.code')" prop="barCode"> <el-form-item :label="t('SparePartsManagement.SpareInfo.code')" prop="barCode">
<el-input v-model="formData.barCode" :placeholder="t('SparePartsManagement.SpareInfo.placeholderCode')" /> <el-row :gutter="10" style="width: 100%">
<el-col :xs="24" :sm="18" :md="16" :lg="14" :xl="12">
<el-input
v-model="formData.barCode"
:placeholder="t('SparePartsManagement.SpareInfo.placeholderCode')"
:disabled="Boolean(formData.isCode) || formType === 'update'"
/>
</el-col>
<el-col :xs="24" :sm="6" :md="4" :lg="3" :xl="2">
<div>
<el-switch
v-model="formData.isCode"
:disabled="formType === 'update'"
@change="handleCodeAutoChange"
/>
</div>
</el-col>
</el-row>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
@ -82,6 +99,39 @@
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col v-if="formType === 'update'" :span="12">
<el-form-item :label="t('SparePartsManagement.SpareInfo.qrcode')" prop="qrcodeUrl">
<div class="product-form-qrcode-wrap">
<el-image
v-if="formData.qrcodeUrl"
:src="formData.qrcodeUrl"
:preview-src-list="[formData.qrcodeUrl]"
preview-teleported
fit="cover"
class="product-form-qrcode-img"
>
<template #error>
<div class="product-form-qrcode-error">
{{ t('SparePartsManagement.SpareInfo.qrcodeLoadError') }}
</div>
</template>
</el-image>
<div v-else class="product-form-qrcode-error">
{{ t('SparePartsManagement.SpareInfo.qrcodeEmpty') }}
</div>
<div class="product-form-qrcode-mask">
<el-button
circle
:loading="regenerateCodeLoading"
:disabled="regenerateCodeLoading"
@click="handleRegenerateCode"
>
<Icon icon="ep:refresh" />
</el-button>
</div>
</div>
</el-form-item>
</el-col>
<el-col :span="24"> <el-col :span="24">
<el-form-item :label="t('SparePartsManagement.SpareInfo.remark')" prop="remark"> <el-form-item :label="t('SparePartsManagement.SpareInfo.remark')" prop="remark">
<el-input type="textarea" v-model="formData.remark" :placeholder="t('SparePartsManagement.SpareInfo.placeholderRemark')"/> <el-input type="textarea" v-model="formData.remark" :placeholder="t('SparePartsManagement.SpareInfo.placeholderRemark')"/>
@ -99,7 +149,6 @@
import { ProductApi, ProductVO } from '@/api/erp/product/product' import { ProductApi, ProductVO } from '@/api/erp/product/product'
import { ProductUnitApi, ProductUnitVO } from '@/api/erp/product/unit' import { ProductUnitApi, ProductUnitVO } from '@/api/erp/product/unit'
import { CommonStatusEnum } from '@/utils/constants' import { CommonStatusEnum } from '@/utils/constants'
import { defaultProps, handleTree } from '@/utils/tree'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
/** ERP 产品 表单 */ /** ERP 产品 表单 */
@ -112,10 +161,13 @@ const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') // const dialogTitle = ref('') //
const formLoading = ref(false) // 12 const formLoading = ref(false) // 12
const formType = ref('') // create - update - const formType = ref('') // create - update -
const regenerateCodeLoading = ref(false)
const formData = ref({ const formData = ref({
id: undefined, id: undefined,
name: undefined, name: undefined,
barCode: undefined, barCode: undefined,
isCode: undefined,
qrcodeUrl: undefined,
categoryId: undefined, categoryId: undefined,
unitId: undefined, unitId: undefined,
status: undefined, status: undefined,
@ -128,9 +180,20 @@ const formData = ref({
minPrice: undefined, minPrice: undefined,
safetyNumber: undefined safetyNumber: undefined
}) })
const validateBarCode = (_rule, value, callback) => {
if (Boolean(formData.value.isCode)) {
callback()
return
}
if (value === undefined || value === null || String(value).trim() === '') {
callback(new Error(t('SparePartsManagement.SpareInfo.validatorCodeRequired')))
return
}
callback()
}
const formRules = reactive({ const formRules = reactive({
name: [{ required: true, message: t('SparePartsManagement.SpareInfo.validatorNameRequired'), trigger: 'blur' }], name: [{ required: true, message: t('SparePartsManagement.SpareInfo.validatorNameRequired'), trigger: 'blur' }],
barCode: [{ required: true, message: t('SparePartsManagement.SpareInfo.validatorCodeRequired'), trigger: 'blur' }], barCode: [{ validator: validateBarCode, trigger: ['blur', 'change'] }],
categoryId: [{ required: true, message: t('SparePartsManagement.SpareInfo.validatorCategoryRequired'), trigger: 'blur' }], categoryId: [{ required: true, message: t('SparePartsManagement.SpareInfo.validatorCategoryRequired'), trigger: 'blur' }],
unitId: [{ required: true, message: t('SparePartsManagement.SpareInfo.validatorUnitRequired'), trigger: 'blur' }], unitId: [{ required: true, message: t('SparePartsManagement.SpareInfo.validatorUnitRequired'), trigger: 'blur' }],
status: [{ required: true, message: t('SparePartsManagement.SpareInfo.validatorStatusRequired'), trigger: 'blur' }] status: [{ required: true, message: t('SparePartsManagement.SpareInfo.validatorStatusRequired'), trigger: 'blur' }]
@ -158,6 +221,31 @@ const open = async (type: string, id?: number) => {
} }
defineExpose({ open }) // open defineExpose({ open }) // open
const handleCodeAutoChange = (value: boolean) => {
if (value) {
formData.value.barCode = undefined
}
formRef.value?.clearValidate('barCode')
}
const handleRegenerateCode = async () => {
if (!formData.value.id || !formData.value.barCode) return
regenerateCodeLoading.value = true
try {
const data = await ProductApi.regenerateCode(formData.value.id, formData.value.barCode)
if (data?.qrcodeUrl) {
formData.value.qrcodeUrl = data.qrcodeUrl
} else {
const productData = await ProductApi.getProduct(formData.value.id)
formData.value.qrcodeUrl = productData?.qrcodeUrl
formData.value.barCode = productData?.barCode ?? formData.value.barCode
}
message.success(t('common.updateSuccess'))
} finally {
regenerateCodeLoading.value = false
}
}
/** 提交表单 */ /** 提交表单 */
const emit = defineEmits(['success']) // success const emit = defineEmits(['success']) // success
const submitForm = async () => { const submitForm = async () => {
@ -189,6 +277,8 @@ const resetForm = () => {
id: undefined, id: undefined,
name: undefined, name: undefined,
barCode: undefined, barCode: undefined,
isCode: true,
qrcodeUrl: undefined,
categoryId: undefined, categoryId: undefined,
unitId: undefined, unitId: undefined,
status: CommonStatusEnum.ENABLE, status: CommonStatusEnum.ENABLE,
@ -203,3 +293,47 @@ const resetForm = () => {
formRef.value?.resetFields() formRef.value?.resetFields()
} }
</script> </script>
<style scoped lang="scss">
.product-form-qrcode-wrap {
position: relative;
height: 150px;
width: fit-content;
min-width: 150px;
border-radius: 10px;
overflow: hidden;
border: 1px solid var(--el-border-color-lighter);
background: var(--el-fill-color-blank);
}
.product-form-qrcode-img {
height: 150px;
width: auto;
display: block;
}
.product-form-qrcode-error {
height: 150px;
min-width: 150px;
display: flex;
align-items: center;
justify-content: center;
color: var(--el-text-color-secondary);
font-size: 12px;
padding: 0 12px;
}
.product-form-qrcode-mask {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.35);
opacity: 0;
transition: opacity 0.2s ease;
}
.product-form-qrcode-wrap:hover .product-form-qrcode-mask {
opacity: 1;
}
</style>

@ -4,15 +4,28 @@
ref="formRef" ref="formRef"
:model="formData" :model="formData"
:rules="formRules" :rules="formRules"
label-width="100px" label-width="120px"
v-loading="formLoading" v-loading="formLoading"
> >
<el-form-item :label="t('MoldManagement.Mold.code')" prop="code"> <el-form-item :label="t('MoldManagement.Mold.code')" prop="code">
<el-input <el-row :gutter="10" style="width: 100%">
v-model="formData.code" <el-col :xs="24" :sm="18" :md="16" :lg="14" :xl="12">
:placeholder="t('MoldManagement.Mold.placeholderCode')" <el-input
:disabled="formType == 'update'" v-model="formData.code"
/> :placeholder="t('MoldManagement.Mold.placeholderCode')"
:disabled="formData.isCode == true || formType === 'update'"
/>
</el-col>
<el-col :xs="24" :sm="6" :md="4" :lg="3" :xl="2">
<div>
<el-switch
v-model="formData.isCode"
:disabled="formType === 'update'"
@change="handleCodeAutoChange"
/>
</div>
</el-col>
</el-row>
</el-form-item> </el-form-item>
<el-form-item :label="t('MoldManagement.Mold.name')" prop="name"> <el-form-item :label="t('MoldManagement.Mold.name')" prop="name">
<el-input <el-input
@ -71,6 +84,33 @@
</el-radio> </el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<el-form-item v-if="formType === 'update'" :label="t('MoldManagement.Mold.qrcode')" prop="qrcodeUrl">
<div class="mold-form-qrcode-wrap">
<el-image
v-if="formData.qrcodeUrl"
:src="formData.qrcodeUrl"
:preview-src-list="[formData.qrcodeUrl]"
preview-teleported
fit="cover"
class="mold-form-qrcode-img"
>
<template #error>
<div class="mold-form-qrcode-error">{{ t('MoldManagement.Mold.qrcodeLoadError') }}</div>
</template>
</el-image>
<div v-else class="mold-form-qrcode-error">{{ t('MoldManagement.Mold.qrcodeEmpty') }}</div>
<div class="mold-form-qrcode-mask">
<el-button
circle
:loading="regenerateCodeLoading"
:disabled="regenerateCodeLoading"
@click="handleRegenerateCode"
>
<Icon icon="ep:refresh" />
</el-button>
</div>
</div>
</el-form-item>
<el-form-item :label="t('MoldManagement.Mold.images')" prop="images"> <el-form-item :label="t('MoldManagement.Mold.images')" prop="images">
<UploadImg v-model="formData.images" /> <UploadImg v-model="formData.images" />
</el-form-item> </el-form-item>
@ -117,9 +157,11 @@ const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') // const dialogTitle = ref('') //
const formLoading = ref(false) // 12 const formLoading = ref(false) // 12
const formType = ref('') // create - update - const formType = ref('') // create - update -
const regenerateCodeLoading = ref(false)
const formData = ref({ const formData = ref({
id: undefined, id: undefined,
code: undefined, code: undefined,
isCode: undefined,
name: undefined, name: undefined,
unitId: undefined, unitId: undefined,
machineId: undefined, machineId: undefined,
@ -127,14 +169,26 @@ const formData = ref({
inTime: undefined, inTime: undefined,
status: undefined, status: undefined,
images: undefined, images: undefined,
qrcodeUrl: undefined,
remark: undefined, remark: undefined,
isEnable: undefined, isEnable: undefined,
fileUrl: '', fileUrl: '',
brandId: undefined brandId: undefined
}) })
const validateCode = (_rule, value, callback) => {
if (Boolean(formData.value.isCode)) {
callback()
return
}
if (value === undefined || value === null || String(value).trim() === '') {
callback(new Error(t('MoldManagement.Mold.validatorCodeRequired')))
return
}
callback()
}
const formRules = reactive({ const formRules = reactive({
code: [ code: [
{ required: true, message: t('MoldManagement.Mold.validatorCodeRequired'), trigger: 'blur' } { validator: validateCode, trigger: ['blur', 'change'] }
], ],
name: [ name: [
{ required: true, message: t('MoldManagement.Mold.validatorNameRequired'), trigger: 'blur' } { required: true, message: t('MoldManagement.Mold.validatorNameRequired'), trigger: 'blur' }
@ -176,6 +230,31 @@ const open = async (type: string, id?: number, brandId: number) => {
} }
defineExpose({ open }) // open defineExpose({ open }) // open
const handleCodeAutoChange = (value: boolean) => {
if (value) {
formData.value.code = undefined
}
formRef.value?.clearValidate('code')
}
const handleRegenerateCode = async () => {
if (!formData.value.id || !formData.value.code) return
regenerateCodeLoading.value = true
try {
const data = await MoldBrandApi.regenerateCode(formData.value.id, formData.value.code)
if (data?.qrcodeUrl) {
formData.value.qrcodeUrl = data.qrcodeUrl
} else {
const moldData = await MoldBrandApi.getMold(formData.value.id)
formData.value.qrcodeUrl = moldData?.qrcodeUrl
formData.value.code = moldData?.code ?? formData.value.code
}
message.success(t('common.updateSuccess'))
} finally {
regenerateCodeLoading.value = false
}
}
/** 提交表单 */ /** 提交表单 */
const emit = defineEmits(['success']) // success const emit = defineEmits(['success']) // success
const submitForm = async () => { const submitForm = async () => {
@ -205,6 +284,7 @@ const resetForm = () => {
formData.value = { formData.value = {
id: undefined, id: undefined,
code: undefined, code: undefined,
isCode: true,
name: undefined, name: undefined,
unitId: undefined, unitId: undefined,
machineId: undefined, machineId: undefined,
@ -212,6 +292,7 @@ const resetForm = () => {
inTime: undefined, inTime: undefined,
status: 3, status: 3,
images: undefined, images: undefined,
qrcodeUrl: undefined,
remark: undefined, remark: undefined,
isEnable: true, isEnable: true,
fileUrl: '', fileUrl: '',
@ -220,3 +301,47 @@ const resetForm = () => {
formRef.value?.resetFields() formRef.value?.resetFields()
} }
</script> </script>
<style scoped lang="scss">
.mold-form-qrcode-wrap {
position: relative;
height: 150px;
width: fit-content;
min-width: 150px;
border-radius: 10px;
overflow: hidden;
border: 1px solid var(--el-border-color-lighter);
background: var(--el-fill-color-blank);
}
.mold-form-qrcode-img {
height: 150px;
width: auto;
display: block;
}
.mold-form-qrcode-error {
height: 150px;
min-width: 150px;
display: flex;
align-items: center;
justify-content: center;
color: var(--el-text-color-secondary);
font-size: 12px;
padding: 0 12px;
}
.mold-form-qrcode-mask {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.35);
opacity: 0;
transition: opacity 0.2s ease;
}
.mold-form-qrcode-wrap:hover .mold-form-qrcode-mask {
opacity: 1;
}
</style>

@ -11,7 +11,24 @@
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="12"> <el-col :span="12">
<el-form-item :label="t('FactoryModeling.ProductInformation.dialogBarCodeLabel')" prop="barCode"> <el-form-item :label="t('FactoryModeling.ProductInformation.dialogBarCodeLabel')" prop="barCode">
<el-input v-model="formData.barCode" :placeholder="t('FactoryModeling.ProductInformation.dialogBarCodePlaceholder')" :disabled = "formType === 'update'"/> <el-row :gutter="10" style="width: 100%">
<el-col :xs="24" :sm="18" :md="16" :lg="14" :xl="12">
<el-input
v-model="formData.barCode"
:placeholder="t('FactoryModeling.ProductInformation.dialogBarCodePlaceholder')"
:disabled="Boolean(formData.isCode) || formType === 'update'"
/>
</el-col>
<el-col :xs="24" :sm="6" :md="4" :lg="3" :xl="2">
<div>
<el-switch
v-model="formData.isCode"
:disabled="formType === 'update'"
@change="handleCodeAutoChange"
/>
</div>
</el-col>
</el-row>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
@ -116,6 +133,39 @@
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col v-if="formType === 'update'" :span="12">
<el-form-item :label="t('FactoryModeling.ProductInformation.qrcode')" prop="qrcodeUrl">
<div class="product-form-qrcode-wrap">
<el-image
v-if="formData.qrcodeUrl"
:src="formData.qrcodeUrl"
:preview-src-list="[formData.qrcodeUrl]"
preview-teleported
fit="cover"
class="product-form-qrcode-img"
>
<template #error>
<div class="product-form-qrcode-error">
{{ t('FactoryModeling.ProductInformation.qrcodeLoadError') }}
</div>
</template>
</el-image>
<div v-else class="product-form-qrcode-error">
{{ t('FactoryModeling.ProductInformation.qrcodeEmpty') }}
</div>
<div class="product-form-qrcode-mask">
<el-button
circle
:loading="regenerateCodeLoading"
:disabled="regenerateCodeLoading"
@click="handleRegenerateCode"
>
<Icon icon="ep:refresh" />
</el-button>
</div>
</div>
</el-form-item>
</el-col>
<el-col :span="24"> <el-col :span="24">
<el-form-item :label="t('FactoryModeling.ProductInformation.dialogRemarkLabel')" prop="remark"> <el-form-item :label="t('FactoryModeling.ProductInformation.dialogRemarkLabel')" prop="remark">
<el-input type="textarea" v-model="formData.remark" :placeholder="t('FactoryModeling.ProductInformation.dialogRemarkPlaceholder')" /> <el-input type="textarea" v-model="formData.remark" :placeholder="t('FactoryModeling.ProductInformation.dialogRemarkPlaceholder')" />
@ -147,10 +197,13 @@ const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') // const dialogTitle = ref('') //
const formLoading = ref(false) // 12 const formLoading = ref(false) // 12
const formType = ref('') // create - update - const formType = ref('') // create - update -
const regenerateCodeLoading = ref(false)
const formData = ref({ const formData = ref({
id: undefined, id: undefined,
name: undefined, name: undefined,
barCode: undefined, barCode: undefined,
isCode: undefined,
qrcodeUrl: undefined,
categoryId: undefined, categoryId: undefined,
unitId: undefined, unitId: undefined,
status: undefined, status: undefined,
@ -163,9 +216,20 @@ const formData = ref({
minPrice: undefined, minPrice: undefined,
safetyNumber: undefined safetyNumber: undefined
}) })
const validateBarCode = (_rule, value, callback) => {
if (Boolean(formData.value.isCode)) {
callback()
return
}
if (value === undefined || value === null || String(value).trim() === '') {
callback(new Error(t('FactoryModeling.ProductInformation.validatorBarCodeRequired')))
return
}
callback()
}
const formRules = reactive({ const formRules = reactive({
name: [{ required: true, message: t('FactoryModeling.ProductInformation.validatorNameRequired'), trigger: 'blur' }], name: [{ required: true, message: t('FactoryModeling.ProductInformation.validatorNameRequired'), trigger: 'blur' }],
barCode: [{ required: true, message: t('FactoryModeling.ProductInformation.validatorBarCodeRequired'), trigger: 'blur' }], barCode: [{ validator: validateBarCode, trigger: ['blur', 'change'] }],
categoryId: [{ required: true, message: t('FactoryModeling.ProductInformation.validatorCategoryRequired'), trigger: 'blur' }], categoryId: [{ required: true, message: t('FactoryModeling.ProductInformation.validatorCategoryRequired'), trigger: 'blur' }],
unitId: [{ required: true, message: t('FactoryModeling.ProductInformation.validatorUnitRequired'), trigger: 'blur' }], unitId: [{ required: true, message: t('FactoryModeling.ProductInformation.validatorUnitRequired'), trigger: 'blur' }],
status: [{ required: true, message: t('FactoryModeling.ProductInformation.validatorStatusRequired'), trigger: 'blur' }] status: [{ required: true, message: t('FactoryModeling.ProductInformation.validatorStatusRequired'), trigger: 'blur' }]
@ -197,6 +261,31 @@ const open = async (type: string, id?: number) => {
} }
defineExpose({ open }) // open defineExpose({ open }) // open
const handleCodeAutoChange = (value: boolean) => {
if (value) {
formData.value.barCode = undefined
}
formRef.value?.clearValidate('barCode')
}
const handleRegenerateCode = async () => {
if (!formData.value.id || !formData.value.barCode) return
regenerateCodeLoading.value = true
try {
const data = await ProductApi.regenerateCode(formData.value.id, formData.value.barCode)
if (data?.qrcodeUrl) {
formData.value.qrcodeUrl = data.qrcodeUrl
} else {
const productData = await ProductApi.getProduct(formData.value.id)
formData.value.qrcodeUrl = productData?.qrcodeUrl
formData.value.barCode = productData?.barCode ?? formData.value.barCode
}
message.success(t('common.updateSuccess'))
} finally {
regenerateCodeLoading.value = false
}
}
/** 提交表单 */ /** 提交表单 */
const emit = defineEmits(['success']) // success const emit = defineEmits(['success']) // success
const submitForm = async () => { const submitForm = async () => {
@ -227,6 +316,8 @@ const resetForm = () => {
id: undefined, id: undefined,
name: undefined, name: undefined,
barCode: undefined, barCode: undefined,
isCode: true,
qrcodeUrl: undefined,
categoryId: undefined, categoryId: undefined,
unitId: undefined, unitId: undefined,
status: CommonStatusEnum.ENABLE, status: CommonStatusEnum.ENABLE,
@ -241,3 +332,47 @@ const resetForm = () => {
formRef.value?.resetFields() formRef.value?.resetFields()
} }
</script> </script>
<style scoped lang="scss">
.product-form-qrcode-wrap {
position: relative;
height: 150px;
width: fit-content;
min-width: 150px;
border-radius: 10px;
overflow: hidden;
border: 1px solid var(--el-border-color-lighter);
background: var(--el-fill-color-blank);
}
.product-form-qrcode-img {
height: 150px;
width: auto;
display: block;
}
.product-form-qrcode-error {
height: 150px;
min-width: 150px;
display: flex;
align-items: center;
justify-content: center;
color: var(--el-text-color-secondary);
font-size: 12px;
padding: 0 12px;
}
.product-form-qrcode-mask {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.35);
opacity: 0;
transition: opacity 0.2s ease;
}
.product-form-qrcode-wrap:hover .product-form-qrcode-mask {
opacity: 1;
}
</style>

@ -2,22 +2,26 @@
<Dialog :title="dialogTitle" v-model="dialogVisible"> <Dialog :title="dialogTitle" v-model="dialogVisible">
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="120px" v-loading="formLoading"> <el-form ref="formRef" :model="formData" :rules="formRules" label-width="120px" v-loading="formLoading">
<el-form-item :label="t('DataCollection.Device.attributeCode')" prop="attributeCode"> <el-form-item :label="t('DataCollection.Device.attributeCode')" prop="attributeCode">
<el-input v-model="formData.attributeCode" :placeholder="t('DataCollection.Device.placeholderAttributeCode')" <el-input
v-model="formData.attributeCode" :placeholder="t('DataCollection.Device.placeholderAttributeCode')"
@input="handleAttributeCodeInput" :disabled="formType === 'update'" /> @input="handleAttributeCodeInput" :disabled="formType === 'update'" />
</el-form-item> </el-form-item>
<el-form-item :label="t('DataCollection.Device.attributeName')" prop="attributeName"> <el-form-item :label="t('DataCollection.Device.attributeName')" prop="attributeName">
<el-input v-model="formData.attributeName" :placeholder="t('DataCollection.Device.placeholderAttributeName')" /> <el-input v-model="formData.attributeName" :placeholder="t('DataCollection.Device.placeholderAttributeName')" />
</el-form-item> </el-form-item>
<el-form-item :label="t('DataCollection.Device.attributeType')" prop="attributeType"> <el-form-item :label="t('DataCollection.Device.attributeType')" prop="attributeType">
<el-select v-model="formData.attributeType" clearable filterable <el-select
v-model="formData.attributeType" clearable filterable
:placeholder="t('DataCollection.Device.placeholderAttributeType')" @change="handleAttributeTypeChange"> :placeholder="t('DataCollection.Device.placeholderAttributeType')" @change="handleAttributeTypeChange">
<el-option v-for="item in typeList" :key="item.id" :label="item.name" :value="item.id" /> <el-option v-for="item in typeList" :key="item.id" :label="item.name" :value="item.id" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item :label="t('DataCollection.Device.dataType')" prop="dataType"> <el-form-item :label="t('DataCollection.Device.dataType')" prop="dataType">
<el-select v-model="formData.dataType" :placeholder="t('DataCollection.Device.placeholderDataType')" <el-select
v-model="formData.dataType" :placeholder="t('DataCollection.Device.placeholderDataType')"
:disabled="formType === 'update'"> :disabled="formType === 'update'">
<el-option v-for="dict in getStrDictOptions(DICT_TYPE.IOT_DEVICE_DATA_TYPE)" :key="dict.value" <el-option
v-for="dict in getStrDictOptions(DICT_TYPE.IOT_DEVICE_DATA_TYPE)" :key="dict.value"
:label="dict.label" :value="dict.value" /> :label="dict.label" :value="dict.value" />
</el-select> </el-select>
</el-form-item> </el-form-item>
@ -25,17 +29,20 @@
<el-input v-model="formData.address" :placeholder="t('DataCollection.Device.placeholderAddress')" /> <el-input v-model="formData.address" :placeholder="t('DataCollection.Device.placeholderAddress')" />
</el-form-item> </el-form-item>
<el-form-item :label="t('DataCollection.Device.dataUnit')" prop="dataUnit"> <el-form-item :label="t('DataCollection.Device.dataUnit')" prop="dataUnit">
<el-select v-model="formData.dataUnit" clearable <el-select
v-model="formData.dataUnit" clearable
:placeholder="t('DataCollection.DeviceModel.placeholderDataUnit')" class="w-1/1"> :placeholder="t('DataCollection.DeviceModel.placeholderDataUnit')" class="w-1/1">
<el-option v-for="unit in unitList" :key="unit.id" :label="unit.name" :value="unit.id" /> <el-option v-for="unit in unitList" :key="unit.id" :label="unit.name" :value="unit.id" />
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item :label="t('DataCollection.Device.ratio')" prop="ratio"> <el-form-item :label="t('DataCollection.Device.ratio')" prop="ratio">
<el-input-number v-model="formData.ratio" :placeholder="t('DataCollection.Device.placeholderRatio')" :min="0.00" <el-input-number
v-model="formData.ratio" :placeholder="t('DataCollection.Device.placeholderRatio')" :min="0.00"
:decision="2" :step="0.01" class="!w-full" :disabled="!ratioEnabled" /> :decision="2" :step="0.01" class="!w-full" :disabled="!ratioEnabled" />
</el-form-item> </el-form-item>
<el-form-item :label="t('DataCollection.Device.remark')" prop="remark"> <el-form-item :label="t('DataCollection.Device.remark')" prop="remark">
<el-input v-model="formData.remark" :placeholder="t('DataCollection.Device.placeholderRemark')" <el-input
v-model="formData.remark" :placeholder="t('DataCollection.Device.placeholderRemark')"
type="textarea" /> type="textarea" />
</el-form-item> </el-form-item>
</el-form> </el-form>

@ -2,7 +2,7 @@
<ContentWrap> <ContentWrap>
<div > <div >
<el-calendar v-model="currentDate" > <el-calendar v-model="currentDate" >
<template v-slot:header="{ date }"> <template #header="{ date }">
<div class="custom-header-with-buttons"> <div class="custom-header-with-buttons">
<!-- 左侧标题和统计信息 --> <!-- 左侧标题和统计信息 -->
<div class="header-left"> <div class="header-left">
@ -24,13 +24,15 @@
@click="handleWorkday(date)" @click="handleWorkday(date)"
>{{ t('FactoryModeling.CalHoliday.setWorkingDays') }} >{{ t('FactoryModeling.CalHoliday.setWorkingDays') }}
</el-button> </el-button>
<el-button style="margin-left: 10px" <el-button
style="margin-left: 10px"
type="success" type="success"
size="small" size="small"
@click="handleHoliday(date)" @click="handleHoliday(date)"
>{{ t('FactoryModeling.CalHoliday.setHoliday') }} >{{ t('FactoryModeling.CalHoliday.setHoliday') }}
</el-button> </el-button>
<el-button style="margin-left: 10px" <el-button
style="margin-left: 10px"
size="small" size="small"
:icon="ArrowLeft" :icon="ArrowLeft"
@click="handleOriginalPrevMonth" @click="handleOriginalPrevMonth"
@ -55,7 +57,7 @@
</div> </div>
</template> </template>
<template v-slot:date-cell="{ data }"> <template #date-cell="{ data }">
<div> <div>
<el-row> <el-row>
<el-col :span="16"> <el-col :span="16">

@ -1,13 +1,26 @@
<template> <template>
<Dialog :title="dialogTitle" v-model="dialogVisible" width="720px"> <Dialog :title="dialogTitle" v-model="dialogVisible" width="720px">
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="90px" v-loading="formLoading"> <el-form ref="formRef" :model="formData" :rules="formRules" label-width="120px" v-loading="formLoading">
<el-form-item :label="t('EquipmentManagement.EquipmentKeyItems.code')" prop="code"> <el-form-item :label="t('EquipmentManagement.EquipmentKeyItems.code')" prop="code">
<el-input <el-row :gutter="10" style="width: 100%">
v-model="formData.code" <el-col :xs="24" :sm="18" :md="16" :lg="14" :xl="12">
:placeholder="t('EquipmentManagement.EquipmentKeyItems.placeholderCode')" <el-input
clearable v-model="formData.code"
:disabled="formType === 'update'" :placeholder="t('EquipmentManagement.EquipmentKeyItems.placeholderCode')"
/> clearable
:disabled="Boolean(formData.isCode) || formType === 'update'"
/>
</el-col>
<el-col :xs="24" :sm="6" :md="4" :lg="3" :xl="2">
<div>
<el-switch
v-model="formData.isCode"
:disabled="formType === 'update'"
@change="handleCodeAutoChange"
/>
</div>
</el-col>
</el-row>
</el-form-item> </el-form-item>
<el-form-item :label="t('EquipmentManagement.EquipmentKeyItems.name')" prop="name"> <el-form-item :label="t('EquipmentManagement.EquipmentKeyItems.name')" prop="name">
<el-input <el-input
@ -32,6 +45,33 @@
type="number" type="number"
/> />
</el-form-item> </el-form-item>
<el-form-item v-if="formType === 'update'" :label="t('EquipmentManagement.EquipmentKeyItems.qrcode')" prop="qrcodeUrl">
<div class="critical-component-qrcode-wrap">
<el-image
v-if="formData.qrcodeUrl"
:src="formData.qrcodeUrl"
:preview-src-list="[formData.qrcodeUrl]"
preview-teleported
fit="cover"
class="critical-component-qrcode-img"
>
<template #error>
<div class="critical-component-qrcode-error">{{ t('EquipmentManagement.EquipmentKeyItems.qrcodeLoadError') }}</div>
</template>
</el-image>
<div v-else class="critical-component-qrcode-error">{{ t('EquipmentManagement.EquipmentKeyItems.qrcodeEmpty') }}</div>
<div class="critical-component-qrcode-mask">
<el-button
circle
:loading="regenerateCodeLoading"
:disabled="regenerateCodeLoading"
@click="handleRegenerateCode"
>
<Icon icon="ep:refresh" />
</el-button>
</div>
</div>
</el-form-item>
<el-form-item :label="t('EquipmentManagement.EquipmentKeyItems.remark')" prop="remark"> <el-form-item :label="t('EquipmentManagement.EquipmentKeyItems.remark')" prop="remark">
<el-input <el-input
v-model="formData.remark" v-model="formData.remark"
@ -60,47 +100,97 @@ const dialogVisible = ref(false)
const dialogTitle = ref('') const dialogTitle = ref('')
const formLoading = ref(false) const formLoading = ref(false)
const formType = ref<'create' | 'update'>('create') const formType = ref<'create' | 'update'>('create')
const regenerateCodeLoading = ref(false)
const formRef = ref() const formRef = ref()
const formData = ref<Partial<CriticalComponentVO>>({ const formData = ref<Partial<CriticalComponentVO>>({
id: undefined, id: undefined,
code: undefined, code: undefined,
isCode: undefined,
name: undefined, name: undefined,
description: undefined, description: undefined,
count: undefined, count: undefined,
remark: undefined remark: undefined,
qrcodeUrl: undefined
}) })
const validateCode = (_rule, value, callback) => {
if (Boolean(formData.value.isCode)) {
callback()
return
}
if (value === undefined || value === null || String(value).trim() === '') {
callback(new Error(t('EquipmentManagement.EquipmentKeyItems.validatorCodeRequired')))
return
}
callback()
}
const formRules = reactive({ const formRules = reactive({
code: [{ required: true, message: '编码不能为空', trigger: 'blur' }], code: [{ validator: validateCode, trigger: ['blur', 'change'] }],
name: [{ required: true, message: '名称不能为空', trigger: 'blur' }] name: [{ required: true, message: t('EquipmentManagement.EquipmentKeyItems.validatorNameRequired'), trigger: 'blur' }]
}) })
const resetForm = () => { const resetForm = () => {
formData.value = { formData.value = {
id: undefined, id: undefined,
code: undefined, code: undefined,
isCode: true,
name: undefined, name: undefined,
description: undefined, description: undefined,
count: undefined, count: undefined,
remark: undefined remark: undefined,
qrcodeUrl: undefined
} }
formRef.value?.resetFields?.() formRef.value?.resetFields?.()
} }
const open = (type: 'create' | 'update', row?: Partial<CriticalComponentVO>) => { const handleCodeAutoChange = (value: boolean) => {
if (value) {
formData.value.code = undefined
}
formRef.value?.clearValidate('code')
}
const handleRegenerateCode = async () => {
if (!formData.value.id || !formData.value.code) return
regenerateCodeLoading.value = true
try {
const data = await CriticalComponentApi.regenerateCode(formData.value.id, formData.value.code)
if (data?.qrcodeUrl) {
formData.value.qrcodeUrl = data.qrcodeUrl
} else {
const detail = await CriticalComponentApi.getCriticalComponent(formData.value.id)
formData.value.qrcodeUrl = detail?.qrcodeUrl
formData.value.code = detail?.code ?? formData.value.code
}
message.success(t('common.updateSuccess'))
} finally {
regenerateCodeLoading.value = false
}
}
const open = async (type: 'create' | 'update', id?: number) => {
dialogVisible.value = true dialogVisible.value = true
dialogTitle.value = t('action.' + type) dialogTitle.value = t('action.' + type)
formType.value = type formType.value = type
resetForm() resetForm()
if (type === 'update' && row) { if (type === 'update' && id) {
formData.value = { formLoading.value = true
id: row.id, try {
code: row.code, const detail = await CriticalComponentApi.getCriticalComponent(id)
name: row.name, formData.value = {
description: row.description, id: detail?.id,
count: row.count, code: detail?.code,
remark: row.remark isCode: detail?.isCode ?? false,
name: detail?.name,
description: detail?.description,
count: detail?.count,
remark: detail?.remark,
qrcodeUrl: detail?.qrcodeUrl
}
} finally {
formLoading.value = false
} }
} }
} }
@ -127,4 +217,48 @@ const submitForm = async () => {
} }
} }
</script> </script>
<style scoped lang="scss">
.critical-component-qrcode-wrap {
position: relative;
height: 150px;
width: fit-content;
min-width: 150px;
border-radius: 10px;
overflow: hidden;
border: 1px solid var(--el-border-color-lighter);
background: var(--el-fill-color-blank);
}
.critical-component-qrcode-img {
height: 150px;
width: auto;
display: block;
}
.critical-component-qrcode-error {
height: 150px;
min-width: 150px;
display: flex;
align-items: center;
justify-content: center;
color: var(--el-text-color-secondary);
font-size: 12px;
padding: 0 12px;
}
.critical-component-qrcode-mask {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.35);
opacity: 0;
transition: opacity 0.2s ease;
}
.critical-component-qrcode-wrap:hover .critical-component-qrcode-mask {
opacity: 1;
}
</style>

@ -90,7 +90,7 @@ v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true"
fixed="right"> fixed="right">
<template #default="scope"> <template #default="scope">
<el-button <el-button
link type="primary" @click="openForm('update', scope.row)" link type="primary" @click="openForm('update', scope.row.id)"
v-hasPermi="['mes:critical-component:update']"> v-hasPermi="['mes:critical-component:update']">
{{ t('EquipmentManagement.EquipmentKeyItems.edit') }} {{ t('EquipmentManagement.EquipmentKeyItems.edit') }}
</el-button> </el-button>
@ -217,8 +217,8 @@ const handleSelectionChange = (rows: CriticalComponentVO[]) => {
} }
const formRef = ref() const formRef = ref()
const openForm = (type: 'create' | 'update', row?: Partial<CriticalComponentVO>) => { const openForm = (type: 'create' | 'update', id?: number) => {
formRef.value.open(type, row) formRef.value.open(type, id)
} }
const openImport = () => { const openImport = () => {

@ -15,8 +15,25 @@
</el-col> --> </el-col> -->
<el-col :span="12"> <el-col :span="12">
<el-form-item :label="t('EquipmentManagement.EquipmentLedger.deviceCode')" prop="deviceCode" required> <el-form-item :label="t('EquipmentManagement.EquipmentLedger.deviceCode')" prop="deviceCode">
<el-input :disabled = "formType == 'update'" v-model="formData.deviceCode" :placeholder="t('EquipmentManagement.EquipmentLedger.placeholderDeviceCode')" /> <el-row :gutter="10" style="width: 100%">
<el-col :xs="24" :sm="18" :md="16" :lg="14" :xl="12">
<el-input
v-model="formData.deviceCode"
:placeholder="t('EquipmentManagement.EquipmentLedger.placeholderDeviceCode')"
:disabled="Boolean(formData.isCode) || formType === 'update'"
/>
</el-col>
<el-col :xs="24" :sm="6" :md="4" :lg="3" :xl="2">
<div>
<el-switch
v-model="formData.isCode"
:disabled="formType === 'update'"
@change="handleCodeAutoChange"
/>
</div>
</el-col>
</el-row>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
@ -180,6 +197,36 @@
<UploadFile :is-show-tip="false" v-model="formData.fileUrl" :limit="9" /> <UploadFile :is-show-tip="false" v-model="formData.fileUrl" :limit="9" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col v-if="formType === 'update'" :span="24">
<el-form-item :label="t('EquipmentManagement.EquipmentLedger.qrcode')" prop="qrcodeUrl">
<div class="device-ledger-qrcode-wrap">
<el-image
v-if="formData.qrcodeUrl"
:src="formData.qrcodeUrl"
:preview-src-list="[formData.qrcodeUrl]"
preview-teleported
fit="cover"
class="device-ledger-qrcode-img"
>
<template #error>
<div class="device-ledger-qrcode-error">{{ t('EquipmentManagement.EquipmentLedger.qrcodeLoadError') }}</div>
</template>
</el-image>
<div v-else class="device-ledger-qrcode-error">{{ t('EquipmentManagement.EquipmentLedger.qrcodeEmpty') }}</div>
<div class="device-ledger-qrcode-mask">
<el-button
circle
:loading="regenerateCodeLoading"
:disabled="regenerateCodeLoading"
@click="handleRegenerateCode"
>
<Icon icon="ep:refresh" />
</el-button>
</div>
</div>
</el-form-item>
</el-col>
<el-col :span="24"> <el-col :span="24">
<el-form-item :label="t('EquipmentManagement.EquipmentLedger.remark')" prop="remark"> <el-form-item :label="t('EquipmentManagement.EquipmentLedger.remark')" prop="remark">
<el-input v-model="formData.remark" :placeholder="t('EquipmentManagement.EquipmentLedger.placeholderRemark')" type="textarea" /> <el-input v-model="formData.remark" :placeholder="t('EquipmentManagement.EquipmentLedger.placeholderRemark')" type="textarea" />
@ -256,6 +303,7 @@ const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') // const dialogTitle = ref('') //
const formLoading = ref(false) // 12 const formLoading = ref(false) // 12
const formType = ref('') // create - update - const formType = ref('') // create - update -
const regenerateCodeLoading = ref(false)
const deviceList = ref<DeviceVO[]>([]) // const deviceList = ref<DeviceVO[]>([]) //
const parseIdsValue = (value: any): number[] => { const parseIdsValue = (value: any): number[] => {
@ -352,6 +400,7 @@ const normalizeFileUrlAsJsonArrayString = (value: any): string | undefined => {
const initFormData = () => ({ const initFormData = () => ({
id: undefined, id: undefined,
deviceCode: undefined, deviceCode: undefined,
isCode: true,
deviceName: undefined, deviceName: undefined,
deviceStatus: undefined, deviceStatus: undefined,
deviceBrand: undefined, deviceBrand: undefined,
@ -369,6 +418,7 @@ const initFormData = () => ({
componentIds: [] as number[], componentIds: [] as number[],
beijianIds: [] as number[], beijianIds: [] as number[],
fileUrl: undefined, fileUrl: undefined,
qrcodeUrl: undefined,
sort: undefined, sort: undefined,
dvId: undefined dvId: undefined
}) })
@ -376,8 +426,19 @@ const initFormData = () => ({
const formData = ref({ const formData = ref({
...initFormData() ...initFormData()
}) })
const validateDeviceCode = (_rule, value, callback) => {
if (Boolean(formData.value.isCode)) {
callback()
return
}
if (value === undefined || value === null || String(value).trim() === '') {
callback(new Error(t('EquipmentManagement.EquipmentLedger.validatorDeviceCodeRequired')))
return
}
callback()
}
const formRules = reactive<FormRules>({ const formRules = reactive<FormRules>({
deviceCode: [{ required: true, message: t('EquipmentManagement.EquipmentLedger.placeholderDeviceCode'), trigger: 'blur' }], deviceCode: [{ validator: validateDeviceCode, trigger: ['blur', 'change'] }],
deviceName: [{ required: true, message: t('EquipmentManagement.EquipmentLedger.placeholderDeviceName'), trigger: 'blur' }], deviceName: [{ required: true, message: t('EquipmentManagement.EquipmentLedger.placeholderDeviceName'), trigger: 'blur' }],
deviceType: [{ required: true, message: t('EquipmentManagement.EquipmentLedger.placeholderDeviceType'), trigger: 'change' }], deviceType: [{ required: true, message: t('EquipmentManagement.EquipmentLedger.placeholderDeviceType'), trigger: 'change' }],
productionDate: [{ required: true, message: t('EquipmentManagement.EquipmentLedger.placeholderProductionDate'), trigger: 'change' }], productionDate: [{ required: true, message: t('EquipmentManagement.EquipmentLedger.placeholderProductionDate'), trigger: 'change' }],
@ -446,6 +507,31 @@ const confirmBeijianDialog = () => {
beijianDialogVisible.value = false beijianDialogVisible.value = false
} }
const handleCodeAutoChange = (value: boolean) => {
if (value) {
formData.value.deviceCode = undefined
}
formRef.value?.clearValidate('deviceCode')
}
const handleRegenerateCode = async () => {
if (!formData.value.id || !formData.value.deviceCode) return
regenerateCodeLoading.value = true
try {
const data = await DeviceLedgerApi.regenerateCode(formData.value.id, formData.value.deviceCode)
if (data?.qrcodeUrl) {
formData.value.qrcodeUrl = data.qrcodeUrl
} else {
const detail = await DeviceLedgerApi.getDeviceLedger(formData.value.id)
formData.value.qrcodeUrl = detail?.qrcodeUrl
formData.value.deviceCode = detail?.deviceCode ?? formData.value.deviceCode
}
message.success(t('common.updateSuccess'))
} finally {
regenerateCodeLoading.value = false
}
}
const ensureOptionsLoaded = async () => { const ensureOptionsLoaded = async () => {
const [deviceTypeRes, userRes, criticalRes, beijianRes] = await Promise.all([ const [deviceTypeRes, userRes, criticalRes, beijianRes] = await Promise.all([
DeviceTypeApi.getDeviceTypeTree({ pageNo: 1, pageSize: 10 }), DeviceTypeApi.getDeviceTypeTree({ pageNo: 1, pageSize: 10 }),
@ -487,12 +573,14 @@ const open = async (type: string, id?: number, defaultDeviceTypeId?: number) =>
formData.value = { formData.value = {
...initFormData(), ...initFormData(),
...(detail as any), ...(detail as any),
isCode: (detail as any)?.isCode ?? false,
deviceType: normalizeNumberish((detail as any)?.deviceType), deviceType: normalizeNumberish((detail as any)?.deviceType),
deviceManagerIds: parseIdsValue((detail as any)?.deviceManager), deviceManagerIds: parseIdsValue((detail as any)?.deviceManager),
productionDate: normalizeYmd((detail as any)?.productionDate), productionDate: normalizeYmd((detail as any)?.productionDate),
factoryEntryDate: normalizeYmd((detail as any)?.factoryEntryDate), factoryEntryDate: normalizeYmd((detail as any)?.factoryEntryDate),
componentIds: parseIdsValue((detail as any)?.componentId), componentIds: parseIdsValue((detail as any)?.componentId),
beijianIds: parseIdsValue((detail as any)?.beijianId), beijianIds: parseIdsValue((detail as any)?.beijianId),
qrcodeUrl: (detail as any)?.qrcodeUrl,
} }
} finally { } finally {
formLoading.value = false formLoading.value = false
@ -605,4 +693,47 @@ const resetForm = () => {
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.device-ledger-qrcode-wrap {
position: relative;
height: 150px;
width: fit-content;
min-width: 150px;
border-radius: 10px;
overflow: hidden;
border: 1px solid var(--el-border-color-lighter);
background: var(--el-fill-color-blank);
}
.device-ledger-qrcode-img {
height: 150px;
width: auto;
display: block;
}
.device-ledger-qrcode-error {
height: 150px;
min-width: 150px;
display: flex;
align-items: center;
justify-content: center;
color: var(--el-text-color-secondary);
font-size: 12px;
padding: 0 12px;
}
.device-ledger-qrcode-mask {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.35);
opacity: 0;
transition: opacity 0.2s ease;
}
.device-ledger-qrcode-wrap:hover .device-ledger-qrcode-mask {
opacity: 1;
}
</style> </style>

@ -148,6 +148,21 @@ link type="danger" @click="handleDelete(scope.row.id)"
<el-descriptions-item :label="t('EquipmentManagement.EquipmentLedger.creatorName')">{{ detailData?.creatorName ?? '' }}</el-descriptions-item> <el-descriptions-item :label="t('EquipmentManagement.EquipmentLedger.creatorName')">{{ detailData?.creatorName ?? '' }}</el-descriptions-item>
<el-descriptions-item :label="t('EquipmentManagement.EquipmentLedger.createTime')">{{ formatDetailDate(detailData?.createTime) }}</el-descriptions-item> <el-descriptions-item :label="t('EquipmentManagement.EquipmentLedger.createTime')">{{ formatDetailDate(detailData?.createTime) }}</el-descriptions-item>
<el-descriptions-item :label="t('EquipmentManagement.EquipmentLedger.updateTime')">{{ formatDetailDate(detailData?.updateTime) }}</el-descriptions-item> <el-descriptions-item :label="t('EquipmentManagement.EquipmentLedger.updateTime')">{{ formatDetailDate(detailData?.updateTime) }}</el-descriptions-item>
<el-descriptions-item v-if="detailData?.qrcodeUrl" label="" :span="3">
<div class="device-ledger-detail-qrcode">
<el-image
:src="detailData.qrcodeUrl"
:preview-src-list="[detailData.qrcodeUrl]"
preview-teleported
fit="cover"
class="device-ledger-detail-qrcode-img"
>
<template #error>
<div class="device-ledger-detail-qrcode-error">{{ t('EquipmentManagement.EquipmentLedger.qrcodeLoadError') }}</div>
</template>
</el-image>
</div>
</el-descriptions-item>
<!-- <el-table-column :label="t('EquipmentManagement.EquipmentLedger.creatorName')" align="center" prop="creatorName" width="150px" sortable /> <!-- <el-table-column :label="t('EquipmentManagement.EquipmentLedger.creatorName')" align="center" prop="creatorName" width="150px" sortable />
@ -1194,4 +1209,28 @@ onMounted(async () => {
.device-ledger-history-item-text { .device-ledger-history-item-text {
color: var(--el-text-color-regular); color: var(--el-text-color-regular);
} }
.device-ledger-detail-qrcode {
width: fit-content;
padding: 8px;
border-radius: 10px;
border: 1px solid var(--el-border-color-lighter);
background: var(--el-fill-color-blank);
}
.device-ledger-detail-qrcode-img {
width: auto;
height: 150px;
display: block;
}
.device-ledger-detail-qrcode-error {
width: 150px;
height: 150px;
display: flex;
align-items: center;
justify-content: center;
color: var(--el-text-color-secondary);
font-size: 12px;
}
</style> </style>

Loading…
Cancel
Save