|
|
<template>
|
|
|
<view class="page-container">
|
|
|
<NavBar :title="t('moldLedger.detailTitle')" />
|
|
|
|
|
|
<view class="content-section">
|
|
|
<view class="info-card">
|
|
|
<view class="section-title">{{ t('moldLedger.basicInfo') }}</view>
|
|
|
|
|
|
<view class="summary-box">
|
|
|
<image v-if="coverImage" class="summary-image" :src="coverImage" mode="aspectFill" />
|
|
|
<view v-else class="summary-placeholder">{{ t('moldLedger.moldPlaceholder') }}</view>
|
|
|
|
|
|
<view class="summary-content">
|
|
|
<view class="summary-grid">
|
|
|
<view class="summary-item">
|
|
|
<text class="summary-label">{{ t('moldLedger.moldName') }}</text>
|
|
|
<text class="summary-value">{{ detailValue(detailData?.name) }}</text>
|
|
|
</view>
|
|
|
<view class="summary-item">
|
|
|
<text class="summary-label">{{ t('moldLedger.product') }}</text>
|
|
|
<text class="summary-value">{{ detailValue(detailData?.productName) }}</text>
|
|
|
</view>
|
|
|
<view class="summary-item">
|
|
|
<text class="summary-label">{{ t('moldLedger.moldCode') }}</text>
|
|
|
<text class="summary-value">{{ detailValue(detailData?.code) }}</text>
|
|
|
</view>
|
|
|
<view class="summary-item">
|
|
|
<text class="summary-label">{{ t('moldLedger.status') }}</text>
|
|
|
<view class="summary-status">
|
|
|
<u-tag :text="statusLabel" :type="statusTagType" size="mini" />
|
|
|
</view>
|
|
|
</view>
|
|
|
<template v-if="summaryExpanded">
|
|
|
<view class="summary-item">
|
|
|
<text class="summary-label">{{ t('moldLedger.versionSpec') }}</text>
|
|
|
<text class="summary-value">{{ detailValue(detailData?.version || detailData?.moldType) }}</text>
|
|
|
</view>
|
|
|
<view class="summary-item">
|
|
|
<text class="summary-label">{{ t('moldLedger.cavityCount') }}</text>
|
|
|
<text class="summary-value">{{ detailValue(childMoldCount) }}</text>
|
|
|
</view>
|
|
|
<view class="summary-item">
|
|
|
<text class="summary-label">{{ t('moldLedger.currentDevice') }}</text>
|
|
|
<text class="summary-value">{{ currentDeviceLabel }}</text>
|
|
|
</view>
|
|
|
<view class="summary-item">
|
|
|
<text class="summary-label">{{ t('moldLedger.useCount') }}</text>
|
|
|
<text class="summary-value">{{ detailValue(detailData?.useTime || detailData?.useCount) }}</text>
|
|
|
</view>
|
|
|
</template>
|
|
|
</view>
|
|
|
<view class="summary-toggle" @click="summaryExpanded = !summaryExpanded">
|
|
|
<text class="summary-toggle-text">{{ summaryExpanded ? t('moldLedger.collapse') : t('moldLedger.expand') }}</text>
|
|
|
<u-icon :name="summaryExpanded ? 'arrow-up' : 'arrow-down'" size="20" color="#22486e" />
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
<view class="tab-card">
|
|
|
<view class="tabs-wrap">
|
|
|
<u-tabs activeColor="#2463eb" :list="tabList" :current="currentTab" :is-scroll="false"
|
|
|
@change="handleTabChange" />
|
|
|
</view>
|
|
|
|
|
|
<view v-if="currentTab === 0">
|
|
|
<view v-if="childLoading" class="hint">{{ t('functionCommon.loading') }}</view>
|
|
|
<view v-else-if="!childMolds.length" class="hint">{{ t('moldLedger.noChildMold') }}</view>
|
|
|
<view v-for="item in childMolds" :key="item.id" class="record-card">
|
|
|
<view class="record-head">
|
|
|
<text class="record-title">{{ detailValue(item.name) }}</text>
|
|
|
<view :class="['status-chip', statusClass(item.type)]">{{ moldTypeText(item.type) }}</view>
|
|
|
</view>
|
|
|
<view class="record-row"><text class="record-label">{{ t('moldLedger.installLocation') }}</text><text class="record-value">
|
|
|
{{ detailValue(item.installLocation || item.installPosition || item.currentPosition || item.machineName)
|
|
|
}}</text></view>
|
|
|
<view class="record-row"><text class="record-label">{{ t('moldLedger.material') }}</text><text class="record-value">{{
|
|
|
detailValue(item.material) }}</text></view>
|
|
|
<view class="record-row"><text class="record-label">{{ t('moldLedger.quantity') }}</text><text class="record-value">{{
|
|
|
detailValue(item.quantity || item.count || 1) }}</text></view>
|
|
|
<view v-if="item.pressureNetTime || item.pressureNetDays" class="pressure-net-info">
|
|
|
<view class="pressure-row">
|
|
|
<text class="pressure-dot">{{ t('moldLedger.lastReplace') }}</text>
|
|
|
<text class="pressure-text">{{ formatDateTime(item.pressureNetTime) }}</text>
|
|
|
</view>
|
|
|
<view class="pressure-row pressure-row--right">
|
|
|
<text class="pressure-dot">{{ t('moldLedger.usedDays', { days: detailValue(item.pressureNetDays) }) }}</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
<view v-if="item.remark" class="remark-line">
|
|
|
<text class="remark-text">{{ t('moldLedger.remark') }}:{{ detailValue(item.remark) }}</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
<view v-else-if="currentTab === 1">
|
|
|
<view v-if="inspectionLoading" class="hint">{{ t('functionCommon.loading') }}</view>
|
|
|
<view v-else-if="!inspectionGroups.length" class="hint">{{ t('moldLedger.noInspection') }}</view>
|
|
|
<view v-for="group in inspectionGroups" :key="group.key" class="record-card">
|
|
|
<view class="timeline-meta">
|
|
|
<text class="timeline-time">{{ group.time }}</text>
|
|
|
<text class="timeline-operator">{{ group.operator }}</text>
|
|
|
</view>
|
|
|
<view v-for="item in group.items" :key="item.key" class="history-item">
|
|
|
<view class="history-title-row">
|
|
|
<text class="history-item-name">{{ item.name }}</text>
|
|
|
<text class="result-badge" :class="`result-${item.resultType}`">{{ item.resultLabel }}</text>
|
|
|
</view>
|
|
|
<view class="record-row"><text class="record-label">{{ t('moldLedger.inspectionMethod') }}</text><text class="record-value">{{
|
|
|
detailValue(item.method) }}</text></view>
|
|
|
<view class="record-row"><text class="record-label">{{ t('moldLedger.criteria') }}</text><text class="record-value">{{
|
|
|
detailValue(item.criteria) }}</text></view>
|
|
|
<view class="record-row"><text class="record-label">{{ t('moldLedger.inspectionTime') }}</text><text class="record-value">{{
|
|
|
detailValue(item.taskTimeLabel) }}</text></view>
|
|
|
<view class="record-row"><text class="record-label">{{ t('moldLedger.remark') }}</text><text class="record-value">{{
|
|
|
detailValue(item.remark) }}</text></view>
|
|
|
<view v-if="item.images.length" class="history-images">
|
|
|
<image v-for="img in item.images" :key="img" class="history-image" :src="img" mode="aspectFill"
|
|
|
@click="previewImages(item.images, img)" />
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
<view v-else-if="currentTab === 2">
|
|
|
<view v-if="repairLoading" class="hint">{{ t('functionCommon.loading') }}</view>
|
|
|
<view v-else-if="!repairRecords.length" class="hint">{{ t('moldLedger.noRepair') }}</view>
|
|
|
<view v-for="row in repairRecords" :key="row.key" class="record-card">
|
|
|
<view class="record-head">
|
|
|
<text class="record-title">{{ detailValue(row.repairCode || row.subjectName || row.repairName) }}</text>
|
|
|
<text class="result-badge" :class="`result-${row.resultType}`">{{ row.resultLabel }}</text>
|
|
|
</view>
|
|
|
<view class="record-row"><text class="record-label">{{ t('moldLedger.repairProject') }}</text><text class="record-value">{{
|
|
|
detailValue(row.subjectName || row.subjectCode) }}</text></view>
|
|
|
<view class="record-row"><text class="record-label">{{ t('moldLedger.projectContent') }}</text><text class="record-value">{{
|
|
|
detailValue(row.subjectContent) }}</text></view>
|
|
|
<view class="record-row"><text class="record-label">{{ t('moldLedger.finishTime') }}</text><text class="record-value">{{
|
|
|
detailValue(row.finishDateLabel) }}</text></view>
|
|
|
<view class="record-row"><text class="record-label">{{ t('moldLedger.remark') }}</text><text class="record-value">{{
|
|
|
detailValue(row.remark) }}</text></view>
|
|
|
<view v-if="row.images.length" class="history-images">
|
|
|
<image v-for="img in row.images" :key="img" class="history-image" :src="img" mode="aspectFill"
|
|
|
@click="previewImages(row.images, img)" />
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
<view v-else-if="currentTab === 3">
|
|
|
<view v-if="maintainLoading" class="hint">{{ t('functionCommon.loading') }}</view>
|
|
|
<view v-else-if="!maintainGroups.length" class="hint">{{ t('moldLedger.noMaintenance') }}</view>
|
|
|
<view v-for="group in maintainGroups" :key="group.key" class="record-card">
|
|
|
<view class="timeline-meta">
|
|
|
<text class="timeline-time">{{ group.time }}</text>
|
|
|
<text class="timeline-operator">{{ group.operator }}</text>
|
|
|
</view>
|
|
|
<view v-for="item in group.items" :key="item.key" class="history-item">
|
|
|
<view class="history-title-row">
|
|
|
<text class="history-item-name">{{ item.name }}</text>
|
|
|
<text class="result-badge" :class="`result-${item.resultType}`">{{ item.resultLabel }}</text>
|
|
|
</view>
|
|
|
<view class="record-row"><text class="record-label">{{ t('moldLedger.maintainMethod') }}</text><text class="record-value">{{
|
|
|
detailValue(item.method) }}</text></view>
|
|
|
<view class="record-row"><text class="record-label">{{ t('moldLedger.criteria') }}</text><text class="record-value">{{
|
|
|
detailValue(item.criteria) }}</text></view>
|
|
|
<view class="record-row"><text class="record-label">{{ t('moldLedger.maintainTime') }}</text><text class="record-value">{{
|
|
|
detailValue(item.taskTimeLabel) }}</text></view>
|
|
|
<view class="record-row"><text class="record-label">{{ t('moldLedger.remark') }}</text><text class="record-value">{{
|
|
|
detailValue(item.remark) }}</text></view>
|
|
|
<view v-if="item.images.length" class="history-images">
|
|
|
<image v-for="img in item.images" :key="img" class="history-image" :src="img" mode="aspectFill"
|
|
|
@click="previewImages(item.images, img)" />
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
<view v-else>
|
|
|
<view v-if="installLoading" class="hint">{{ t('functionCommon.loading') }}</view>
|
|
|
<view v-else-if="!installRecords.length" class="hint">{{ t('moldLedger.noInstall') }}</view>
|
|
|
<view v-for="row in installRecords" :key="String(row.id || row.createTime)" class="record-card">
|
|
|
<view class="record-head">
|
|
|
<text class="record-title">{{ operateTypeLabel(row.operateType) }}</text>
|
|
|
<text class="record-status">{{ detailValue(row.deviceName) }}</text>
|
|
|
</view>
|
|
|
<view class="record-row"><text class="record-label">{{ t('moldLedger.operateMold') }}</text><text class="record-value">{{
|
|
|
detailValue(row.moldName) }}</text></view>
|
|
|
<view class="record-row"><text class="record-label">{{ t('moldLedger.operator') }}</text><text class="record-value">{{
|
|
|
detailValue(row.creatorName) }}</text></view>
|
|
|
<view class="record-row"><text class="record-label">{{ t('moldLedger.time') }}</text><text class="record-value">{{
|
|
|
formatDateTime(row.createTime) }}</text></view>
|
|
|
<view class="record-row"><text class="record-label">{{ t('moldLedger.remark') }}</text><text class="record-value">{{
|
|
|
detailValue(row.remark) }}</text></view>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
import { ref, computed } from 'vue'
|
|
|
import { onLoad } from '@dcloudio/uni-app'
|
|
|
import { useI18n } from 'vue-i18n'
|
|
|
import NavBar from '@/components/common/NavBar.vue'
|
|
|
import {
|
|
|
getMoldBrandDetail,
|
|
|
getMoldInspectionByMoldId,
|
|
|
getMoldMaintenanceByMoldId,
|
|
|
getMoldPage,
|
|
|
getMoldRepairListByMoldId
|
|
|
} from '@/api/mes/mold'
|
|
|
import { getMoldOperatePage } from '@/api/mes/moldoperate'
|
|
|
import { DICT_TYPE, getDictLabel, initAllDict } from '@/utils/dict'
|
|
|
|
|
|
const { t } = useI18n()
|
|
|
const brandId = ref(undefined)
|
|
|
const summaryExpanded = ref(false)
|
|
|
const detailData = ref(null)
|
|
|
const currentTab = ref(0)
|
|
|
const loadedTabs = ref(new Set())
|
|
|
const childLoading = ref(false)
|
|
|
const inspectionLoading = ref(false)
|
|
|
const repairLoading = ref(false)
|
|
|
const maintainLoading = ref(false)
|
|
|
const installLoading = ref(false)
|
|
|
const childMolds = ref([])
|
|
|
const inspectionList = ref([])
|
|
|
const repairList = ref([])
|
|
|
const maintainList = ref([])
|
|
|
const installRecords = ref([])
|
|
|
|
|
|
const tabList = ref([
|
|
|
{ name: t('moldLedger.tabChildMold') },
|
|
|
{ name: t('moldLedger.tabInspection') },
|
|
|
{ name: t('moldLedger.tabRepair') },
|
|
|
{ name: t('moldLedger.tabMaintenance') },
|
|
|
{ name: t('moldLedger.tabInstall') }
|
|
|
])
|
|
|
|
|
|
const coverImage = computed(() => parseImages(detailData.value?.images)[0] || '')
|
|
|
const currentDeviceLabel = computed(() =>
|
|
|
detailValue(detailData.value?.currentDevice || detailData.value?.machineName || detailData.value?.deviceName)
|
|
|
)
|
|
|
const childMoldCount = computed(() => childMolds.value.length || detailData.value?.childMoldCount || detailData.value?.moldSize || 0)
|
|
|
const statusLabel = computed(() => getDictLabel(DICT_TYPE.ERP_MOLD_STATUS, detailData.value?.status, detailValue(detailData.value?.status)))
|
|
|
const statusTagType = computed(() => {
|
|
|
const label = String(statusLabel.value || '')
|
|
|
if (label.includes('正常') || label.includes('使用')) return 'success'
|
|
|
if (label.includes('维修') || label.includes('警')) return 'warning'
|
|
|
if (label.includes('报废') || label.includes('停')) return 'error'
|
|
|
return 'primary'
|
|
|
})
|
|
|
|
|
|
const inspectionGroups = computed(() =>
|
|
|
buildStepGroups(inspectionList.value, {
|
|
|
timeFieldCandidates: ['taskTime', 'inspectionTime', 'createTime'],
|
|
|
nameFieldCandidates: ['inspectionItemName', 'name', 'itemName'],
|
|
|
resultFieldCandidates: ['inspectionResult', 'result'],
|
|
|
methodFieldCandidates: ['inspectionMethod', 'method'],
|
|
|
criteriaFieldCandidates: ['judgmentCriteria', 'criteria'],
|
|
|
imagesFieldCandidates: ['images'],
|
|
|
remarkFieldCandidates: ['remark']
|
|
|
})
|
|
|
)
|
|
|
|
|
|
const maintainGroups = computed(() =>
|
|
|
buildStepGroups(maintainList.value, {
|
|
|
timeFieldCandidates: ['taskTime', 'inspectionTime', 'createTime'],
|
|
|
nameFieldCandidates: ['maintainItemName', 'inspectionItemName', 'name', 'itemName'],
|
|
|
resultFieldCandidates: ['maintainResult', 'inspectionResult', 'result'],
|
|
|
methodFieldCandidates: ['inspectionMethod', 'method'],
|
|
|
criteriaFieldCandidates: ['judgmentCriteria', 'criteria'],
|
|
|
imagesFieldCandidates: ['images'],
|
|
|
remarkFieldCandidates: ['remark']
|
|
|
})
|
|
|
)
|
|
|
|
|
|
const repairRecords = computed(() => {
|
|
|
const rows = Array.isArray(repairList.value) ? repairList.value : []
|
|
|
return rows.map((row, index) => {
|
|
|
const resultMeta = formatRepairResult(row?.repairResult !== undefined ? row.repairResult : row?.result)
|
|
|
return {
|
|
|
...row,
|
|
|
key: String(row?.id ?? row?.repairCode ?? index),
|
|
|
finishDateLabel: formatDateTime(row?.finishDate || row?.createTime),
|
|
|
resultLabel: resultMeta.label,
|
|
|
resultType: resultMeta.type,
|
|
|
images: parseImages(row?.malfunctionUrl || row?.malfunctionImages || row?.images)
|
|
|
}
|
|
|
})
|
|
|
})
|
|
|
|
|
|
onLoad(async (query) => {
|
|
|
const id = query?.id ? decodeURIComponent(String(query.id)) : ''
|
|
|
brandId.value = id || undefined
|
|
|
await initAllDict()
|
|
|
await fetchDetail()
|
|
|
await loadTabData(0)
|
|
|
})
|
|
|
|
|
|
async function fetchDetail() {
|
|
|
if (!brandId.value) {
|
|
|
uni.showToast({ title: t('moldLedger.missingBrandId'), icon: 'none' })
|
|
|
return
|
|
|
}
|
|
|
try {
|
|
|
const res = await getMoldBrandDetail(brandId.value)
|
|
|
detailData.value = normalizeDetail(res)
|
|
|
} catch (e) {
|
|
|
uni.showToast({ title: t('moldLedger.detailLoadFailed'), icon: 'none' })
|
|
|
}
|
|
|
}
|
|
|
|
|
|
async function handleTabChange(e) {
|
|
|
const idx = e && typeof e === 'object' ? e.index : e
|
|
|
currentTab.value = Number(idx === undefined ? 0 : idx)
|
|
|
await loadTabData(currentTab.value)
|
|
|
}
|
|
|
|
|
|
async function loadTabData(tabIndex) {
|
|
|
if (loadedTabs.value.has(tabIndex)) return
|
|
|
loadedTabs.value.add(tabIndex)
|
|
|
switch (Number(tabIndex)) {
|
|
|
case 0:
|
|
|
await fetchChildMolds()
|
|
|
break
|
|
|
case 1:
|
|
|
await fetchInspectionRecords()
|
|
|
break
|
|
|
case 2:
|
|
|
await fetchRepairRecords()
|
|
|
break
|
|
|
case 3:
|
|
|
await fetchMaintainRecords()
|
|
|
break
|
|
|
case 4:
|
|
|
await fetchInstallRecords()
|
|
|
break
|
|
|
default:
|
|
|
break
|
|
|
}
|
|
|
}
|
|
|
|
|
|
async function fetchChildMolds() {
|
|
|
if (!brandId.value) return
|
|
|
childLoading.value = true
|
|
|
try {
|
|
|
let page = 1
|
|
|
const size = 20
|
|
|
let rows = []
|
|
|
let total = 0
|
|
|
do {
|
|
|
const res = await getMoldPage({ pageNo: page, pageSize: size, brandId: brandId.value })
|
|
|
const pageData = normalizePageData(res)
|
|
|
rows = rows.concat(pageData.list)
|
|
|
total = pageData.total
|
|
|
page += 1
|
|
|
if (!pageData.list.length) break
|
|
|
} while (rows.length < total)
|
|
|
childMolds.value = rows
|
|
|
} catch (e) {
|
|
|
childMolds.value = []
|
|
|
uni.showToast({ title: t('moldLedger.childLoadFailed'), icon: 'none' })
|
|
|
} finally {
|
|
|
childLoading.value = false
|
|
|
}
|
|
|
}
|
|
|
|
|
|
async function fetchInspectionRecords() {
|
|
|
if (!brandId.value) return
|
|
|
inspectionLoading.value = true
|
|
|
try {
|
|
|
const res = await getMoldInspectionByMoldId(brandId.value)
|
|
|
inspectionList.value = normalizeListData(res)
|
|
|
} catch (e) {
|
|
|
inspectionList.value = []
|
|
|
uni.showToast({ title: t('moldLedger.inspectionLoadFailed'), icon: 'none' })
|
|
|
} finally {
|
|
|
inspectionLoading.value = false
|
|
|
}
|
|
|
}
|
|
|
|
|
|
async function fetchRepairRecords() {
|
|
|
if (!brandId.value) return
|
|
|
repairLoading.value = true
|
|
|
try {
|
|
|
const res = await getMoldRepairListByMoldId(brandId.value)
|
|
|
repairList.value = normalizeListData(res)
|
|
|
} catch (e) {
|
|
|
repairList.value = []
|
|
|
uni.showToast({ title: t('moldLedger.repairLoadFailed'), icon: 'none' })
|
|
|
} finally {
|
|
|
repairLoading.value = false
|
|
|
}
|
|
|
}
|
|
|
|
|
|
async function fetchMaintainRecords() {
|
|
|
if (!brandId.value) return
|
|
|
maintainLoading.value = true
|
|
|
try {
|
|
|
const res = await getMoldMaintenanceByMoldId(brandId.value)
|
|
|
maintainList.value = normalizeListData(res)
|
|
|
} catch (e) {
|
|
|
maintainList.value = []
|
|
|
uni.showToast({ title: t('moldLedger.maintainLoadFailed'), icon: 'none' })
|
|
|
} finally {
|
|
|
maintainLoading.value = false
|
|
|
}
|
|
|
}
|
|
|
|
|
|
async function fetchInstallRecords() {
|
|
|
if (!brandId.value) return
|
|
|
installLoading.value = true
|
|
|
try {
|
|
|
const res = await getMoldOperatePage({ pageNo: 1, pageSize: 100, moldId: brandId.value })
|
|
|
installRecords.value = normalizePageData(res).list
|
|
|
} catch (e) {
|
|
|
installRecords.value = []
|
|
|
uni.showToast({ title: t('moldLedger.installLoadFailed'), icon: 'none' })
|
|
|
} finally {
|
|
|
installLoading.value = false
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function normalizePageData(res) {
|
|
|
const root = res && res.data !== undefined ? res.data : res
|
|
|
const pageResult = root?.pageResult || root?.data?.pageResult || root?.data || root || {}
|
|
|
const candidateList = pageResult?.list || pageResult?.rows || pageResult?.records || []
|
|
|
const candidateTotal = pageResult?.total ?? root?.total ?? root?.data?.total ?? candidateList.length
|
|
|
return {
|
|
|
list: Array.isArray(candidateList) ? candidateList : [],
|
|
|
total: Number(candidateTotal || 0)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function normalizeListData(res) {
|
|
|
const root = res && res.data !== undefined ? res.data : res
|
|
|
if (Array.isArray(root)) return root
|
|
|
if (Array.isArray(root?.data)) return root.data
|
|
|
if (Array.isArray(root?.list)) return root.list
|
|
|
if (Array.isArray(root?.rows)) return root.rows
|
|
|
if (Array.isArray(root?.records)) return root.records
|
|
|
if (Array.isArray(root?.data?.list)) return root.data.list
|
|
|
if (Array.isArray(root?.data?.rows)) return root.data.rows
|
|
|
if (Array.isArray(root?.data?.records)) return root.data.records
|
|
|
return []
|
|
|
}
|
|
|
|
|
|
function normalizeDetail(res) {
|
|
|
const root = res && res.data !== undefined ? res.data : res
|
|
|
if (root?.data && typeof root.data === 'object') return root.data
|
|
|
if (root && typeof root === 'object') return root
|
|
|
return {}
|
|
|
}
|
|
|
|
|
|
function operateTypeLabel(type) {
|
|
|
return String(type) === '2' ? t('moldLedger.lowerMold') : t('moldLedger.upperMold')
|
|
|
}
|
|
|
|
|
|
function moldStatusText(status) {
|
|
|
return getDictLabel(DICT_TYPE.ERP_MOLD_STATUS, status, detailValue(status))
|
|
|
}
|
|
|
|
|
|
function moldTypeText(type) {
|
|
|
return getDictLabel(DICT_TYPE.SUBMOLD_TYPE, type, detailValue(type))
|
|
|
}
|
|
|
|
|
|
function statusTextClass(status) {
|
|
|
const label = moldStatusText(status)
|
|
|
if (label.includes('正常') || label.includes('使用')) return 'text-success'
|
|
|
if (label.includes('维修') || label.includes('警')) return 'text-warning'
|
|
|
if (label.includes('报废') || label.includes('停')) return 'text-danger'
|
|
|
return 'text-default'
|
|
|
}
|
|
|
|
|
|
function statusClass(type) {
|
|
|
const label = moldTypeText(type)
|
|
|
if (label.includes('上模') || label.includes('主模')) return 'status-success'
|
|
|
if (label.includes('下模') || label.includes('副模')) return 'status-warning'
|
|
|
return 'status-default'
|
|
|
}
|
|
|
|
|
|
function detailValue(value) {
|
|
|
if (value === 0) return '0'
|
|
|
if (value === false) return t('functionCommon.no')
|
|
|
if (value === true) return t('functionCommon.yes')
|
|
|
if (value === null || value === undefined) return '-'
|
|
|
const text = String(value).trim()
|
|
|
return text || '-'
|
|
|
}
|
|
|
|
|
|
function parseImages(value) {
|
|
|
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)
|
|
|
}
|
|
|
|
|
|
function formatDateTime(value) {
|
|
|
if (!value) return '-'
|
|
|
if (Array.isArray(value) && value.length >= 3) {
|
|
|
const [y, m, d, hh = 0, mm = 0, ss = 0] = value
|
|
|
const pad = (n) => String(n).padStart(2, '0')
|
|
|
return `${y}-${pad(m)}-${pad(d)} ${pad(hh)}:${pad(mm)}:${pad(ss)}`
|
|
|
}
|
|
|
const text = String(value).trim()
|
|
|
if (!text) return '-'
|
|
|
const numeric = Number(text)
|
|
|
if (Number.isFinite(numeric)) {
|
|
|
const timestamp = text.length === 10 ? numeric * 1000 : numeric
|
|
|
const date = new Date(timestamp)
|
|
|
if (!Number.isNaN(date.getTime())) {
|
|
|
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')} ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}:${String(date.getSeconds()).padStart(2, '0')}`
|
|
|
}
|
|
|
}
|
|
|
const date = new Date(text)
|
|
|
if (!Number.isNaN(date.getTime())) {
|
|
|
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')} ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}:${String(date.getSeconds()).padStart(2, '0')}`
|
|
|
}
|
|
|
return text
|
|
|
}
|
|
|
|
|
|
function pickFirst(obj, keys) {
|
|
|
for (const key of keys) {
|
|
|
if (obj && obj[key] !== undefined && obj[key] !== null && String(obj[key]).trim() !== '') {
|
|
|
return obj[key]
|
|
|
}
|
|
|
}
|
|
|
return undefined
|
|
|
}
|
|
|
|
|
|
function formatResult(value) {
|
|
|
const raw = value === null || value === undefined ? '' : String(value).trim()
|
|
|
const upper = raw.toUpperCase()
|
|
|
if (!raw) return { label: '-', type: 'info' }
|
|
|
if (raw === '0') return { label: t('moldLedger.resultPending'), type: 'info' }
|
|
|
if (raw === '1' || upper === 'OK') return { label: t('moldLedger.resultPass'), type: 'success' }
|
|
|
if (raw === '2' || upper === 'NG') return { label: t('moldLedger.resultWarning'), type: 'warning' }
|
|
|
return { label: raw, type: 'info' }
|
|
|
}
|
|
|
|
|
|
function formatRepairResult(value) {
|
|
|
const raw = value === null || value === undefined ? '' : String(value).trim()
|
|
|
if (raw === '1') return { label: t('moldLedger.repairCompleted'), type: 'success' }
|
|
|
if (raw === '2') return { label: t('moldLedger.repairAbnormal'), type: 'danger' }
|
|
|
if (raw === '0') return { label: t('moldLedger.repairProcessing'), type: 'warning' }
|
|
|
return { label: detailValue(value), type: 'info' }
|
|
|
}
|
|
|
|
|
|
function buildStepGroups(rows, options) {
|
|
|
const groupsMap = new Map()
|
|
|
const list = Array.isArray(rows) ? rows : []
|
|
|
for (const row of list) {
|
|
|
const time = formatDateTime(pickFirst(row, options.timeFieldCandidates) || row?.createTime)
|
|
|
const operator = detailValue(row?.operator || row?.creatorName || row?.creator)
|
|
|
const groupKey = `${row?.managementId || ''}_${time}_${operator}`
|
|
|
const name = pickFirst(row, options.nameFieldCandidates) || '-'
|
|
|
const resultMeta = formatResult(pickFirst(row, options.resultFieldCandidates))
|
|
|
const item = {
|
|
|
key: String(row?.id ?? `${groupKey}_${name}`),
|
|
|
name: detailValue(name),
|
|
|
resultLabel: resultMeta.label,
|
|
|
resultType: resultMeta.type,
|
|
|
method: pickFirst(row, options.methodFieldCandidates),
|
|
|
criteria: pickFirst(row, options.criteriaFieldCandidates),
|
|
|
remark: pickFirst(row, options.remarkFieldCandidates),
|
|
|
images: parseImages(pickFirst(row, options.imagesFieldCandidates)),
|
|
|
taskTimeLabel: formatDateTime(row?.taskTime || row?.inspectionTime || row?.createTime)
|
|
|
}
|
|
|
if (!groupsMap.has(groupKey)) {
|
|
|
groupsMap.set(groupKey, { key: groupKey, time: time || '-', operator, items: [item] })
|
|
|
} else {
|
|
|
groupsMap.get(groupKey).items.push(item)
|
|
|
}
|
|
|
}
|
|
|
return Array.from(groupsMap.values())
|
|
|
}
|
|
|
|
|
|
function previewImages(list, current) {
|
|
|
if (!list || !list.length) return
|
|
|
uni.previewImage({ urls: list, current })
|
|
|
}
|
|
|
</script>
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
.page-container {
|
|
|
min-height: 100vh;
|
|
|
background: #f5f6f7;
|
|
|
}
|
|
|
|
|
|
.content-section {
|
|
|
padding: 24rpx 24rpx 32rpx;
|
|
|
}
|
|
|
|
|
|
.info-card,
|
|
|
.tab-card {
|
|
|
margin-bottom: 24rpx;
|
|
|
padding: 28rpx;
|
|
|
border-radius: 16rpx;
|
|
|
background: #ffffff;
|
|
|
}
|
|
|
|
|
|
.section-title {
|
|
|
margin-bottom: 16rpx;
|
|
|
font-size: 30rpx;
|
|
|
color: #111827;
|
|
|
font-weight: 700;
|
|
|
}
|
|
|
|
|
|
.summary-box {
|
|
|
display: flex;
|
|
|
gap: 22rpx;
|
|
|
}
|
|
|
|
|
|
.summary-image,
|
|
|
.summary-placeholder {
|
|
|
width: 172rpx;
|
|
|
height: 172rpx;
|
|
|
border-radius: 18rpx;
|
|
|
flex-shrink: 0;
|
|
|
}
|
|
|
|
|
|
.summary-image {
|
|
|
background: #eef2f7;
|
|
|
}
|
|
|
|
|
|
.summary-placeholder {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
background: linear-gradient(180deg, #edf3fb 0%, #dfe8f4 100%);
|
|
|
color: #5f7490;
|
|
|
font-size: 30rpx;
|
|
|
font-weight: 700;
|
|
|
}
|
|
|
|
|
|
.summary-content {
|
|
|
flex: 1;
|
|
|
min-width: 0;
|
|
|
}
|
|
|
|
|
|
.summary-grid {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
gap: 16rpx;
|
|
|
}
|
|
|
|
|
|
.summary-item {
|
|
|
display: flex;
|
|
|
align-items: flex-start;
|
|
|
justify-content: space-between;
|
|
|
gap: 20rpx;
|
|
|
min-width: 0;
|
|
|
}
|
|
|
|
|
|
.summary-label {
|
|
|
font-size: 26rpx;
|
|
|
color: #64748b;
|
|
|
flex-shrink: 0;
|
|
|
min-width: 140rpx;
|
|
|
}
|
|
|
|
|
|
.summary-value {
|
|
|
flex: 1;
|
|
|
font-size: 26rpx;
|
|
|
color: #1f2937;
|
|
|
line-height: 1.4;
|
|
|
text-align: right;
|
|
|
}
|
|
|
|
|
|
.summary-status {
|
|
|
flex: 1;
|
|
|
text-align: right;
|
|
|
}
|
|
|
|
|
|
.summary-toggle {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
gap: 8rpx;
|
|
|
padding-top: 16rpx;
|
|
|
margin-top: 20rpx;
|
|
|
border-top: 1rpx solid #eeeff2;
|
|
|
}
|
|
|
|
|
|
.summary-toggle-text {
|
|
|
font-size: 24rpx;
|
|
|
color: #22486e;
|
|
|
}
|
|
|
|
|
|
.tab-card {
|
|
|
padding-bottom: 8rpx;
|
|
|
}
|
|
|
|
|
|
.tabs-wrap {
|
|
|
margin-bottom: 20rpx;
|
|
|
}
|
|
|
|
|
|
.hint {
|
|
|
padding: 40rpx 0;
|
|
|
text-align: center;
|
|
|
font-size: 28rpx;
|
|
|
color: #94a3b8;
|
|
|
}
|
|
|
|
|
|
.record-card {
|
|
|
margin-bottom: 20rpx;
|
|
|
padding: 24rpx;
|
|
|
border-radius: 22rpx;
|
|
|
background: linear-gradient(180deg, #ffffff 0%, #fbfcfe 100%);
|
|
|
border: 1rpx solid #edf2f7;
|
|
|
}
|
|
|
|
|
|
.record-head {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: space-between;
|
|
|
gap: 16rpx;
|
|
|
margin-bottom: 16rpx;
|
|
|
}
|
|
|
|
|
|
.record-title {
|
|
|
flex: 1;
|
|
|
min-width: 0;
|
|
|
font-size: 30rpx;
|
|
|
color: #1f2937;
|
|
|
font-weight: 700;
|
|
|
}
|
|
|
|
|
|
.record-status {
|
|
|
font-size: 24rpx;
|
|
|
font-weight: 600;
|
|
|
}
|
|
|
|
|
|
.status-chip {
|
|
|
padding: 8rpx 18rpx;
|
|
|
border-radius: 999rpx;
|
|
|
font-size: 24rpx;
|
|
|
font-weight: 600;
|
|
|
line-height: 1.2;
|
|
|
}
|
|
|
|
|
|
.status-success {
|
|
|
color: #15803d;
|
|
|
background: #dcfce7;
|
|
|
}
|
|
|
|
|
|
.status-warning {
|
|
|
color: #c2410c;
|
|
|
background: #ffedd5;
|
|
|
}
|
|
|
|
|
|
.status-default {
|
|
|
color: #64748b;
|
|
|
background: #f1f5f9;
|
|
|
}
|
|
|
|
|
|
.record-grid {
|
|
|
display: grid;
|
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
|
gap: 20rpx 28rpx;
|
|
|
}
|
|
|
|
|
|
.record-cell {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
gap: 6rpx;
|
|
|
min-width: 0;
|
|
|
}
|
|
|
|
|
|
.record-label {
|
|
|
font-size: 24rpx;
|
|
|
color: #94a3b8;
|
|
|
flex-shrink: 0;
|
|
|
min-width: 120rpx;
|
|
|
}
|
|
|
|
|
|
.record-value {
|
|
|
flex: 1;
|
|
|
font-size: 27rpx;
|
|
|
color: #1f2937;
|
|
|
font-weight: 500;
|
|
|
line-height: 1.4;
|
|
|
word-break: break-all;
|
|
|
}
|
|
|
|
|
|
.record-row {
|
|
|
display: flex;
|
|
|
align-items: flex-start;
|
|
|
justify-content: space-between;
|
|
|
gap: 20rpx;
|
|
|
margin-bottom: 10rpx;
|
|
|
}
|
|
|
|
|
|
.record-row:last-child {
|
|
|
margin-bottom: 0;
|
|
|
}
|
|
|
|
|
|
.pressure-net-info {
|
|
|
margin-top: 18rpx;
|
|
|
padding-top: 16rpx;
|
|
|
border-top: 1rpx solid #eeeff2;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: space-between;
|
|
|
}
|
|
|
|
|
|
.pressure-row {
|
|
|
display: flex;
|
|
|
align-items: baseline;
|
|
|
gap: 12rpx;
|
|
|
}
|
|
|
|
|
|
.pressure-row--right {
|
|
|
text-align: right;
|
|
|
}
|
|
|
|
|
|
.pressure-dot {
|
|
|
font-size: 23rpx;
|
|
|
color: #94a3b8;
|
|
|
flex-shrink: 0;
|
|
|
}
|
|
|
|
|
|
.pressure-text {
|
|
|
font-size: 23rpx;
|
|
|
color: #334155;
|
|
|
}
|
|
|
|
|
|
.remark-line {
|
|
|
margin-top: 10rpx;
|
|
|
padding: 12rpx 16rpx;
|
|
|
background: #fafbfc;
|
|
|
border-radius: 10rpx;
|
|
|
border: 1rpx solid #eeeff2;
|
|
|
}
|
|
|
|
|
|
.remark-text {
|
|
|
font-size: 23rpx;
|
|
|
color: #64748b;
|
|
|
line-height: 1.5;
|
|
|
}
|
|
|
|
|
|
.timeline-meta {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: space-between;
|
|
|
gap: 16rpx;
|
|
|
margin-bottom: 18rpx;
|
|
|
}
|
|
|
|
|
|
.timeline-time {
|
|
|
font-size: 28rpx;
|
|
|
color: #1f4f81;
|
|
|
font-weight: 700;
|
|
|
}
|
|
|
|
|
|
.timeline-operator {
|
|
|
font-size: 24rpx;
|
|
|
color: #64748b;
|
|
|
}
|
|
|
|
|
|
.history-item {
|
|
|
padding: 20rpx;
|
|
|
border-radius: 18rpx;
|
|
|
background: #f8fbff;
|
|
|
margin-bottom: 16rpx;
|
|
|
}
|
|
|
|
|
|
.history-item:last-child {
|
|
|
margin-bottom: 0;
|
|
|
}
|
|
|
|
|
|
.history-title-row {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: space-between;
|
|
|
gap: 16rpx;
|
|
|
margin-bottom: 12rpx;
|
|
|
}
|
|
|
|
|
|
.history-item-name {
|
|
|
font-size: 28rpx;
|
|
|
color: #1f2937;
|
|
|
font-weight: 700;
|
|
|
}
|
|
|
|
|
|
.result-badge {
|
|
|
padding: 8rpx 18rpx;
|
|
|
border-radius: 999rpx;
|
|
|
font-size: 22rpx;
|
|
|
font-weight: 600;
|
|
|
}
|
|
|
|
|
|
.result-success {
|
|
|
background: rgba(34, 197, 94, 0.12);
|
|
|
color: #16a34a;
|
|
|
}
|
|
|
|
|
|
.result-warning {
|
|
|
background: rgba(245, 158, 11, 0.14);
|
|
|
color: #d97706;
|
|
|
}
|
|
|
|
|
|
.result-danger {
|
|
|
background: rgba(239, 68, 68, 0.12);
|
|
|
color: #dc2626;
|
|
|
}
|
|
|
|
|
|
.result-info {
|
|
|
background: rgba(148, 163, 184, 0.16);
|
|
|
color: #64748b;
|
|
|
}
|
|
|
|
|
|
.history-images {
|
|
|
display: flex;
|
|
|
flex-wrap: wrap;
|
|
|
gap: 12rpx;
|
|
|
margin-top: 14rpx;
|
|
|
}
|
|
|
|
|
|
.history-image {
|
|
|
width: 150rpx;
|
|
|
height: 150rpx;
|
|
|
border-radius: 14rpx;
|
|
|
background: #edf2f7;
|
|
|
}
|
|
|
|
|
|
.text-success {
|
|
|
color: #16a34a;
|
|
|
}
|
|
|
|
|
|
.text-warning {
|
|
|
color: #d97706;
|
|
|
}
|
|
|
|
|
|
.text-danger {
|
|
|
color: #dc2626;
|
|
|
}
|
|
|
|
|
|
.text-default {
|
|
|
color: #64748b;
|
|
|
}
|
|
|
</style>
|