You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

433 lines
21 KiB
Vue

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<view class="page-container">
<NavBar :title="t('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: 18rpx; padding: 18rpx 24rpx calc(18rpx + env(safe-area-inset-bottom)); background: #ffffff; box-shadow: 0 -8rpx 24rpx rgba(15, 23, 42, 0.06); }
.action-btn { flex: 1; height: 84rpx; border-radius: 16rpx; display: flex; align-items: center; justify-content: center; font-size: 30rpx; font-weight: 600; }
.back-btn { background: #eef2f7; color: #475569; }
.submit-btn { background: #1f4b79; 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>