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.
besure_app/src/pages_function/pages/moldoperate/deviceSelect.vue

477 lines
12 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, disabled: fromType === 'up' && hasMoldOnDevice(device) }"
@click="handleDeviceClick(device)"
>
<view class="device-card-header">
<text class="device-name">{{ textValue(device.deviceName) }}</text>
<view class="device-card-header-right">
<view
v-if="fromType === 'up' && hasMoldOnDevice(device)"
class="dismount-btn"
@click.stop="handleDismount(device)"
>
<text class="dismount-btn-text">{{ t('moldOperate.tabDown') }}</text>
</view>
<view class="device-status-tag" :class="getStatusClass(device.deviceStatus)">
{{ getStatusLabel(device) }}
</view>
</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">{{ getTopLineName(device.deviceLine) }}</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 { onLoad, onShow } from '@dcloudio/uni-app'
import { useI18n } from 'vue-i18n'
import NavBar from '@/components/common/NavBar.vue'
import { getDeviceLedgerList } from '@/api/mes/moldoperate'
import { getDeviceLineTree } from '@/api/mes/deviceLine'
import { getMoldBrandPage } from '@/api/mes/mold'
const { t } = useI18n()
const deviceList = ref([])
const selectedId = ref(null)
const searchText = ref('')
const loading = ref(false)
// 页面来源:'up' 表示上模(显示所有设备,有下模按钮),'down' 表示下模(只显示有模具的设备,无下模按钮)
const fromType = ref('up')
const lineInfoMap = ref(new Map())
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) || '-'
}
// 设备名 → 在机模具名 映射表
const deviceMoldMap = ref(new Map())
async function loadDeviceMolds() {
try {
const res = await getMoldBrandPage({ pageSize: 100 })
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 : [])
const map = new Map()
for (const mold of list) {
const deviceName = mold.deviceName
if (deviceName) {
if (!map.has(deviceName)) map.set(deviceName, [])
map.get(deviceName).push(mold.name || '')
}
}
deviceMoldMap.value = map
} catch (e) {
console.error('loadDeviceMolds error', e)
}
}
// 当前在机模具 - 从模具型号接口获取
function getCurrentMold(device) {
if (!device) return t('moldOperate.noMoldOnDevice')
const deviceName = device.deviceName
if (deviceName && deviceMoldMap.value.has(deviceName)) {
const names = deviceMoldMap.value.get(deviceName) || []
return names.join('')
}
// fallback 设备台账静态字段
return textValue(device.currentMold || device.moldName) || t('moldOperate.noMoldOnDevice')
}
// 判断设备是否有在机模具
function handleDeviceClick(device) {
if (fromType.value === 'up' && hasMoldOnDevice(device)) {
uni.showToast({ title: '该设备已有在机模具,不能选择上模', icon: 'none' })
return
}
selectedId.value = device.id
}
function hasMoldOnDevice(device) {
if (!device) return false
const deviceName = device.deviceName
// 优先从模具型号接口数据判断
if (deviceName && deviceMoldMap.value.has(deviceName)) {
const names = deviceMoldMap.value.get(deviceName) || []
return names.length > 0
}
// fallback 设备台账静态字段
const staticMold = textValue(device.currentMold || device.moldName)
return staticMold !== '-' && staticMold !== t('moldOperate.noMoldOnDevice')
}
// 点击下模按钮,跳转到下模页面
function handleDismount(device) {
if (!device) return
// 存入 globalData下模页面 onShow 中读取设备信息
getApp().globalData._deviceSelectResult = { ...device }
uni.navigateTo({
url: '/pages_function/pages/moldoperate/dismount'
})
}
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)
}
}
const filteredList = computed(() => {
const keyword = searchText.value.trim().toLowerCase()
let list = deviceList.value
// 下模页面只显示有在机模具的设备
if (fromType.value === 'down') {
list = list.filter((d) => hasMoldOnDevice(d))
}
if (!keyword) return list
return list.filter((d) => {
return (d.deviceName || '').toLowerCase().includes(keyword) ||
(d.deviceCode || '').toLowerCase().includes(keyword) ||
(getTopLineName(d.deviceLine) || '').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)
if (device) {
const lineName = getTopLineName(device.deviceLine)
if (lineName && lineName !== '-') {
device.workshopName = lineName
}
}
// 存入 globalData 后再返回,目标页 onShow 中读取
getApp().globalData._deviceSelectResult = device || null
uni.navigateBack()
}
onShow(async () => {
await Promise.allSettled([loadDevices(), loadLineTree(), loadDeviceMolds()])
})
onLoad((options) => {
fromType.value = options?.fromType === 'down' ? 'down' : 'up'
})
</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-card-header-right {
display: flex;
align-items: center;
gap: 12rpx;
flex-shrink: 0;
}
.dismount-btn {
padding: 6rpx 16rpx;
border-radius: 8rpx;
background: #1f4b79;
}
.dismount-btn-text {
font-size: 22rpx;
font-weight: 500;
color: #ffffff;
white-space: nowrap;
}
.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: 18rpx 24rpx calc(18rpx + 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: 84rpx;
line-height: 84rpx;
text-align: center;
border-radius: 16rpx;
font-size: 30rpx;
font-weight: 600;
}
.confirm-btn {
background: #1f4b79;
color: #fff;
}
</style>