黄伟杰 6 days ago
commit a955989558

5
.gitignore vendored

@ -46,4 +46,7 @@ debug.log
!*.allowed_extension
# 不排除下列文件名
!important_file.txt
!important_file.txt
# 新增本地开发配置
.env*.local

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>

@ -112,12 +112,9 @@ const deviceMoldMap = ref(new Map())
async function loadDeviceMolds() {
try {
const res = await getMoldBrandPage({ pageSize: 100 })
console.log('[deviceSelect] getMoldBrandPage 原始响应:', JSON.stringify(res))
const root = res && res.data !== undefined ? res.data : res
console.log('[deviceSelect] root 类型:', typeof root, 'keys:', Object.keys(root || {}))
const pageData = root?.pageResult || root
const list = Array.isArray(pageData) ? pageData : (Array.isArray(pageData?.list) ? pageData.list : [])
console.log('[deviceSelect] 模具型号总数:', list.length, '前3条:', list.slice(0, 3).map(m => ({ name: m.name, deviceName: m.deviceName })))
const map = new Map()
for (const mold of list) {
const deviceName = mold.deviceName
@ -127,7 +124,6 @@ async function loadDeviceMolds() {
}
}
deviceMoldMap.value = map
console.log('[deviceSelect] 在机模具映射表:', map.size, '个设备, keys:', [...map.keys()])
} catch (e) {
console.error('loadDeviceMolds error', e)
}

@ -4,24 +4,24 @@
<NavBar :title="t('moldOperate.tabDown')">
<template #right>
<view class="nav-right-btn" @click="goToHistory">
<uni-icons type="clock" size="22" color="#1f7cff"></uni-icons>
<text class="nav-right-text">{{ t('moldPressureNet.history') }}</text>
<uni-icons type="calendar" size="22" color="#22486e"></uni-icons>
</view>
</template>
</NavBar>
<!-- 操作按钮区 -->
<view class="action-row">
<view class="action-btn scan-btn" @click="handleScan">
<view class="btn-icon-wrap">
<text class="iconfont icon-scan btn-icon"></text>
</view>
<text class="btn-text">{{ t('moldOperate.scanDevice') }}</text>
<view class="scan-input-row">
<input
class="scan-input"
v-model="scanCodeInput"
placeholder="红外扫码或输入设备码"
confirm-type="done"
:focus="true"
@confirm="onScanInputConfirm"
/>
</view>
<view class="action-btn select-btn" @click="openDevicePicker">
<view class="btn-icon-wrap">
<text class="iconfont icon-device btn-icon"></text>
</view>
<text class="btn-text">{{ t('moldOperate.selectDevice') }}</text>
</view>
</view>
@ -254,6 +254,7 @@ const tempSelectedMoldId = ref(null)
// ---- ----
const remarkText = ref('')
const selectedOperator = ref(null)
const scanCodeInput = ref('')
// ---- ----
function textValue(v) {
@ -358,17 +359,39 @@ async function loadLowerMolds(deviceName) {
}
}
// ---- ----
// ---- / ----
function matchDeviceByCode(code) {
if (!code) return null
let matched = null
if (code.toUpperCase().startsWith('EQUIPMENT-')) {
const idFromQr = code.replace(/EQUIPMENT-/i, '')
matched = deviceOptions.value.find((d) => String(d.raw.id) === idFromQr)
}
if (!matched) {
matched = deviceOptions.value.find((d) =>
Object.values(d.raw).some((v) =>
typeof v === 'string' && v.trim().toUpperCase() === code.toUpperCase()
) || d.label.toUpperCase().includes(code.toUpperCase())
)
}
if (!matched) {
const idMatch = code.match(/(\d+)$/)
if (idMatch) {
matched = deviceOptions.value.find((d) => String(d.raw.id) === idMatch[1])
}
}
return matched
}
function handleScan() {
uni.scanCode({
onlyFromCamera: false,
scanType: ['qrCode', 'barCode'],
success(res) {
const code = res.result?.trim()
const code = (res.result || '').trim()
if (!code) return
const matched = deviceOptions.value.find((d) =>
d.raw.deviceCode === code || String(d.raw.code) === code || d.label.includes(code)
)
scanCodeInput.value = code
const matched = matchDeviceByCode(code)
if (matched) {
selectDevice(matched.raw)
} else {
@ -383,6 +406,17 @@ function handleScan() {
})
}
function onScanInputConfirm() {
const code = scanCodeInput.value.trim()
if (!code) return
const matched = matchDeviceByCode(code)
if (matched) {
selectDevice(matched.raw)
} else {
uni.showToast({ title: t('moldOperate.deviceNotFound'), icon: 'none' })
}
}
// ---- ----
function selectDevice(device) {
selectedDevice.value = device || {}
@ -528,21 +562,16 @@ onShow(async () => {
<style lang="scss" scoped>
/* ====== 导航栏右侧按钮 ====== */
.nav-right-btn {
width: 64rpx;
height: 64rpx;
display: flex;
align-items: center;
gap: 6rpx;
padding: 8rpx 18rpx;
justify-content: center;
background: #ffffff;
border-radius: 999rpx;
border-radius: 50%;
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.08);
}
.nav-right-text {
font-size: 24rpx;
color: #374151;
font-weight: 500;
}
.page-container {
min-height: 100vh;
background: #f5f6f8;
@ -552,39 +581,48 @@ onShow(async () => {
/* ====== 操作按钮区 ====== */
.action-row {
display: flex;
gap: 20rpx;
align-items: center;
gap: 16rpx;
padding: 20rpx 24rpx;
}
.action-btn {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
gap: 12rpx;
height: 96rpx;
border-radius: 12rpx;
height: 72rpx;
padding: 0 24rpx;
border-radius: 10rpx;
background: #1f4b79;
color: #fff;
font-size: 28rpx;
font-size: 26rpx;
font-weight: 600;
white-space: nowrap;
.btn-icon-wrap {
width: 44rpx;
height: 44rpx;
display: flex;
align-items: center;
justify-content: center;
.btn-text {
font-size: 26rpx;
line-height: 1;
}
}
.btn-icon {
font-size: 36rpx;
color: #fff;
}
.scan-input-row {
flex: 1;
display: flex;
align-items: center;
height: 72rpx;
border-radius: 10rpx;
overflow: hidden;
}
.btn-text {
font-size: 28rpx;
}
.scan-input {
flex: 1;
height: 72rpx;
padding: 0 20rpx;
font-size: 26rpx;
color: #333;
background: #fff;
border: 1rpx solid #d0d5dd;
border-radius: 10rpx;
}
/* ====== 卡片通用样式 ====== */

@ -4,8 +4,7 @@
<NavBar :title="t('moldOperate.tabUp')">
<template #right>
<view class="nav-right-btn" @click="goToHistory">
<uni-icons type="clock" size="22" color="#1f7cff"></uni-icons>
<text class="nav-right-text">{{ t('moldPressureNet.history') }}</text>
<uni-icons type="calendar" size="22" color="#22486e"></uni-icons>
</view>
</template>
</NavBar>
@ -13,16 +12,17 @@
<!-- ========== 上模 ========== -->
<!-- 操作按钮区 -->
<view class="action-row">
<view class="action-btn scan-btn" @click="handleScan">
<view class="btn-icon-wrap">
<text class="iconfont icon-scan btn-icon"></text>
</view>
<text class="btn-text">{{ t('moldOperate.scanDevice') }}</text>
<view class="scan-input-row">
<input
class="scan-input"
v-model="scanCodeInput"
placeholder="红外扫码或输入设备码"
confirm-type="done"
:focus="true"
@confirm="onScanInputConfirm"
/>
</view>
<view class="action-btn select-btn" @click="openDevicePicker">
<view class="btn-icon-wrap">
<text class="iconfont icon-device btn-icon"></text>
</view>
<text class="btn-text">{{ t('moldOperate.selectDevice') }}</text>
</view>
</view>
@ -150,6 +150,7 @@ import { useI18n } from 'vue-i18n'
import NavBar from '@/components/common/NavBar.vue'
import { getDeviceLedgerList, createMoldOperate } from '@/api/mes/moldoperate'
import { getMoldBrandPage } from '@/api/mes/mold'
import { getDeviceLineTree } from '@/api/mes/deviceLine'
import useUserStore from '@/store/modules/user'
const { t } = useI18n()
@ -190,6 +191,70 @@ async function loadDevices() {
}
}
// 线 - 线
const lineInfoMap = ref(new Map())
function flattenLineTree(nodes, parentId) {
if (!Array.isArray(nodes)) return
for (const node of nodes) {
if (node.id != null && node.name != null) {
lineInfoMap.value.set(String(node.id), {
id: node.id,
name: node.name,
parentId: node.parentId != null ? node.parentId : (parentId || null),
parentChain: node.parentChain || ''
})
}
if (Array.isArray(node.children)) {
flattenLineTree(node.children, node.id)
}
}
}
function getTopLineName(deviceLineId) {
if (deviceLineId == null) return '-'
const node = lineInfoMap.value.get(String(deviceLineId))
if (!node) return '-'
if (node.parentChain) {
const firstId = node.parentChain.split(',')[0]?.trim()
if (firstId) {
const topNode = lineInfoMap.value.get(firstId)
if (topNode) return topNode.name
}
}
let current = node
const visited = new Set()
while (current.parentId != null && current.parentId > 0 && !visited.has(current.id)) {
visited.add(current.id)
const parent = lineInfoMap.value.get(String(current.parentId))
if (!parent) break
current = parent
}
return current.name || '-'
}
async function loadLineTree() {
if (lineInfoMap.value.size > 0) return
try {
const res = await getDeviceLineTree()
const tree = (res && res.data !== undefined) ? res.data : res
const nodes = Array.isArray(tree) ? tree : (tree?.list || tree?.children || [])
flattenLineTree(nodes)
} catch (e) {
console.error('load line tree error', e)
}
}
// 线线
function setDeviceLineName(device) {
if (device && device.deviceLine != null) {
const lineName = getTopLineName(device.deviceLine)
if (lineName && lineName !== '-') {
device.workshopName = lineName
}
}
}
// ==================== ====================
const selectedDevice = ref({})
@ -197,6 +262,7 @@ const selectedMountMolds = ref([])
const tempSelectedDeviceId = ref(null)
const remarkText = ref('')
const selectedOperator = ref(null)
const scanCodeInput = ref('') // /
// -
const deviceStatusClass = computed(() => {
@ -238,13 +304,10 @@ async function fetchCurrentMolds(deviceName) {
try {
//
const params = { deviceName, pageSize: 100 }
console.log('[上模] fetchCurrentMolds 请求参数:', JSON.stringify(params))
const res = await getMoldBrandPage(params)
console.log('[上模] fetchCurrentMolds 原始响应:', JSON.stringify(res))
const root = res && res.data !== undefined ? res.data : res
const pageData = root?.pageResult || root
const list = Array.isArray(pageData) ? pageData : (Array.isArray(pageData?.list) ? pageData.list : [])
console.log('[上模] fetchCurrentMolds 解析后:', list.length, '条数据', list.map(m => ({ id: m.id, name: m.name, currentDevice: m.currentDevice })))
currentMoldList.value = list
} catch (e) {
console.error('fetchCurrentMolds error', e)
@ -345,17 +408,40 @@ async function handleConfirmMount() {
}
// ==================== : / / ====================
function matchDeviceByCode(code) {
if (!code) return null
let matched = null
if (code.toUpperCase().startsWith('EQUIPMENT-')) {
const idFromQr = code.replace(/EQUIPMENT-/i, '')
matched = deviceOptions.value.find((d) => String(d.raw.id) === idFromQr)
}
if (!matched) {
matched = deviceOptions.value.find((d) =>
Object.values(d.raw).some((v) =>
typeof v === 'string' && v.trim().toUpperCase() === code.toUpperCase()
) || d.label.toUpperCase().includes(code.toUpperCase())
)
}
if (!matched) {
const idMatch = code.match(/(\d+)$/)
if (idMatch) {
matched = deviceOptions.value.find((d) => String(d.raw.id) === idMatch[1])
}
}
return matched
}
function handleScan() {
uni.scanCode({
onlyFromCamera: false,
scanType: ['qrCode', 'barCode'],
success(res) {
const code = res.result?.trim()
const code = (res.result || '').trim()
if (!code) return
const matched = deviceOptions.value.find((d) =>
d.raw.deviceCode === code || String(d.raw.code) === code || d.label.includes(code)
)
scanCodeInput.value = code //
const matched = matchDeviceByCode(code)
if (matched) {
setDeviceLineName(matched.raw)
selectDevice(matched.raw)
} else {
uni.showToast({ title: t('moldOperate.deviceNotFound'), icon: 'none' })
@ -369,6 +455,19 @@ function handleScan() {
})
}
//
function onScanInputConfirm() {
const code = scanCodeInput.value.trim()
if (!code) return
const matched = matchDeviceByCode(code)
if (matched) {
setDeviceLineName(matched.raw)
selectDevice(matched.raw)
} else {
uni.showToast({ title: t('moldOperate.deviceNotFound'), icon: 'none' })
}
}
function handleCancel() {
selectedDevice.value = {}
selectedMountMolds.value = []
@ -385,7 +484,7 @@ function goToHistory() {
onShow(async () => {
autoSetOperator()
await Promise.allSettled([loadDevices()])
await Promise.allSettled([loadDevices(), loadLineTree()])
// globalData
const device = getApp().globalData._deviceSelectResult
if (device) {
@ -410,59 +509,77 @@ onShow(async () => {
/* ====== 导航栏右侧按钮 ====== */
.nav-right-btn {
width: 64rpx;
height: 64rpx;
display: flex;
align-items: center;
gap: 6rpx;
padding: 8rpx 18rpx;
justify-content: center;
background: #ffffff;
border-radius: 999rpx;
border-radius: 50%;
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.08);
}
.nav-right-text {
font-size: 24rpx;
color: #374151;
font-weight: 500;
}
/* ====== 操作按钮区 ====== */
.action-row {
display: flex;
gap: 20rpx;
align-items: center;
gap: 16rpx;
padding: 20rpx 24rpx;
}
.action-btn {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
gap: 12rpx;
height: 96rpx;
border-radius: 12rpx;
gap: 10rpx;
height: 72rpx;
padding: 0 24rpx;
border-radius: 10rpx;
background: #1f4b79;
color: #fff;
font-size: 28rpx;
font-size: 26rpx;
font-weight: 600;
white-space: nowrap;
.btn-icon-wrap {
width: 44rpx;
height: 44rpx;
width: 36rpx;
height: 36rpx;
display: flex;
align-items: center;
justify-content: center;
}
.btn-icon {
font-size: 36rpx;
font-size: 30rpx;
color: #fff;
}
.btn-text {
font-size: 28rpx;
font-size: 26rpx;
line-height: 1;
}
}
/* 扫码输入框行 */
.scan-input-row {
flex: 1;
display: flex;
align-items: center;
height: 72rpx;
border-radius: 10rpx;
overflow: hidden;
}
.scan-input {
flex: 1;
height: 72rpx;
padding: 0 20rpx;
font-size: 26rpx;
color: #333;
background: #fff;
border: 1rpx solid #d0d5dd;
border-radius: 10rpx;
}
/* ====== 卡片通用样式 ====== */
.section-card {
margin: 16rpx 24rpx;

@ -317,7 +317,6 @@ export function resolveMenuUrl(menu) {
for (const key of keys) {
const normalizedKey = normalizeMenuKey(key)
if (normalizedKey && MENU_ROUTE_MAP[normalizedKey]) {
console.log('[resolveMenuUrl] menu.name="', menu?.name, '" matched key="', normalizedKey, '" → route="', MENU_ROUTE_MAP[normalizedKey], '"')
return MENU_ROUTE_MAP[normalizedKey]
}
}

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