fixbug:设备台账详情-点检记录、保养记录、报修记录

master
ck-chenkang 6 days ago
parent bac605c5ab
commit 1ccfea3b77

4213
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -916,12 +916,27 @@ export default {
maintainHistory: 'Maintenance',
repairHistory: 'Repair',
operator: 'Operator',
inspectionMethod: 'Inspection Method',
criteria: 'Criteria',
inspectionTime: 'Inspection Time',
maintainMethod: 'Maintenance Method',
maintainTime: 'Maintenance Time',
repairName: 'Repair Order Name',
faultPhenomenon: 'Fault Phenomenon',
faultDescription: 'Fault Description',
replacementParts: 'Replacement Parts',
repairContent: 'Repair Content',
finishDate: 'Finish Date',
faultImages: 'Fault Images',
repairedImages: 'After-repair Images',
noHistoryData: 'No history data',
resultPending: 'Pending',
resultPass: 'Pass',
resultFail: 'Fail',
repairPending: 'Pending',
repairProcessing: 'Processing',
repairCompleted: 'Completed'
repairCompleted: 'Completed',
repairAbnormal: 'Abnormal'
},
equipmentMaintenance: {
moduleName: 'Equipment Repair',

@ -919,12 +919,27 @@ export default {
maintainHistory: '保养记录',
repairHistory: '报修记录',
operator: '操作人',
inspectionMethod: '点检方式',
criteria: '判定标准',
inspectionTime: '点检时间',
maintainMethod: '保养方式',
maintainTime: '保养时间',
repairName: '维修单名称',
faultPhenomenon: '故障现象',
faultDescription: '故障描述',
replacementParts: '更换配件',
repairContent: '维修内容',
finishDate: '完成日期',
faultImages: '故障图片',
repairedImages: '维修后图片',
noHistoryData: '暂无历史记录',
resultPending: '待处理',
resultPass: '合格',
resultFail: '不合格',
repairPending: '待处理',
repairProcessing: '处理中',
repairCompleted: '已完成'
repairCompleted: '已完成',
repairAbnormal: '异常'
},
equipmentMaintenance: {
moduleName: '设备维修',

@ -94,59 +94,119 @@
</view>
<!-- 历史记录Tabs -->
<view class="info-card">
<view class="tab-bar">
<view
v-for="tab in tabs"
:key="tab.key"
:class="['tab-item', activeTab === tab.key ? 'active' : '']"
@click="switchTab(tab.key)"
>
<text class="tab-text">{{ tab.label }}</text>
</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="activeTab === 'check'" class="tab-content">
<view v-if="!inspectionList.length" class="empty-tip">{{ t('equipmentLedger.noHistoryData') }}</view>
<view v-for="(item, idx) in inspectionList" :key="idx" class="history-item">
<view class="history-header">
<text class="history-time">{{ formatDateTime(item.inspectionTime) }}</text>
<text :class="['history-result', getResultClass(item.inspectionResult)]">{{ getResultText(item.inspectionResult) }}</text>
<view v-if="currentTab === 0">
<view v-if="!inspectionGroups.length" class="hint">{{ t('equipmentLedger.noHistoryData') }}</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 class="history-body">
<text class="history-name">{{ item.inspectionItemName || item.name || '-' }}</text>
<text class="history-operator">{{ t('equipmentLedger.operator') }}: {{ item.operatorName || item.inspectorName || '-' }}</text>
<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('equipmentLedger.inspectionMethod') }}</text><text class="record-value">{{ textValue(item.method) }}</text></view>
<view class="record-row"><text class="record-label">{{ t('equipmentLedger.criteria') }}</text><text class="record-value">{{ textValue(item.criteria) }}</text></view>
<view class="record-row"><text class="record-label">{{ t('equipmentLedger.inspectionTime') }}</text><text class="record-value">{{ textValue(item.taskTimeLabel) }}</text></view>
<view class="record-row"><text class="record-label">{{ t('equipmentLedger.remark') }}</text><text class="record-value">{{ textValue(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-if="activeTab === 'maintain'" class="tab-content">
<view v-if="!maintainList.length" class="empty-tip">{{ t('equipmentLedger.noHistoryData') }}</view>
<view v-for="(item, idx) in maintainList" :key="idx" class="history-item">
<view class="history-header">
<text class="history-time">{{ formatDateTime(item.maintainTime || item.inspectionTime) }}</text>
<text :class="['history-result', getResultClass(item.maintainResult || item.inspectionResult)]">{{ getResultText(item.maintainResult || item.inspectionResult) }}</text>
<view v-else-if="currentTab === 1">
<view v-if="!maintainGroups.length" class="hint">{{ t('equipmentLedger.noHistoryData') }}</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 class="history-body">
<text class="history-name">{{ item.maintainItemName || item.inspectionItemName || item.name || '-' }}</text>
<text class="history-operator">{{ t('equipmentLedger.operator') }}: {{ item.operatorName || item.inspectorName || '-' }}</text>
<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('equipmentLedger.maintainMethod') }}</text><text class="record-value">{{ textValue(item.method) }}</text></view>
<view class="record-row"><text class="record-label">{{ t('equipmentLedger.criteria') }}</text><text class="record-value">{{ textValue(item.criteria) }}</text></view>
<view class="record-row"><text class="record-label">{{ t('equipmentLedger.maintainTime') }}</text><text class="record-value">{{ textValue(item.taskTimeLabel) }}</text></view>
<view class="record-row"><text class="record-label">{{ t('equipmentLedger.remark') }}</text><text class="record-value">{{ textValue(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-if="activeTab === 'repair'" class="tab-content">
<view v-if="!repairList.length" class="empty-tip">{{ t('equipmentLedger.noHistoryData') }}</view>
<view v-for="(item, idx) in repairList" :key="idx" class="history-item">
<view class="history-header">
<text class="history-time">{{ formatDateTime(item.createTime) }}</text>
<text :class="['history-result', getRepairStatusClass(item.status)]">{{ getRepairStatusText(item.status) }}</text>
<view v-else-if="currentTab === 2">
<view v-if="!repairRecords.length" class="hint">{{ t('equipmentLedger.noHistoryData') }}</view>
<view v-for="row in repairRecords" :key="row.key" class="record-card">
<view class="record-head">
<text class="record-title">{{ textValue(row.repairCode || row.repairName) }}</text>
<text class="result-badge" :class="`result-${row.resultType}`">{{ row.resultLabel }}</text>
</view>
<view class="history-body">
<text class="history-name">{{ item.repairNo || item.description || '-' }}</text>
<text class="history-operator">{{ t('equipmentLedger.operator') }}: {{ item.creatorName || '-' }}</text>
<view class="record-row"><text class="record-label">{{ t('equipmentLedger.repairName') }}</text><text class="record-value">{{ textValue(row.repairName) }}</text></view>
<view class="record-row"><text class="record-label">{{ t('equipmentLedger.faultPhenomenon') }}</text><text class="record-value">{{ textValue(row.faultPhenomenon) }}</text></view>
<view class="record-row"><text class="record-label">{{ t('equipmentLedger.faultDescription') }}</text><text class="record-value">{{ textValue(row.faultDescription) }}</text></view>
<view class="record-row"><text class="record-label">{{ t('equipmentLedger.replacementParts') }}</text><text class="record-value">{{ textValue(row.replacementParts) }}</text></view>
<view class="record-row"><text class="record-label">{{ t('equipmentLedger.repairContent') }}</text><text class="record-value">{{ textValue(row.repairContent) }}</text></view>
<view class="record-row"><text class="record-label">{{ t('equipmentLedger.finishDate') }}</text><text class="record-value">{{ textValue(row.finishDateLabel) }}</text></view>
<view class="record-row"><text class="record-label">{{ t('equipmentLedger.remark') }}</text><text class="record-value">{{ textValue(row.remark) }}</text></view>
<view v-if="row.faultImageList.length" class="image-block">
<text class="image-title">{{ t('equipmentLedger.faultImages') }}</text>
<view class="history-images">
<image
v-for="img in row.faultImageList"
:key="img"
class="history-image"
:src="img"
mode="aspectFill"
@click="previewImages(row.faultImageList, img)"
/>
</view>
</view>
<view v-if="row.repairedImageList.length" class="image-block">
<text class="image-title">{{ t('equipmentLedger.repairedImages') }}</text>
<view class="history-images">
<image
v-for="img in row.repairedImageList"
:key="img"
class="history-image"
:src="img"
mode="aspectFill"
@click="previewImages(row.repairedImageList, img)"
/>
</view>
</view>
</view>
</view>
@ -172,16 +232,16 @@ const dictStore = useDictStore()
const detailId = ref(undefined)
const detailData = ref(null)
const deviceTypeList = ref([])
const activeTab = ref('check')
const currentTab = ref(0)
const inspectionList = ref([])
const maintainList = ref([])
const repairList = ref([])
const statusUpdating = ref(false)
const tabs = computed(() => [
{ key: 'check', label: t('equipmentLedger.checkHistory') },
{ key: 'maintain', label: t('equipmentLedger.maintainHistory') },
{ key: 'repair', label: t('equipmentLedger.repairHistory') }
const tabList = computed(() => [
{ name: t('equipmentLedger.checkHistory') },
{ name: t('equipmentLedger.maintainHistory') },
{ name: t('equipmentLedger.repairHistory') }
])
const statusOptions = computed(() => {
@ -198,6 +258,46 @@ const scheduledText = computed(() => {
return Number(val) === 1 ? t('equipmentLedger.yes') : t('equipmentLedger.no')
})
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', 'maintainTime', 'inspectionTime', 'createTime'],
nameFieldCandidates: ['maintainItemName', 'inspectionItemName', 'name', 'itemName'],
resultFieldCandidates: ['maintainResult', 'inspectionResult', 'result'],
methodFieldCandidates: ['inspectionMethod', 'maintainMethod', '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,
faultImageList: parseImages(row?.faultImages),
repairedImageList: parseImages(row?.repairedImages)
}
})
})
onLoad(async (query) => {
const id = query?.id ? decodeURIComponent(String(query.id)) : ''
detailId.value = id || undefined
@ -255,32 +355,43 @@ function normalizeDetail(res) {
return {}
}
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 []
}
async function fetchInspectionHistory() {
try {
const res = await request({ url: '/admin-api/mes/ticket-management/getInspectionByDeviceId', method: 'get', params: { deviceId: detailId.value } })
const data = res && res.data !== undefined ? res.data : res
inspectionList.value = Array.isArray(data) ? data : []
inspectionList.value = normalizeListData(res)
} catch (e) { inspectionList.value = [] }
}
async function fetchMaintainHistory() {
try {
const res = await request({ url: '/admin-api/mes/ticket-management/getMaintenanceByDeviceId', method: 'get', params: { deviceId: detailId.value } })
const data = res && res.data !== undefined ? res.data : res
maintainList.value = Array.isArray(data) ? data : []
maintainList.value = normalizeListData(res)
} catch (e) { maintainList.value = [] }
}
async function fetchRepairHistory() {
try {
const res = await request({ url: '/admin-api/mes/dv-repair/getRepairListByDeviceId', method: 'get', params: { deviceId: detailId.value } })
const data = res && res.data !== undefined ? res.data : res
repairList.value = Array.isArray(data) ? data : []
repairList.value = normalizeListData(res)
} catch (e) { repairList.value = [] }
}
function switchTab(key) {
activeTab.value = key
function handleTabChange(e) {
const idx = e && typeof e === 'object' ? e.index : e
currentTab.value = Number(idx === undefined ? 0 : idx)
}
function getStatusText(status) {
@ -321,32 +432,6 @@ async function onStatusChange(e) {
}
}
function getResultText(result) {
if (result === 'PASS' || result === 'pass' || result === 'OK' || result === 'ok' || result === 1 || result === '1') return t('equipmentLedger.resultPass')
if (result === 'FAIL' || result === 'fail' || result === 'NG' || result === 'ng' || result === 0 || result === '0') return t('equipmentLedger.resultFail')
return textValue(result)
}
function getResultClass(result) {
if (result === 'PASS' || result === 'pass' || result === 'OK' || result === 'ok' || result === 1 || result === '1') return 'text-success'
if (result === 'FAIL' || result === 'fail' || result === 'NG' || result === 'ng' || result === 0 || result === '0') return 'text-danger'
return ''
}
function getRepairStatusText(status) {
if (status === 0 || status === '0') return t('equipmentLedger.repairPending')
if (status === 1 || status === '1') return t('equipmentLedger.repairProcessing')
if (status === 2 || status === '2') return t('equipmentLedger.repairCompleted')
return textValue(status)
}
function getRepairStatusClass(status) {
if (status === 0 || status === '0') return 'text-warning'
if (status === 1 || status === '1') return 'text-primary'
if (status === 2 || status === '2') return 'text-success'
return ''
}
function fieldValue(field) {
return textValue(detailData.value ? detailData.value[field] : undefined)
}
@ -360,6 +445,77 @@ function textValue(value) {
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 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('equipmentLedger.resultPending'), type: 'info' }
if (raw === '1' || upper === 'OK' || upper === 'PASS') return { label: t('equipmentLedger.resultPass'), type: 'success' }
if (raw === '2' || upper === 'NG' || upper === 'FAIL') return { label: t('equipmentLedger.resultFail'), type: 'danger' }
return { label: raw, type: 'info' }
}
function formatRepairResult(value) {
const raw = value === null || value === undefined ? '' : String(value).trim()
if (raw === '1') return { label: t('equipmentLedger.repairCompleted'), type: 'success' }
if (raw === '2') return { label: t('equipmentLedger.repairAbnormal'), type: 'danger' }
if (raw === '0') return { label: t('equipmentLedger.repairProcessing'), type: 'warning' }
return { label: textValue(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 = textValue(row?.operator || row?.operatorName || row?.inspectorName || 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: textValue(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?.maintainTime || 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 formatDateValue(value) {
if (!value) return '-'
if (Array.isArray(value) && value.length >= 3) {
@ -417,19 +573,30 @@ function formatDateTime(value) {
.text-primary { color: #1a3a5c; }
.remark-row { border-bottom: none; }
.remark-value { white-space: pre-wrap; }
.tab-bar { display: flex; border-bottom: 2rpx solid #edf0f3; margin-bottom: 20rpx; }
.tab-item { flex: 1; text-align: center; padding: 16rpx 0; position: relative; }
.tab-text { font-size: 28rpx; color: #8a9099; }
.tab-item.active .tab-text { color: #1a3a5c; font-weight: 700; }
.tab-item.active::after { content: ''; position: absolute; bottom: -2rpx; left: 30%; right: 30%; height: 4rpx; background: #1a3a5c; border-radius: 2rpx; }
.tab-content { min-height: 200rpx; }
.empty-tip { text-align: center; padding: 40rpx 0; color: #99a1aa; font-size: 26rpx; }
.history-item { padding: 20rpx 0; border-bottom: 1rpx solid #f0f2f5; }
.history-item:last-child { border-bottom: none; }
.history-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10rpx; }
.history-time { font-size: 24rpx; color: #8a9099; }
.history-result { font-size: 26rpx; font-weight: 600; }
.history-body { display: flex; justify-content: space-between; align-items: center; }
.history-name { font-size: 28rpx; color: #30363d; }
.history-operator { font-size: 24rpx; color: #8a9099; }
.tab-card { margin-top: 20rpx; background: #ffffff; border-radius: 20rpx; padding: 28rpx; box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05); }
.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-label { font-size: 24rpx; color: #94a3b8; flex-shrink: 0; min-width: 150rpx; }
.record-value { flex: 1; font-size: 27rpx; color: #1f2937; font-weight: 500; line-height: 1.4; word-break: break-all; text-align: right; }
.record-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 20rpx; margin-bottom: 10rpx; }
.record-row:last-child { margin-bottom: 0; }
.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 { flex: 1; min-width: 0; 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; }
.image-block { margin-top: 14rpx; }
.image-title { display: block; margin-bottom: 10rpx; font-size: 24rpx; color: #94a3b8; }
.history-images { display: flex; flex-wrap: wrap; gap: 12rpx; margin-top: 14rpx; }
.history-image { width: 150rpx; height: 150rpx; border-radius: 14rpx; background: #edf2f7; }
</style>

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save