feat:模具管理-添加模具类型/模具台账模块

master
黄伟杰 1 month ago
parent 1514cdf7b4
commit 0ea15894bd

@ -1,3 +1,4 @@
import upload from '@/utils/upload'
import request from '@/utils/request'
export function getMoldDetail(id) {
@ -32,3 +33,90 @@ export function getMoldRepairListByMoldId(moldId, params = {}) {
})
}
export function getMoldBrandPage(params = {}) {
return request({
url: '/admin-api/erp/mold-brand/page',
method: 'get',
params
})
}
export function getMoldBrandDetail(id) {
return request({
url: '/admin-api/erp/mold-brand/get',
method: 'get',
params: { id }
})
}
export function getMoldBrandTree() {
return request({
url: '/admin-api/erp/mold-brand/tree',
method: 'get'
})
}
export function getMoldPage(params = {}) {
return request({
url: '/admin-api/erp/mold-brand/mold/page',
method: 'get',
params
})
}
export function createMoldBrand(data) {
return request({
url: '/admin-api/erp/mold-brand/create',
method: 'post',
data
})
}
export function updateMoldBrand(data) {
return request({
url: '/admin-api/erp/mold-brand/update',
method: 'put',
data
})
}
export function deleteMoldBrand(id) {
return request({
url: '/admin-api/erp/mold-brand/delete',
method: 'delete',
params: { id }
})
}
export function createMold(data) {
return request({
url: '/admin-api/erp/mold-brand/mold/create',
method: 'post',
data
})
}
export function updateMold(data) {
return request({
url: '/admin-api/erp/mold-brand/mold/update',
method: 'put',
data
})
}
export function deleteMold(id) {
return request({
url: '/admin-api/erp/mold-brand/mold/delete',
method: 'delete',
params: { id }
})
}
export function uploadMoldImage(filePath, name = 'file') {
return upload({
url: '/admin-api/infra/file/upload',
name,
filePath,
showLoading: false
})
}

@ -9,6 +9,13 @@ export function getUnitList() {
method: 'get'
})
}
export function getProductUnitSimpleList() {
return request({
url: '/admin-api/erp/product-unit/simple-list',
method: 'get'
})
}
// 原料列表
export function getItemList() {
return request({

@ -420,6 +420,34 @@
"navigationBarTitleText": "设备关键件",
"navigationStyle": "custom"
}
},
{
"path": "moldType/index",
"style": {
"navigationBarTitleText": "模具类型",
"navigationStyle": "custom"
}
},
{
"path": "moldType/detail",
"style": {
"navigationBarTitleText": "模具类型详情",
"navigationStyle": "custom"
}
},
{
"path": "moldLedger/index",
"style": {
"navigationBarTitleText": "模具台账",
"navigationStyle": "custom"
}
},
{
"path": "moldLedger/detail",
"style": {
"navigationBarTitleText": "模具台账详情",
"navigationStyle": "custom"
}
}
]
}

@ -371,8 +371,8 @@ function handleClick(name) {
'设备分类': '/pages_function/pages/equipmentCategory/index',
'设备台账': '/pages_function/pages/equipmentLedger/index',
'设备关键件': '/pages_function/pages/equipmentKeypart/index',
'模具类型': '',
'模具台账': '',
'模具类型': '/pages_function/pages/moldType/index',
'模具台账': '/pages_function/pages/moldLedger/index',
'模具出库': '',
'模具入库': '',
'上下模': '',

@ -0,0 +1,697 @@
<template>
<view class="page-container">
<view class="fixed-header">
<AppTitleHeader title="模具台账详情" />
</view>
<view class="content-section">
<view class="info-card">
<view class="card-title">基本信息</view>
<view class="info-list">
<view class="info-row">
<text class="info-label">模具名称</text>
<text class="info-value">{{ getDetailField('name') }}</text>
</view>
<view class="info-row">
<text class="info-label">模具编号</text>
<text class="info-value">{{ getDetailField('code') }}</text>
</view>
<view class="info-row">
<text class="info-label">模具状态</text>
<u-tag :text="statusLabel" :type="statusTagType" size="mini" />
</view>
<view class="info-row">
<text class="info-label">模具型号</text>
<text class="info-value">{{ getDetailField('brandName') }}</text>
</view>
<view class="info-row">
<text class="info-label">模具规格</text>
<text class="info-value">{{ getDetailField('moldType') }}</text>
</view>
<view class="info-row">
<text class="info-label">工序</text>
<text class="info-value">{{ getDetailField('orgType') }}</text>
</view>
<view class="info-row">
<text class="info-label">模穴数</text>
<text class="info-value">{{ getDetailField('moldSize') }}</text>
</view>
<view class="info-row">
<text class="info-label">使用次数/</text>
<text class="info-value">{{ getDetailField('useTime') }}</text>
</view>
<view class="info-row">
<text class="info-label">使用设备</text>
<text class="info-value">{{ machineLabel }}</text>
</view>
<view class="info-row">
<text class="info-label">入库日期</text>
<text class="info-value">{{ inTimeLabel }}</text>
</view>
<view class="info-row">
<text class="info-label">是否启用</text>
<text class="info-value">{{ enableLabel }}</text>
</view>
<view class="info-row">
<text class="info-label">备注</text>
<text class="info-value">{{ getDetailField('remark') }}</text>
</view>
</view>
</view>
<view class="info-card">
<view class="card-title">履历</view>
<view class="tabs-box">
<u-tabs activeColor="#1a3a5c" :list="tabList" :current="currentTab" :is-scroll="false" @change="handleTabChange" />
</view>
<view>
<view v-if="currentTab === 0">
<view v-if="!inspectionGroups.length" class="empty"></view>
<view v-for="group in inspectionGroups" :key="group.key" class="history-group">
<view class="history-group-head">
<text class="history-group-time">[{{ group.time }}]</text>
<text class="history-group-operator">操作人: {{ group.operator }}</text>
</view>
<view v-for="item in group.items" :key="item.key" class="history-item">
<view class="history-item-head">
<text class="result-badge" :class="'result-' + item.resultType">{{ item.resultLabel }}</text>
<text class="history-item-name">{{ item.name }}</text>
</view>
<view class="history-item-body">
<view class="history-row">
<text class="history-label">点检方式</text>
<text class="history-value">{{ detailValue(item.method) }}</text>
</view>
<view class="history-row">
<text class="history-label">判定标准</text>
<text class="history-value">{{ detailValue(item.criteria) }}</text>
</view>
<view class="history-row">
<text class="history-label">点检时间</text>
<text class="history-value">{{ detailValue(item.taskTimeLabel) }}</text>
</view>
<view class="history-row">
<text class="history-label">创建时间</text>
<text class="history-value">{{ detailValue(item.createTimeLabel) }}</text>
</view>
<view class="history-row">
<text class="history-label">备注</text>
<text class="history-value">{{ detailValue(item.remark) }}</text>
</view>
<view v-if="item.images && 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>
<view v-else-if="currentTab === 1">
<view v-if="!maintainGroups.length" class="empty"></view>
<view v-for="group in maintainGroups" :key="group.key" class="history-group">
<view class="history-group-head">
<text class="history-group-time">[{{ group.time }}]</text>
<text class="history-group-operator">操作人: {{ group.operator }}</text>
</view>
<view v-for="item in group.items" :key="item.key" class="history-item">
<view class="history-item-head">
<text class="result-badge" :class="'result-' + item.resultType">{{ item.resultLabel }}</text>
<text class="history-item-name">{{ item.name }}</text>
</view>
<view class="history-item-body">
<view class="history-row">
<text class="history-label">保养方式</text>
<text class="history-value">{{ detailValue(item.method) }}</text>
</view>
<view class="history-row">
<text class="history-label">判定标准</text>
<text class="history-value">{{ detailValue(item.criteria) }}</text>
</view>
<view class="history-row">
<text class="history-label">保养时间</text>
<text class="history-value">{{ detailValue(item.taskTimeLabel) }}</text>
</view>
<view class="history-row">
<text class="history-label">创建时间</text>
<text class="history-value">{{ detailValue(item.createTimeLabel) }}</text>
</view>
<view class="history-row">
<text class="history-label">备注</text>
<text class="history-value">{{ detailValue(item.remark) }}</text>
</view>
<view v-if="item.images && 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>
<view v-else>
<view v-if="!repairGroups.length" class="empty"></view>
<view v-for="group in repairGroups" :key="group.key" class="repair-group">
<view class="repair-group-head">
<text class="repair-group-name">{{ group.name }}</text>
<text class="repair-group-meta">{{ group.items.length }}</text>
</view>
<view v-for="row in group.items" :key="row.key" class="repair-item">
<view class="repair-item-head">
<text class="repair-tag">{{ detailValue(row.subjectCode) }}</text>
<text class="repair-title">{{ detailValue(row.subjectName) }}</text>
</view>
<view class="repair-item-body">
<view class="history-row">
<text class="history-label">项目内容</text>
<text class="history-value">{{ detailValue(row.subjectContent) }}</text>
</view>
<view class="history-row">
<text class="history-label">维修结果</text>
<text class="history-value">
<text class="result-badge" :class="'result-' + row.resultType">{{ row.resultLabel }}</text>
</text>
</view>
<view class="history-row">
<text class="history-label">备注</text>
<text class="history-value">{{ detailValue(row.remark) }}</text>
</view>
<view class="history-row">
<text class="history-label">完成日期</text>
<text class="history-value">{{ detailValue(row.finishDateLabel) }}</text>
</view>
<view v-if="row.images && row.images.length" class="history-images">
<image v-for="img in row.images" :key="img" class="history-image" :src="img" mode="aspectFill" @click="previewImages(row.images, img)" />
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import AppTitleHeader from '@/components/common/AppTitleHeader.vue'
import { getMoldDetail, getMoldInspectionByMoldId, getMoldMaintenanceByMoldId, getMoldRepairListByMoldId } from '@/api/mes/mold'
import { getDictLabel, initAllDict } from '@/utils/dict'
const loading = ref(false)
const moldId = ref(undefined)
const detailData = ref(null)
const inspectionList = ref([])
const maintainList = ref([])
const repairList = ref([])
const tabList = ref([{ name: '点检履历' }, { name: '保养履历' }, { name: '维修履历' }])
const currentTab = ref(0)
function getDetailField(field) {
const d = detailData.value
return detailValue(d ? d[field] : undefined)
}
const statusMeta = computed(() => {
const d = detailData.value
const raw = d ? d.status : undefined
const label = getDictLabel('erp_mold_status', raw, detailValue(raw))
return formatStatus(label)
})
const statusLabel = computed(() => statusMeta.value.label)
const statusType = computed(() => statusMeta.value.type)
const statusTagType = computed(() => {
if (statusType.value === 'error') return 'error'
if (statusType.value === 'warning') return 'warning'
return 'success'
})
const machineLabel = computed(() => {
const d = detailData.value
const v = d ? d.machineName || d.machineId : undefined
return detailValue(v)
})
const inTimeLabel = computed(() => {
const d = detailData.value
return formatDateOnly(d ? d.inTime : undefined)
})
const enableLabel = computed(() => {
const d = detailData.value
return formatEnable(d ? d.isEnable : undefined)
})
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', 'inspectionTime', 'createTime'],
nameFieldCandidates: ['maintainItemName', 'inspectionItemName', 'name', 'itemName'],
resultFieldCandidates: ['maintainResult', 'inspectionResult', 'result'],
methodFieldCandidates: ['inspectionMethod', 'method'],
criteriaFieldCandidates: ['judgmentCriteria', 'criteria'],
imagesFieldCandidates: ['images'],
remarkFieldCandidates: ['remark']
})
)
const repairGroups = computed(() => {
const groupsMap = new Map()
const rows = Array.isArray(repairList.value) ? repairList.value : []
for (const row of rows) {
const groupKey = String((row && (row.repairCode || row.repairId || row.subjectName || row.id)) ? (row.repairCode || row.repairId || row.subjectName || row.id) : '-')
if (!groupsMap.has(groupKey)) {
groupsMap.set(groupKey, {
key: groupKey,
name: String((row && (row.repairName || row.repairCode)) ? (row.repairName || row.repairCode) : groupKey),
items: []
})
}
const resultMeta = formatResult(row ? (row.repairResult !== undefined ? row.repairResult : row.result) : undefined)
groupsMap.get(groupKey).items.push({
key: String(row && row.id !== undefined && row.id !== null ? row.id : `${groupKey}_${Math.random()}`),
subjectCode: row ? row.subjectCode : undefined,
subjectName: row ? row.subjectName : undefined,
subjectContent: row ? row.subjectContent : undefined,
remark: row ? row.remark : undefined,
finishDateLabel: formatDateOnly(row ? row.finishDate : undefined),
resultLabel: resultMeta.label,
resultType: resultMeta.type,
images: parseImages(row ? (row.malfunctionUrl || row.malfunctionImages) : undefined)
})
}
return Array.from(groupsMap.values())
})
function handleTabChange(e) {
const idx = e && typeof e === 'object' ? e.index : e
currentTab.value = Number(idx === 0 ? 0 : idx || 0)
}
onLoad(async (query) => {
const rawId = query && (query.id !== undefined ? query.id : query.code)
const decoded = rawId ? decodeURIComponent(String(rawId)) : ''
moldId.value = decoded ? decoded : undefined
await initAllDict()
await fetchAll()
})
async function fetchAll() {
if (!moldId.value) {
uni.showToast({ title: '缺少模具ID', icon: 'none' })
return
}
loading.value = true
try {
const [detailRes, inspectionRes, maintainRes, repairRes] = await Promise.all([
getMoldDetail(moldId.value),
getMoldInspectionByMoldId(moldId.value),
getMoldMaintenanceByMoldId(moldId.value),
getMoldRepairListByMoldId(moldId.value)
])
detailData.value = detailRes.data
inspectionList.value = normalizeList(inspectionRes)
maintainList.value = normalizeList(maintainRes)
repairList.value = normalizeList(repairRes)
} catch (e) {
uni.showToast({ title: '加载失败', icon: 'none' })
} finally {
loading.value = false
}
}
function normalizeList(res) {
const data = res && res.data !== undefined ? res.data : res
if (Array.isArray(data)) return data
if (data && Array.isArray(data.data)) return data.data
if (data && data.data && Array.isArray(data.data.list)) return data.data.list
if (data && data.data && Array.isArray(data.data.rows)) return data.data.rows
if (data && data.data && Array.isArray(data.data.records)) return data.data.records
if (data && Array.isArray(data.list)) return data.list
if (data && Array.isArray(data.rows)) return data.rows
if (data && Array.isArray(data.records)) return data.records
return []
}
function detailValue(v) {
if (v === 0) return '0'
if (v === false) return '否'
if (v === true) return '是'
if (v === null || v === undefined) return '-'
const s = String(v).trim()
return s ? s : '-'
}
function formatEnable(v) {
if (v === true || v === 'true' || v === 1 || v === '1') return '是'
if (v === false || v === 'false' || v === 0 || v === '0') return '否'
return detailValue(v)
}
function formatStatus(v) {
const raw = v === null || v === undefined ? '' : String(v).trim()
const upper = raw.toUpperCase()
if (!raw) return { label: '-', type: 'normal' }
if (raw === '1' || upper === 'OK' || raw.includes('正常')) return { label: raw, type: 'normal' }
if (raw === '2' || upper === 'NG' || raw.includes('停') || raw.includes('禁') || raw.includes('坏') || raw.includes('修')) return { label: raw, type: 'error' }
return { label: raw, type: 'warning' }
}
function formatResult(v) {
const raw = v === null || v === undefined ? '' : String(v).trim()
const upper = raw.toUpperCase()
if (!raw) return { label: '-', type: 'info' }
if (raw === '0') return { label: '待检测', type: 'info' }
if (raw === '1' || upper === 'OK') return { label: '通过', type: 'success' }
if (raw === '2' || upper === 'NG') return { label: '不通过', type: 'danger' }
return { label: raw, type: 'info' }
}
function formatHistoryTime(value) {
if (!value) return ''
if (Array.isArray(value) && value.length >= 3) {
const [y, m, d, hh, mm, ss] = value
const pad = (n) => String(n).padStart(2, '0')
if (hh !== undefined) return `${y}-${pad(m)}-${pad(d)} ${pad(hh)}:${pad(mm)}:${pad(ss)}`
return `${y}-${pad(m)}-${pad(d)}`
}
const s = String(value).trim()
if (!s) return ''
const num = Number(s)
if (Number.isFinite(num)) {
const ms = s.length === 10 ? num * 1000 : num
const d = new Date(ms)
if (!Number.isNaN(d.getTime())) return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}:${String(d.getSeconds()).padStart(2, '0')}`
}
const d = new Date(s)
if (!Number.isNaN(d.getTime())) return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}:${String(d.getSeconds()).padStart(2, '0')}`
return s
}
function formatDateOnly(value) {
const t = formatHistoryTime(value)
if (!t) return '-'
return String(t).split(' ')[0]
}
function parseImages(value) {
if (!value) return []
if (Array.isArray(value)) return value.map(String).filter(Boolean)
const cleaned = String(value).replace(/[`'"]/g, '').trim()
return cleaned.split(',').map((v) => v.trim()).filter(Boolean)
}
function pickFirst(obj, keys) {
for (const k of keys) {
if (obj && obj[k] !== undefined && obj[k] !== null && String(obj[k]).trim() !== '') return obj[k]
}
return undefined
}
function buildStepGroups(rows, options) {
const groupsMap = new Map()
const list = Array.isArray(rows) ? rows : []
for (const row of list) {
const time = formatHistoryTime(pickFirst(row, options.timeFieldCandidates) || (row ? row.createTime : undefined))
const operator = detailValue(row ? (row.operator || row.creatorName || row.creator) : undefined)
const managementId = row && row.managementId !== undefined && row.managementId !== null ? row.managementId : ''
const groupKey = `${managementId}__${time}__${operator}`
const name = pickFirst(row, options.nameFieldCandidates) || '-'
const resultMeta = formatResult(pickFirst(row, options.resultFieldCandidates))
const item = {
key: String(row && row.id !== undefined && row.id !== null ? row.id : `${groupKey}_${String(name)}`),
name: detailValue(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: formatHistoryTime(row ? (row.taskTime || row.inspectionTime) : undefined),
createTimeLabel: formatHistoryTime(row ? row.createTime : undefined)
}
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 })
}
</script>
<style lang="scss" scoped>
.page-container {
min-height: 100vh;
background-color: #f0f2f5;
}
.fixed-header {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1000;
}
.content-section {
padding: 168rpx 30rpx 30rpx;
}
.info-card {
background: #ffffff;
border-radius: 20rpx;
padding: 30rpx;
margin-top: 24rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
}
.card-title {
font-size: 32rpx;
font-weight: 600;
color: #1a3a5c;
margin-bottom: 24rpx;
padding-bottom: 20rpx;
border-bottom: 2rpx solid #f0f2f5;
}
.info-list {
display: flex;
flex-direction: column;
}
.info-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx 0;
border-bottom: 1rpx solid #f5f7fa;
}
.info-row:last-child {
border-bottom: none;
}
.info-label {
font-size: 28rpx;
color: #999999;
}
.info-value {
font-size: 28rpx;
color: #333333;
}
.tabs-box {
margin-bottom: 24rpx;
}
.empty {
padding: 32rpx 0;
text-align: center;
font-size: 28rpx;
color: #999999;
}
.history-group {
background: #f8fafc;
border-radius: 16rpx;
padding: 20rpx;
margin-bottom: 20rpx;
}
.history-group-head {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
}
.history-group-time {
font-size: 24rpx;
color: #1a3a5c;
font-weight: 600;
}
.history-group-operator {
font-size: 24rpx;
color: #666666;
}
.history-item {
background: #ffffff;
border-radius: 14rpx;
padding: 18rpx;
margin-bottom: 16rpx;
}
.history-item:last-child {
margin-bottom: 0;
}
.history-item-head {
display: flex;
align-items: center;
margin-bottom: 14rpx;
}
.history-item-name {
font-size: 28rpx;
color: #333333;
font-weight: 600;
}
.result-badge {
padding: 6rpx 14rpx;
border-radius: 999rpx;
font-size: 22rpx;
margin-right: 14rpx;
}
.result-success {
background: rgba(24, 188, 55, 0.12);
color: #18bc37;
}
.result-danger {
background: rgba(255, 77, 79, 0.12);
color: #ff4d4f;
}
.result-info {
background: rgba(153, 153, 153, 0.12);
color: #666666;
}
.history-item-body {
display: flex;
flex-direction: column;
}
.history-row {
display: flex;
justify-content: space-between;
align-items: flex-start;
padding: 10rpx 0;
}
.history-label {
font-size: 24rpx;
color: #999999;
width: 160rpx;
flex-shrink: 0;
}
.history-value {
font-size: 24rpx;
color: #333333;
flex: 1;
text-align: right;
}
.history-images {
margin-top: 12rpx;
display: flex;
flex-wrap: wrap;
gap: 12rpx;
}
.history-image {
width: 160rpx;
height: 160rpx;
border-radius: 12rpx;
background: #f0f2f5;
}
.repair-group {
background: #f8fafc;
border-radius: 16rpx;
padding: 20rpx;
margin-bottom: 20rpx;
}
.repair-group-head {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
}
.repair-group-name {
font-size: 28rpx;
color: #1a3a5c;
font-weight: 600;
}
.repair-group-meta {
font-size: 24rpx;
color: #666666;
}
.repair-item {
background: #ffffff;
border-radius: 14rpx;
padding: 18rpx;
margin-bottom: 16rpx;
}
.repair-item:last-child {
margin-bottom: 0;
}
.repair-item-head {
display: flex;
align-items: center;
margin-bottom: 14rpx;
}
.repair-tag {
padding: 6rpx 14rpx;
border-radius: 999rpx;
font-size: 22rpx;
background: rgba(74, 144, 194, 0.12);
color: #2d5a87;
margin-right: 14rpx;
}
.repair-title {
font-size: 28rpx;
color: #333333;
font-weight: 600;
}
</style>

File diff suppressed because it is too large Load Diff

@ -0,0 +1,211 @@
<template>
<view class="page-container">
<AppTitleHeader title="模具类型详情" />
<view class="content-section">
<view class="info-card">
<view class="card-title">基础信息</view>
<view class="info-list">
<view class="info-row">
<text class="info-label">型号编码</text>
<text class="info-value">{{ fieldValue('code') }}</text>
</view>
<view class="info-row">
<text class="info-label">型号名称</text>
<text class="info-value">{{ fieldValue('name') }}</text>
</view>
<view class="info-row">
<text class="info-label">规格</text>
<text class="info-value">{{ fieldValue('moldType') }}</text>
</view>
<view class="info-row">
<text class="info-label">工序</text>
<text class="info-value">{{ orgTypeText }}</text>
</view>
<view class="info-row">
<text class="info-label">预期寿命(小时)</text>
<text class="info-value">{{ fieldValue('useTime') }}</text>
</view>
<view class="info-row">
<text class="info-label">模穴数</text>
<text class="info-value">{{ fieldValue('moldSize') }}</text>
</view>
<view class="info-row">
<text class="info-label">维保模式</text>
<text class="info-value">{{ fieldValue('maintainType') }}</text>
</view>
<view class="info-row">
<text class="info-label">维保周期</text>
<text class="info-value">{{ fieldValue('maintainTime') }}</text>
</view>
<view class="info-row">
<text class="info-label">产品ID</text>
<text class="info-value">{{ fieldValue('productId') }}</text>
</view>
<view class="info-row">
<text class="info-label">是否启用</text>
<text class="info-value">{{ enableText }}</text>
</view>
<view class="info-row">
<text class="info-label">创建时间</text>
<text class="info-value">{{ formatDateTime(detailData?.createTime) }}</text>
</view>
<view class="info-row">
<text class="info-label">更新时间</text>
<text class="info-value">{{ formatDateTime(detailData?.updateTime) }}</text>
</view>
<view class="info-row remark-row">
<text class="info-label">备注</text>
<text class="info-value remark-value">{{ fieldValue('remark') }}</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import AppTitleHeader from '@/components/common/AppTitleHeader.vue'
import { getMoldBrandDetail } from '@/api/mes/mold'
import { getDictLabel, initAllDict } from '@/utils/dict'
const detailId = ref(undefined)
const detailData = ref(null)
const orgTypeText = computed(() =>
getDictLabel('mes_org_type', detailData.value?.orgType, textValue(detailData.value?.orgType))
)
const enableText = computed(() => {
const value = detailData.value?.isEnable
if (value === true || value === 1 || value === '1' || value === 'true') return '是'
if (value === false || value === 0 || value === '0' || value === 'false') return '否'
return textValue(value)
})
onLoad(async (query) => {
const id = query?.id ? decodeURIComponent(String(query.id)) : ''
detailId.value = id || undefined
await initAllDict()
await fetchDetail()
})
async function fetchDetail() {
if (!detailId.value) {
uni.showToast({ title: '缺少模具类型ID', icon: 'none' })
return
}
try {
const res = await getMoldBrandDetail(detailId.value)
detailData.value = normalizeDetail(res)
} catch (e) {
uni.showToast({ title: '详情加载失败', 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 {}
}
function fieldValue(field) {
return textValue(detailData.value ? detailData.value[field] : undefined)
}
function textValue(value) {
if (value === 0) return '0'
if (value === false) return '否'
if (value === true) return '是'
if (value === null || value === undefined) return '-'
const text = String(value).trim()
return text || '-'
}
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;
}
.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;
}
.remark-row {
border-bottom: none;
}
.remark-value {
white-space: pre-wrap;
}
</style>

@ -0,0 +1,756 @@
<template>
<view class="page-container">
<AppTitleHeader title="模具类型" subTitle="按编码或名称快速查询" :showSubTitle="true" />
<view class="search-section">
<view class="search-wrapper">
<view class="search-icon">
<text class="iconfont icon-search"></text>
</view>
<input
v-model="searchKeyword"
class="search-input"
type="text"
placeholder="请输入模具型号编码或名称"
placeholder-class="input-placeholder"
@confirm="handleSearch"
/>
<view v-if="searchKeyword" class="clear-btn" @click="clearSearch">
<text class="clear-icon">×</text>
</view>
</view>
<view class="search-btn" @click="handleSearch">
<text class="search-btn-text">查询</text>
</view>
</view>
<scroll-view
scroll-y
class="content-scroll"
:scroll-top="scrollTop"
@scroll="handleScroll"
@scrolltolower="loadMore"
:lower-threshold="80"
>
<view class="list-wrap">
<view v-for="item in list" :key="item.id" class="type-card" @click="openDetail(item)">
<view class="card-header">
<view class="header-left">
<text class="type-name">{{ textValue(item.name) }}</text>
<text class="type-code">编码: {{ textValue(item.code) }}</text>
</view>
<view class="card-actions">
<view class="action-btn edit-btn" @click.stop="openEdit(item)">
<text class="action-icon"></text>
</view>
<view class="action-btn delete-btn" @click.stop="confirmDelete(item)">
<text class="action-icon">🗑</text>
</view>
</view>
</view>
<view class="card-body">
<view class="row">
<text class="label">规格</text>
<text class="value">{{ textValue(item.moldType) }}</text>
</view>
<view class="row">
<text class="label">工序</text>
<text class="value">{{ orgTypeLabel(item.orgType) }}</text>
</view>
<view class="row">
<text class="label">预期寿命</text>
<text class="value">{{ textValue(item.useTime) }}</text>
</view>
<view class="row">
<text class="label">模穴数</text>
<text class="value">{{ textValue(item.moldSize) }}</text>
</view>
<view class="row">
<text class="label">创建时间</text>
<text class="value">{{ formatDateTime(item.createTime) }}</text>
</view>
</view>
</view>
<view v-if="loading && pageNo === 1" class="loading-text">...</view>
<view v-else-if="!list.length" class="empty-text">暂无模具类型数据</view>
<view v-else-if="loadingMore" class="loading-text">正在加载更多...</view>
<view v-else-if="finished" class="finished-text">没有更多数据了</view>
</view>
</scroll-view>
<view class="add-btn" @click="openCreate">
<text class="add-icon">+</text>
</view>
<view v-if="showGoTop" class="go-top-btn" @click="goTop">
<text class="go-top-icon"></text>
</view>
<uni-popup ref="formPopupRef" type="center" background-color="#fff">
<view class="popup-content">
<view class="popup-header">
<text class="popup-title">{{ formMode === 'create' ? '新增模具类型' : '编辑模具类型' }}</text>
<view class="popup-close" @click="closeForm">
<text class="close-icon">×</text>
</view>
</view>
<scroll-view scroll-y class="form-scroll">
<view class="form-content">
<view class="form-item">
<text class="form-label">编码 <text class="required-star">*</text></text>
<input v-model="formData.code" class="form-input" type="text" placeholder="请输入编码" :disabled="formMode === 'update'"/>
</view>
<view class="form-item">
<text class="form-label">名称 <text class="required-star">*</text></text>
<input v-model="formData.name" class="form-input" type="text" placeholder="请输入名称" />
</view>
<view class="form-item">
<text class="form-label">规格 <text class="required-star">*</text></text>
<input v-model="formData.moldType" class="form-input" type="text" placeholder="请输入规格" />
</view>
<view class="form-item">
<text class="form-label">工序</text>
<input v-model="formData.orgType" class="form-input" type="text" placeholder="请输入工序编码" />
</view>
<view class="form-item">
<text class="form-label">预期寿命(小时)</text>
<input v-model="formData.useTime" class="form-input" type="number" placeholder="请输入预期寿命" />
</view>
<view class="form-item">
<text class="form-label">模穴数 <text class="required-star">*</text></text>
<input v-model="formData.moldSize" class="form-input" type="number" placeholder="请输入模穴数" />
</view>
<view class="form-item">
<text class="form-label">产品ID <text class="required-star">*</text></text>
<input v-model="formData.productId" class="form-input" type="number" placeholder="请输入产品ID" />
</view>
<view class="form-item">
<text class="form-label">备注</text>
<textarea v-model="formData.remark" class="form-textarea" placeholder="请输入备注" :maxlength="200" />
</view>
</view>
</scroll-view>
<view class="form-footer">
<view class="footer-btn cancel-btn" @click="closeForm">
<text class="btn-text">取消</text>
</view>
<view class="footer-btn confirm-btn" @click="submitForm">
<text class="btn-text">保存</text>
</view>
</view>
</view>
</uni-popup>
</view>
</template>
<script setup>
import { ref, reactive } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import AppTitleHeader from '@/components/common/AppTitleHeader.vue'
import { createMoldBrand, deleteMoldBrand, getMoldBrandDetail, getMoldBrandPage, updateMoldBrand } from '@/api/mes/mold'
import { getDictLabel, initAllDict } from '@/utils/dict'
const formPopupRef = ref(null)
const searchKeyword = ref('')
const list = ref([])
const loading = ref(false)
const loadingMore = ref(false)
const finished = ref(false)
const pageNo = ref(1)
const pageSize = ref(10)
const total = ref(0)
const scrollTop = ref(0)
const showGoTop = ref(false)
const formMode = ref('create')
const formData = reactive({
id: undefined,
code: '',
name: '',
moldType: '',
orgType: '',
useTime: '',
moldSize: '',
productId: '',
remark: ''
})
onLoad(async () => {
await initAllDict()
await fetchList(true)
})
async function handleSearch() {
await fetchList(true)
}
async function clearSearch() {
searchKeyword.value = ''
await fetchList(true)
}
async function loadMore() {
if (loading.value || loadingMore.value || finished.value) return
pageNo.value += 1
await fetchList(false)
}
async function fetchList(reset) {
if (reset) {
pageNo.value = 1
finished.value = false
}
if (pageNo.value === 1) {
loading.value = true
} else {
loadingMore.value = true
}
try {
const params = {
pageNo: pageNo.value,
pageSize: pageSize.value,
code: searchKeyword.value.trim() || undefined,
name: searchKeyword.value.trim() || undefined
}
const res = await getMoldBrandPage(params)
const page = normalizePageData(res)
total.value = page.total
if (reset) {
list.value = page.list
} else {
list.value = [...list.value, ...page.list]
}
const loadedCount = list.value.length
finished.value = loadedCount >= total.value || page.list.length < pageSize.value
} catch (e) {
if (!reset) {
pageNo.value = Math.max(1, pageNo.value - 1)
}
uni.showToast({ title: '加载失败', icon: 'none' })
} finally {
loading.value = false
loadingMore.value = false
}
}
function handleScroll(e) {
const top = e?.detail?.scrollTop || 0
showGoTop.value = top > 600
}
function goTop() {
scrollTop.value = 0
}
function normalizePageData(res) {
const root = res && res.data !== undefined ? res.data : res
const candidateList =
root?.list ||
root?.rows ||
root?.records ||
root?.data?.list ||
root?.data?.rows ||
root?.data?.records ||
[]
const candidateTotal =
root?.total ??
root?.data?.total ??
(Array.isArray(candidateList) ? candidateList.length : 0)
return {
list: Array.isArray(candidateList) ? candidateList : [],
total: Number(candidateTotal || 0)
}
}
function orgTypeLabel(value) {
return getDictLabel('mes_org_type', value, textValue(value))
}
function textValue(value) {
if (value === 0) return '0'
if (value === false) return '否'
if (value === true) return '是'
if (value === null || value === undefined) return '-'
const text = String(value).trim()
return text || '-'
}
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
}
function openCreate() {
formMode.value = 'create'
resetForm()
formPopupRef.value?.open()
}
async function openEdit(item) {
const id = item?.id
if (id === undefined || id === null) {
uni.showToast({ title: '缺少ID无法编辑', icon: 'none' })
return
}
try {
const res = await getMoldBrandDetail(id)
const detail = normalizeDetailData(res)
formMode.value = 'update'
formData.id = detail?.id
formData.code = textValueForInput(detail?.code)
formData.name = textValueForInput(detail?.name)
formData.moldType = textValueForInput(detail?.moldType)
formData.orgType = textValueForInput(detail?.orgType)
formData.useTime = textValueForInput(detail?.useTime)
formData.moldSize = textValueForInput(detail?.moldSize)
formData.productId = textValueForInput(detail?.productId)
formData.remark = textValueForInput(detail?.remark)
formPopupRef.value?.open()
} catch (e) {
uni.showToast({ title: '加载编辑数据失败', icon: 'none' })
}
}
async function confirmDelete(item) {
const id = item?.id
if (id === undefined || id === null) {
uni.showToast({ title: '缺少ID无法删除', icon: 'none' })
return
}
uni.showModal({
title: '确认删除',
content: `确认删除模具类型"${textValue(item?.name)}"吗?`,
success: async (res) => {
if (!res.confirm) return
try {
await deleteMoldBrand(id)
uni.showToast({ title: '删除成功', icon: 'success' })
await fetchList(true)
} catch (e) {
uni.showToast({ title: '删除失败', icon: 'none' })
}
}
})
}
function closeForm() {
formPopupRef.value?.close()
}
async function submitForm() {
if (!formData.code.trim() || !formData.name.trim() || !formData.moldType.trim()) {
uni.showToast({ title: '编码、名称、规格必填', icon: 'none' })
return
}
if (String(formData.productId).trim() === '' || String(formData.moldSize).trim() === '') {
uni.showToast({ title: '产品ID、模穴数必填', icon: 'none' })
return
}
const payload = {
id: formMode.value === 'update' ? formData.id : undefined,
code: formData.code.trim(),
name: formData.name.trim(),
moldType: formData.moldType.trim(),
orgType: formData.orgType.trim() || undefined,
useTime: toNumberOrUndefined(formData.useTime),
moldSize: toNumberOrUndefined(formData.moldSize),
productId: toNumberOrUndefined(formData.productId),
remark: formData.remark.trim() || undefined,
isEnable: true
}
try {
if (formMode.value === 'create') {
await createMoldBrand(payload)
uni.showToast({ title: '新增成功', icon: 'success' })
} else {
await updateMoldBrand(payload)
uni.showToast({ title: '更新成功', icon: 'success' })
}
closeForm()
await fetchList(true)
} catch (e) {
uni.showToast({ title: '保存失败', icon: 'none' })
}
}
function resetForm() {
formData.id = undefined
formData.code = ''
formData.name = ''
formData.moldType = ''
formData.orgType = ''
formData.useTime = ''
formData.moldSize = ''
formData.productId = ''
formData.remark = ''
}
function toNumberOrUndefined(value) {
if (value === null || value === undefined || String(value).trim() === '') return undefined
const num = Number(value)
return Number.isFinite(num) ? num : undefined
}
function textValueForInput(value) {
if (value === null || value === undefined) return ''
return String(value)
}
function normalizeDetailData(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 {}
}
function openDetail(item) {
const id = item?.id
if (!id && id !== 0) {
uni.showToast({ title: '缺少ID无法查看详情', icon: 'none' })
return
}
uni.navigateTo({
url: `/pages_function/pages/moldType/detail?id=${encodeURIComponent(String(id))}`
})
}
</script>
<style lang="scss" scoped>
.page-container {
min-height: 100vh;
background-color: #f0f2f5;
}
.search-section {
background: #ffffff;
margin: 20rpx 24rpx;
border-radius: 18rpx;
padding: 20rpx;
box-shadow: 0 4rpx 18rpx rgba(0, 0, 0, 0.04);
}
.search-wrapper {
display: flex;
align-items: center;
background: #f5f7fa;
border-radius: 44rpx;
padding: 0 20rpx;
}
.search-icon {
margin-right: 16rpx;
.iconfont {
font-size: 34rpx;
color: #666666;
}
}
.search-input {
flex: 1;
height: 72rpx;
font-size: 28rpx;
color: #333333;
background: transparent;
}
.input-placeholder {
color: #999999;
}
.clear-btn {
width: 48rpx;
height: 48rpx;
display: flex;
align-items: center;
justify-content: center;
.clear-icon {
font-size: 34rpx;
color: #999999;
}
}
.search-btn {
margin-top: 18rpx;
height: 76rpx;
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
border-radius: 14rpx;
display: flex;
align-items: center;
justify-content: center;
}
.search-btn-text {
color: #ffffff;
font-size: 30rpx;
font-weight: 600;
}
.content-scroll {
height: calc(100vh - 360rpx);
}
.list-wrap {
padding: 0 24rpx 30rpx;
}
.type-card {
background: #ffffff;
border-radius: 18rpx;
padding: 24rpx;
margin-bottom: 18rpx;
box-shadow: 0 4rpx 18rpx rgba(0, 0, 0, 0.05);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
padding-bottom: 18rpx;
border-bottom: 1rpx solid #edf0f3;
}
.header-left {
display: flex;
flex-direction: column;
}
.card-actions {
display: flex;
gap: 14rpx;
}
.action-btn {
width: 60rpx;
height: 60rpx;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
}
.action-icon {
color: #ffffff;
font-size: 30rpx;
}
.edit-btn {
background: #1a3a5c;
}
.delete-btn {
background: #ff4d4f;
}
.type-name {
font-size: 32rpx;
font-weight: 600;
color: #1a3a5c;
margin-bottom: 8rpx;
}
.type-code {
font-size: 24rpx;
color: #8a9099;
}
.arrow {
font-size: 44rpx;
color: #a1a8b0;
}
.card-body {
padding-top: 16rpx;
}
.row {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 12rpx;
}
.label {
font-size: 26rpx;
color: #8a9099;
}
.value {
font-size: 27rpx;
color: #30363d;
max-width: 62%;
text-align: right;
}
.add-btn {
position: fixed;
right: 28rpx;
bottom: 140rpx;
width: 96rpx;
height: 96rpx;
border-radius: 48rpx;
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 8rpx 24rpx rgba(26, 58, 92, 0.32);
z-index: 99;
}
.add-icon {
color: #ffffff;
font-size: 56rpx;
line-height: 1;
}
.go-top-btn {
position: fixed;
right: 28rpx;
bottom: 254rpx;
width: 88rpx;
height: 88rpx;
border-radius: 44rpx;
background: rgba(26, 58, 92, 0.9);
display: flex;
align-items: center;
justify-content: center;
z-index: 99;
box-shadow: 0 6rpx 16rpx rgba(26, 58, 92, 0.25);
}
.go-top-icon {
color: #ffffff;
font-size: 36rpx;
font-weight: 700;
}
.popup-content {
width: 680rpx;
max-height: 80vh;
border-radius: 20rpx;
overflow: hidden;
}
.popup-header {
padding: 24rpx;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1rpx solid #edf0f3;
}
.popup-title {
font-size: 32rpx;
color: #1a3a5c;
font-weight: 700;
}
.close-icon {
font-size: 38rpx;
color: #8e95a0;
}
.form-scroll {
max-height: 56vh;
}
.form-content {
padding: 24rpx;
}
.form-item {
margin-bottom: 18rpx;
}
.form-label {
display: block;
margin-bottom: 8rpx;
font-size: 26rpx;
color: #8a9099;
}
.required-star {
color: #ff4d4f;
}
.form-input {
height: 76rpx;
padding: 0 20rpx;
border-radius: 12rpx;
background: #f5f7fa;
font-size: 28rpx;
color: #30363d;
}
.form-textarea {
width: 100%;
min-height: 130rpx;
padding: 18rpx 20rpx;
border-radius: 12rpx;
background: #f5f7fa;
font-size: 28rpx;
color: #30363d;
}
.form-footer {
display: flex;
gap: 14rpx;
padding: 20rpx 24rpx 24rpx;
}
.footer-btn {
flex: 1;
height: 76rpx;
border-radius: 12rpx;
display: flex;
align-items: center;
justify-content: center;
}
.cancel-btn {
background: #edf0f4;
}
.confirm-btn {
background: linear-gradient(135deg, #1a3a5c 0%, #2d5a87 100%);
}
.btn-text {
font-size: 28rpx;
font-weight: 600;
color: #ffffff;
}
.cancel-btn .btn-text {
color: #586070;
}
.loading-text,
.empty-text,
.finished-text {
text-align: center;
padding: 28rpx 0;
color: #99a1aa;
font-size: 26rpx;
}
</style>

@ -57,7 +57,7 @@ import AppTitleHeader from '@/components/common/AppTitleHeader.vue'
const spareCode = ref('');
const isScanning = ref(false);
const SCAN_TYPE = 'SPARE';
const SCAN_TYPE = 'PRODUCTMATERIAL';
const MODULE_NAME = '备件';
function parseScanResult(res) {

Loading…
Cancel
Save