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.

947 lines
30 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 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>