feat:模具组页面重构

main
黄伟杰 1 week ago
parent e2e2bbe383
commit 25954461e3

@ -6,13 +6,26 @@ export interface MoldBrandVO {
code: string // 型号编码
name: string // 型号名称
moldType: string // 规格
productId: number // 产品ID
useTime: number // 预期寿命(小时)
maintainType: number // 维保模式
maintainTime: number // 维保周期
productId?: number // 产品ID
productName?: string // 产品名称
productIds?: Array<number> | string // 产品ID列表
products?: any[]
images?: string // 图片
version?: string // 版本
status?: number // 状态
currentPosition?: string // 当前位置
currentDevice?: string // 当前设备
machineName?: string // 当前设备
useTime?: number // 预期寿命(小时)
maintainType?: number // 维保模式
maintainTime?: number // 维保周期
moldSize: number // 模具系数
remark: string // 备注
remark?: string // 备注
isEnable: boolean // 是否启用
isCode?: boolean // 是否自动生成编码
orgType?: string
createTime?: string
qrCodeUrl?: string
}
export interface MoldBrandTreeVO extends MoldBrandVO {

@ -605,6 +605,17 @@ const remainingRouter: AppRouteRecordRaw[] = [
name: 'ErpCenter',
meta: { hidden: true },
children: [
{
path: 'mold-brand/detail/:id',
name: 'ErpMoldBrandDetail',
meta: {
title: '模具组详情',
noCache: true,
hidden: true,
activeMenu: '/erp/mold'
},
component: () => import('@/views/erp/mold/detail/brand.vue')
},
{
path: 'mold/detail/:id',
name: 'ErpMoldDetail',

@ -1,210 +1,247 @@
<template>
<Dialog :title="dialogTitle" v-model="dialogVisible">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="100px"
v-loading="formLoading"
>
<el-form-item :label="t('MoldManagement.MoldBrand.code')" prop="code">
<el-input
v-model="formData.code"
:placeholder="t('MoldManagement.MoldBrand.placeholderCode')"
:disabled="formType == 'update'"
/>
</el-form-item>
<el-form-item :label="t('MoldManagement.MoldBrand.name')" prop="name">
<el-input
v-model="formData.name"
:placeholder="t('MoldManagement.MoldBrand.placeholderName')"
/>
</el-form-item>
<el-form-item :label="t('MoldManagement.MoldBrand.moldType')" prop="moldType">
<el-input
v-model="formData.moldType"
:placeholder="t('MoldManagement.MoldBrand.placeholderMoldType')"
/>
</el-form-item>
<el-form-item prop="orgType">
<template #label>
<span>
{{ t('MoldManagement.MoldBrand.orgType') }}
<el-tooltip :content="t('MoldManagement.MoldBrand.orgTypeTooltip')" placement="top">
<Icon icon="ep:question-filled" />
</el-tooltip>
</span>
</template>
<el-select
v-model="formData.orgType"
:placeholder="t('MoldManagement.MoldBrand.placeholderOrgType')"
>
<el-option
v-for="dict in getStrDictOptions(DICT_TYPE.MES_ORG_TYPE)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item :label="t('MoldManagement.MoldBrand.moldSize')" prop="moldSize">
<el-input
v-model="formData.moldSize"
:placeholder="t('MoldManagement.MoldBrand.placeholderMoldSize')"
/>
</el-form-item>
<el-form-item :label="t('MoldManagement.MoldBrand.useTime')" prop="useTime">
<el-input-number
v-model="formData.useTime"
:min="0"
class="!w-1/1"
:placeholder="t('MoldManagement.MoldBrand.placeholderUseTime')"
/>
</el-form-item>
<!-- <el-form-item label="维保模式" prop="maintainType">
<el-select v-model="formData.maintainType" placeholder="请选择维保模式">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.ERP_MAINTAIN_TYPE)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="维保周期" prop="maintainTime">
<el-input-number
v-model="formData.maintainTime"
:min="0"
:precision="2"
class="!w-1/1"
placeholder="输入维保周期"
/>
</el-form-item> -->
<el-form-item :label="t('MoldManagement.MoldBrand.remark')" prop="remark">
<el-input
v-model="formData.remark"
:placeholder="t('MoldManagement.MoldBrand.placeholderRemark')"
type="textarea"
/>
</el-form-item>
<Dialog :title="dialogTitle" v-model="dialogVisible" width="720px">
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px" v-loading="formLoading">
<el-row :gutter="16">
<el-col :span="12">
<el-form-item label="编码" prop="code">
<el-row :gutter="20" style="width: 100%">
<el-col :span="18">
<el-input v-model="formData.code" placeholder="请输入型号编码" :disabled="Boolean(formData.isCode) || formType === 'update'" />
</el-col>
<el-col :span="6">
<div>
<el-switch v-model="formData.isCode" :disabled="formType === 'update'" @change="handleCodeAutoChange" />
</div>
</el-col>
</el-row>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入型号名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="产品型号" prop="productId">
<el-select v-model="formData.productId" filterable clearable placeholder="请选择产品型号" class="w-1/1" @change="handleProductChange">
<el-option v-for="item in productList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="版本" prop="version">
<el-input v-model="formData.version" placeholder="请输入版本号如1.0" @input="handleVersionInput" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="状态" prop="status">
<el-select v-model="formData.status" placeholder="请选择状态" class="w-1/1">
<el-option v-for="dict in statusOptions" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="模穴数" prop="moldSize">
<el-input-number v-model="formData.moldSize" :min="1" :precision="0" class="w-1/1" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="预期寿命" prop="useTime">
<el-input-number v-model="formData.useTime" :min="0" class="w-1/1" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="图片" prop="images">
<UploadImg v-model="formData.images" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="备注" prop="remark">
<el-input v-model="formData.remark" type="textarea" placeholder="请输入备注" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="是否启用" prop="isEnable">
<el-radio-group v-model="formData.isEnable">
<el-radio :label="true">启用</el-radio>
<el-radio :label="false">停用</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<el-button @click="submitForm" type="primary" :disabled="formLoading">
{{ t('action.save') }}
</el-button>
<el-button @click="dialogVisible = false">{{ t('dialog.close') }}</el-button>
<el-button type="primary" :disabled="formLoading" @click="submitForm"></el-button>
<el-button @click="dialogVisible = false">关闭</el-button>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { getIntDictOptions, DICT_TYPE, getStrDictOptions } from '@/utils/dict'
import { MoldBrandApi, MoldBrandVO } from '@/api/erp/mold'
import { ProductApi, ProductVO } from '@/api/erp/product/product'
/** 模具型号 表单 */
import { MoldBrandApi, type MoldBrandVO } from '@/api/erp/mold'
import { ProductApi, type ProductVO } from '@/api/erp/product/product'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { useDictStoreWithOut } from '@/store/modules/dict'
defineOptions({ name: 'MoldBrandForm' })
const { t } = useI18n() //
const message = useMessage() //
const productList = ref<ProductVO[]>([]) //
const dialogVisible = ref(false) //
const dialogTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
const formData = ref({
id: undefined,
code: undefined,
name: undefined,
moldType: undefined,
const message = useMessage()
const dictStore = useDictStoreWithOut()
const productList = ref<ProductVO[]>([])
const dialogVisible = ref(false)
const dialogTitle = ref('')
const formLoading = ref(false)
const formType = ref('')
const formRef = ref()
const statusOptions = computed(() => getIntDictOptions(DICT_TYPE.ERP_MOLD_STATUS))
const formData = ref<MoldBrandVO>({
id: undefined as unknown as number,
code: '',
name: '',
moldType: '',
productId: undefined,
useTime: undefined,
productName: '',
productIds: [],
images: '',
version: '',
status: 0,
useTime: 0,
maintainType: undefined,
maintainTime: undefined,
moldSize: undefined,
remark: undefined,
isEnable: undefined
moldSize: 1,
remark: '',
isEnable: true,
isCode: true
})
const validateCode = (_rule: any, value: any, callback: any) => {
if (Boolean(formData.value.isCode)) {
callback()
return
}
if (value === undefined || value === null || String(value).trim() === '') {
callback(new Error('型号编码不能为空'))
return
}
callback()
}
const handleCodeAutoChange = (value: boolean) => {
if (value) {
formData.value.code = ''
}
formRef.value?.clearValidate('code')
}
const handleVersionInput = (value: string) => {
formData.value.version = value.replace(/[^0-9.]/g, '').replace(/(\..*)\./g, '$1')
}
const stripVersionPrefix = (val: any): string => {
if (!val) return ''
return String(val).replace(/^V/i, '')
}
const addVersionPrefix = (val: any): string => {
if (!val) return ''
const raw = String(val).replace(/^V/i, '')
return raw ? `V${raw}` : ''
}
const formRules = reactive({
code: [
{ required: true, message: t('MoldManagement.MoldBrand.validatorCodeRequired'), trigger: 'blur' }
],
name: [
{ required: true, message: t('MoldManagement.MoldBrand.validatorNameRequired'), trigger: 'blur' }
],
moldType: [
{ required: true, message: t('MoldManagement.MoldBrand.validatorMoldTypeRequired'), trigger: 'blur' }
],
productId: [
{ required: true, message: t('MoldManagement.MoldBrand.validatorProductRequired'), trigger: 'blur' }
],
moldSize: [
{ required: true, message: t('MoldManagement.MoldBrand.validatorMoldSizeRequired'), trigger: 'blur' }
]
code: [{ validator: validateCode, trigger: 'blur' }],
name: [{ required: true, message: '型号名称不能为空', trigger: 'blur' }],
moldType: [{ required: true, message: '模具类型不能为空', trigger: 'blur' }],
productId: [{ required: true, message: '产品型号不能为空', trigger: 'change' }],
moldSize: [{ required: true, message: '模穴数不能为空', trigger: 'blur' }],
isEnable: [{ required: true, message: '请选择启用状态', trigger: 'change' }]
})
const formRef = ref() // Ref
/** 打开弹窗 */
const resetForm = () => {
formData.value = {
id: undefined as unknown as number,
code: '',
name: '',
moldType: '',
productId: undefined,
productName: '',
productIds: [],
images: '',
version: '',
status: 0,
useTime: 0,
maintainType: undefined,
maintainTime: undefined,
moldSize: 1,
remark: '',
isEnable: true,
isCode: true
}
formRef.value?.resetFields()
}
const handleProductChange = (value?: number) => {
const current = productList.value.find((item) => item.id === value)
formData.value.productName = current?.name || ''
formData.value.productIds = value ? [value] : []
}
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
dialogTitle.value = type === 'create' ? '新增模具组' : '编辑模具组'
formType.value = type
resetForm()
//
if (id) {
formLoading.value = true
try {
formData.value = await MoldBrandApi.getMoldBrand(id)
} finally {
formLoading.value = false
await dictStore.setDictMap()
productList.value = await ProductApi.getMesProductSimpleList()
if (!id) return
formLoading.value = true
try {
const data = await MoldBrandApi.getMoldBrand(id)
formData.value = {
...formData.value,
...data,
isCode: (data as any)?.isCode ?? false,
version: stripVersionPrefix(data?.version),
productIds: Array.isArray(data?.productIds)
? data.productIds
: data?.productId
? [data.productId]
: []
}
} finally {
formLoading.value = false
}
//
productList.value = await ProductApi.getMesProductSimpleList()
}
defineExpose({ open }) // open
defineExpose({ open })
const emit = defineEmits(['success'])
/** 提交表单 */
const emit = defineEmits(['success']) // success
const submitForm = async () => {
//
await formRef.value.validate()
//
formLoading.value = true
try {
formData.value.isEnable = true
const data = formData.value as unknown as MoldBrandVO
handleProductChange(formData.value.productId)
const payload: MoldBrandVO = {
...formData.value,
version: addVersionPrefix(formData.value.version),
productIds: Array.isArray(formData.value.productIds)
? formData.value.productIds
: formData.value.productId
? [formData.value.productId]
: [],
isEnable: Boolean(formData.value.isEnable)
}
if (formType.value === 'create') {
await MoldBrandApi.createMoldBrand(data)
message.success(t('common.createSuccess'))
await MoldBrandApi.createMoldBrand(payload)
message.success('新增成功')
} else {
await MoldBrandApi.updateMoldBrand(data)
message.success(t('common.updateSuccess'))
await MoldBrandApi.updateMoldBrand(payload)
message.success('更新成功')
}
dialogVisible.value = false
//
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
code: undefined,
name: undefined,
moldType: undefined,
productId: undefined,
useTime: undefined,
maintainType: undefined,
maintainTime: undefined,
moldSize: undefined,
remark: undefined,
isEnable: undefined
}
formRef.value?.resetFields()
}
</script>

@ -1,18 +1,12 @@
<template>
<div class="mold-panel">
<div class="mold-panel__header">
<div class="mold-panel__title">{{ dialogTitle }}</div>
<el-button text @click="closeForm">
<Icon icon="ep:close" />
</el-button>
</div>
<div class="mold-dialog" v-loading="formLoading">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="120px"
>
<Dialog :title="dialogTitle" v-model="dialogVisible" width="800px">
<el-form
ref="formRef"
:model="formData"
:rules="formRules"
label-width="120px"
v-loading="formLoading"
>
<el-form-item :label="t('MoldManagement.Mold.code')" prop="code">
<el-row :gutter="10" style="width: 100%">
<el-col :xs="24" :sm="18" :md="16" :lg="14" :xl="12">
@ -134,14 +128,13 @@
<UploadFile v-model="formData.fileUrl" :limit="1" :isShowTip="false"/>
</el-form-item>
</el-form>
</div>
<div class="mold-footer">
<template #footer>
<el-button @click="closeForm">{{ t('dialog.close') }}</el-button>
<el-button @click="submitForm" type="primary" :disabled="formLoading">
{{ t('action.save') }}
</el-button>
</div>
</div>
</template>
</Dialog>
</template>
<script setup lang="ts">
import { getIntDictOptions, DICT_TYPE, getBoolDictOptions } from '@/utils/dict'
@ -153,6 +146,7 @@ const unitList = ref<ProductUnitVO[]>([]) // 产品单位列表
const { t } = useI18n() //
const message = useMessage() //
const dialogVisible = ref(false) //
const dialogTitle = ref('') //
const formLoading = ref(false) // 12
const formType = ref('') // create - update -
@ -209,6 +203,7 @@ const formRef = ref() // 表单 Ref
/** 打开弹窗 */
const open = async (type: string, id?: number, brandId: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
@ -300,6 +295,7 @@ const submitForm = async () => {
}
const closeForm = () => {
dialogVisible.value = false
emit('closed')
}
@ -328,55 +324,4 @@ const resetForm = () => {
</script>
<style lang="scss" scoped>
.mold-panel {
background: #fff;
border-radius: 12px;
box-shadow: var(--el-box-shadow-light);
}
.mold-panel__header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 20px;
border-bottom: 1px solid #ebeef5;
}
.mold-panel__title {
color: #1f2937;
font-size: 18px;
font-weight: 600;
}
.mold-dialog {
max-height: calc(100vh - 220px);
padding: 20px 20px 0;
padding-right: 16px;
overflow-y: auto;
}
.mold-footer {
display: flex;
justify-content: flex-end;
gap: 12px;
width: 100%;
padding: 16px 20px 20px;
border-top: 1px solid #ebeef5;
}
@media (max-width: 768px) {
.mold-panel__header {
padding: 14px 16px;
}
.mold-dialog {
max-height: none;
padding: 16px 16px 0;
overflow-y: visible;
}
.mold-footer {
padding: 16px;
}
}
</style>

@ -1,91 +1,52 @@
<template>
<!-- 列表 -->
<ContentWrap>
<el-button type="primary" plain @click="openForm('create')" v-hasPermi="['erp:mold-brand:create']">
<Icon icon="ep:plus" class="mr-5px" /> {{ t('action.add') }}
</el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['erp:mold-brand:export']"
>
<Icon icon="ep:download" class="mr-5px" /> {{ t('action.export') }}
</el-button>
<div class="mb-12px">
<el-button type="primary" plain @click="openForm('create')" v-hasPermi="['erp:mold-brand:create']">
<Icon icon="ep:plus" class="mr-5px" /> 新增子模具
</el-button>
<el-button type="success" plain @click="handleExport" :loading="exportLoading" v-hasPermi="['erp:mold-brand:export']">
<Icon icon="ep:download" class="mr-5px" /> 批量导出
</el-button>
</div>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column :label="t('MoldManagement.Mold.code')" align="center" prop="code" sortable />
<el-table-column :label="t('MoldManagement.Mold.name')" align="left" prop="name" sortable />
<!-- <el-table-column label="单位" align="center" prop="unitName" sortable /> -->
<el-table-column :label="t('MoldManagement.Mold.useTime')" align="center" prop="useTime" sortable />
<el-table-column :label="t('MoldManagement.Mold.inTime')" align="center" prop="inTime" :formatter="dateFormatter" sortable />
<el-table-column :label="t('MoldManagement.Mold.status')" align="center" prop="status" sortable>
<el-table-column label="序号" type="index" width="70" align="center" />
<el-table-column label="子模具名称" prop="name" min-width="160" />
<el-table-column label="类型" prop="moldType" min-width="120" />
<el-table-column label="安装位置" min-width="140">
<template #default="scope">
<dict-tag :type="DICT_TYPE.ERP_MOLD_STATUS" :value="scope.row.status" />
{{ scope.row.installPosition || scope.row.currentPosition || scope.row.machineName || '-' }}
</template>
</el-table-column>
<el-table-column :label="t('MoldManagement.Mold.machineName')" align="center" prop="machineName" sortable />
<!-- <el-table-column label="模具图片" align="center" prop="images" /> -->
<el-table-column :label="t('MoldManagement.Mold.remark')" align="center" prop="remark" />
<el-table-column :label="t('MoldManagement.Mold.isEnable')" align="center" prop="isEnable">
<el-table-column label="材质" min-width="120">
<template #default="scope">
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.isEnable" />
{{ scope.row.material || '-' }}
</template>
</el-table-column>
<el-table-column :label="t('MoldManagement.Mold.createTime')" align="center" prop="createTime" :formatter="dateFormatter" width="180px" sortable />
<el-table-column :label="t('MoldManagement.Mold.operate')" align="center" fixed="right" width="200px">
<el-table-column label="数量" width="90" align="center">
<template #default="scope">
<el-button link @click="openDetail(scope.row.id)">
{{ t('MoldManagement.Mold.detail') }}
</el-button>
<!-- <el-button
link
type="success"
@click="openRecordForm('create', scope.row.id, scope.row.brandId)"
v-hasPermi="['mes:mold-record:update']"
>
维保
</el-button> -->
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['erp:mold-brand:update']"
>
{{ t('action.edit') }}
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['erp:mold-brand:delete']"
>
{{ t('action.del') }}
</el-button>
{{ scope.row.quantity || scope.row.count || 1 }}
</template>
</el-table-column>
<el-table-column label="操作" fixed="right" width="160">
<template #default="scope">
<el-button link type="primary" @click="openForm('update', scope.row.id)">编辑</el-button>
<el-button link type="danger" @click="handleDelete(scope.row.id)"></el-button>
</template>
</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>
<!-- 表单弹窗添加/修改 -->
<MoldForm ref="formRef" @success="getList" />
<!-- 表单弹窗添加/修改 -->
<MoldRecordForm ref="recordFormRef" @success="getList" />
</template>
<script setup lang="ts">
import { DICT_TYPE } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import { defineAsyncComponent, nextTick } from 'vue'
import download from '@/utils/download'
import { MoldBrandApi } from '@/api/erp/mold'
import MoldForm from './MoldForm.vue'
import MoldRecordForm from "@/views/erp/mold/components/MoldRecordForm.vue";
const { t } = useI18n() //
const message = useMessage() //
const MoldForm = defineAsyncComponent(() => import('./MoldForm.vue') as any)
const MoldRecordForm = defineAsyncComponent(() => import('@/views/erp/mold/components/MoldRecordForm.vue') as any)
const props = defineProps<{
brandId?: number // id
@ -113,11 +74,6 @@ const getList = async () => {
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 监听主表的关联字段的变化,加载对应的子表数据 */
watch(
() => props.brandId,
@ -126,18 +82,24 @@ watch(
return
}
queryParams.brandId = val
handleQuery()
queryParams.pageNo = 1
getList()
},
{ immediate: true, deep: true }
)
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
const openForm = async (type: string, id?: number) => {
if (!props.brandId) {
message.error('请选择一个模具型号')
return
}
await nextTick()
if (!formRef.value || typeof formRef.value.open !== 'function') {
message.error('表单组件未就绪,请稍后重试')
return
}
formRef.value.open(type, id, props.brandId)
}
@ -148,7 +110,7 @@ const handleDelete = async (id: number) => {
await message.delConfirm()
//
await MoldBrandApi.deleteMold(id)
message.success(t('common.delSuccess'))
message.success('删除成功')
//
await getList()
} catch { }
@ -167,7 +129,7 @@ const handleExport = async () => {
...queryParams,
brandId: props.brandId
})
download.excel(data, '模具.xls')
download.excel(data, '模具.xls')
} catch {
} finally {
exportLoading.value = false
@ -179,10 +141,5 @@ const recordFormRef = ref()
const openRecordForm = (type: string, id?: number, brandId?: number) => {
recordFormRef.value.open(type, id, brandId)
}
const { push } = useRouter()
const openDetail = (id: number) => {
push({ name: 'ErpMoldDetail', params: { id } })
}
</script>

@ -0,0 +1,738 @@
<template>
<ContentWrap>
<div v-loading="detailLoading" class="mold-brand-detail">
<div class="mold-brand-detail__crumb">模具详情页面 / 模具台账 / 模具详情</div>
<div class="mold-brand-detail__hero">
<div class="mold-brand-detail__left">
<div class="mold-brand-detail__image-card">
<el-image
v-if="imageList.length"
:src="imageList[0]"
:preview-src-list="imageList"
fit="cover"
preview-teleported
class="mold-brand-detail__image"
/>
<el-empty v-else description="暂无图片" />
</div>
<div class="mold-brand-detail__qr-card">
<div class="mold-brand-detail__qr-title">模具二维码</div>
<el-image
v-if="detailData?.qrCodeUrl"
:src="detailData.qrCodeUrl"
:preview-src-list="[detailData.qrCodeUrl]"
fit="contain"
preview-teleported
class="mold-brand-detail__qr"
/>
<el-empty v-else description="暂无二维码" />
<div class="mold-brand-detail__qr-code">{{ detailData?.code || '-' }}</div>
</div>
</div>
<div class="mold-brand-detail__main">
<div class="mold-brand-detail__topbar">
<div>
<div class="mold-brand-detail__title-row">
<span class="mold-brand-detail__title">{{ detailData?.name || '-' }}<span v-if="detailData?.version">{{ detailData.version }}</span></span>
<el-tag v-if="detailData?.status !== undefined" type="success" effect="light">
<dict-tag :type="DICT_TYPE.ERP_MOLD_STATUS" :value="detailData?.status" />
</el-tag>
</div>
</div>
<div class="mold-brand-detail__actions">
<el-button type="primary" @click="handleReservedAction('上模')"></el-button>
<el-button type="success" @click="handleReservedAction('下模')"></el-button>
<el-button type="warning" @click="handleReservedAction('维修')"></el-button>
<el-dropdown>
<el-button>更多<Icon icon="ep:arrow-down" class="ml-4px" /></el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="handleReservedAction('更多操作')"></el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
<div class="mold-brand-detail__grid">
<div class="mold-brand-detail__field"><span>编码</span><strong>{{ detailData?.code || '-' }}</strong></div>
<div class="mold-brand-detail__field"><span>当前设备</span><strong>{{ detailData?.currentDevice || detailData?.machineName || '-' }}</strong></div>
<div class="mold-brand-detail__field"><span>产品型号</span><strong>{{ detailData?.productName || '-' }}</strong></div>
<div class="mold-brand-detail__field"><span>版本</span><strong>{{ detailData?.version || '-' }}</strong></div>
<div class="mold-brand-detail__field"><span>安装时间</span><strong>{{ formatSimpleDate(detailData?.installTime) }}</strong></div>
<div class="mold-brand-detail__field"><span>客户</span><strong>{{ detailData?.customerName || detailData?.customer || '-' }}</strong></div>
<div class="mold-brand-detail__field mold-brand-detail__field--life">
<span>寿命状态</span>
<div v-if="lifeRate !== null" class="mold-brand-detail__life">
<el-progress :percentage="lifeRate" :stroke-width="10" :show-text="true" />
</div>
<strong v-else>-</strong>
</div>
<div class="mold-brand-detail__field"><span>模具类型</span><strong>{{ detailData?.moldType || '-' }}</strong></div>
<div class="mold-brand-detail__field"><span>子模数量</span><strong>{{ childMolds.length || detailData?.moldSize || 0 }}</strong></div>
</div>
<div class="mold-brand-detail__tabs">
<el-tabs v-model="activeTab">
<el-tab-pane label="子模具" name="molds">
<component :is="MoldListComp" :brand-id="brandId" />
</el-tab-pane>
<el-tab-pane label="点检记录" name="inspection">
<div class="device-ledger-tab-toolbar">
<el-date-picker
v-model="inspectionDateRange"
type="daterange"
value-format="YYYY-MM-DD HH:mm:ss"
start-placeholder="开始时间"
end-placeholder="结束时间"
range-separator="至"
:unlink-panels="true"
class="mr-10px"
/>
<el-button type="primary" plain @click="handleQueryInspection"></el-button>
<el-button @click="handleResetInspection"></el-button>
<el-button type="success" plain :loading="inspectionExportLoading" @click="handleExportInspection"></el-button>
</div>
<el-empty v-if="!inspectionStepGroups.length" />
<el-steps v-else direction="vertical" :active="inspectionStepGroups.length" class="device-ledger-history-steps">
<el-step v-for="group in inspectionStepGroups" :key="group.key">
<template #title>
<div class="device-ledger-history-title">
<span class="device-ledger-history-time">[{{ group.time }}]</span>
<span class="device-ledger-history-operator">操作人: {{ group.operator }}</span>
</div>
</template>
<template #description>
<div class="device-ledger-history-items">
<div v-for="item in group.items" :key="item.key" class="device-ledger-history-item">
<div class="device-ledger-history-item-head">
<el-tag :type="getResultTagType(item.result)">{{ getResultLabel(item.result) }}</el-tag>
<span class="device-ledger-history-item-text">{{ item.name }}</span>
</div>
<div class="device-ledger-history-item-body">
<div class="device-ledger-history-item-row"><span class="device-ledger-history-item-label">点检方式</span><span class="device-ledger-history-item-value"><dict-tag type="Inspection_method" :value="item.method" /></span></div>
<div class="device-ledger-history-item-row"><span class="device-ledger-history-item-label">判定标准</span><span class="device-ledger-history-item-value">{{ item.criteria ?? '-' }}</span></div>
<div class="device-ledger-history-item-row"><span class="device-ledger-history-item-label">点检时间</span><span class="device-ledger-history-item-value">{{ formatHistoryTime(item.taskTime) }}</span></div>
<div class="device-ledger-history-item-row"><span class="device-ledger-history-item-label">备注</span><span class="device-ledger-history-item-value">{{ item.remark ?? '-' }}</span></div>
</div>
</div>
</div>
</template>
</el-step>
</el-steps>
</el-tab-pane>
<el-tab-pane label="维修记录" name="repair">
<div class="device-ledger-tab-toolbar">
<el-date-picker
v-model="repairDateRange"
type="daterange"
value-format="YYYY-MM-DD HH:mm:ss"
start-placeholder="开始时间"
end-placeholder="结束时间"
range-separator="至"
:unlink-panels="true"
class="mr-10px"
/>
<el-button type="primary" plain @click="handleQueryRepair"></el-button>
<el-button @click="handleResetRepair"></el-button>
<el-button type="success" plain :loading="repairExportLoading" @click="handleExportRepair"></el-button>
</div>
<el-empty v-if="!repairGroups.length" />
<el-collapse v-else v-model="repairActiveNames" class="device-ledger-repair-collapse">
<el-collapse-item v-for="group in repairGroups" :key="group.key" :name="group.key">
<template #title>
<div class="device-ledger-repair-title">
<span class="device-ledger-repair-name">{{ group.name }}</span>
<span class="device-ledger-repair-meta">{{ group.items.length }}</span>
</div>
</template>
<div class="device-ledger-history-items">
<div v-for="row in group.items" :key="String(row.id ?? row.subjectId ?? row.subjectCode)" class="device-ledger-history-item">
<div class="device-ledger-history-item-head">
<el-tag type="info" effect="light">{{ row.subjectCode ?? '-' }}</el-tag>
<span class="device-ledger-history-item-text">{{ row.subjectName ?? '-' }}</span>
</div>
<div class="device-ledger-history-item-body">
<div class="device-ledger-history-item-row"><span class="device-ledger-history-item-label">项目内容</span><span class="device-ledger-history-item-value">{{ row.subjectContent ?? '-' }}</span></div>
<div class="device-ledger-history-item-row"><span class="device-ledger-history-item-label">维修结果</span><span class="device-ledger-history-item-value"><el-tag :type="getResultTagType(row.result ?? row.repairResult)">{{ getResultLabel(row.repairResult ?? row.result) }}</el-tag></span></div>
<div class="device-ledger-history-item-row"><span class="device-ledger-history-item-label">完成日期</span><span class="device-ledger-history-item-value">{{ String(formatHistoryTime(row.finishDate)).split(' ')[0] }}</span></div>
<div class="device-ledger-history-item-row"><span class="device-ledger-history-item-label">备注</span><span class="device-ledger-history-item-value">{{ row.remark ?? '-' }}</span></div>
</div>
</div>
</div>
</el-collapse-item>
</el-collapse>
</el-tab-pane>
<el-tab-pane label="保养记录" name="maintain">
<div class="device-ledger-tab-toolbar">
<el-date-picker
v-model="maintainDateRange"
type="daterange"
value-format="YYYY-MM-DD HH:mm:ss"
start-placeholder="开始时间"
end-placeholder="结束时间"
range-separator="至"
:unlink-panels="true"
class="mr-10px"
/>
<el-button type="primary" plain @click="handleQueryMaintain"></el-button>
<el-button @click="handleResetMaintain"></el-button>
<el-button type="success" plain :loading="maintainExportLoading" @click="handleExportMaintain"></el-button>
</div>
<el-empty v-if="!maintainStepGroups.length" />
<el-steps v-else direction="vertical" :active="maintainStepGroups.length" class="device-ledger-history-steps">
<el-step v-for="group in maintainStepGroups" :key="group.key">
<template #title>
<div class="device-ledger-history-title">
<span class="device-ledger-history-time">[{{ group.time }}]</span>
<span class="device-ledger-history-operator">操作人: {{ group.operator }}</span>
</div>
</template>
<template #description>
<div class="device-ledger-history-items">
<div v-for="item in group.items" :key="item.key" class="device-ledger-history-item">
<div class="device-ledger-history-item-head">
<el-tag :type="getResultTagType(item.result)">{{ getResultLabel(item.result) }}</el-tag>
<span class="device-ledger-history-item-text">{{ item.name }}</span>
</div>
<div class="device-ledger-history-item-body">
<div class="device-ledger-history-item-row"><span class="device-ledger-history-item-label">保养方式</span><span class="device-ledger-history-item-value"><dict-tag type="Inspection_method" :value="item.method" /></span></div>
<div class="device-ledger-history-item-row"><span class="device-ledger-history-item-label">判定标准</span><span class="device-ledger-history-item-value">{{ item.criteria ?? '-' }}</span></div>
<div class="device-ledger-history-item-row"><span class="device-ledger-history-item-label">保养时间</span><span class="device-ledger-history-item-value">{{ String(formatHistoryTime(item.taskTime)).split(' ')[0] }}</span></div>
<div class="device-ledger-history-item-row"><span class="device-ledger-history-item-label">备注</span><span class="device-ledger-history-item-value">{{ item.remark ?? '-' }}</span></div>
</div>
</div>
</div>
</template>
</el-step>
</el-steps>
</el-tab-pane>
<el-tab-pane label="安装记录" name="install">
<el-table :data="installRecords" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="操作类型" min-width="100">
<template #default="scope">
{{ scope.row.operateType === '1' ? '上模' : scope.row.operateType === '2' ? '下模' : '-' }}
</template>
</el-table-column>
<el-table-column label="模具名称" prop="moldName" min-width="150" />
<el-table-column label="设备" prop="deviceName" min-width="120" />
<el-table-column label="操作人" prop="creatorName" min-width="100" />
<el-table-column label="备注" prop="remark" min-width="140" />
<el-table-column label="操作时间" prop="createTime" min-width="180">
<template #default="scope">{{ formatHistoryTime(scope.row.createTime) }}</template>
</el-table-column>
</el-table>
<el-empty v-if="!installRecords.length" description="暂无安装记录" />
</el-tab-pane>
</el-tabs>
</div>
</div>
</div>
</div>
</ContentWrap>
</template>
<script setup lang="ts">
import { defineAsyncComponent } from 'vue'
import { DICT_TYPE } from '@/utils/dict'
import { formatDate } from '@/utils/formatTime'
import download from '@/utils/download'
import { MoldBrandApi, type MoldBrandVO } from '@/api/erp/mold'
import { TicketManagementApi } from '@/api/mold/ticketManagement'
import { MoldRepairApi } from '@/api/mold/moldrepair'
import { MoldOperateApi } from '@/api/mes/moldoperate'
defineOptions({ name: 'ErpMoldBrandDetail' })
const message = useMessage()
const route = useRoute()
const MoldListComp = defineAsyncComponent(() => import('../components/MoldList.vue') as any)
const brandId = computed(() => Number(route.params.id))
const detailLoading = ref(false)
const detailData = ref<(MoldBrandVO & Record<string, any>) | null>(null)
const childMolds = ref<any[]>([])
const activeTab = ref('molds')
const inspectionHistory = ref<any[]>([])
const maintainHistory = ref<any[]>([])
const repairList = ref<any[]>([])
const installRecords = ref<any[]>([])
const repairActiveNames = ref<string[]>([])
const inspectionDateRange = ref<string[] | undefined>()
const maintainDateRange = ref<string[] | undefined>()
const repairDateRange = ref<string[] | undefined>()
const inspectionExportLoading = ref(false)
const maintainExportLoading = ref(false)
const repairExportLoading = ref(false)
const imageList = computed(() => parseImages(detailData.value?.images))
const lifeRate = computed<number | null>(() => {
const raw = detailData.value?.lifeRate ?? detailData.value?.lifeStatus ?? detailData.value?.useRate
if (raw === undefined || raw === null || raw === '') return null
const value = Number(raw)
return Number.isFinite(value) ? Math.max(0, Math.min(100, value)) : null
})
const formatSimpleDate = (value: any) => {
if (!value) return '-'
const date = new Date(value)
return Number.isNaN(date.getTime()) ? String(value) : formatDate(date, 'YYYY-MM-DD')
}
const formatHistoryTime = (value: any) => {
const raw = value ?? '-'
if (Array.isArray(raw) && raw.length >= 3) {
const [y, m, d, hh, mm, ss] = raw
const pad = (n: any) => String(n).padStart(2, '0')
if (hh !== undefined) return `${y}-${pad(m)}-${pad(d)} ${pad(hh)}:${pad(mm)}:${pad(ss)}`
return `${y}-${pad(m)}-${pad(d)}`
}
const asDate = new Date(raw)
if (!Number.isNaN(asDate.getTime())) return formatDate(asDate, 'YYYY-MM-DD HH:mm:ss')
return String(raw)
}
const parseImages = (value: any): string[] => {
if (!value) return []
if (Array.isArray(value)) return value.map(String).filter(Boolean)
return String(value)
.replace(/[`'\"]/g, '')
.split(',')
.map((item) => item.trim())
.filter(Boolean)
}
const getResultLabel = (value: any) => {
const v = value === '' || value === null || value === undefined ? undefined : String(value)
if (!v) return '-'
const upper = v.toUpperCase()
if (v === '0') return '待检测'
if (v === '1' || upper === 'OK') return '通过'
if (v === '2' || upper === 'NG') return '不通过'
return v
}
const getResultTagType = (value: any) => {
const v = value === '' || value === null || value === undefined ? undefined : String(value)
if (!v) return 'info'
const upper = v.toUpperCase()
if (v === '1' || upper === 'OK') return 'success'
if (v === '2' || upper === 'NG') return 'danger'
return 'info'
}
type HistoryStepItem = {
key: string
name: string
result: any
method?: any
criteria?: any
remark?: any
taskTime?: any
}
type HistoryStepGroup = { key: string; time: string; operator: string; items: HistoryStepItem[] }
const buildStepGroups = (
rows: any[],
options: { timeField: string; nameFieldCandidates: string[]; resultFieldCandidates: string[] }
) => {
const groups = new Map<string, HistoryStepGroup>()
for (const row of rows ?? []) {
const time = formatHistoryTime(row?.taskTime ?? row?.[options.timeField] ?? row?.createTime)
const operator = String(row?.operator ?? row?.creatorName ?? row?.creator ?? '-')
const groupKey = `${row?.managementId ?? ''}__${time}__${operator}`
const name =
options.nameFieldCandidates
.map((key) => row?.[key])
.find((item) => item !== undefined && item !== null && String(item).trim() !== '') ?? '-'
const result =
options.resultFieldCandidates.map((key) => row?.[key]).find((item) => item !== undefined && item !== null) ?? undefined
const item: HistoryStepItem = {
key: String(row?.id ?? `${groupKey}_${name}`),
name: String(name),
result,
method: row?.inspectionMethod,
criteria: row?.judgmentCriteria,
remark: row?.remark,
taskTime: row?.taskTime
}
if (!groups.has(groupKey)) {
groups.set(groupKey, { key: groupKey, time, operator, items: [item] })
} else {
groups.get(groupKey)!.items.push(item)
}
}
return Array.from(groups.values()).sort((a, b) => String(b.time).localeCompare(String(a.time)))
}
const inspectionStepGroups = computed(() =>
buildStepGroups(inspectionHistory.value, {
timeField: 'inspectionTime',
nameFieldCandidates: ['inspectionItemName', 'name'],
resultFieldCandidates: ['inspectionResult']
})
)
const maintainStepGroups = computed(() =>
buildStepGroups(maintainHistory.value, {
timeField: 'inspectionTime',
nameFieldCandidates: ['maintainItemName', 'inspectionItemName', 'name'],
resultFieldCandidates: ['maintainResult', 'inspectionResult']
})
)
const repairGroups = computed(() => {
const groupsMap = new Map<string, { key: string; name: string; items: any[] }>()
for (const row of repairList.value ?? []) {
const key = String(row.repairCode ?? row.repairId ?? row.subjectName ?? '-')
if (!groupsMap.has(key)) {
groupsMap.set(key, { key, name: String(row.repairName ?? row.repairCode ?? key), items: [] })
}
groupsMap.get(key)!.items.push(row)
}
return Array.from(groupsMap.values())
})
const handleReservedAction = (action: string) => {
message.info(`${action}功能预留中`)
}
let getChildMoldsPromise: Promise<any[]> | null = null
const getChildMolds = async () => {
if (!brandId.value) return []
if (getChildMoldsPromise) return getChildMoldsPromise
getChildMoldsPromise = (async () => {
const pageSize = 10
const allList: any[] = []
let pageNo = 1
let total = 0
while (true) {
const data = await MoldBrandApi.getMoldPage({ pageNo, pageSize, brandId: brandId.value })
const list = data?.list ?? []
allList.push(...list)
total = Number(data?.total ?? allList.length)
if (allList.length >= total || list.length < pageSize) break
pageNo++
}
childMolds.value = allList
return childMolds.value
})()
try {
return await getChildMoldsPromise
} finally {
getChildMoldsPromise = null
}
}
const collectByChildMolds = async (worker: (moldId: number) => Promise<any>, mergeList = true) => {
const molds = childMolds.value.length ? childMolds.value : await getChildMolds()
const ids = molds.map((item) => Number(item.id)).filter(Boolean)
if (!ids.length) return []
const results = await Promise.all(ids.map((id) => worker(id).catch(() => [])))
return mergeList ? results.flat() : results
}
const fetchInspectionHistory = async () => {
if (!brandId.value) return
const params: any = { moldId: brandId.value }
if (inspectionDateRange.value && inspectionDateRange.value.length === 2) {
params.startTime = inspectionDateRange.value[0]
params.endTime = inspectionDateRange.value[1]
}
const data = await TicketManagementApi.getInspectionByMoldId(params)
inspectionHistory.value = Array.isArray(data) ? data : []
}
const fetchMaintainHistory = async () => {
if (!brandId.value) return
const params: any = { moldId: brandId.value }
if (maintainDateRange.value && maintainDateRange.value.length === 2) {
params.startTime = maintainDateRange.value[0]
params.endTime = maintainDateRange.value[1]
}
const data = await TicketManagementApi.getMaintenanceByMoldId(params)
maintainHistory.value = Array.isArray(data) ? data : []
}
const fetchRepairHistory = async () => {
if (!brandId.value) return
const params: any = { moldId: brandId.value }
if (repairDateRange.value && repairDateRange.value.length === 2) {
params.startTime = repairDateRange.value[0]
params.endTime = repairDateRange.value[1]
}
const data = await MoldRepairApi.getRepairListByMoldId(params)
repairList.value = Array.isArray(data) ? data : []
repairActiveNames.value = repairGroups.value.map((item) => item.key)
}
const fetchInstallRecords = async () => {
installRecords.value = await collectByChildMolds(async (moldId) => {
const data = await MoldOperateApi.getMoldOperatePage({ pageNo: 1, pageSize: 100, moldId })
return data?.list ?? []
})
}
const handleQueryInspection = async () => {
await fetchInspectionHistory()
}
const handleResetInspection = async () => {
inspectionDateRange.value = undefined
await fetchInspectionHistory()
}
const handleExportInspection = async () => {
inspectionExportLoading.value = true
message.info('点检记录导出功能待后端确认后接入')
inspectionExportLoading.value = false
}
const handleQueryMaintain = async () => {
await fetchMaintainHistory()
}
const handleResetMaintain = async () => {
maintainDateRange.value = undefined
await fetchMaintainHistory()
}
const handleExportMaintain = async () => {
maintainExportLoading.value = true
message.info('保养记录导出功能待后端确认后接入')
maintainExportLoading.value = false
}
const handleQueryRepair = async () => {
await fetchRepairHistory()
}
const handleResetRepair = async () => {
repairDateRange.value = undefined
await fetchRepairHistory()
}
const handleExportRepair = async () => {
repairExportLoading.value = true
message.info('维修记录导出功能待后端确认后接入')
repairExportLoading.value = false
}
const getDetail = async () => {
if (!brandId.value) {
message.error('缺少模具组ID')
return
}
detailLoading.value = true
try {
detailData.value = await MoldBrandApi.getMoldBrand(brandId.value)
await getChildMolds()
await Promise.all([fetchInspectionHistory(), fetchMaintainHistory(), fetchRepairHistory(), fetchInstallRecords()])
} finally {
detailLoading.value = false
}
}
onMounted(() => {
getDetail()
})
</script>
<style scoped>
.mold-brand-detail__crumb {
margin-bottom: 16px;
color: var(--el-text-color-secondary);
font-size: 13px;
}
.mold-brand-detail__hero {
display: grid;
grid-template-columns: 320px minmax(0, 1fr);
gap: 20px;
margin-bottom: 20px;
}
.mold-brand-detail__left {
display: flex;
flex-direction: column;
gap: 16px;
}
.mold-brand-detail__image-card,
.mold-brand-detail__qr-card {
padding: 16px;
border: 1px solid var(--el-border-color-lighter);
border-radius: 16px;
background: #fff;
}
.mold-brand-detail__image {
width: 100%;
height: 280px;
border-radius: 12px;
}
.mold-brand-detail__qr-title {
margin-bottom: 12px;
color: var(--el-text-color-primary);
font-weight: 600;
text-align: center;
}
.mold-brand-detail__qr {
width: 180px;
height: 180px;
margin: 0 auto;
display: block;
}
.mold-brand-detail__qr-code {
margin-top: 12px;
text-align: center;
font-weight: 600;
}
.mold-brand-detail__main {
padding: 16px 20px;
border: 1px solid var(--el-border-color-lighter);
border-radius: 16px;
background: #fff;
}
.mold-brand-detail__topbar {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 16px;
margin-bottom: 18px;
}
.mold-brand-detail__title-row {
display: flex;
align-items: center;
gap: 12px;
flex-wrap: wrap;
}
.mold-brand-detail__title {
color: var(--el-text-color-primary);
font-size: 28px;
font-weight: 700;
}
.mold-brand-detail__actions {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.mold-brand-detail__grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 18px 24px;
}
.mold-brand-detail__field {
display: flex;
flex-direction: column;
gap: 6px;
}
.mold-brand-detail__field span {
color: var(--el-text-color-secondary);
font-size: 13px;
}
.mold-brand-detail__field strong {
color: var(--el-text-color-primary);
font-size: 18px;
font-weight: 600;
}
.mold-brand-detail__field--life strong {
font-size: 14px;
}
.mold-brand-detail__tabs {
margin-top: 30px;
padding: 8px 12px 0;
border: 1px solid var(--el-border-color-lighter);
border-radius: 16px;
background: #fff;
}
.device-ledger-tab-toolbar {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 16px;
}
.device-ledger-history-steps,
.device-ledger-repair-collapse {
margin-top: 10px;
}
.device-ledger-history-title,
.device-ledger-repair-title {
display: flex;
gap: 16px;
align-items: center;
flex-wrap: wrap;
}
.device-ledger-history-time,
.device-ledger-repair-name {
font-weight: 600;
}
.device-ledger-history-items {
display: flex;
flex-direction: column;
gap: 12px;
}
.device-ledger-history-item {
padding: 12px 14px;
border: 1px solid var(--el-border-color-lighter);
border-radius: 12px;
}
.device-ledger-history-item-head {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 10px;
}
.device-ledger-history-item-body {
display: grid;
gap: 8px;
}
.device-ledger-history-item-row {
display: grid;
grid-template-columns: 88px minmax(0, 1fr);
gap: 12px;
}
.device-ledger-history-item-label {
color: var(--el-text-color-secondary);
}
@media (max-width: 1024px) {
.mold-brand-detail__hero {
grid-template-columns: 1fr;
}
.mold-brand-detail__grid {
grid-template-columns: 1fr;
}
}
</style>

@ -1,146 +1,110 @@
<template>
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
min-label-width="68px"
>
<el-form-item :label="t('MoldManagement.MoldBrand.code')" prop="code">
<div class="mold-brand-page__header">
<div
v-for="card in statusCards"
:key="card.key"
class="mold-brand-page__stat"
:class="{ 'is-active': currentStatusKey === card.key }"
@click="changeStatus(card.key, card.status)"
>
<div class="mold-brand-page__stat-title">{{ card.label }}</div>
<div class="mold-brand-page__stat-value">{{ card.value }}</div>
</div>
</div>
<el-form ref="queryFormRef" :model="queryParams" :inline="true" class="-mb-15px mt-16px">
<el-form-item prop="keyword">
<el-input
v-model="queryParams.code"
:placeholder="t('MoldManagement.MoldBrand.placeholderCode')"
v-model="queryParams.keyword"
placeholder="请输入编码/名称/产品型号"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item :label="t('MoldManagement.MoldBrand.name')" prop="name">
<el-input
v-model="queryParams.name"
:placeholder="t('MoldManagement.MoldBrand.placeholderName')"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item :label="t('MoldManagement.MoldBrand.orgType')" prop="orgType">
<el-select
v-model="queryParams.orgType"
:placeholder="t('MoldManagement.MoldBrand.placeholderOrgType')"
clearable
class="!w-240px"
>
<el-form-item prop="status">
<el-select v-model="queryParams.status" placeholder="模具状态" clearable class="!w-180px">
<el-option
v-for="dict in orgTypeOptions"
v-for="dict in statusOptions"
:key="dict.value"
:label="dict.label"
:value="dict.value"
:value="Number(dict.value)"
/>
</el-select>
</el-form-item>
<!-- <el-form-item label="维保模式" prop="maintainType">
<el-select
v-model="queryParams.maintainType"
placeholder="请选择维保模式"
clearable
class="!w-240px"
>
<el-form-item prop="productId">
<el-select v-model="queryParams.productId" placeholder="产品型号" clearable filterable class="!w-180px">
<el-option v-for="product in productOptions" :key="product.id" :label="product.name" :value="product.id" />
</el-select>
</el-form-item>
<el-form-item prop="currentDevice">
<el-select v-model="queryParams.currentDevice" placeholder="当前设备" clearable filterable class="!w-180px">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.ERP_MAINTAIN_TYPE)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
v-for="device in deviceOptions"
:key="device.id"
:label="`${device.deviceName}${device.deviceCode}`"
:value="device.deviceName"
/>
</el-select>
</el-form-item> -->
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> {{ t('common.query') }}</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> {{ t('common.reset') }}</el-button>
<el-button
type="primary"
plain
@click="openForm('create')"
v-hasPermi="['erp:mold-brand:create']"
>
<Icon icon="ep:plus" class="mr-5px" /> {{ t('action.add') }}
<el-button @click="resetQuery"></el-button>
<el-button type="primary" @click="handleQuery"></el-button>
<el-button type="primary" plain @click="openForm('create')" v-hasPermi="['erp:mold-brand:create']">
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
<el-button
type="success"
plain
@click="handleExport"
:loading="exportLoading"
v-hasPermi="['erp:mold-brand:export']"
>
<Icon icon="ep:download" class="mr-5px" /> {{ t('action.export') }}
<el-button type="success" plain @click="handleExport" :loading="exportLoading" v-hasPermi="['erp:mold-brand:export']">
<Icon icon="ep:download" class="mr-5px" /> 导出
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table
ref="tableRef"
v-loading="loading"
:data="list"
:stripe="true"
:show-overflow-tooltip="true"
row-key="id"
highlight-current-row
@current-change="handleCurrentChange"
@selection-change="handleSelectionChange"
>
<!-- <el-table-column type="selection" width="55" fixed="left" reserve-selection /> -->
<el-table-column :label="t('MoldManagement.MoldBrand.code')" align="center" prop="code" sortable />
<el-table-column :label="t('MoldManagement.MoldBrand.name')" align="left" prop="name" sortable />
<el-table-column :label="t('MoldManagement.MoldBrand.moldType')" align="center" prop="moldType" sortable />
<el-table-column :label="t('MoldManagement.MoldBrand.orgType')" align="center" prop="orgType" sortable>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" row-key="id">
<el-table-column label="图片" align="center" width="92">
<template #default="scope">
<dict-tag :type="DICT_TYPE.MES_ORG_TYPE" :value="scope.row.orgType" />
<el-image
v-if="getImageList(scope.row.images).length"
:src="getImageList(scope.row.images)[0]"
:preview-src-list="getImageList(scope.row.images)"
fit="cover"
preview-teleported
class="mold-brand-page__thumb"
/>
<span v-else class="mold-brand-page__empty">-</span>
</template>
</el-table-column>
<el-table-column :label="t('MoldManagement.MoldBrand.useTime')" align="center" prop="useTime" sortable />
<!-- <el-table-column label="维保模式" align="center" prop="maintainType" sortable>
<el-table-column label="编码" prop="code" min-width="150" sortable />
<el-table-column label="名称" prop="name" min-width="160" sortable />
<el-table-column label="产品型号" prop="productName" min-width="140" />
<el-table-column label="版本" prop="version" width="100" />
<el-table-column label="状态" prop="status" width="100" align="center">
<template #default="scope">
<dict-tag :type="DICT_TYPE.ERP_MAINTAIN_TYPE" :value="scope.row.maintainType" />
<dict-tag :type="DICT_TYPE.ERP_MOLD_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="维保周期" align="center" prop="maintainTime" sortable /> -->
<el-table-column :label="t('MoldManagement.MoldBrand.moldSize')" align="center" prop="moldSize" />
<el-table-column :label="t('MoldManagement.MoldBrand.remark')" align="center" prop="remark" />
<el-table-column
:label="t('MoldManagement.MoldBrand.createTime')"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180px"
sortable />
<el-table-column :label="t('MoldManagement.MoldBrand.operate')" align="center" fixed="right" width="150px">
<el-table-column label="当前设备" min-width="140">
<template #default="scope">
<el-button
link
type="primary"
@click="openForm('update', scope.row.id)"
v-hasPermi="['erp:mold-brand:update']"
>
{{ t('action.edit') }}
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['erp:mold-brand:delete']"
>
{{ t('action.del') }}
</el-button>
{{ scope.row.currentDevice || scope.row.machineName || '-' }}
</template>
</el-table-column>
<el-table-column label="子模数" prop="moldSize" width="90" align="center" />
<el-table-column label="创建时间" prop="createTime" :formatter="dateFormatter" width="180" sortable />
<el-table-column label="操作" fixed="right" min-width="240">
<template #default="scope">
<el-button link type="primary" @click="openDetail(scope.row.id)"></el-button>
<el-button link type="primary" @click="handleReservedAction('上模')"></el-button>
<el-button link type="primary" @click="handleReservedAction('下模')"></el-button>
<el-button link type="warning" @click="handleReservedAction('维修')"></el-button>
<el-button link type="primary" @click="previewQrcode(scope.row)"></el-button>
<el-button link type="primary" @click="openForm('update', scope.row.id)" v-hasPermi="['erp:mold-brand:update']"></el-button>
<el-button link type="danger" @click="handleDelete(scope.row.id)" v-hasPermi="['erp:mold-brand:delete']"></el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
@ -149,144 +113,246 @@
/>
</ContentWrap>
<!-- 表单弹窗添加/修改 -->
<MoldBrandForm ref="formRef" @success="getList" />
<!-- 子表的列表 -->
<!-- <ContentWrap v-if="currentBrandId">
<div class="mb-10px text-14px font-600">
{{ currentBrandDisplay }}
<el-dialog v-model="qrcodeVisible" title="模具二维码" width="360px">
<div class="mold-brand-page__qrcode-wrap">
<el-image
v-if="currentQrcodeUrl"
:src="currentQrcodeUrl"
:preview-src-list="[currentQrcodeUrl]"
fit="contain"
preview-teleported
class="mold-brand-page__qrcode"
/>
<el-empty v-else description="暂无二维码" />
</div>
<el-tabs model-value="mold">
<el-tab-pane :label="t('MoldManagement.MoldBrand.tabMold')" name="mold">
<MoldList :brand-id="currentBrandId" />
</el-tab-pane> -->
<!-- <el-tab-pane label="产品" name="moldBrandProduct">
<MoldBrandProductList :brand-id="currentBrandId" />
</el-tab-pane> -->
<!-- </el-tabs>
</ContentWrap> -->
<!-- <ContentWrap v-else>
<el-empty :description="t('MoldManagement.MoldBrand.emptyTip')" />
</ContentWrap> -->
</el-dialog>
<component :is="MoldBrandFormComp" ref="formRef" @success="getList" />
</template>
<script setup lang="ts">
import { getStrDictOptions, DICT_TYPE } from '@/utils/dict'
import { defineAsyncComponent } from 'vue'
import { ProductApi, type ProductVO } from '@/api/erp/product/product'
import { MoldBrandApi, type MoldBrandVO } from '@/api/erp/mold'
import { DeviceLedgerApi, type DeviceLedgerVO } from '@/api/mes/deviceledger'
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import { MoldBrandApi, MoldBrandVO } from '@/api/erp/mold'
import MoldBrandForm from './MoldBrandForm.vue'
import MoldList from './components/MoldList.vue'
import MoldBrandProductList from './components/MoldBrandProductList.vue'
/** 模具型号 列表 */
defineOptions({ name: 'MoldBrand' })
const message = useMessage() //
const { t } = useI18n() //
const loading = ref(true) //
const list = ref<MoldBrandVO[]>([]) //
const total = ref(0) //
const message = useMessage()
const { push } = useRouter()
const loading = ref(false)
const exportLoading = ref(false)
const list = ref<MoldBrandVO[]>([])
const total = ref(0)
const productOptions = ref<ProductVO[]>([])
const deviceOptions = ref<DeviceLedgerVO[]>([])
const currentStatusKey = ref('all')
const qrcodeVisible = ref(false)
const currentQrcodeUrl = ref('')
const statusOptions = computed(() => getIntDictOptions(DICT_TYPE.ERP_MOLD_STATUS))
const counters = reactive({
allCount: 0,
onMachineCount: 0,
standbyCount: 0,
repairingCount: 0,
scrappedCount: 0
})
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
code: undefined,
name: undefined,
orgType: undefined,
moldType: undefined,
productId: undefined,
useTime: [],
maintainType: undefined,
maintainTime: [],
moldSize: undefined,
remark: undefined,
isEnable: undefined,
createTime: []
keyword: undefined as string | undefined,
code: undefined as string | undefined,
name: undefined as string | undefined,
productId: undefined as number | undefined,
productName: undefined as string | undefined,
status: undefined as number | undefined,
currentDevice: undefined as string | undefined,
currentPosition: undefined as string | undefined
})
const queryFormRef = ref() //
const exportLoading = ref(false) //
const orgTypeOptions = computed(() => getStrDictOptions(DICT_TYPE.MES_ORG_TYPE))
const tableRef = ref()
const selectedIds = ref<Array<number | string>>([])
const handleSelectionChange = (rows: any[]) => {
selectedIds.value = rows?.map((row) => row.id).filter((id) => id !== undefined && id !== null && id !== '') ?? []
const queryFormRef = ref()
const formRef = ref()
const MoldBrandFormComp = defineAsyncComponent(() => import('./MoldBrandForm.vue') as any)
const statusCards = computed(() => [
{ key: 'all', label: '全部', value: counters.allCount, status: undefined },
{ key: 'onMachine', label: '在机', value: counters.onMachineCount, status: 1 },
{ key: 'standby', label: '待用', value: counters.standbyCount, status: 0 },
{ key: 'repairing', label: '维修', value: counters.repairingCount, status: 2 },
{ key: 'scrapped', label: '报废', value: counters.scrappedCount, status: 3 }
])
const parseKeyword = () => {
const keyword = queryParams.keyword?.trim()
queryParams.code = keyword || undefined
queryParams.name = keyword || undefined
}
const getImageList = (images?: string) => {
if (!images) return []
return String(images)
.split(',')
.map((item) => item.trim())
.filter(Boolean)
}
const normalizePageResult = (data: any) => {
const pageResult = data?.pageResult ?? data ?? {}
list.value = pageResult.list ?? []
total.value = pageResult.total ?? 0
counters.allCount = Number(data?.allCount ?? pageResult.total ?? 0)
counters.onMachineCount = Number(data?.onMachineCount ?? 0)
counters.standbyCount = Number(data?.standbyCount ?? 0)
counters.repairingCount = Number(data?.repairingCount ?? 0)
counters.scrappedCount = Number(data?.scrappedCount ?? 0)
}
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await MoldBrandApi.getMoldBrandPage(queryParams)
list.value = data.list
total.value = data.total
parseKeyword()
const product = productOptions.value.find((item) => item.id === queryParams.productId)
queryParams.productName = product?.name
const data = await MoldBrandApi.getMoldBrandPage({
...queryParams,
currentDevice: queryParams.currentDevice
})
normalizePageResult(data)
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
queryFormRef.value?.resetFields()
currentStatusKey.value = 'all'
queryParams.status = undefined
handleQuery()
}
const changeStatus = (key: string, status?: number) => {
currentStatusKey.value = key
queryParams.status = status
handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
//
await message.delConfirm()
//
await MoldBrandApi.deleteMoldBrand(id)
message.success(t('common.delSuccess'))
//
message.success('删除成功')
await getList()
} catch {}
}
/** 导出按钮操作 */
const handleExport = async () => {
try {
//
await message.exportConfirm()
//
exportLoading.value = true
const data = await MoldBrandApi.exportMoldBrand({
...queryParams,
...(selectedIds.value.length ? { ids: selectedIds.value.join(',') } : {})
})
download.excel(data, '模具型号.xls')
} catch {
parseKeyword()
const data = await MoldBrandApi.exportMoldBrand(queryParams)
download.excel(data, '模具台账.xls')
} finally {
exportLoading.value = false
}
}
/** 选中行操作 */
const currentRow = ref<MoldBrandVO | null>(null) //
const currentBrandId = computed(() => currentRow.value?.id)
const currentBrandDisplay = computed(() => {
if (!currentRow.value) return ''
return `${currentRow.value.name}-${currentRow.value.code}`
})
const handleCurrentChange = (row: MoldBrandVO | null) => {
currentRow.value = row
const handleReservedAction = (action: string) => {
message.info(`${action}功能预留中`)
}
const previewQrcode = (row: MoldBrandVO) => {
currentQrcodeUrl.value = row.qrCodeUrl || ''
qrcodeVisible.value = true
}
const openDetail = (id: number) => {
push({ name: 'ErpMoldBrandDetail', params: { id } })
}
/** 初始化 **/
onMounted(async () => {
productOptions.value = await ProductApi.getMesProductSimpleList()
deviceOptions.value = await DeviceLedgerApi.getDeviceLedgerList()
await getList()
})
</script>
<style scoped>
.mold-brand-page__header {
display: grid;
grid-template-columns: repeat(5, 10vw);
gap: 12px;
}
.mold-brand-page__stat {
width: 10vw;
padding: 16px 18px;
border: 1px solid var(--el-border-color-light);
border-radius: 12px;
cursor: pointer;
background: linear-gradient(180deg, #fff 0%, #f8fbff 100%);
transition: all 0.2s ease;
}
.mold-brand-page__stat.is-active {
border-color: var(--el-color-primary);
box-shadow: 0 10px 24px rgba(64, 158, 255, 0.12);
}
.mold-brand-page__stat-title {
color: var(--el-text-color-regular);
font-size: 14px;
}
.mold-brand-page__stat-value {
margin-top: 8px;
color: var(--el-text-color-primary);
font-size: 28px;
font-weight: 700;
line-height: 1;
}
.mold-brand-page__thumb {
width: 56px;
height: 56px;
border-radius: 8px;
border: 1px solid var(--el-border-color-lighter);
}
.mold-brand-page__empty {
color: var(--el-text-color-placeholder);
}
.mold-brand-page__qrcode-wrap {
display: flex;
justify-content: center;
}
.mold-brand-page__qrcode {
width: 220px;
height: 220px;
}
@media (max-width: 1200px) {
.mold-brand-page__header {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 768px) {
.mold-brand-page__header {
grid-template-columns: 1fr;
}
}
</style>

Loading…
Cancel
Save