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.

367 lines
9.8 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('moldOperate.selectDevice')" />
<!-- 搜索区 -->
<view class="search-bar">
<view class="search-input-wrap">
<text class="search-icon iconfont icon-search"></text>
<input class="search-input" v-model="searchText" :placeholder="t('equipmentLedger.placeholderDeviceName')" @input="onSearch" placeholder-class="search-placeholder" />
<text v-if="searchText" class="search-clear" @click="clearSearch">✕</text>
</view>
</view>
<!-- 设备列表 -->
<scroll-view scroll-y class="device-list" v-if="filteredList.length > 0">
<view
v-for="device in filteredList"
:key="device.id"
class="device-card"
:class="{ active: selectedId === device.id }"
@click="selectedId = device.id"
>
<view class="device-card-header">
<text class="device-name">{{ textValue(device.deviceName) }}</text>
<view class="device-status-tag" :class="getStatusClass(device.deviceStatus)">
{{ getStatusLabel(device) }}
</view>
</view>
<view class="device-card-body">
<view class="device-info-row">
<text class="info-label">{{ t('moldOperate.deviceCode') }}</text>
<text class="info-value">{{ textValue(device.deviceCode) }}</text>
</view>
<view class="device-info-row">
<text class="info-label">{{ t('moldOperate.productionLine') }}</text>
<text class="info-value">{{ textValue(device.workshopName) }}</text>
</view>
<view class="device-info-row">
<text class="info-label">{{ t('moldOperate.currentMold') }}</text>
<text class="info-value">{{ getCurrentMold(device) }}</text>
</view>
<view class="device-info-row">
<text class="info-label">{{ t('moldOperate.deviceStatus') }}</text>
<text class="info-value">{{ getStatusLabel(device) }}</text>
</view>
</view>
</view>
</scroll-view>
<!-- 空状态 -->
<view v-else class="empty-wrap">
<text v-if="loading" class="empty-text">{{ t('functionCommon.loading') }}</text>
<text v-else class="empty-text">{{ t('moldOperate.noDeviceData') }}</text>
</view>
<!-- 底部确认按钮 -->
<view class="bottom-actions">
<view class="bottom-btn confirm-btn" @click="handleConfirm">
{{ t('functionCommon.confirm') }}
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import { useI18n } from 'vue-i18n'
import NavBar from '@/components/common/NavBar.vue'
import { getDeviceLedgerList } from '@/api/mes/moldoperate'
const { t } = useI18n()
const deviceList = ref([])
const selectedId = ref(null)
const searchText = ref('')
const loading = ref(false)
function textValue(v) {
if (v === 0) return '0'
if (v == null) return '-'
const s = String(v).trim()
return s || '-'
}
function getStatusClass(status) {
const s = Number(status)
if (s === 0) return 'running-tag'
if (s === 1) return 'stop-tag'
if (s >= 2) return 'fault-tag'
return ''
}
function getStatusLabel(device) {
const status = Number(device.deviceStatus)
const map = {
0: t('moldOperate.statusRunning'),
1: t('moldOperate.statusStop'),
2: t('moldOperate.statusFault'),
3: t('moldOperate.statusFault')
}
return map[status] || textValue(device.deviceStatus) || '-'
}
// 当前在机模具 - 优先从持久化存储读取(实时),否则 fallback 设备台账字段
function getCurrentMold(device) {
const deviceId = device?.id
const deviceCode = device?.deviceCode
// 1. 优先读新版 _mountedMoldInfoMap按 deviceId 索引,再用 deviceCode fallback
try {
const map = uni.getStorageSync('_mountedMoldInfoMap') || {}
// 先按 id 匹配
const key = String(deviceId)
let info = deviceId != null ? map[key] : null
// 再按 deviceCode 匹配(兼容 id 可能不一致的情况)
if (!info && deviceCode) {
const found = Object.values(map).find((item) => item?.deviceCode === deviceCode)
if (found) info = found
}
// 调试日志:只在首次加载时输出一次
if (!getCurrentMold._logged) {
getCurrentMold._logged = true
console.log('[deviceSelect] === 调试信息 ===')
console.log('[deviceSelect] storage map keys:', Object.keys(map))
console.log('[deviceSelect] storage map values:', map)
console.log('[deviceSelect] 当前设备 deviceId:', deviceId, 'type:', typeof deviceId, 'key:', key)
console.log('[deviceSelect] 当前设备 deviceCode:', deviceCode)
console.log('[deviceSelect] id匹配结果:', info ? '命中' : '未命中')
if (!info) {
console.log('[deviceSelect] 尝试 deviceCode fallback...')
for (const [k, v] of Object.entries(map)) {
console.log('[deviceSelect] storage[', k, '].deviceCode =', v?.deviceCode, 'vs 设备 code =', deviceCode, '匹配:', v?.deviceCode === deviceCode)
}
}
// fallback 检查
const saved = uni.getStorageSync('_mountedMoldInfo') || null
console.log('[deviceSelect] 旧版 _mountedMoldInfo:', saved)
console.log('[deviceSelect] 设备台账字段 currentMold:', device?.currentMold, 'moldName:', device?.moldName, 'moldId:', device?.moldId)
}
if (info?.mold) {
return info.mold.name || info.mold.moldName || '-'
}
} catch (e) {
console.warn('[deviceSelect] read _mountedMoldInfoMap error', e)
}
// 2. 兼容旧版 _mountedMoldInfo
let saved = null
try { saved = uni.getStorageSync('_mountedMoldInfo') || null } catch {}
if (saved) {
const idMatch = deviceId != null && String(saved.deviceId) === String(deviceId)
const codeMatch = deviceCode && saved.deviceCode === deviceCode
if (idMatch || codeMatch) {
return saved.mold?.name || saved.mold?.moldName || '-'
}
}
// 3. fallback 设备台账静态字段
const staticMold = textValue(device.currentMold || device.moldName || device.moldId)
return staticMold === '-' ? t('moldOperate.noMoldOnDevice') : staticMold
}
const filteredList = computed(() => {
const keyword = searchText.value.trim().toLowerCase()
if (!keyword) return deviceList.value
return deviceList.value.filter((d) => {
return (d.deviceName || '').toLowerCase().includes(keyword) ||
(d.deviceCode || '').toLowerCase().includes(keyword) ||
(d.workshopName || '').toLowerCase().includes(keyword)
})
})
function onSearch() {}
function clearSearch() {
searchText.value = ''
}
async function loadDevices() {
loading.value = true
try {
const res = await getDeviceLedgerList({ pageNo: 1, pageSize: 100 })
const root = res && res.data !== undefined ? res.data : res
deviceList.value = Array.isArray(root)
? root
: Array.isArray(root?.list) ? root.list
: Array.isArray(root?.rows) ? root.rows
: Array.isArray(root?.records) ? root.records
: []
} catch (e) {
console.error('loadDevices error', e)
} finally {
loading.value = false
}
}
function handleConfirm() {
if (!selectedId.value) {
uni.showToast({ title: t('moldOperate.validatorDeviceRequired'), icon: 'none' })
return
}
const device = deviceList.value.find((d) => d.id === selectedId.value)
// 存入 globalData 后再返回,目标页 onShow 中读取
getApp().globalData._deviceSelectResult = device || null
uni.navigateBack()
}
onShow(async () => {
await loadDevices()
})
</script>
<style lang="scss" scoped>
.page-container {
min-height: 100vh;
background: #f5f6f8;
padding-bottom: 140rpx;
}
/* 搜索栏 */
.search-bar {
padding: 16rpx 24rpx;
background: #fff;
}
.search-input-wrap {
display: flex;
align-items: center;
height: 72rpx;
padding: 0 16rpx;
background: #f5f6f8;
border-radius: 36rpx;
}
.search-icon {
font-size: 32rpx;
color: #999;
margin-right: 12rpx;
}
.search-input {
flex: 1;
font-size: 28rpx;
color: #333;
}
.search-placeholder {
color: #bbb;
}
.search-clear {
font-size: 28rpx;
color: #999;
padding: 8rpx;
}
/* 设备列表 */
.device-list {
padding: 16rpx 24rpx;
}
.device-card {
background: #fff;
border-radius: 14rpx;
padding: 24rpx;
margin-bottom: 16rpx;
border: 2rpx solid transparent;
&.active {
border-color: #2563eb;
background: #f0f5ff;
}
}
.device-card-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16rpx;
}
.device-name {
font-size: 30rpx;
font-weight: 700;
color: #1a1a1a;
}
.device-status-tag {
padding: 4rpx 16rpx;
border-radius: 6rpx;
font-size: 22rpx;
font-weight: 500;
&.running-tag { color: #059669; background: #ecfdf5; border: 1rpx solid #a7f3d0; }
&.stop-tag { color: #d97706; background: #fffbeb; border: 1rpx solid #fde68a; }
&.fault-tag { color: #dc2626; background: #fef2f2; border: 1rpx solid #fecaca; }
}
.device-card-body {
border-top: 1rpx solid #f0f0f0;
padding-top: 16rpx;
}
.device-info-row {
display: flex;
align-items: center;
margin-bottom: 10rpx;
&:last-child {
margin-bottom: 0;
}
}
.info-label {
width: 140rpx;
font-size: 24rpx;
color: #999;
flex-shrink: 0;
}
.info-value {
font-size: 26rpx;
color: #333;
}
/* 空状态 */
.empty-wrap {
padding: 120rpx 0;
text-align: center;
}
.empty-text {
font-size: 28rpx;
color: #999;
}
/* 底部确认按钮 */
.bottom-actions {
position: fixed;
left: 0;
right: 0;
bottom: 0;
padding: 16rpx 24rpx calc(16rpx + env(safe-area-inset-bottom));
background: #fff;
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.06);
z-index: 99;
}
.bottom-btn {
width: 100%;
height: 88rpx;
line-height: 88rpx;
text-align: center;
border-radius: 12rpx;
font-size: 30rpx;
font-weight: 600;
}
.confirm-btn {
background: #2563eb;
color: #fff;
}
</style>