feat:模具管理-更换压网模块

master
黄伟杰 4 days ago
parent e3230555a0
commit b78db18264

@ -80,6 +80,22 @@ export function getBrandList(params = {}) {
})
}
export function createPressureNetRecord(data) {
return request({
url: '/admin-api/erp/mold-pressure-net-record/batch-create',
method: 'post',
data
})
}
export function getPressureNetRecordPage(params = {}) {
return request({
url: '/admin-api/erp/mold-pressure-net-record/page',
method: 'get',
params
})
}
export function getInTransitMoldAllList() {
return request({
url: '/admin-api/erp/mold/getInTransitMoldAllList',

@ -1121,6 +1121,9 @@ export default {
selectTemplateError: 'Please select inspection template',
noItems: 'Please select inspection template first',
noTemplate: 'No templates',
resultListTitle: 'Inspection Results',
editResult: 'Edit',
cancelEdit: 'Cancel Edit',
submit: 'Submit Inspection',
submitSuccess: 'Inspection submitted successfully',
submitFailed: 'Failed to submit inspection',
@ -1181,11 +1184,57 @@ export default {
selectTemplateError: 'Please select maintenance template',
noItems: 'Please select maintenance template first',
noTemplate: 'No templates',
resultListTitle: 'Inspection Results',
editResult: 'Edit',
cancelEdit: 'Cancel Edit',
submit: 'Submit Maintenance',
submitSuccess: 'Maintenance submitted successfully',
submitFailed: 'Failed to submit maintenance',
reInspect: 'Re-maintain'
},
moldPressureNet: {
moduleName: 'Replace Pressure Net',
recordTitle: 'Pressure Net Replacement',
recordDesc: 'Select mold group and sub-mold, then confirm replacement time',
history: 'History',
historyTitle: 'Pressure Net History',
moldGroup: 'Mold Group',
selectMoldGroup: 'Select Mold Group',
searchMoldGroup: 'Enter mold group name',
noMoldGroup: 'No mold groups',
moldGroupName: 'Mold Group Name',
moldGroupCode: 'Mold Group Code',
product: 'Product',
subMold: 'Sub-mold',
selectSubMold: 'Select Sub-mold',
reSelectMoldGroup: 'Re-select Mold Group',
reSelectSubMold: 'Re-select Sub-mold',
subMoldCode: 'Sub-mold Code',
subMoldName: 'Sub-mold Name',
searchSubMold: 'Search sub-mold name/code',
noSubMold: 'No sub-molds',
replaceInfo: 'Replacement Info',
replaceTime: 'Replacement Time',
pressureNetTime: 'Pressure Net Time',
selectReplaceTime: 'Select replacement time',
operator: 'Operator',
selectOperator: 'Select operator',
noOperator: 'No operators',
remark: 'Remark',
remarkPlaceholder: 'Enter replacement reason or remark',
confirmReplace: 'Confirm',
selectMoldGroupError: 'Please select a mold group',
selectSubMoldError: 'Please select a sub-mold',
selectedCount: '{count} sub-mold(s) selected',
selectReplaceTimeError: 'Please select replacement time',
submitSuccess: 'Saved successfully',
submitFailed: 'Save failed',
historyMoldIdPlaceholder: 'Enter sub-mold ID, blank for all',
searchBrandName: 'Mold Group Name',
selectDate: 'Select Date',
createTime: 'Create Time',
noHistory: 'No pressure net history'
},
moldRepair: {
moduleName: 'Mold Repair',
createTitle: 'Create Mold Repair',

@ -130,7 +130,9 @@ const literalMap = {
'新增点检': 'moldCheck.addTitle',
'模具保养': 'moldMaintain.moduleName',
'保养详情': 'moldMaintain.detailTitle',
'新增保养': 'moldMaintain.addTitle'
'新增保养': 'moldMaintain.addTitle',
'更换压网': 'moldPressureNet.moduleName',
'压网历史': 'moldPressureNet.historyTitle'
}
export function getCurrentLocale() {

@ -1124,6 +1124,9 @@ export default {
selectTemplateError: '请选择点检模板',
noItems: '请先选择点检模板',
noTemplate: '暂无模板',
resultListTitle: '检验结果',
editResult: '编辑',
cancelEdit: '取消编辑',
submit: '提交点检',
submitSuccess: '点检提交成功',
submitFailed: '点检提交失败',
@ -1184,11 +1187,57 @@ export default {
selectTemplateError: '请选择保养模板',
noItems: '请先选择保养模板',
noTemplate: '暂无模板',
resultListTitle: '检验结果',
editResult: '编辑',
cancelEdit: '取消编辑',
submit: '提交保养',
submitSuccess: '保养提交成功',
submitFailed: '保养提交失败',
reInspect: '再次保养'
},
moldPressureNet: {
moduleName: '更换压网',
recordTitle: '压网更换记录',
recordDesc: '请选择模具组和子模,确认更换时间',
history: '历史数据',
historyTitle: '压网历史',
moldGroup: '模具组',
selectMoldGroup: '选择模具组',
searchMoldGroup: '请输入模具组名称',
noMoldGroup: '暂无模具组',
moldGroupName: '模具组名称',
moldGroupCode: '模具组编号',
product: '产品',
subMold: '子模',
selectSubMold: '选择子模',
reSelectMoldGroup: '重新选择模具组',
reSelectSubMold: '重新选择子模',
subMoldCode: '子模编码',
subMoldName: '子模名称',
searchSubMold: '搜索子模名称/编号',
noSubMold: '暂无子模',
replaceInfo: '更换信息',
replaceTime: '更换时间',
pressureNetTime: '压网时间',
selectReplaceTime: '请选择更换时间',
operator: '操作人',
selectOperator: '请选择操作人',
noOperator: '暂无操作人',
remark: '备注',
remarkPlaceholder: '请输入更换原因或备注',
confirmReplace: '确认更换',
selectMoldGroupError: '请选择模具组',
selectSubMoldError: '请选择子模',
selectedCount: '已选{count}个子模',
selectReplaceTimeError: '请选择更换时间',
submitSuccess: '压网更换保存成功',
submitFailed: '压网更换保存失败',
historyMoldIdPlaceholder: '输入子模ID筛选不填查全部',
searchBrandName: '模具组名称',
selectDate: '选择日期',
createTime: '创建时间',
noHistory: '暂无压网历史记录'
},
moldRepair: {
moduleName: '模具维修',
createTitle: '新增模具维修',

@ -742,6 +742,20 @@
"navigationBarTitleText": "新增保养",
"navigationStyle": "custom"
}
},
{
"path": "moldPressureNet/index",
"style": {
"navigationBarTitleText": "更换压网",
"navigationStyle": "custom"
}
},
{
"path": "moldPressureNet/history",
"style": {
"navigationBarTitleText": "压网历史",
"navigationStyle": "custom"
}
}
]
}

@ -1,10 +1,10 @@
<template>
<template>
<view class="page-container">
<NavBar :title="t('moldCheck.addTitle')" />
<scroll-view scroll-y class="detail-scroll">
<view class="content-section">
<!-- 模具信息 -->
<!-- 叿淇 -->
<view class="section-card">
<view class="section-header">
<view class="section-icon">
@ -52,7 +52,7 @@
</view>
</view>
<!-- 任务信息 -->
<!-- 浠诲姟淇 -->
<view class="section-card">
<view class="section-header">
<view class="section-icon">
@ -77,9 +77,17 @@
</view>
</view>
<!-- 点检项列表 -->
<!-- 鐐规椤瑰垪琛?-->
<view v-if="!inspectionItems.length" class="hint">{{ t('moldCheck.noItems') }}</view>
<view v-else class="inspection-list">
<view v-else class="inspection-section">
<view class="inspection-toolbar">
<text class="inspection-toolbar-title">{{ t('moldCheck.resultListTitle') }}</text>
<view :class="['edit-toggle-btn', ticketResultEditable ? 'active' : '']" @click="toggleTicketResultEditable">
<uni-icons :type="ticketResultEditable ? 'closeempty' : 'compose'" size="18" :color="ticketResultEditable ? '#ef4444' : '#1f7cff'"></uni-icons>
<text>{{ ticketResultEditable ? t('moldCheck.cancelEdit') : t('moldCheck.editResult') }}</text>
</view>
</view>
<view class="inspection-list">
<view v-for="(item, index) in inspectionItems" :key="item.id || index" class="inspection-card">
<view class="inspection-header">
<view class="inspection-index">{{ index + 1 }}</view>
@ -103,6 +111,7 @@
:min="-999999999"
:max="999999999"
:step="1"
:disabled="!ticketResultEditable"
@change="onNumberInputChange(item, $event)"
/>
</view>
@ -114,6 +123,7 @@
class="form-textarea"
:placeholder="t('moldCheck.inputPlaceholder')"
maxlength="500"
:disabled="!ticketResultEditable"
/>
</view>
<view class="field-block">
@ -126,9 +136,9 @@
mode="aspectFill"
@click="previewImage(img, parseImages(item.images))"
/>
<view class="image-remove" @click="removeImage(item, imgIndex)">×</view>
<view v-if="ticketResultEditable" class="image-remove" @click="removeImage(item, imgIndex)">×</view>
</view>
<view v-if="parseImages(item.images).length < 3" class="image-upload" @click="chooseImages(item)">
<view v-if="ticketResultEditable && parseImages(item.images).length < 3" class="image-upload" @click="chooseImages(item)">
<text class="image-upload-icon">+</text>
</view>
</view>
@ -140,19 +150,20 @@
class="form-textarea"
:placeholder="t('moldCheck.inputPlaceholder')"
maxlength="500"
:disabled="!ticketResultEditable"
/>
</view>
<view class="field-block">
<text class="field-label block-label">{{ t('moldCheck.resultText') }}</text>
<view class="result-option-group">
<view
:class="['result-option', resultOptionClass(item, '1')]"
:class="['result-option', resultOptionClass(item, '1'), ticketResultEditable ? '' : 'result-option-disabled']"
@click="setDecision(item, '1')"
>
{{ t('moldCheck.inspectionResultPass') }}
</view>
<view
:class="['result-option', resultOptionClass(item, '2')]"
:class="['result-option', resultOptionClass(item, '2'), ticketResultEditable ? '' : 'result-option-disabled']"
@click="setDecision(item, '2')"
>
{{ t('moldCheck.inspectionResultFail') }}
@ -164,6 +175,7 @@
</view>
</view>
</view>
</view>
</scroll-view>
<view class="action-bar">
@ -171,7 +183,7 @@
<view :class="['action-btn', 'submit-btn', submitLoading ? 'action-btn-disabled' : '']" @click="handleSubmit">{{ t('moldCheck.submit') }}</view>
</view>
<!-- 模具选择弹窗 -->
<!-- 叿閫夋嫨寮圭獥 -->
<view v-if="showMoldPicker" class="picker-mask" @click="closeMoldPicker">
<view class="picker-popup" @click.stop>
<view class="picker-header">
@ -203,7 +215,7 @@
</view>
</view>
<!-- 模板选择弹窗 -->
<!-- 澘閫夋嫨寮圭獥 -->
<view v-if="showTemplatePicker" class="picker-mask" @click="closeTemplatePicker">
<view class="picker-popup" @click.stop>
<view class="picker-header">
@ -247,7 +259,7 @@ import { DICT_TYPE, getDictLabel, initAllDict } from '@/utils/dict'
const { t } = useI18n()
//
// Mold info, loaded from route params or picker.
const moldInfo = reactive({
id: '',
code: '',
@ -268,10 +280,12 @@ const templateFinished = ref(false)
const templatePageNo = ref(1)
const templatePageSize = ref(20)
const inspectionItems = ref([])
const ticketResultEditable = ref(false)
const ticketResultBackup = ref(null)
const submitLoading = ref(false)
const statusClass = ref('')
//
// Mold picker.
const showMoldPicker = ref(false)
const moldList = ref([])
const moldLoading = ref(false)
@ -284,12 +298,12 @@ onLoad(async (query) => {
const moldId = String(query?.id || '')
if (moldId) {
// ID
// āD
const urlCode = query?.code ? decodeURIComponent(query.code) : ''
const urlName = query?.name ? decodeURIComponent(query.name) : ''
if (urlCode || urlName) {
//
//
moldInfo.id = moldId
moldInfo.code = urlCode
moldInfo.name = urlName
@ -300,11 +314,10 @@ onLoad(async (query) => {
moldInfo.statusLabel = getDictLabel(DICT_TYPE.ERP_MOLD_STATUS, moldInfo.status, moldInfo.status)
updateStatusClass()
} else {
// ID
await loadMoldInfo(moldId)
}
}
// ID
// ID
})
async function loadMoldInfo(id) {
@ -327,18 +340,17 @@ async function loadMoldInfo(id) {
function updateStatusClass() {
const status = String(moldInfo.status)
if (status === '1' || moldInfo.statusLabel.includes('正常') || moldInfo.statusLabel.includes('使用')) {
if (status === '1') {
statusClass.value = 'status-success'
} else if (status === '2' || moldInfo.statusLabel.includes('维修') || moldInfo.statusLabel.includes('警')) {
} else if (status === '2') {
statusClass.value = 'status-warning'
} else if (status === '3' || moldInfo.statusLabel.includes('报废') || moldInfo.statusLabel.includes('停')) {
} else if (status === '3') {
statusClass.value = 'status-danger'
} else {
statusClass.value = 'status-default'
}
}
//
function openMoldPicker() {
showMoldPicker.value = true
if (!moldList.value.length) {
@ -390,7 +402,6 @@ function selectMold(mold) {
closeMoldPicker()
}
//
function openTemplatePicker() {
showTemplatePicker.value = true
if (!templateList.value.length) {
@ -434,6 +445,8 @@ async function loadMoreTemplates() {
function selectTemplate(template) {
selectedTemplate.value = template
ticketResultEditable.value = false
ticketResultBackup.value = null
loadInspectionItems(template.id)
closeTemplatePicker()
}
@ -476,7 +489,7 @@ function shouldShowInput(item) {
function isNumericValueType(value) {
const normalized = String(value ?? '')
const label = String(valueTypeText(value) || '').toLowerCase()
return normalized === '2' || /数值|数字|number|numeric|digit|decimal/.test(label)
return normalized === '2' || /鏁板€紎鏁板瓧|number|numeric|digit|decimal/.test(label)
}
function numberInputValue(item) {
@ -485,6 +498,7 @@ function numberInputValue(item) {
}
function onNumberInputChange(item, value) {
if (!ticketResultEditable.value) return
item.textInput = String(value)
}
@ -513,10 +527,12 @@ function resultOptionClass(item, value) {
}
function setDecision(item, value) {
if (!ticketResultEditable.value) return
item.inspectionResult = String(value)
}
async function chooseImages(item) {
if (!ticketResultEditable.value) return
try {
const currentCount = parseImages(item.images).length
const remain = Math.max(0, 3 - currentCount)
@ -546,6 +562,7 @@ async function chooseImages(item) {
}
function removeImage(item, index) {
if (!ticketResultEditable.value) return
const next = parseImages(item.images)
next.splice(index, 1)
item.images = next.join(',')
@ -572,6 +589,19 @@ function goBack() {
uni.navigateBack()
}
function toggleTicketResultEditable() {
if (ticketResultEditable.value) {
if (ticketResultBackup.value) {
inspectionItems.value = JSON.parse(JSON.stringify(ticketResultBackup.value))
ticketResultBackup.value = null
}
ticketResultEditable.value = false
return
}
ticketResultBackup.value = JSON.parse(JSON.stringify(inspectionItems.value))
ticketResultEditable.value = true
}
async function handleSubmit() {
if (submitLoading.value) return
@ -591,7 +621,7 @@ async function handleSubmit() {
}
const hasUnselected = inspectionItems.value.some((item) => String(item?.inspectionResult || '0') === '0')
if (inspectionItems.value.length && hasUnselected) {
if (ticketResultEditable.value && inspectionItems.value.length && hasUnselected) {
uni.showToast({ title: t('moldCheck.selectAllDecisionError'), icon: 'none' })
return
}
@ -665,6 +695,11 @@ async function handleSubmit() {
.template-select { display: flex; align-items: center; justify-content: space-between; height: 88rpx; padding: 0 24rpx; background: #f8fafc; border-radius: 14rpx; }
.template-text { font-size: 28rpx; color: #374151; }
.template-text.placeholder { color: #9ca3af; }
.inspection-section { display: flex; flex-direction: column; gap: 16rpx; }
.inspection-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 16rpx; padding: 0 4rpx; }
.inspection-toolbar-title { font-size: 30rpx; font-weight: 700; color: #1f2937; }
.edit-toggle-btn { height: 60rpx; padding: 0 18rpx; border-radius: 999rpx; border: 1rpx solid #bfdbfe; background: #eff6ff; color: #1f7cff; font-size: 24rpx; font-weight: 600; display: flex; align-items: center; gap: 8rpx; flex-shrink: 0; }
.edit-toggle-btn.active { border-color: #fecaca; background: #fef2f2; color: #ef4444; }
.inspection-list { display: flex; flex-direction: column; gap: 20rpx; }
.inspection-card { background: #ffffff; border-radius: 24rpx; padding: 24rpx; box-shadow: 0 6rpx 18rpx rgba(15, 23, 42, 0.05); }
.inspection-header { display: flex; align-items: center; gap: 16rpx; }
@ -681,6 +716,7 @@ async function handleSubmit() {
.result-option { min-width: 160rpx; height: 72rpx; padding: 0 24rpx; border-radius: 14rpx; border: 1rpx solid #d1d5db; color: #6b7280; font-size: 30rpx; display: flex; align-items: center; justify-content: center; background: #ffffff; }
.result-option-active { border-color: #60a5fa; background: #eff6ff; color: #1f7cff; }
.result-option-danger { border-color: #fca5a5; background: #fef2f2; color: #ef4444; }
.result-option-disabled { opacity: 0.55; }
.pending-tag { padding: 8rpx 14rpx; border-radius: 10rpx; background: #f3f4f6; color: #9ca3af; font-size: 24rpx; }
.form-textarea { width: 100%; min-height: 76rpx; max-height: 108rpx; background: #f8fafc; border-radius: 12rpx; padding: 10rpx 16rpx; font-size: 26rpx; color: #374151; box-sizing: border-box; }
.number-box-wrap { width: 100%; }

@ -79,7 +79,15 @@
<!-- 鐐规椤瑰垪琛?-->
<view v-if="!inspectionItems.length" class="hint">{{ t('moldMaintain.noItems') }}</view>
<view v-else class="inspection-list">
<view v-else class="inspection-section">
<view class="inspection-toolbar">
<text class="inspection-toolbar-title">{{ t('moldMaintain.resultListTitle') }}</text>
<view :class="['edit-toggle-btn', ticketResultEditable ? 'active' : '']" @click="toggleTicketResultEditable">
<uni-icons :type="ticketResultEditable ? 'closeempty' : 'compose'" size="18" :color="ticketResultEditable ? '#ef4444' : '#1f7cff'"></uni-icons>
<text>{{ ticketResultEditable ? t('moldMaintain.cancelEdit') : t('moldMaintain.editResult') }}</text>
</view>
</view>
<view class="inspection-list">
<view v-for="(item, index) in inspectionItems" :key="item.id || index" class="inspection-card">
<view class="inspection-header">
<view class="inspection-index">{{ index + 1 }}</view>
@ -103,6 +111,7 @@
:min="-999999999"
:max="999999999"
:step="1"
:disabled="!ticketResultEditable"
@change="onNumberInputChange(item, $event)"
/>
</view>
@ -114,6 +123,7 @@
class="form-textarea"
:placeholder="t('moldMaintain.inputPlaceholder')"
maxlength="500"
:disabled="!ticketResultEditable"
/>
</view>
<view class="field-block">
@ -126,9 +136,9 @@
mode="aspectFill"
@click="previewImage(img, parseImages(item.images))"
/>
<view class="image-remove" @click="removeImage(item, imgIndex)"></view>
<view v-if="ticketResultEditable" class="image-remove" @click="removeImage(item, imgIndex)">×</view>
</view>
<view v-if="parseImages(item.images).length < 3" class="image-upload" @click="chooseImages(item)">
<view v-if="ticketResultEditable && parseImages(item.images).length < 3" class="image-upload" @click="chooseImages(item)">
<text class="image-upload-icon">+</text>
</view>
</view>
@ -140,19 +150,20 @@
class="form-textarea"
:placeholder="t('moldMaintain.inputPlaceholder')"
maxlength="500"
:disabled="!ticketResultEditable"
/>
</view>
<view class="field-block">
<text class="field-label block-label">{{ t('moldMaintain.resultText') }}</text>
<view class="result-option-group">
<view
:class="['result-option', resultOptionClass(item, '1')]"
:class="['result-option', resultOptionClass(item, '1'), ticketResultEditable ? '' : 'result-option-disabled']"
@click="setDecision(item, '1')"
>
{{ t('moldMaintain.inspectionResultPass') }}
</view>
<view
:class="['result-option', resultOptionClass(item, '2')]"
:class="['result-option', resultOptionClass(item, '2'), ticketResultEditable ? '' : 'result-option-disabled']"
@click="setDecision(item, '2')"
>
{{ t('moldMaintain.inspectionResultFail') }}
@ -164,6 +175,7 @@
</view>
</view>
</view>
</view>
</scroll-view>
<view class="action-bar">
@ -268,6 +280,8 @@ const templateFinished = ref(false)
const templatePageNo = ref(1)
const templatePageSize = ref(20)
const inspectionItems = ref([])
const ticketResultEditable = ref(false)
const ticketResultBackup = ref(null)
const submitLoading = ref(false)
const statusClass = ref('')
@ -431,6 +445,8 @@ async function loadMoreTemplates() {
function selectTemplate(template) {
selectedTemplate.value = template
ticketResultEditable.value = false
ticketResultBackup.value = null
loadInspectionItems(template.id)
closeTemplatePicker()
}
@ -482,6 +498,7 @@ function numberInputValue(item) {
}
function onNumberInputChange(item, value) {
if (!ticketResultEditable.value) return
item.textInput = String(value)
}
@ -510,10 +527,12 @@ function resultOptionClass(item, value) {
}
function setDecision(item, value) {
if (!ticketResultEditable.value) return
item.inspectionResult = String(value)
}
async function chooseImages(item) {
if (!ticketResultEditable.value) return
try {
const currentCount = parseImages(item.images).length
const remain = Math.max(0, 3 - currentCount)
@ -543,6 +562,7 @@ async function chooseImages(item) {
}
function removeImage(item, index) {
if (!ticketResultEditable.value) return
const next = parseImages(item.images)
next.splice(index, 1)
item.images = next.join(',')
@ -569,6 +589,19 @@ function goBack() {
uni.navigateBack()
}
function toggleTicketResultEditable() {
if (ticketResultEditable.value) {
if (ticketResultBackup.value) {
inspectionItems.value = JSON.parse(JSON.stringify(ticketResultBackup.value))
ticketResultBackup.value = null
}
ticketResultEditable.value = false
return
}
ticketResultBackup.value = JSON.parse(JSON.stringify(inspectionItems.value))
ticketResultEditable.value = true
}
async function handleSubmit() {
if (submitLoading.value) return
@ -588,7 +621,7 @@ async function handleSubmit() {
}
const hasUnselected = inspectionItems.value.some((item) => String(item?.inspectionResult || '0') === '0')
if (inspectionItems.value.length && hasUnselected) {
if (ticketResultEditable.value && inspectionItems.value.length && hasUnselected) {
uni.showToast({ title: t('moldMaintain.selectAllDecisionError'), icon: 'none' })
return
}
@ -662,6 +695,11 @@ async function handleSubmit() {
.template-select { display: flex; align-items: center; justify-content: space-between; height: 88rpx; padding: 0 24rpx; background: #f8fafc; border-radius: 14rpx; }
.template-text { font-size: 28rpx; color: #374151; }
.template-text.placeholder { color: #9ca3af; }
.inspection-section { display: flex; flex-direction: column; gap: 16rpx; }
.inspection-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 16rpx; padding: 0 4rpx; }
.inspection-toolbar-title { font-size: 30rpx; font-weight: 700; color: #1f2937; }
.edit-toggle-btn { height: 60rpx; padding: 0 18rpx; border-radius: 999rpx; border: 1rpx solid #bfdbfe; background: #eff6ff; color: #1f7cff; font-size: 24rpx; font-weight: 600; display: flex; align-items: center; gap: 8rpx; flex-shrink: 0; }
.edit-toggle-btn.active { border-color: #fecaca; background: #fef2f2; color: #ef4444; }
.inspection-list { display: flex; flex-direction: column; gap: 20rpx; }
.inspection-card { background: #ffffff; border-radius: 24rpx; padding: 24rpx; box-shadow: 0 6rpx 18rpx rgba(15, 23, 42, 0.05); }
.inspection-header { display: flex; align-items: center; gap: 16rpx; }
@ -678,6 +716,7 @@ async function handleSubmit() {
.result-option { min-width: 160rpx; height: 72rpx; padding: 0 24rpx; border-radius: 14rpx; border: 1rpx solid #d1d5db; color: #6b7280; font-size: 30rpx; display: flex; align-items: center; justify-content: center; background: #ffffff; }
.result-option-active { border-color: #60a5fa; background: #eff6ff; color: #1f7cff; }
.result-option-danger { border-color: #fca5a5; background: #fef2f2; color: #ef4444; }
.result-option-disabled { opacity: 0.55; }
.pending-tag { padding: 8rpx 14rpx; border-radius: 10rpx; background: #f3f4f6; color: #9ca3af; font-size: 24rpx; }
.form-textarea { width: 100%; min-height: 76rpx; max-height: 108rpx; background: #f8fafc; border-radius: 12rpx; padding: 10rpx 16rpx; font-size: 26rpx; color: #374151; box-sizing: border-box; }
.number-box-wrap { width: 100%; }

@ -0,0 +1,233 @@
<template>
<view class="page-container">
<NavBar :title="t('moldPressureNet.historyTitle')" />
<view class="filter-bar">
<view class="keyword-box">
<input
v-model="brandNameKeyword"
class="keyword-input"
:placeholder="t('moldPressureNet.searchBrandName')"
confirm-type="search"
@confirm="fetchList(true)"
/>
</view>
<view class="keyword-box time-box" @click="openTimePicker">
<text :class="pressureNetTimeFilter ? 'time-text' : 'time-placeholder'">{{ pressureNetTimeFilter || t('moldPressureNet.pressureNetTime') }}</text>
<uni-icons type="calendar" size="18" color="#9ca3af"></uni-icons>
</view>
<view class="filter-btn" @click="fetchList(true)">{{ t('functionCommon.search') }}</view>
<view class="filter-btn" @click="resetFilter">{{ t('functionCommon.reset') }}</view>
</view>
<scroll-view scroll-y class="list-scroll" @scrolltolower="loadMore" :lower-threshold="80">
<view class="list-wrap">
<view v-for="item in list" :key="item.id" class="record-card">
<view class="record-header">
<text class="record-title">{{ item.moldName || '-' }}</text>
<text class="record-time">{{ formatDateTime(item.pressureNetTime) }}</text>
</view>
<view class="record-body">
<view class="record-row">
<text class="record-label">{{ t('moldPressureNet.moldGroup') }}</text>
<text class="record-value">{{ item.moldBrandName || '-' }}</text>
</view>
<view class="record-row">
<text class="record-label">{{ t('moldPressureNet.createTime') }}</text>
<text class="record-value">{{ formatDateTime(item.createTime) }}</text>
</view>
<view class="record-row">
<text class="record-label">{{ t('moldPressureNet.remark') }}</text>
<text class="record-value">{{ item.remark || '-' }}</text>
</view>
</view>
</view>
<view v-if="loading && pageNo === 1" class="hint">{{ t('functionCommon.loading') }}</view>
<view v-else-if="!list.length" class="hint">{{ t('moldPressureNet.noHistory') }}</view>
<view v-else-if="loadingMore" class="hint">{{ t('functionCommon.loadingMore') }}</view>
<view v-else-if="finished" class="hint">{{ t('functionCommon.noMoreData') }}</view>
</view>
</scroll-view>
<!-- 压网时间筛选弹窗 -->
<view v-if="showTimePicker" class="picker-mask" @click="closeTimePicker">
<view class="picker-popup" @click.stop>
<view class="picker-header">
<text class="picker-title">{{ t('moldPressureNet.pressureNetTime') }}</text>
<view class="picker-close" @click="closeTimePicker">
<uni-icons type="close" size="24" color="#6b7280"></uni-icons>
</view>
</view>
<view class="picker-body">
<picker mode="date" :value="tempDate" @change="onDateChange">
<view class="time-field">{{ tempDate || $t('moldPressureNet.selectDate') }}</view>
</picker>
</view>
<view class="picker-footer-bar">
<view class="picker-footer-btn secondary" @click="clearTimeFilter">{{ $t('functionCommon.clear') }}</view>
<view class="picker-footer-btn primary" @click="confirmTimeFilter">{{ $t('common.complete') }}</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue'
import { onLoad, onReachBottom } from '@dcloudio/uni-app'
import { useI18n } from 'vue-i18n'
import NavBar from '@/components/common/NavBar.vue'
import { getPressureNetRecordPage } from '@/api/mes/mold'
const { t } = useI18n()
const list = ref([])
const loading = ref(false)
const loadingMore = ref(false)
const finished = ref(false)
const pageNo = ref(1)
const pageSize = ref(10)
const brandNameKeyword = ref('')
const pressureNetTimeFilter = ref('')
//
const showTimePicker = ref(false)
const tempDate = ref('')
onLoad(() => {
fetchList(true)
})
onReachBottom(() => {
loadMore()
})
function normalizePageData(res) {
const root = res && res.data !== undefined ? res.data : res
const rows = root?.list || root?.rows || root?.records || root?.data?.list || root?.data?.rows || []
const total = root?.total ?? root?.data?.total ?? (Array.isArray(rows) ? rows.length : 0)
return {
list: Array.isArray(rows) ? rows : [],
total: Number(total || 0)
}
}
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
}
const brandName = brandNameKeyword.value.trim()
if (brandName) params.moldBrandName = brandName
const timeVal = pressureNetTimeFilter.value.trim()
if (timeVal) params.pressureNetTime = timeVal
const res = await getPressureNetRecordPage(params)
const page = normalizePageData(res)
list.value = reset ? page.list : [...list.value, ...page.list]
finished.value = list.value.length >= page.total || page.list.length < pageSize.value
} catch (error) {
if (!reset) pageNo.value = Math.max(1, pageNo.value - 1)
uni.showToast({ title: t('functionCommon.loadFailed'), icon: 'none' })
} finally {
loading.value = false
loadingMore.value = false
}
}
async function loadMore() {
if (loading.value || loadingMore.value || finished.value) return
pageNo.value += 1
await fetchList(false)
}
function resetFilter() {
brandNameKeyword.value = ''
pressureNetTimeFilter.value = ''
fetchList(true)
}
//
function openTimePicker() {
tempDate.value = pressureNetTimeFilter.value || ''
showTimePicker.value = true
}
function closeTimePicker() {
showTimePicker.value = false
}
function onDateChange(event) {
tempDate.value = event?.detail?.value || ''
}
function confirmTimeFilter() {
pressureNetTimeFilter.value = tempDate.value
closeTimePicker()
fetchList(true)
}
function clearTimeFilter() {
pressureNetTimeFilter.value = ''
tempDate.value = ''
closeTimePicker()
fetchList(true)
}
function formatDateTime(value) {
if (!value) return '-'
if (Array.isArray(value) && value.length >= 3) {
const [year, month, day, hour = 0, minute = 0, second = 0] = value
const pad = (num) => String(num).padStart(2, '0')
return `${year}-${pad(month)}-${pad(day)} ${pad(hour)}:${pad(minute)}:${pad(second)}`
}
const date = new Date(value)
if (Number.isNaN(date.getTime())) return String(value)
const pad = (num) => String(num).padStart(2, '0')
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`
}
</script>
<style lang="scss" scoped>
.page-container { min-height: 100vh; background: #f5f7fb; }
.filter-bar { display: flex; align-items: center; gap: 12rpx; padding: 18rpx 24rpx 0; flex-wrap: wrap; }
.keyword-box, .filter-btn { height: 66rpx; background: #ffffff; border: 1rpx solid #d9dde5; box-sizing: border-box; display: flex; align-items: center; }
.keyword-box { flex: 1; min-width: 200rpx; padding: 0 20rpx; }
.keyword-input { width: 100%; font-size: 24rpx; color: #374151; }
.time-box { justify-content: space-between; min-width: 220rpx; }
.time-text { font-size: 24rpx; color: #374151; }
.time-placeholder { font-size: 24rpx; color: #9ca3af; }
.filter-btn { flex-shrink: 0; justify-content: center; min-width: 104rpx; padding: 0 18rpx; font-size: 24rpx; color: #4b5563; }
.list-scroll { height: calc(100vh - 170rpx); }
.list-wrap { padding: 4rpx 24rpx 40rpx; }
.record-card { margin-top: 20rpx; padding: 24rpx; border-radius: 16rpx; background: #ffffff; box-shadow: 0 8rpx 24rpx rgba(15, 23, 42, 0.06); }
.record-header { display: flex; align-items: center; justify-content: space-between; gap: 16rpx; padding-bottom: 18rpx; border-bottom: 1rpx solid #eef2f7; }
.record-title { font-size: 32rpx; font-weight: 700; color: #111827; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.record-time { flex-shrink: 0; font-size: 24rpx; color: #1f7cff; }
.record-body { margin-top: 16rpx; display: flex; flex-direction: column; gap: 12rpx; }
.record-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 20rpx; }
.record-label { width: 150rpx; flex-shrink: 0; font-size: 26rpx; color: #8a94a6; }
.record-value { flex: 1; text-align: right; font-size: 27rpx; color: #374151; line-height: 1.5; word-break: break-all; }
.hint { padding: 40rpx 0; text-align: center; color: #9ca3af; font-size: 26rpx; }
.picker-mask { position: fixed; left: 0; right: 0; top: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); z-index: 100; display: flex; align-items: flex-end; }
.picker-popup { width: 100%; background: #ffffff; border-radius: 24rpx 24rpx 0 0; padding-bottom: env(safe-area-inset-bottom); }
.picker-header { display: flex; align-items: center; justify-content: space-between; padding: 28rpx 32rpx; border-bottom: 1rpx solid #e5e7eb; }
.picker-title { font-size: 34rpx; font-weight: 600; color: #1f2937; }
.picker-close { width: 48rpx; height: 48rpx; display: flex; align-items: center; justify-content: center; }
.picker-body { padding: 32rpx; }
.time-field { height: 80rpx; display: flex; align-items: center; justify-content: center; background: #f8fafc; border: 1rpx solid #e5e7eb; border-radius: 12rpx; font-size: 28rpx; color: #374151; }
.picker-footer-bar { display: flex; gap: 16rpx; padding: 16rpx 32rpx 24rpx; }
.picker-footer-btn { flex: 1; height: 80rpx; border-radius: 12rpx; display: flex; align-items: center; justify-content: center; font-size: 28rpx; font-weight: 600; }
.picker-footer-btn.secondary { background: #f3f4f6; color: #6b7280; }
.picker-footer-btn.primary { background: linear-gradient(135deg, #1f7cff, #3b82f6); color: #ffffff; }
</style>

@ -0,0 +1,432 @@
<template>
<view class="page-container">
<NavBar :title="t('moldPressureNet.moduleName')">
<template #right>
<view class="nav-right-btn" @click="goHistory">
<uni-icons type="clock" size="22" color="#1f7cff"></uni-icons>
<text class="nav-right-text">{{ t('moldPressureNet.history') }}</text>
</view>
</template>
</NavBar>
<scroll-view scroll-y class="detail-scroll">
<view class="content-section">
<!-- 模具组信息 -->
<view class="section-card">
<view class="section-header">
<view class="section-icon">
<uni-icons type="info" size="24" color="#1f7cff"></uni-icons>
</view>
<text class="section-title">{{ t('moldPressureNet.moldGroup') }}</text>
</view>
<view class="form-field">
<text class="form-label">{{ t('moldPressureNet.selectMoldGroup') }}<text class="required-star">*</text></text>
<view :class="['mold-select-area', selectedBrand.id ? 'selected' : '']" @click="openBrandPicker">
<view class="mold-select-content">
<text :class="selectedBrand.id ? 'mold-select-value' : 'mold-select-placeholder'">{{ selectedBrand.id ? (selectedBrand.name || selectedBrand.code || '-') : t('moldPressureNet.selectMoldGroup') }}</text>
<text v-if="selectedBrand.id" class="mold-select-subtext">{{ selectedBrand.code || t('moldPressureNet.reSelectMoldGroup') }}</text>
</view>
<uni-icons type="right" size="18" color="#9ca3af"></uni-icons>
</view>
</view>
<view v-if="selectedBrand.id" class="mold-info-panel">
<view class="mold-info-grid">
<view class="mold-info-item">
<text class="mold-info-label">{{ t('moldPressureNet.moldGroupCode') }}</text>
<text class="mold-info-value">{{ selectedBrand.code || '-' }}</text>
</view>
<view class="mold-info-item">
<text class="mold-info-label">{{ t('moldPressureNet.moldGroupName') }}</text>
<text class="mold-info-value">{{ selectedBrand.name || '-' }}</text>
</view>
<view class="mold-info-item">
<text class="mold-info-label">{{ t('moldPressureNet.product') }}</text>
<text class="mold-info-value">{{ selectedBrand.productName || '-' }}</text>
</view>
</view>
</view>
</view>
<!-- 子模具信息 -->
<view class="section-card">
<view class="section-header">
<view class="section-icon">
<uni-icons type="settings" size="24" color="#1f7cff"></uni-icons>
</view>
<text class="section-title">{{ t('moldPressureNet.subMold') }}</text>
</view>
<view class="form-field">
<text class="form-label">{{ t('moldPressureNet.selectSubMold') }}<text class="required-star">*</text></text>
<view :class="['mold-select-area', selectedSubMoldIds.length ? 'selected' : '']" @click="openSubMoldPicker">
<view class="mold-select-content">
<text :class="selectedSubMoldIds.length ? 'mold-select-value' : 'mold-select-placeholder'">{{ selectedSubMoldIds.length ? t('moldPressureNet.selectedCount', { count: selectedSubMoldIds.length }) : t('moldPressureNet.selectSubMold') }}</text>
<text v-if="selectedSubMoldIds.length" class="mold-select-subtext">{{ t('moldPressureNet.reSelectSubMold') }}</text>
</view>
<uni-icons type="right" size="18" color="#9ca3af"></uni-icons>
</view>
</view>
<view v-if="selectedSubMoldIds.length" class="sub-mold-tags">
<view v-for="item in selectedSubMoldItems" :key="item.id" class="sub-mold-tag">
<text class="sub-mold-tag-text">{{ item.name || item.code || '-' }}</text>
<view class="sub-mold-tag-remove" @click="removeSubMold(item.id)">×</view>
</view>
</view>
</view>
<!-- 换网信息 -->
<view class="section-card">
<view class="section-header">
<view class="section-icon">
<uni-icons type="compose" size="24" color="#1f7cff"></uni-icons>
</view>
<text class="section-title">{{ t('moldPressureNet.replaceInfo') }}</text>
</view>
<view class="form-field">
<text class="form-label">{{ t('moldPressureNet.pressureNetTime') }}<text class="required-star">*</text></text>
<uni-datetime-picker v-model="pressureNetTime" type="datetime" :clear-icon="false" />
</view>
<view class="form-field">
<text class="form-label">{{ t('moldPressureNet.remark') }}</text>
<textarea
v-model="remark"
class="form-textarea"
:placeholder="t('moldPressureNet.remarkPlaceholder')"
maxlength="200"
/>
</view>
</view>
</view>
</scroll-view>
<view class="action-bar">
<view class="action-btn back-btn" @click="goBack">{{ t('functionCommon.cancel') }}</view>
<view :class="['action-btn', 'submit-btn', submitLoading ? 'action-btn-disabled' : '']" @click="handleSubmit">
{{ t('moldPressureNet.confirmReplace') }}
</view>
</view>
<!-- 模具组选择弹窗 -->
<view v-if="showBrandPicker" class="picker-mask" @click="closeBrandPicker">
<view class="picker-popup" @click.stop>
<view class="picker-header">
<text class="picker-title">{{ t('moldPressureNet.selectMoldGroup') }}</text>
<view class="picker-close" @click="closeBrandPicker">
<uni-icons type="close" size="24" color="#6b7280"></uni-icons>
</view>
</view>
<view class="picker-search">
<input v-model="brandKeyword" class="picker-search-input" :placeholder="t('moldPressureNet.searchMoldGroup')" confirm-type="search" @confirm="loadBrandList" />
</view>
<scroll-view scroll-y class="picker-content">
<view
v-for="item in brandList"
:key="item.id"
:class="['picker-item', selectedBrand.id === String(item.id) ? 'picker-item-active' : '']"
@click="selectBrand(item)"
>
<view class="picker-item-main">
<text class="picker-item-name">{{ item.name || item.code || '-' }}</text>
<text class="picker-item-code">{{ item.code || '' }}</text>
</view>
<text v-if="item.productName" class="picker-item-desc">{{ item.productName }}</text>
</view>
<view v-if="brandLoading" class="picker-loading">{{ t('functionCommon.loading') }}</view>
<view v-else-if="!brandList.length" class="picker-empty">{{ t('moldPressureNet.noMoldGroup') }}</view>
</scroll-view>
</view>
</view>
<!-- 子模具选择弹窗多选 -->
<view v-if="showSubMoldPicker" class="picker-mask" @click="closeSubMoldPicker">
<view class="picker-popup" @click.stop>
<view class="picker-header">
<text class="picker-title">{{ t('moldPressureNet.selectSubMold') }}</text>
<view class="picker-close" @click="closeSubMoldPicker">
<uni-icons type="close" size="24" color="#6b7280"></uni-icons>
</view>
</view>
<scroll-view scroll-y class="picker-content">
<view
v-for="item in subMoldList"
:key="item.id"
:class="['picker-item', isSubMoldSelected(item.id) ? 'picker-item-active' : '']"
@click="toggleSubMold(item)"
>
<view class="picker-item-main">
<view class="picker-checkbox">
<view :class="['checkbox-icon', isSubMoldSelected(item.id) ? 'checked' : '']">
<text v-if="isSubMoldSelected(item.id)" class="checkbox-tick"></text>
</view>
</view>
<view class="picker-item-info">
<text class="picker-item-name">{{ item.name || item.code || '-' }}</text>
<text class="picker-item-code">{{ item.code || '' }}</text>
</view>
</view>
</view>
<view v-if="subMoldLoading" class="picker-loading">{{ t('functionCommon.loading') }}</view>
<view v-else-if="!subMoldList.length" class="picker-empty">{{ t('moldPressureNet.noSubMold') }}</view>
</scroll-view>
<view class="picker-footer">
<text class="picker-footer-count">{{ t('moldPressureNet.selectedCount', { count: tempSelectedIds.length }) }}</text>
<view class="picker-footer-btn" @click="confirmSubMoldSelection">{{ t('common.complete') }}</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { computed, reactive, ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { useI18n } from 'vue-i18n'
import NavBar from '@/components/common/NavBar.vue'
import { createPressureNetRecord, getBrandList, getMoldList } from '@/api/mes/mold'
const { t } = useI18n()
const selectedBrand = reactive({ id: '', name: '', code: '', productName: '' })
const selectedSubMoldIds = ref([])
const pressureNetTime = ref('')
const remark = ref('')
const submitLoading = ref(false)
//
const brandList = ref([])
const brandLoading = ref(false)
const brandKeyword = ref('')
const showBrandPicker = ref(false)
//
const subMoldList = ref([])
const subMoldLoading = ref(false)
const showSubMoldPicker = ref(false)
const tempSelectedIds = ref([])
//
const selectedSubMoldItems = computed(() => {
return subMoldList.value.filter((item) => selectedSubMoldIds.value.includes(String(item.id)))
})
onLoad(async (query) => {
const brandId = String(query?.brandId || query?.moldBrandId || '')
if (brandId) {
selectedBrand.id = brandId
selectedBrand.name = query?.brandName ? decodeURIComponent(query.brandName) : ''
selectedBrand.code = query?.brandCode ? decodeURIComponent(query.brandCode) : ''
selectedBrand.productName = query?.productName ? decodeURIComponent(query.productName) : ''
await loadSubMolds()
}
})
//
function openBrandPicker() {
showBrandPicker.value = true
if (!brandList.value.length) loadBrandList()
}
function closeBrandPicker() {
showBrandPicker.value = false
}
async function loadBrandList() {
brandLoading.value = true
try {
const params = {}
const keyword = brandKeyword.value.trim()
if (keyword) params.name = keyword
const res = await getBrandList(params)
const root = res && res.data !== undefined ? res.data : res
brandList.value = Array.isArray(root) ? root : (root?.list || root?.rows || root?.records || root?.data?.list || [])
} catch (error) {
uni.showToast({ title: t('functionCommon.loadFailed'), icon: 'none' })
} finally {
brandLoading.value = false
}
}
async function selectBrand(item) {
selectedBrand.id = String(item.id || '')
selectedBrand.name = String(item.name || '')
selectedBrand.code = String(item.code || '')
selectedBrand.productName = String(item.productName || '')
selectedSubMoldIds.value = []
closeBrandPicker()
await loadSubMolds()
}
//
function openSubMoldPicker() {
if (!selectedBrand.id) {
uni.showToast({ title: t('moldPressureNet.selectMoldGroupError'), icon: 'none' })
return
}
tempSelectedIds.value = [...selectedSubMoldIds.value]
showSubMoldPicker.value = true
if (!subMoldList.value.length) loadSubMolds()
}
function closeSubMoldPicker() {
showSubMoldPicker.value = false
}
function isSubMoldSelected(id) {
return tempSelectedIds.value.includes(String(id))
}
function toggleSubMold(item) {
const id = String(item.id)
const index = tempSelectedIds.value.indexOf(id)
if (index > -1) {
tempSelectedIds.value.splice(index, 1)
} else {
tempSelectedIds.value.push(id)
}
}
function confirmSubMoldSelection() {
selectedSubMoldIds.value = [...tempSelectedIds.value]
closeSubMoldPicker()
}
function removeSubMold(id) {
const strId = String(id)
selectedSubMoldIds.value = selectedSubMoldIds.value.filter((item) => item !== strId)
}
async function loadSubMolds() {
if (!selectedBrand.id) return
subMoldLoading.value = true
try {
const res = await getMoldList({ brandId: selectedBrand.id })
const root = res && res.data !== undefined ? res.data : res
subMoldList.value = Array.isArray(root) ? root : (root?.list || root?.rows || root?.records || root?.data?.list || [])
} catch (error) {
uni.showToast({ title: t('functionCommon.loadFailed'), icon: 'none' })
} finally {
subMoldLoading.value = false
}
}
function goBack() {
uni.navigateBack()
}
function goHistory() {
uni.navigateTo({ url: '/pages_function/pages/moldPressureNet/history' })
}
async function handleSubmit() {
if (submitLoading.value) return
if (!selectedBrand.id) {
uni.showToast({ title: t('moldPressureNet.selectMoldGroupError'), icon: 'none' })
return
}
if (!selectedSubMoldIds.value.length) {
uni.showToast({ title: t('moldPressureNet.selectSubMoldError'), icon: 'none' })
return
}
if (!pressureNetTime.value) {
uni.showToast({ title: t('moldPressureNet.selectReplaceTimeError'), icon: 'none' })
return
}
submitLoading.value = true
try {
// web MoldMaintainView.vue:
const createReqVOList = selectedSubMoldIds.value.map((moldId) => {
const selected = subMoldList.value.find((item) => String(item.id) === moldId)
return {
moldBrandId: selectedBrand.id,
moldBrandName: selectedBrand.name,
moldId: moldId,
moldName: selected?.name || '',
pressureNetTime: pressureNetTime.value,
remark: remark.value.trim() || undefined
}
})
await createPressureNetRecord(createReqVOList)
uni.showToast({ title: t('moldPressureNet.submitSuccess'), icon: 'success' })
//
selectedBrand.id = ''
selectedBrand.name = ''
selectedBrand.code = ''
selectedBrand.productName = ''
selectedSubMoldIds.value = []
pressureNetTime.value = ''
remark.value = ''
} catch (error) {
uni.showToast({ title: t('moldPressureNet.submitFailed'), icon: 'none' })
} finally {
submitLoading.value = false
}
}
</script>
<style lang="scss" scoped>
.page-container { min-height: 100vh; background-color: #f5f7fb; }
.nav-right-btn { display: flex; align-items: center; gap: 6rpx; padding: 8rpx 18rpx; background: #ffffff; border-radius: 999rpx; box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.08); }
.nav-right-text { font-size: 24rpx; color: #374151; font-weight: 500; }
.detail-scroll { height: calc(100vh - 172rpx); }
.content-section { padding: 20rpx 24rpx 28rpx; }
.section-card { background: #ffffff; border-radius: 20rpx; padding: 24rpx; margin-bottom: 20rpx; border: 1rpx solid #eef2f7; box-shadow: 0 6rpx 18rpx rgba(15, 23, 42, 0.04); }
.section-header { display: flex; align-items: center; gap: 12rpx; margin-bottom: 22rpx; padding-bottom: 18rpx; border-bottom: 1rpx solid #f1f5f9; }
.section-icon { width: 40rpx; height: 40rpx; border-radius: 10rpx; background: #eff6ff; display: flex; align-items: center; justify-content: center; }
.section-title { font-size: 32rpx; font-weight: 600; color: #1f2937; }
.form-field { display: flex; flex-direction: column; gap: 12rpx; }
.form-field + .form-field { margin-top: 24rpx; }
.form-label { font-size: 26rpx; color: #4b5563; font-weight: 500; }
.required-star { color: #ef4444; font-size: 28rpx; margin-left: 4rpx; }
.mold-select-area { display: flex; align-items: center; justify-content: space-between; min-height: 92rpx; padding: 16rpx 22rpx; background: #f8fafc; border: 1rpx solid #e5e7eb; border-radius: 14rpx; box-sizing: border-box; }
.mold-select-area.selected { background: #f9fbff; border-color: #bfdbfe; box-shadow: 0 4rpx 12rpx rgba(31, 124, 255, 0.08); }
.mold-select-content { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 6rpx; }
.mold-select-placeholder { font-size: 28rpx; color: #9ca3af; }
.mold-select-value { font-size: 28rpx; font-weight: 600; color: #1f2937; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mold-select-subtext { font-size: 24rpx; color: #6b7280; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mold-info-panel { padding: 20rpx; background: #f8fafc; border: 1rpx solid #e8eef6; border-radius: 16rpx; margin-top: 20rpx; }
.mold-info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0; background: #ffffff; border: 1rpx solid #eef2f7; border-radius: 14rpx; overflow: hidden; }
.mold-info-item { min-width: 0; display: flex; flex-direction: column; gap: 8rpx; padding: 18rpx 20rpx; border-right: 1rpx solid #f1f5f9; border-bottom: 1rpx solid #f1f5f9; }
.mold-info-item:nth-child(2n) { border-right: 0; }
.mold-info-item:nth-last-child(-n + 2) { border-bottom: 0; }
.mold-info-label { font-size: 23rpx; color: #8a94a6; }
.mold-info-value { font-size: 28rpx; color: #334155; line-height: 1.35; word-break: break-all; }
.sub-mold-tags { display: flex; flex-wrap: wrap; gap: 12rpx; margin-top: 16rpx; }
.sub-mold-tag { display: flex; align-items: center; gap: 8rpx; padding: 10rpx 18rpx; background: #eff6ff; border: 1rpx solid #bfdbfe; border-radius: 999rpx; }
.sub-mold-tag-text { font-size: 24rpx; color: #1f7cff; max-width: 200rpx; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sub-mold-tag-remove { width: 32rpx; height: 32rpx; border-radius: 50%; background: rgba(31, 124, 255, 0.15); display: flex; align-items: center; justify-content: center; font-size: 22rpx; color: #1f7cff; }
.template-select { display: flex; align-items: center; justify-content: space-between; height: 88rpx; padding: 0 24rpx; background: #f8fafc; border-radius: 14rpx; }
.template-text { font-size: 28rpx; color: #374151; }
.template-text.placeholder { color: #9ca3af; }
.form-textarea { width: 100%; min-height: 120rpx; background: #f8fafc; border-radius: 14rpx; padding: 18rpx 22rpx; font-size: 26rpx; color: #374151; box-sizing: border-box; }
.action-bar { position: fixed; left: 0; right: 0; bottom: 0; display: flex; gap: 20rpx; padding: 20rpx 24rpx calc(20rpx + env(safe-area-inset-bottom)); background: #f5f7fb; }
.action-btn { flex: 1; height: 88rpx; border-radius: 18rpx; display: flex; align-items: center; justify-content: center; font-size: 30rpx; font-weight: 600; }
.back-btn { background: #ffffff; color: #1f7cff; border: 1rpx solid #bfdbfe; }
.submit-btn { background: linear-gradient(135deg, #1f7cff, #3b82f6); color: #ffffff; }
.action-btn-disabled { background: #94a3b8; }
.picker-mask { position: fixed; left: 0; right: 0; top: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); z-index: 100; display: flex; align-items: flex-end; }
.picker-popup { width: 100%; background: #ffffff; border-radius: 24rpx 24rpx 0 0; padding-bottom: env(safe-area-inset-bottom); }
.picker-header { display: flex; align-items: center; justify-content: space-between; padding: 28rpx 32rpx; border-bottom: 1rpx solid #e5e7eb; }
.picker-title { font-size: 34rpx; font-weight: 600; color: #1f2937; }
.picker-close { width: 48rpx; height: 48rpx; display: flex; align-items: center; justify-content: center; }
.picker-search { padding: 16rpx 32rpx; }
.picker-search-input { width: 100%; height: 72rpx; padding: 0 20rpx; background: #f3f4f6; border-radius: 12rpx; font-size: 26rpx; box-sizing: border-box; }
.picker-content { max-height: 60vh; }
.picker-item { padding: 24rpx 32rpx; border-bottom: 1rpx solid #f3f4f6; }
.picker-item-main { display: flex; align-items: center; justify-content: space-between; gap: 16rpx; }
.picker-item-name { font-size: 28rpx; color: #374151; flex: 1; min-width: 0; }
.picker-item-code { font-size: 24rpx; color: #9ca3af; flex-shrink: 0; }
.picker-item-desc { font-size: 24rpx; color: #9ca3af; margin-top: 8rpx; }
.picker-item-active { background: #eff6ff; }
.picker-item-active .picker-item-name { color: #1f7cff; font-weight: 600; }
.picker-loading { padding: 24rpx 0; text-align: center; color: #9ca3af; font-size: 26rpx; }
.picker-empty { padding: 48rpx; text-align: center; color: #9ca3af; font-size: 26rpx; }
.picker-checkbox { flex-shrink: 0; margin-right: 8rpx; }
.checkbox-icon { width: 40rpx; height: 40rpx; border-radius: 8rpx; border: 2rpx solid #d1d5db; background: #ffffff; display: flex; align-items: center; justify-content: center; }
.checkbox-icon.checked { background: #1f7cff; border-color: #1f7cff; }
.checkbox-tick { color: #ffffff; font-size: 24rpx; font-weight: 700; line-height: 1; }
.picker-item-info { flex: 1; min-width: 0; display: flex; align-items: center; justify-content: space-between; gap: 16rpx; }
.picker-footer { display: flex; align-items: center; justify-content: space-between; padding: 20rpx 32rpx; border-top: 1rpx solid #e5e7eb; }
.picker-footer-count { font-size: 26rpx; color: #6b7280; }
.picker-footer-btn { height: 72rpx; padding: 0 40rpx; border-radius: 12rpx; background: linear-gradient(135deg, #1f7cff, #3b82f6); color: #ffffff; font-size: 28rpx; font-weight: 600; display: flex; align-items: center; justify-content: center; }
</style>

@ -34,6 +34,11 @@ const MENU_ROUTE_MAP = {
'保养记录': '/pages_function/pages/moldMaintain/index',
moldmaintain: '/pages_function/pages/moldMaintain/index',
moldmaintenance: '/pages_function/pages/moldMaintain/index',
'更换压网': '/pages_function/pages/moldPressureNet/index',
'压网更换': '/pages_function/pages/moldPressureNet/index',
'压网记录': '/pages_function/pages/moldPressureNet/index',
moldpressurenet: '/pages_function/pages/moldPressureNet/index',
pressurenet: '/pages_function/pages/moldPressureNet/index',
'产品物料分类': '/pages_function/pages/materialCategory/index',
materialcategory: '/pages_function/pages/materialCategory/index',
'产品物料信息': '/pages_function/pages/materialInfo/index',

Loading…
Cancel
Save