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.

631 lines
17 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">
<!-- 顶部导航栏 -->
<view class="custom-nav">
<view class="nav-back" @click="goBack">
<text class="back-icon">&lt;</text>
</view>
<text class="nav-title">{{ t('moldOperate.tabUp') }}</text>
<view class="nav-placeholder"></view>
</view>
<!-- ========== 上模 ========== -->
<!-- 操作按钮区 -->
<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>
<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>
<!-- 已选设备卡片 -->
<view class="section-card">
<view class="section-title-bar">
<view class="section-bar-line"></view>
<text class="section-title">{{ t('moldOperate.selectedDevice') }}</text>
</view>
<view class="card-body-grid">
<view class="grid-row">
<view class="grid-cell">
<text class="grid-label">{{ t('moldOperate.deviceName') }}</text>
<text class="grid-value">{{ textValue(selectedDevice.deviceName) }}</text>
</view>
<view class="grid-cell">
<text class="grid-label">{{ t('moldOperate.deviceCode') }}</text>
<text class="grid-value">{{ textValue(selectedDevice.deviceCode) }}</text>
</view>
</view>
<view class="grid-row">
<view class="grid-cell">
<text class="grid-label">{{ t('moldOperate.productionLine') }}</text>
<text class="grid-value">{{ textValue(selectedDevice.workshopName) }}</text>
</view>
<view class="grid-cell">
<text class="grid-label">{{ t('moldOperate.currentMold') }}</text>
<text class="grid-value highlight">{{ currentMoldDisplay }}</text>
</view>
</view>
<view class="grid-row">
<view class="grid-cell full-width">
<text class="grid-label">{{ t('moldOperate.deviceStatus') }}</text>
<view class="status-tag" :class="deviceStatusClass">{{ deviceStatusLabel }}</view>
</view>
</view>
</view>
</view>
<!-- 选择待上模模具 -->
<view class="section-card">
<view class="section-title-bar">
<view class="section-bar-line"></view>
<text class="section-title">{{ t('moldOperate.selectMountMold') }}</text>
</view>
<template v-if="selectedMountMolds.length > 0">
<view v-for="(mold, index) in selectedMountMolds" :key="mold.id || index" class="card-body-grid">
<view class="grid-row">
<view class="grid-cell">
<text class="grid-label">{{ t('moldOperate.moldName') }}</text>
<text class="grid-value highlight">{{ textValue(mold.name || mold.moldName) }}</text>
</view>
<view class="grid-cell">
<text class="grid-label">{{ t('moldOperate.moldCode') }}</text>
<text class="grid-value">{{ textValue(mold.code || mold.moldCode) }}</text>
</view>
</view>
<view class="grid-row">
<view class="grid-cell">
<text class="grid-label">{{ t('moldOperate.product') }}</text>
<text class="grid-value">{{ textValue(mold.productName) }}</text>
</view>
<view class="grid-cell">
<text class="grid-label">{{ t('moldOperate.status') }}</text>
<view :class="['status-tag', getMoldStatusClass(mold.status)]">{{ getMoldStatusText(mold.status) }}</view>
</view>
</view>
</view>
<!-- 更换上模对象按钮 -->
<view class="change-target-btn" @click="openMountMoldPicker">
<text class="change-icon">&#8644;</text>
<text class="change-text">{{ t('moldOperate.changeMountTarget') }}</text>
</view>
</template>
<view v-else class="empty-mold-hint" @click="openMountMoldPicker">
<text class="empty-mold-text">+ {{ t('moldOperate.clickSelectMold') }}</text>
</view>
</view>
<!-- 底部操作栏 - 上模 -->
<view class="bottom-actions">
<view class="bottom-btn confirm-btn" @click="handleConfirmMount">{{ t('moldOperate.confirmMount') }}</view>
<view class="bottom-btn cancel-btn" @click="handleCancel">{{ t('functionCommon.cancel') }}</view>
</view>
</view>
</template>
<script setup>
import { computed, ref } from 'vue'
import { onShow } from '@dcloudio/uni-app'
import { useI18n } from 'vue-i18n'
import { getDeviceLedgerList, createMoldOperate } from '@/api/mes/moldoperate'
const { t } = useI18n()
// ---- 工具函数 ----
function textValue(v) {
if (v === 0) return '0'
if (v == null) return '-'
const s = String(v).trim()
return s || '-'
}
// ==================== 共享: 设备列表 ====================
const deviceOptions = ref([])
async function loadDevices() {
try {
const res = await getDeviceLedgerList({ pageNo: 1, pageSize: 100 })
const root = res && res.data !== undefined ? res.data : res
const data = Array.isArray(root)
? root
: Array.isArray(root?.list) ? root.list
: Array.isArray(root?.rows) ? root.rows
: Array.isArray(root?.records) ? root.records
: []
deviceOptions.value = data.map((d) => ({
value: d.id,
label: `${d.deviceCode || ''} ${d.deviceName || ''}`.trim(),
raw: d
}))
} catch (e) {
console.error('loadDevices error', e)
}
}
// ==================== 上模模块 ====================
const selectedDevice = ref({})
const selectedMountMolds = ref([])
const tempSelectedDeviceId = ref(null)
// 设备状态 - 上模
const deviceStatusClass = computed(() => {
const status = Number(selectedDevice.value?.deviceStatus)
if (status === 0) return 'running-tag'
if (status === 1) return 'stop-tag'
if (status >= 2) return 'fault-tag'
return ''
})
const deviceStatusLabel = computed(() => {
const status = Number(selectedDevice.value?.deviceStatus)
const map = {
0: t('moldOperate.statusRunning'),
1: t('moldOperate.statusStop'),
2: t('moldOperate.statusFault'),
3: t('moldOperate.statusFault')
}
return map[status] || textValue(selectedDevice.value?.deviceStatus) || '-'
})
const MOLD_STATUS_MAP = { 0: '在机', 1: '待用', 2: '维修', 3: '报废' }
function getMoldStatusText(s) { return MOLD_STATUS_MAP[s] || textValue(s) }
function getMoldStatusClass(s) {
if (s === 0) return 'inuse-tag'
if (s === 2) return 'repair-tag'
if (s === 3) return 'scrap-tag'
return 'standby-tag'
}
// ---- 持久化在机模具信息按设备ID索引避免多设备覆盖----
const MOUNTED_MOLD_KEY = '_mountedMoldInfoMap'
function getMountedMoldMap() {
try {
const data = uni.getStorageSync(MOUNTED_MOLD_KEY) || {}
return data
} catch (e) {
console.warn('[上模] getMountedMoldMap 异常', e)
return {}
}
}
function getMountedMoldByDevice(deviceId) {
if (!deviceId) return null
const map = getMountedMoldMap()
const key = String(deviceId)
return map[key] || null
}
function saveMountedMoldInfo(info) {
if (!info || !info.deviceId) return
try {
const map = getMountedMoldMap()
map[String(info.deviceId)] = info
uni.setStorageSync(MOUNTED_MOLD_KEY, map)
console.log('[上模] saveMountedMoldInfo 写入成功, deviceId=', info.deviceId, 'map keys=', Object.keys(map))
} catch (e) {
console.warn('[上模] saveMountedMoldInfo 异常', e)
}
}
function clearMountedMoldInfo(deviceId) {
if (!deviceId) {
try { uni.removeStorageSync(MOUNTED_MOLD_KEY) } catch {}
return
}
try {
const map = getMountedMoldMap()
delete map[String(deviceId)]
uni.setStorageSync(MOUNTED_MOLD_KEY, map)
} catch {}
}
// 当前在机模具 - 优先从持久化存储获取(实时),否则用设备台账静态字段
const currentMoldDisplay = computed(() => {
if (!selectedDevice.value?.id) return '-'
const saved = getMountedMoldByDevice(selectedDevice.value.id)
if (saved) {
console.log('[上模] currentMoldDisplay 命中持久化, deviceId=', selectedDevice.value.id, 'mold=', saved.mold?.name || saved.mold?.moldName)
return saved.mold?.name || saved.mold?.moldName || '-'
}
return textValue(selectedDevice.value.currentMold)
})
function selectDevice(device) {
selectedDevice.value = device || {}
tempSelectedDeviceId.value = device ? device.id : null
selectedMountMolds.value = []
}
function openDevicePicker() {
uni.navigateTo({ url: '/pages_function/pages/moldoperate/deviceSelect' })
}
function openMountMoldPicker() {
if (!selectedDevice.value?.id) {
uni.showToast({ title: t('moldOperate.validatorDeviceRequired'), icon: 'none' })
return
}
// 把已选模具 ID 传过去做回显
getApp().globalData._moldSelectPreSelected = selectedMountMolds.value.map((m) => String(m.id))
uni.navigateTo({ url: '/pages_function/pages/moldoperate/moldSelect' })
}
function validFormMount() {
if (!selectedDevice.value?.id) {
uni.showToast({ title: t('moldOperate.validatorDeviceRequired'), icon: 'none' })
return false
}
if (!selectedMountMolds.value.length) {
uni.showToast({ title: t('moldOperate.validatorMoldRequired'), icon: 'none' })
return false
}
return true
}
async function handleConfirmMount() {
if (!validFormMount()) return
try {
const moldIds = selectedMountMolds.value.map((m) => m.id)
const payload = {
operateType: '1',
deviceId: String(selectedDevice.value.id),
moldId: moldIds.length === 1 ? String(moldIds[0]) : moldIds.map(String).join(',')
}
console.log('=== 上模提交参数 ===', JSON.stringify(payload))
console.log('=== deviceId type:', typeof payload.deviceId, 'value:', payload.deviceId)
console.log('=== moldId type:', typeof payload.moldId, 'value:', payload.moldId)
const res = await createMoldOperate(payload)
console.log('=== 上模返回 ===', JSON.stringify(res))
uni.showToast({ title: t('functionCommon.createSuccess'), icon: 'success' })
// 上模成功后持久化保存,供下模/设备选择页读取
if (selectedMountMolds.value.length > 0) {
const info = {
deviceId: selectedDevice.value.id,
deviceCode: selectedDevice.value.deviceCode,
deviceName: selectedDevice.value.deviceName,
mold: selectedMountMolds.value[0],
mountTime: new Date().toLocaleString()
}
saveMountedMoldInfo(info)
// 同步到 globalData兼容旧逻辑
getApp().globalData._mountedMoldInfo = info
getApp().globalData._mountedMoldInfoMap = getMountedMoldMap()
console.log('=== 已保存在机模具信息 ===', JSON.stringify(info))
}
selectedDevice.value = {}
selectedMountMolds.value = []
} catch (e) {
console.error('=== 上模失败 ===', e)
const errMsg = e?.msg || (typeof e === 'string' ? e : e?.message) || '系统异常'
console.error('=== 错误详情 ===', errMsg)
uni.showToast({ title: t('functionCommon.saveFailed') + ': ' + errMsg, icon: 'none', duration: 3000 })
}
}
// ==================== 共享: 扫码 / 取消 / 返回 ====================
function handleScan() {
uni.scanCode({
onlyFromCamera: false,
scanType: ['qrCode', 'barCode'],
success(res) {
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)
)
if (matched) {
selectDevice(matched.raw)
} else {
uni.showToast({ title: t('moldOperate.deviceNotFound'), icon: 'none' })
}
},
fail(err) {
if (err.errMsg && !err.errMsg.includes('cancel')) {
console.warn('scan failed:', err)
}
}
})
}
function handleCancel() {
selectedDevice.value = {}
selectedMountMolds.value = []
}
function goBack() {
uni.navigateBack({ fail: () => uni.switchTab({ url: '/pages/index/index' }) })
}
onShow(async () => {
await Promise.allSettled([loadDevices()])
// 从 globalData 读取设备选择页回传的设备
const device = getApp().globalData._deviceSelectResult
if (device) {
getApp().globalData._deviceSelectResult = null
selectDevice(device)
}
// 从 globalData 读取模具选择页回传的模具列表
const molds = getApp().globalData._moldSelectResult
if (molds) {
getApp().globalData._moldSelectResult = null
selectedMountMolds.value = molds
}
})
</script>
<style lang="scss" scoped>
.page-container {
min-height: 100vh;
background: #f5f6f8;
padding-bottom: 140rpx;
}
/* ====== 自定义导航栏 ====== */
.custom-nav {
display: flex;
align-items: center;
justify-content: space-between;
height: 88rpx;
padding: 0 24rpx;
background: #1a365d;
color: #fff;
.nav-back {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.back-icon {
font-size: 36rpx;
font-weight: bold;
}
.nav-title {
font-size: 34rpx;
font-weight: 600;
}
.nav-placeholder {
width: 60rpx;
}
}
/* ====== 操作按钮区 ====== */
.action-row {
display: flex;
gap: 20rpx;
padding: 20rpx 24rpx;
}
.action-btn {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
gap: 12rpx;
height: 96rpx;
border-radius: 12rpx;
background: #2563eb;
color: #fff;
font-size: 28rpx;
font-weight: 600;
.btn-icon-wrap {
width: 44rpx;
height: 44rpx;
display: flex;
align-items: center;
justify-content: center;
}
.btn-icon {
font-size: 36rpx;
color: #fff;
}
.btn-text {
font-size: 28rpx;
}
}
/* ====== 卡片通用样式 ====== */
.section-card {
margin: 16rpx 24rpx;
background: #ffffff;
border-radius: 14rpx;
overflow: hidden;
}
.section-title-bar {
display: flex;
align-items: center;
gap: 10rpx;
padding: 22rpx 24rpx 0;
}
.section-bar-line {
width: 6rpx;
height: 32rpx;
border-radius: 3rpx;
background: #2563eb;
}
.section-title {
font-size: 30rpx;
font-weight: 700;
color: #1a1a1a;
}
.card-body-grid {
padding: 20rpx 24rpx 28rpx;
}
/* ====== 网格布局 ====== */
.grid-row {
display: flex;
gap: 20rpx;
margin-top: 18rpx;
&:first-child {
margin-top: 0;
}
}
.grid-cell {
flex: 1;
min-width: 0;
&.full-width {
flex-basis: 100%;
display: flex;
align-items: center;
gap: 12rpx;
}
}
.grid-label {
display: block;
font-size: 24rpx;
color: #999;
margin-bottom: 6rpx;
}
.grid-value {
display: block;
font-size: 27rpx;
color: #333;
word-break: break-all;
&.highlight {
color: #2563eb;
font-weight: 600;
}
}
/* ====== 状态标签 ====== */
.status-tag {
display: inline-flex;
align-items: center;
padding: 6rpx 18rpx;
border-radius: 6rpx;
font-size: 24rpx;
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;
}
// 模具状态
&.inuse-tag {
color: #059669; background: #ecfdf5; border: 1rpx solid #a7f3d0;
}
&.standby-tag {
color: #2563eb; background: #eff6ff; border: 1rpx solid #bfdbfe;
}
&.repair-tag {
color: #d97706; background: #fffbeb; border: 1rpx solid #fde68a;
}
&.scrap-tag {
color: #dc2626; background: #fef2f2; border: 1rpx solid #fecaca;
}
}
/* ====== 更换下模对象按钮 ====== */
.change-target-btn {
display: flex;
align-items: center;
justify-content: center;
gap: 8rpx;
margin: 0 24rpx 24rpx;
padding: 20rpx 0;
border: 1rpx dashed #ccc;
border-radius: 10rpx;
&:active { background: #fafafa; }
.change-icon { font-size: 28rpx; color: #666; }
.change-text { font-size: 26rpx; color: #666; }
}
/* ====== 空提示 ====== */
.empty-mold-hint {
padding: 40rpx 24rpx;
text-align: center;
}
.empty-mold-text {
font-size: 28rpx;
color: #2563eb;
&.loading-text {
color: #999;
}
&.empty-gray {
color: #bbb;
}
}
/* ====== 底部操作栏 ====== */
.bottom-actions {
position: fixed;
left: 0;
right: 0;
bottom: 0;
display: flex;
gap: 20rpx;
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;
&.single-btn {
gap: 0;
}
}
.bottom-btn {
height: 88rpx;
line-height: 88rpx;
text-align: center;
border-radius: 12rpx;
font-size: 30rpx;
font-weight: 600;
}
.confirm-btn {
flex: 1;
background: #2563eb;
color: #fff;
}
.cancel-btn {
flex: 1;
background: #fff;
color: #666;
border: 1rpx solid #ddd;
}
</style>