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.

381 lines
17 KiB
Vue

<template>
<view class="page-container">
<view class="fixed-header">
<NavBar :title="t('equipmentLedger.detailTitle')" />
</view>
<scroll-view scroll-y class="detail-scroll">
<view class="content-section">
<!-- 基础信息 -->
<view class="info-card">
<view class="card-title">{{ t('equipmentLedger.basicInfo') }}</view>
<view class="info-list">
<view class="info-row">
<text class="info-label">{{ t('equipmentLedger.deviceCode') }}</text>
<text class="info-value">{{ fieldValue('deviceCode') }}</text>
</view>
<view class="info-row">
<text class="info-label">{{ t('equipmentLedger.deviceName') }}</text>
<text class="info-value">{{ fieldValue('deviceName') }}</text>
</view>
<view class="info-row">
<text class="info-label">{{ t('equipmentLedger.deviceStatus') }}</text>
<text :class="['info-value', getStatusClass(detailData?.deviceStatus)]">{{ getStatusText(detailData?.deviceStatus) }}</text>
</view>
<view class="info-row">
<text class="info-label">{{ t('equipmentLedger.deviceType') }}</text>
<text class="info-value">{{ fieldValue('deviceTypeName') || getDeviceTypeNameById(detailData?.deviceType) }}</text>
</view>
<view class="info-row">
<text class="info-label">{{ t('equipmentLedger.deviceSpec') }}</text>
<text class="info-value">{{ fieldValue('deviceSpec') }}</text>
</view>
<view class="info-row">
<text class="info-label">{{ t('equipmentLedger.isScheduled') }}</text>
<text class="info-value">{{ scheduledText }}</text>
</view>
<view v-if="detailData?.isScheduled === 1 || detailData?.isSchedueld === 1" class="info-row">
<text class="info-label">{{ t('equipmentLedger.ratedCapacity') }}</text>
<text class="info-value">{{ fieldValue('ratedCapacity') }}</text>
</view>
<view v-if="detailData?.isScheduled === 1 || detailData?.isSchedueld === 1" class="info-row">
<text class="info-label">{{ t('equipmentLedger.dailyAverageValue') }}</text>
<text class="info-value">{{ fieldValue('dailyAverageValue') }}</text>
</view>
<view v-if="detailData?.isScheduled === 1 || detailData?.isSchedueld === 1" class="info-row">
<text class="info-label">{{ t('equipmentLedger.dataCollectionCapacity') }}</text>
<text class="info-value">{{ fieldValue('dataCollectionCapacity') }}</text>
</view>
<view class="info-row">
<text class="info-label">{{ t('equipmentLedger.productionDate') }}</text>
<text class="info-value">{{ formatDateValue(detailData?.productionDate) }}</text>
</view>
<view class="info-row">
<text class="info-label">{{ t('equipmentLedger.factoryEntryDate') }}</text>
<text class="info-value">{{ formatDateValue(detailData?.factoryEntryDate) }}</text>
</view>
<view class="info-row">
<text class="info-label">{{ t('equipmentLedger.deviceLocation') }}</text>
<text class="info-value">{{ fieldValue('deviceLocation') }}</text>
</view>
<view class="info-row">
<text class="info-label">{{ t('equipmentLedger.deviceManagerName') }}</text>
<text class="info-value">{{ fieldValue('deviceManagerName') }}</text>
</view>
<view class="info-row">
<text class="info-label">{{ t('equipmentLedger.workshop') }}</text>
<text class="info-value">{{ fieldValue('workshopName') }}</text>
</view>
<view class="info-row">
<text class="info-label">{{ t('equipmentLedger.creatorName') }}</text>
<text class="info-value">{{ fieldValue('creatorName') }}</text>
</view>
<view class="info-row">
<text class="info-label">{{ t('equipmentLedger.createTime') }}</text>
<text class="info-value">{{ formatDateTime(detailData?.createTime) }}</text>
</view>
<view class="info-row remark-row">
<text class="info-label">{{ t('equipmentLedger.remark') }}</text>
<text class="info-value remark-value">{{ fieldValue('remark') }}</text>
</view>
</view>
</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>
<!-- 点检历史 -->
<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>
<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>
</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>
<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>
</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>
<view class="history-body">
<text class="history-name">{{ item.repairNo || item.description || '-' }}</text>
<text class="history-operator">{{ t('equipmentLedger.operator') }}: {{ item.creatorName || '-' }}</text>
</view>
</view>
</view>
</view>
</view>
</scroll-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 { getDeviceLedger } from '@/api/mes/deviceLedger'
import { getDeviceTypeTree } from '@/api/mes/deviceType'
import { DICT_TYPE, getDictLabel, initAllDict } from '@/utils/dict'
import request from '@/utils/request'
const { t } = useI18n()
const detailId = ref(undefined)
const detailData = ref(null)
const deviceTypeList = ref([])
const activeTab = ref('check')
const inspectionList = ref([])
const maintainList = ref([])
const repairList = ref([])
const tabs = computed(() => [
{ key: 'check', label: t('equipmentLedger.checkHistory') },
{ key: 'maintain', label: t('equipmentLedger.maintainHistory') },
{ key: 'repair', label: t('equipmentLedger.repairHistory') }
])
const scheduledText = computed(() => {
const val = detailData.value?.isSchedueld ?? detailData.value?.isScheduled
return Number(val) === 1 ? t('equipmentLedger.yes') : t('equipmentLedger.no')
})
onLoad(async (query) => {
const id = query?.id ? decodeURIComponent(String(query.id)) : ''
detailId.value = id || undefined
await initAllDict()
await fetchDeviceTypeList()
await fetchDetail()
})
async function fetchDeviceTypeList() {
try {
const res = await getDeviceTypeTree({})
const root = res && res.data !== undefined ? res.data : res
const treeData = Array.isArray(root) ? root : (Array.isArray(root?.data) ? root.data : [])
deviceTypeList.value = flattenTree(treeData)
} catch (e) {}
}
function flattenTree(nodes) {
const result = []
nodes.forEach(node => {
result.push(node)
if (node.children && node.children.length) {
result.push(...flattenTree(node.children))
}
})
return result
}
function getDeviceTypeNameById(id) {
if (!id) return '-'
const found = deviceTypeList.value.find(item => String(item.id) === String(id))
return found ? found.name : String(id)
}
async function fetchDetail() {
if (!detailId.value) {
uni.showToast({ title: t('equipmentLedger.noId'), icon: 'none' })
return
}
try {
const res = await getDeviceLedger(detailId.value)
detailData.value = normalizeDetail(res)
await fetchInspectionHistory()
await fetchMaintainHistory()
await fetchRepairHistory()
} catch (e) {
uni.showToast({ title: t('equipmentLedger.loadFailed'), icon: 'none' })
}
}
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 {}
}
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 : []
} 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 : []
} 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 : []
} catch (e) { repairList.value = [] }
}
function switchTab(key) {
activeTab.value = key
}
function getStatusText(status) {
return getDictLabel(DICT_TYPE.MES_TZ_STATUS, status, textValue(status))
}
function getStatusClass(status) {
const s = String(status)
if (s === '0' || s === '1') return 'text-success'
return 'text-danger'
}
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)
}
function textValue(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 formatDateValue(value) {
if (!value) return '-'
if (Array.isArray(value) && value.length >= 3) {
const [y, m, d] = value
const pad = (n) => String(n).padStart(2, '0')
return `${y}-${pad(m)}-${pad(d)}`
}
const text = String(value).trim()
if (!text) return '-'
return text.split(' ')[0]
}
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
}
</script>
<style lang="scss" scoped>
.page-container { min-height: 100vh; background-color: #f0f2f5; }
.fixed-header { position: sticky; top: 0; z-index: 10; }
.detail-scroll { height: calc(100vh - 120rpx); }
.content-section { padding: 0 24rpx 24rpx; }
.info-card { margin-top: 20rpx; background: #ffffff; border-radius: 20rpx; padding: 28rpx; box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05); }
.card-title { font-size: 32rpx; color: #1a3a5c; font-weight: 700; margin-bottom: 18rpx; }
.info-list { background: #ffffff; }
.info-row { display: flex; justify-content: space-between; align-items: flex-start; padding: 18rpx 0; border-bottom: 1rpx solid #edf0f3; }
.info-label { font-size: 27rpx; color: #8a9099; width: 220rpx; }
.info-value { flex: 1; text-align: right; font-size: 28rpx; color: #30363d; line-height: 1.45; }
.text-success { color: #52c41a; }
.text-danger { color: #ff4d4f; }
.text-warning { color: #faad14; }
.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; }
</style>