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.

1048 lines
35 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>
<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 class="search-bar">
<view class="search-row">
<view class="search-field">
<text class="search-label">编码</text>
<input v-model="childSearchCode" class="search-input" placeholder="请输入编码" confirm-type="search" @confirm="handleChildSearch" />
</view>
<view class="search-field">
<text class="search-label">名称</text>
<input v-model="childSearchName" class="search-input" placeholder="请输入名称" confirm-type="search" @confirm="handleChildSearch" />
</view>
</view>
<view class="search-btns">
<view class="search-btn primary" @click="handleChildSearch">
<uni-icons type="search" size="16" color="#fff"></uni-icons>
<text>查询</text>
</view>
<view class="search-btn" @click="resetChildSearch">
<uni-icons type="refresh" size="16" color="#fff"></uni-icons>
<text>重置</text>
</view>
<view class="search-btn add" @click="goAddChildMold">
<uni-icons type="plusempty" size="16" color="#fff"></uni-icons>
<text>新增</text>
</view>
</view>
</view>
<view v-if="childLoading" class="hint">{{ t('functionCommon.loading') }}</view>
<view v-else-if="!filteredChildMolds.length" class="hint">{{ t('moldLedger.noChildMold') }}</view>
<view v-for="item in filteredChildMolds" :key="item.id" class="record-card">
<view class="record-head">
<view class="record-head-left">
<text class="record-title">{{ detailValue(item.name) }}</text>
<text class="record-code">{{ detailValue(item.code || item.moldCode) }}</text>
</view>
<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" @click="openRepairDetail(row)">
<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" @click.stop>
<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, onShow } from '@dcloudio/uni-app'
import { useI18n } from 'vue-i18n'
import NavBar from '@/components/common/NavBar.vue'
import {
getMoldBrandDetail,
getMoldInspectionByMoldId,
getMoldMaintenanceByMoldId,
getMoldPage
} from '@/api/mes/mold'
import { getMoldOperatePage } from '@/api/mes/moldoperate'
import { getMoldRepairList } from '@/api/mes/moldrepair'
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 childSearchCode = ref('')
const childSearchName = ref('')
const filteredChildMolds = computed(() => {
if (!childSearchCode.value.trim() && !childSearchName.value.trim()) return childMolds.value
return childMolds.value.filter(item => {
const matchCode = !childSearchCode.value.trim() || ((item.code || item.moldCode || '').toLowerCase().includes(childSearchCode.value.trim().toLowerCase()))
const matchName = !childSearchName.value.trim() || ((item.name || item.moldName || '').toLowerCase().includes(childSearchName.value.trim().toLowerCase()))
return matchCode && matchName
})
})
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('使用') || 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 ?? row?.repairStatus ?? 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 || row?.faultImages)
}
})
})
function goAddChildMold() {
if (!brandId.value) return
uni.navigateTo({ url: `/pages_function/pages/moldLedger/childMoldForm?brandId=${encodeURIComponent(String(brandId.value))}` })
}
function handleChildSearch() { }
function resetChildSearch() {
childSearchCode.value = ''
childSearchName.value = ''
}
function refreshChildMoldsIfNeeded() {
loadedTabs.value.delete(0)
fetchChildMolds()
}
onLoad(async (query) => {
const id = query?.id ? decodeURIComponent(String(query.id)) : ''
brandId.value = id || undefined
await initAllDict()
await fetchDetail()
await loadTabData(0)
})
onShow(() => {
const needRefresh = getApp().globalData?._moldChildMoldNeedRefresh
if (needRefresh) {
getApp().globalData._moldChildMoldNeedRefresh = false
refreshChildMoldsIfNeeded()
}
})
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 getMoldRepairList({ moldId: 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('使用') || 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 })
}
function openRepairDetail(row) {
const id = row?.id
if (id === undefined || id === null || id === '') {
uni.showToast({ title: t('moldRepair.noId'), icon: 'none' })
return
}
uni.navigateTo({
url: `/pages_function/pages/moldRepair/form?mode=detail&id=${encodeURIComponent(String(id))}`
})
}
</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;
}
.tab-actions { display: flex; justify-content: flex-end; padding: 12rpx 24rpx 16rpx; position: relative; z-index: 10; }
.tab-action-btn { padding: 14rpx 32rpx; border-radius: 10rpx; background: #1f4b79; color: #fff; font-size: 26rpx; font-weight: 600; display: inline-flex; }
.tab-action-btn:active { opacity: 0.85; }
.search-bar { display: flex; flex-direction: column; gap: 12rpx; padding: 12rpx 0 16rpx; }
.search-row { display: flex; gap: 12rpx; }
.search-field { display: flex; align-items: center; gap: 8rpx; flex: 1; }
.search-label { font-size: 24rpx; color: #4b5563; white-space: nowrap; flex-shrink: 0; }
.search-input { flex: 1; height: 60rpx; padding: 0 14rpx; background: #f8fafc; border: 1rpx solid #e5e7eb; border-radius: 8rpx; font-size: 24rpx; color: #374151; box-sizing: border-box; min-width: 0; }
.search-btns { display: flex; gap: 12rpx; justify-content: flex-end; }
.search-btn { flex: 1; height: 60rpx; border-radius: 8rpx; border: 1rpx solid #1f4b79; background: #1f4b79; display: flex; align-items: center; justify-content: center; gap: 4rpx; font-size: 24rpx; color: #fff; white-space: nowrap; }
.search-btn:active { opacity: 0.85; }
.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-head-left { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 4rpx; }
.record-title {
min-width: 0;
font-size: 30rpx;
color: #1f2937;
font-weight: 700;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.record-code {
font-size: 24rpx;
color: #9ca3af;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.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>