You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1102 lines
46 KiB
Vue

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<ContentWrap>
<div v-loading="detailLoading" class="mold-brand-detail">
<div class="mold-brand-detail__header">
<el-button @click="goBack">
<Icon icon="ep:arrow-left" class="mr-5px" /> {{ t('MoldManagement.MoldBrandDetail.back') }}
</el-button>
</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="t('MoldManagement.MoldBrandDetail.noImage')" />
</div>
<div class="mold-brand-detail__qr-card">
<div class="mold-brand-detail__qr-title">{{ t('MoldManagement.MoldBrandDetail.qrcodeTitle') }}</div>
<QrcodeActionCard :image-url="detailData?.qrCodeUrl" :print-id="detailData?.id" :print-template-type="4"
:print-title="t('MoldManagement.MoldBrandDetail.qrcodePrintTitle', { name: detailData?.name || t('MoldManagement.MoldBrandDetail.qrcodeTitle') })" :print-paper-width="80" :print-paper-height="80"
:print-max-width="220" :empty-text="t('MoldManagement.MoldBrandDetail.qrcodeEmpty')" :error-text="t('MoldManagement.MoldBrandDetail.qrcodeLoadError')" :refresh-url="getQrcodeRefreshUrl()"
:refresh-disabled="!detailData?.id || !detailData?.code" :refresh-confirm-text="t('MoldManagement.MoldBrandDetail.qrcodeRefreshConfirm')"
@refresh-success="handleQrcodeRefreshSuccess" />
<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 class="mold-brand-detail__title-row">
<span class="mold-brand-detail__title">{{ detailData?.name || '-' }}<span v-if="detailData?.version">{{
detailData.version }}</span></span>
<dict-tag :type="DICT_TYPE.ERP_MOLD_STATUS" :value="detailData?.status" />
</div>
</div>
<div class="mold-brand-detail__grid">
<div class="mold-brand-detail__field"><span>{{ t('MoldManagement.MoldBrandDetail.code') }}</span><strong>{{ detailData?.code || '-' }}</strong></div>
<div class="mold-brand-detail__field"><span>{{ t('MoldManagement.MoldBrandDetail.currentDevice') }}</span><strong>{{ detailData?.currentDevice ||
detailData?.machineName
|| '-' }}</strong></div>
<div class="mold-brand-detail__field"><span>{{ t('MoldManagement.MoldBrandDetail.productName') }}</span><strong>{{ detailData?.productName || '-' }}</strong>
</div>
<div class="mold-brand-detail__field"><span>{{ t('MoldManagement.MoldBrandDetail.version') }}</span><strong>{{ detailData?.version || '-' }}</strong></div>
<div class="mold-brand-detail__field mold-brand-detail__field--life">
<span>{{ t('MoldManagement.MoldBrandDetail.lifeStatus') }}</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>{{ t('MoldManagement.MoldBrandDetail.childMoldCount') }}</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="t('MoldManagement.MoldBrandDetail.tabMolds')" name="molds">
<component :is="MoldListComp" :brand-id="brandId" />
</el-tab-pane>
<el-tab-pane :label="t('MoldManagement.MoldBrandDetail.tabInspection')" name="inspection">
<div v-loading="inspectionLoading">
<el-form :inline="true" class="device-ledger-tab-toolbar">
<el-form-item :label="t('MoldManagement.MoldBrandDetail.time')">
<el-date-picker v-model="inspectionDateRange" type="daterange" value-format="YYYY-MM-DD HH:mm:ss"
:start-placeholder="t('MoldManagement.MoldBrandDetail.startTime')" :end-placeholder="t('MoldManagement.MoldBrandDetail.endTime')" :range-separator="t('MoldManagement.MoldBrandDetail.to')" :unlink-panels="true"
style="width: 340px" />
</el-form-item>
<el-form-item>
<el-button type="primary" plain @click="handleQueryInspection">{{ t('MoldManagement.MoldBrandDetail.query') }}</el-button>
<el-button @click="handleResetInspection">{{ t('MoldManagement.MoldBrandDetail.reset') }}</el-button>
<el-button type="success" plain :loading="inspectionExportLoading"
@click="handleExportInspection">{{ t('MoldManagement.MoldBrandDetail.export') }}</el-button>
</el-form-item>
</el-form>
<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">{{ t('MoldManagement.MoldBrandDetail.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">{{ t('MoldManagement.MoldBrandDetail.inspectionMethod') }}</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">{{ t('MoldManagement.MoldBrandDetail.criteria') }}</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">{{ t('MoldManagement.MoldBrandDetail.inspectionTime') }}</span><span
class="device-ledger-history-item-value">{{ formatHistoryTime(item.taskTime) }}</span>
</div>
<div class="device-ledger-history-item-row device-ledger-history-item-row--images"><span
class="device-ledger-history-item-label">{{ t('MoldManagement.MoldBrandDetail.images') }}</span><span
class="device-ledger-history-item-value">
<div v-if="parseImages(item.images).length" class="device-ledger-repair-images">
<el-image
v-for="image in parseImages(item.images)"
:key="image"
:src="image"
:preview-src-list="parseImages(item.images)"
preview-teleported
fit="cover"
class="device-ledger-repair-image"
/>
</div>
<div v-else class="device-ledger-repair-image-placeholder">-</div>
</span></div>
<div class="device-ledger-history-item-row"><span
class="device-ledger-history-item-label">{{ t('MoldManagement.MoldBrandDetail.remark') }}</span><span
class="device-ledger-history-item-value">{{ item.remark ?? '-' }}</span></div>
</div>
</div>
</div>
</template>
</el-step>
</el-steps>
</div>
</el-tab-pane>
<el-tab-pane :label="t('MoldManagement.MoldBrandDetail.tabRepair')" name="repair">
<div v-loading="repairLoading">
<el-form :inline="true" class="device-ledger-tab-toolbar">
<el-form-item :label="t('MoldManagement.MoldBrandDetail.repairCode')">
<el-input v-model="repairQueryCode" :placeholder="t('MoldManagement.MoldBrandDetail.placeholderRepairCode')" clearable
@keyup.enter="handleQueryRepair" class="!w-200px" />
</el-form-item>
<el-form-item :label="t('MoldManagement.MoldBrandDetail.repairStatus')">
<el-select v-model="repairQueryStatus" :placeholder="t('MoldManagement.MoldBrandDetail.selectRepairStatus')"
clearable class="!w-150px">
<el-option :label="t('MoldManagement.MoldBrandDetail.repairStatusPending')" value="0" />
<el-option :label="t('MoldManagement.MoldBrandDetail.repairStatusDone')" value="1" />
</el-select>
</el-form-item>
<el-form-item :label="t('MoldManagement.MoldBrandDetail.time')">
<el-date-picker v-model="repairDateRange" type="daterange" value-format="YYYY-MM-DD HH:mm:ss"
:start-placeholder="t('MoldManagement.MoldBrandDetail.startTime')" :end-placeholder="t('MoldManagement.MoldBrandDetail.endTime')" :range-separator="t('MoldManagement.MoldBrandDetail.to')" :unlink-panels="true"
style="width: 340px" />
</el-form-item>
<el-form-item>
<el-button type="primary" plain @click="handleQueryRepair">{{ t('MoldManagement.MoldBrandDetail.query') }}</el-button>
<el-button @click="handleResetRepair">{{ t('MoldManagement.MoldBrandDetail.reset') }}</el-button>
<el-button type="success" plain :loading="repairExportLoading"
@click="handleExportRepair">{{ t('MoldManagement.MoldBrandDetail.export') }}</el-button>
</el-form-item>
</el-form>
<el-empty v-if="!repairList.length" />
<el-collapse v-else v-model="repairActiveNames" class="device-ledger-repair-collapse">
<el-collapse-item v-for="row in repairList" :key="row.id ?? row.repairCode" :name="String(row.id ?? row.repairCode)">
<template #title>
<div class="device-ledger-repair-title">
<span class="device-ledger-repair-name">{{ row.repairCode ?? '-' }}</span>
</div>
</template>
<div class="device-ledger-history-items">
<div class="device-ledger-history-item">
<div class="device-ledger-history-item-body">
<div class="device-ledger-history-item-row"><span
class="device-ledger-history-item-label">{{ t('MoldManagement.MoldBrandDetail.moldCode') }}</span><span
class="device-ledger-history-item-value">{{ row.moldCode ?? '-' }}</span></div>
<div class="device-ledger-history-item-row"><span
class="device-ledger-history-item-label">{{ t('MoldManagement.MoldBrandDetail.repairStatus') }}</span><span
class="device-ledger-history-item-value"><el-tag
:type="getRepairStatusTagType(row.status)">{{
getRepairStatusLabel(row.status) }}</el-tag></span></div>
<div class="device-ledger-history-item-row"><span
class="device-ledger-history-item-label">{{ t('EquipmentManagement.DvRepair.repairStatus') }}</span><span
class="device-ledger-history-item-value"><el-tag
:type="getRepairResultTagType(row.repairStatus)">{{
getRepairResultLabel(row.repairStatus) }}</el-tag></span></div>
<div class="device-ledger-history-item-row"><span
class="device-ledger-history-item-label">{{ t('MoldManagement.MoldBrandDetail.requireDate') }}</span><span
class="device-ledger-history-item-value">{{ formatHistoryTime(row.requireDate) }}</span></div>
<div class="device-ledger-history-item-row"><span
class="device-ledger-history-item-label">{{ t('MoldManagement.MoldBrandDetail.finishDate') }}</span><span
class="device-ledger-history-item-value">{{ formatHistoryTime(row.finishDate) }}</span></div>
<div class="device-ledger-history-item-row device-ledger-history-item-row--images"><span
class="device-ledger-history-item-label">{{ t('MoldManagement.MoldRepair.faultImages') }}</span><span
class="device-ledger-history-item-value">
<div v-if="parseImages(row.faultImages).length" class="device-ledger-repair-images">
<el-image
v-for="image in parseImages(row.faultImages)"
:key="image"
:src="image"
:preview-src-list="parseImages(row.faultImages)"
preview-teleported
fit="cover"
class="device-ledger-repair-image"
/>
</div>
<div v-else class="device-ledger-repair-image-placeholder">-</div>
</span></div>
</div>
</div>
</div>
</el-collapse-item>
</el-collapse>
</div>
</el-tab-pane>
<el-tab-pane :label="t('MoldManagement.MoldBrandDetail.tabMaintain')" name="maintain">
<div v-loading="maintainLoading">
<el-form :inline="true" class="device-ledger-tab-toolbar">
<el-form-item :label="t('MoldManagement.MoldBrandDetail.time')">
<el-date-picker v-model="maintainDateRange" type="daterange" value-format="YYYY-MM-DD HH:mm:ss"
:start-placeholder="t('MoldManagement.MoldBrandDetail.startTime')" :end-placeholder="t('MoldManagement.MoldBrandDetail.endTime')" :range-separator="t('MoldManagement.MoldBrandDetail.to')" :unlink-panels="true"
style="width: 340px" />
</el-form-item>
<el-form-item>
<el-button type="primary" plain @click="handleQueryMaintain">{{ t('MoldManagement.MoldBrandDetail.query') }}</el-button>
<el-button @click="handleResetMaintain">{{ t('MoldManagement.MoldBrandDetail.reset') }}</el-button>
<el-button type="success" plain :loading="maintainExportLoading"
@click="handleExportMaintain">{{ t('MoldManagement.MoldBrandDetail.export') }}</el-button>
</el-form-item>
</el-form>
<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">{{ t('MoldManagement.MoldBrandDetail.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">{{ t('MoldManagement.MoldBrandDetail.maintainMethod') }}</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">{{ t('MoldManagement.MoldBrandDetail.criteria') }}</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">{{ t('MoldManagement.MoldBrandDetail.maintainTime') }}</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">{{ t('MoldManagement.MoldBrandDetail.remark') }}</span><span
class="device-ledger-history-item-value">{{ item.remark ?? '-' }}</span></div>
</div>
</div>
</div>
</template>
</el-step>
</el-steps>
</div>
</el-tab-pane>
<el-tab-pane :label="t('MoldManagement.MoldBrandDetail.tabInstall')" name="install">
<div v-loading="installLoading">
<el-form :inline="true" class="device-ledger-tab-toolbar">
<el-form-item :label="t('MoldManagement.MoldBrandDetail.operateType')">
<el-select v-model="installOperateType" :placeholder="t('MoldManagement.MoldBrandDetail.selectOperateType')"
clearable class="!w-150px">
<el-option :label="t('MoldManagement.MoldBrandDetail.moldUp')" value="1" />
<el-option :label="t('MoldManagement.MoldBrandDetail.moldDown')" value="2" />
</el-select>
</el-form-item>
<el-form-item :label="t('MoldManagement.MoldBrandDetail.time')">
<el-date-picker v-model="installDateRange" type="daterange" value-format="YYYY-MM-DD HH:mm:ss"
:start-placeholder="t('MoldManagement.MoldBrandDetail.startTime')" :end-placeholder="t('MoldManagement.MoldBrandDetail.endTime')" :range-separator="t('MoldManagement.MoldBrandDetail.to')" :unlink-panels="true" />
</el-form-item>
<el-form-item>
<el-button type="primary" plain @click="handleQueryInstall">{{ t('MoldManagement.MoldBrandDetail.query') }}</el-button>
<el-button @click="handleResetInstall">{{ t('MoldManagement.MoldBrandDetail.reset') }}</el-button>
</el-form-item>
</el-form>
<el-table :data="installRecords" :stripe="true" :show-overflow-tooltip="true">
<el-table-column :label="t('MoldManagement.MoldBrandDetail.moldName')" prop="moldName" min-width="150" sortable />
<el-table-column :label="t('MoldManagement.MoldBrandDetail.deviceName')" prop="deviceName" min-width="120" sortable />
<el-table-column :label="t('MoldManagement.MoldBrandDetail.operateType')" prop="operateType" min-width="100" align="center">
<template #default="scope">
<span v-if="scope.row.operateType === 1 || scope.row.operateType === '1'">{{ t('MoldManagement.MoldBrandDetail.moldUp') }}</span>
<span v-else-if="scope.row.operateType === 2 || scope.row.operateType === '2'">{{ t('MoldManagement.MoldBrandDetail.moldDown') }}</span>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column :label="t('MoldManagement.MoldBrandDetail.creatorName')" prop="creatorName" min-width="100" sortable />
<el-table-column :label="t('MoldManagement.MoldBrandDetail.createTime')" prop="createTime" min-width="180" sortable>
<template #default="scope">{{ formatHistoryTime(scope.row.createTime) }}</template>
</el-table-column>
<el-table-column :label="t('MoldManagement.MoldBrandDetail.remark')" prop="remark" min-width="140" />
</el-table>
<el-empty v-if="!installRecords.length" :description="t('MoldManagement.MoldBrandDetail.noInstallRecords')" />
<Pagination :total="installTotal" v-model:page="installPageNo" v-model:limit="installPageSize"
@pagination="fetchInstallRecords" />
</div>
</el-tab-pane>
<el-tab-pane :label="t('MoldManagement.MoldBrandDetail.tabDrawings')" name="drawings">
<el-table :data="drawingRows" :stripe="true" :show-overflow-tooltip="true">
<el-table-column :label="t('MoldManagement.MoldBrandDetail.drawings')" prop="url" min-width="220">
<template #default="scope">
<el-link :href="scope.row.url" target="_blank" type="primary" :underline="false">
{{ scope.row.name }}
</el-link>
</template>
</el-table-column>
</el-table>
<el-empty v-if="!drawingRows.length" :description="t('MoldManagement.MoldBrandDetail.noRecords')" />
</el-tab-pane>
<el-tab-pane :label="t('MoldManagement.MoldBrandDetail.tabOperationManual')" name="operationManual">
<el-table :data="operationManualRows" :stripe="true" :show-overflow-tooltip="true">
<el-table-column :label="t('MoldManagement.MoldBrandDetail.operationManual')" prop="url" min-width="220">
<template #default="scope">
<el-link :href="scope.row.url" target="_blank" type="primary" :underline="false">
{{ scope.row.name }}
</el-link>
</template>
</el-table-column>
</el-table>
<el-empty v-if="!operationManualRows.length" :description="t('MoldManagement.MoldBrandDetail.noRecords')" />
</el-tab-pane>
<el-tab-pane :label="t('MoldManagement.MoldBrandDetail.tabOperationVideo')" name="operationVideo">
<el-table :data="operationVideoRows" :stripe="true" :show-overflow-tooltip="true">
<el-table-column :label="t('MoldManagement.MoldBrandDetail.operationVideo')" prop="url" min-width="260">
<template #default="scope">
<video :src="scope.row.url" controls class="mold-brand-asset-video"></video>
</template>
</el-table-column>
</el-table>
<el-empty v-if="!operationVideoRows.length" :description="t('MoldManagement.MoldBrandDetail.noRecords')" />
</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'
import QrcodeActionCard from '@/components/QrcodeActionCard/index.vue'
defineOptions({ name: 'ErpMoldBrandDetail' })
const { t } = useI18n()
const message = useMessage()
const route = useRoute()
const router = useRouter()
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 loadedTabs = ref<Set<string>>(new Set(['molds']))
const inspectionHistory = ref<any[]>([])
const maintainHistory = ref<any[]>([])
const repairList = ref<any[]>([])
const repairQueryCode = ref<string | undefined>()
const repairQueryStatus = ref<string | undefined>()
const installRecords = ref<any[]>([])
const installTotal = ref(0)
const installPageNo = ref(1)
const installPageSize = ref(10)
const installMoldId = ref<number | undefined>()
const installRemark = ref<string | undefined>()
const installDateRange = ref<string[] | undefined>()
const installOperateType = ref<string | undefined>()
const moldList = 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 inspectionLoading = ref(false)
const repairLoading = ref(false)
const maintainLoading = ref(false)
const installLoading = ref(false)
const imageList = computed(() => parseImages(detailData.value?.images))
const drawingRows = computed(() => parseAssetRows(detailData.value?.drawings))
const operationManualRows = computed(() => parseAssetRows(detailData.value?.operationManual))
const operationVideoRows = computed(() => parseAssetRows(detailData.value?.operationVideo))
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 parseAssetUrls = (value: any): string[] => {
if (!value) return []
if (Array.isArray(value)) return value.flatMap((item) => parseAssetUrls(item))
if (typeof value === 'object' && value.fileUrl) return [String(value.fileUrl).trim()].filter(Boolean)
const text = String(value).trim()
if (!text) return []
if (text.startsWith('{') || text.startsWith('[')) {
try {
return parseAssetUrls(JSON.parse(text))
} catch {}
}
return text
.replace(/[`'"]/g, '')
.split(',')
.map((item) => item.trim())
.filter(Boolean)
}
const getAssetName = (url: string) => {
const cleanUrl = String(url || '').split('?')[0]
const idx = cleanUrl.lastIndexOf('/')
const name = idx !== -1 ? cleanUrl.substring(idx + 1) : cleanUrl
try {
return decodeURIComponent(name)
} catch {
return name
}
}
const parseAssetRows = (value: any) => {
if (!value) return []
const text = String(value).trim()
if (text.startsWith('{') || text.startsWith('[')) {
try {
const parsed = JSON.parse(text)
if (Array.isArray(parsed)) {
return parsed.map((item) => {
if (item && typeof item === 'object' && item.fileUrl) {
return {
url: String(item.fileUrl),
name: item.fileName ? String(item.fileName) : getAssetName(item.fileUrl)
}
}
return null
}).filter(Boolean)
}
if (parsed && typeof parsed === 'object' && parsed.fileUrl) {
return [{
url: String(parsed.fileUrl),
name: parsed.fileName ? String(parsed.fileName) : getAssetName(parsed.fileUrl)
}]
}
} catch {}
}
// 旧格式处理
return parseAssetUrls(value).map((url) => ({
url,
name: getAssetName(url)
}))
}
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 t('MoldManagement.MoldBrandDetail.pending')
if (v === '1' || upper === 'OK') return t('MoldManagement.MoldBrandDetail.pass')
if (v === '2' || upper === 'NG') return t('MoldManagement.MoldBrandDetail.fail')
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'
}
const getRepairStatusLabel = (value: any) => {
const v = value === '' || value === null || value === undefined ? undefined : String(value)
if (v === '0') return t('MoldManagement.MoldBrandDetail.repairStatusPending')
if (v === '1') return t('MoldManagement.MoldBrandDetail.repairStatusDone')
return '-'
}
const getRepairStatusTagType = (value: any) => {
const v = value === '' || value === null || value === undefined ? undefined : String(value)
if (v === '1') return 'success'
if (v === '0') return 'warning'
return 'info'
}
const getRepairResultLabel = (value: any) => {
const v = value === '' || value === null || value === undefined ? undefined : String(value)
if (v === '0') return t('MoldManagement.MoldBrandDetail.repairResultPending')
if (v === '1') return t('MoldManagement.MoldBrandDetail.repairResultOk')
if (v === '2') return t('MoldManagement.MoldBrandDetail.repairResultNg')
return '-'
}
const getRepairResultTagType = (value: any) => {
const v = value === '' || value === null || value === undefined ? undefined : String(value)
if (v === '1') return 'success'
if (v === '2') return 'danger'
if (v === '0') return 'info'
return 'info'
}
type HistoryStepItem = {
key: string
name: string
result: any
method?: any
criteria?: any
remark?: any
taskTime?: any
images?: 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,
images: row?.images
}
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 getQrcodeRefreshUrl = () => {
if (!detailData.value?.id || !detailData.value?.code) return ''
return `/erp/mold-brand/regenerate-code?id=${detailData.value.id}&code=${encodeURIComponent(String(detailData.value.code))}`
}
const handleQrcodeRefreshSuccess = async (data: any) => {
if (!detailData.value?.id) return
if (data?.qrcodeUrl) {
detailData.value.qrcodeUrl = data.qrcodeUrl
return
}
const moldData = await MoldBrandApi.getMoldBrand(detailData.value.id)
detailData.value.qrcodeUrl = moldData?.qrcodeUrl
detailData.value.code = moldData?.code ?? detailData.value.code
}
const goBack = () => {
router.push('/mold/mold-brand')
}
const goOperate = (type: number) => {
router.push({ path: '/mold/mold-brand', query: { operateType: String(type), moldId: String(detailData.value?.id) } })
}
const handleReservedAction = (action: string) => {
message.info(t('MoldManagement.MoldBrandDetail.reservedAction', { 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
inspectionLoading.value = true
try {
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 : []
} finally {
inspectionLoading.value = false
}
}
const fetchMaintainHistory = async () => {
if (!brandId.value) return
maintainLoading.value = true
try {
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 : []
} finally {
maintainLoading.value = false
}
}
const fetchRepairHistory = async () => {
if (!brandId.value) return
repairLoading.value = true
try {
const params: any = { moldId: brandId.value }
if (repairQueryCode.value) params.repairCode = repairQueryCode.value
if (repairQueryStatus.value) params.status = repairQueryStatus.value
if (repairDateRange.value && repairDateRange.value.length === 2) {
params.requireDate = [repairDateRange.value[0], repairDateRange.value[1]]
}
const data = await MoldRepairApi.getMoldRepairList(params)
repairList.value = Array.isArray(data) ? data : []
repairActiveNames.value = repairList.value.map((row: any) => String(row.id ?? row.repairCode))
} finally {
repairLoading.value = false
}
}
const fetchInstallRecords = async () => {
if (!brandId.value) return
installLoading.value = true
try {
moldList.value = await MoldBrandApi.getBrandList()
const params: any = { pageNo: installPageNo.value, pageSize: installPageSize.value, moldId: brandId.value }
if (installRemark.value) params.remark = installRemark.value
if (installOperateType.value) params.operateType = installOperateType.value
if (installDateRange.value && installDateRange.value.length === 2) {
params.createTime = [installDateRange.value[0], installDateRange.value[1]]
}
const data = await MoldOperateApi.getMoldOperatePage(params)
installRecords.value = data?.list ?? []
installTotal.value = data?.total ?? 0
} finally {
installLoading.value = false
}
}
const handleQueryInstall = () => {
installPageNo.value = 1
fetchInstallRecords()
}
const handleResetInstall = () => {
installMoldId.value = undefined
installRemark.value = undefined
installOperateType.value = undefined
installDateRange.value = undefined
installPageNo.value = 1
fetchInstallRecords()
}
const handleQueryInspection = async () => {
await fetchInspectionHistory()
}
const handleResetInspection = async () => {
inspectionDateRange.value = undefined
await fetchInspectionHistory()
}
const handleExportInspection = async () => {
inspectionExportLoading.value = true
message.info(t('MoldManagement.MoldBrandDetail.inspectionExportTip'))
inspectionExportLoading.value = false
}
const handleQueryMaintain = async () => {
await fetchMaintainHistory()
}
const handleResetMaintain = async () => {
maintainDateRange.value = undefined
await fetchMaintainHistory()
}
const handleExportMaintain = async () => {
maintainExportLoading.value = true
message.info(t('MoldManagement.MoldBrandDetail.maintainExportTip'))
maintainExportLoading.value = false
}
const handleQueryRepair = async () => {
await fetchRepairHistory()
}
const handleResetRepair = async () => {
repairQueryCode.value = undefined
repairQueryStatus.value = undefined
repairDateRange.value = undefined
await fetchRepairHistory()
}
const handleExportRepair = async () => {
repairExportLoading.value = true
message.info(t('MoldManagement.MoldBrandDetail.repairExportTip'))
repairExportLoading.value = false
}
const getDetail = async () => {
if (!brandId.value) {
message.error(t('MoldManagement.MoldBrandDetail.missingBrandId'))
return
}
detailLoading.value = true
try {
detailData.value = await MoldBrandApi.getMoldBrand(brandId.value)
} finally {
detailLoading.value = false
}
}
const loadTabData = async (tab: string) => {
if (loadedTabs.value.has(tab)) return
loadedTabs.value.add(tab)
switch (tab) {
case 'inspection':
await getChildMolds()
fetchInspectionHistory()
break
case 'repair':
await getChildMolds()
fetchRepairHistory()
break
case 'maintain':
await getChildMolds()
fetchMaintainHistory()
break
case 'install':
await getChildMolds()
fetchInstallRecords()
break
}
}
watch(activeTab, (val) => {
loadTabData(val)
})
onMounted(() => {
getDetail()
})
</script>
<style scoped>
.mold-brand-detail__header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.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__qr-card {
display: flex;
flex-direction: column;
align-items: center;
}
.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 {
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-row--images {
align-items: start;
}
.device-ledger-history-item-label {
color: var(--el-text-color-secondary);
}
.device-ledger-repair-images {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.device-ledger-repair-image,
.device-ledger-repair-image-placeholder {
width: 64px;
height: 64px;
border-radius: 6px;
}
.device-ledger-repair-image {
border: 1px solid var(--el-border-color-lighter);
}
.device-ledger-repair-image-placeholder {
display: inline-flex;
align-items: center;
justify-content: center;
border: 1px dashed var(--el-border-color);
color: var(--el-text-color-placeholder);
background: var(--el-fill-color-light);
}
.mold-brand-asset-image {
width: 96px;
height: 96px;
border: 1px solid var(--el-border-color-lighter);
border-radius: 6px;
}
.mold-brand-asset-video {
width: 280px;
max-width: 100%;
height: 160px;
border-radius: 6px;
background: #000;
}
@media (max-width: 1024px) {
.mold-brand-detail__hero {
grid-template-columns: 1fr;
}
.mold-brand-detail__grid {
grid-template-columns: 1fr;
}
}
</style>