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.

491 lines
22 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('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">
<uni-icons type="info" size="24" color="#1f7cff"></uni-icons>
</view>
<text class="section-title">{{ t('moldCheck.currentMoldInfo') }}</text>
</view>
<view v-if="moldInfo.id" class="mold-info-grid">
<view class="mold-info-item">
<text class="mold-info-label">{{ t('moldCheck.moldCode') }}</text>
<text class="mold-info-value">{{ moldInfo.code || '-' }}</text>
</view>
<view class="mold-info-item">
<text class="mold-info-label">{{ t('moldCheck.moldName') }}</text>
<text class="mold-info-value">{{ moldInfo.name || '-' }}</text>
</view>
<view class="mold-info-item">
<text class="mold-info-label">{{ t('moldCheck.product') }}</text>
<text class="mold-info-value">{{ moldInfo.productName || '-' }}</text>
</view>
<view class="mold-info-item">
<text class="mold-info-label">{{ t('moldCheck.moldSize') }}</text>
<text class="mold-info-value">{{ moldInfo.moldSize || '-' }}</text>
</view>
<view class="mold-info-item">
<text class="mold-info-label">{{ t('moldCheck.status') }}</text>
<text :class="['mold-info-value', 'status-tag', statusClass]">{{ moldInfo.statusLabel || '-' }}</text>
</view>
<view class="mold-info-item">
<text class="mold-info-label">{{ t('moldCheck.currentDevice') }}</text>
<text class="mold-info-value">{{ moldInfo.currentDevice || '-' }}</text>
</view>
</view>
<view class="mold-select-area" @click="openMoldPicker">
<text class="required-star">*</text>
<text :class="moldInfo.id ? 'mold-select-change' : 'mold-select-placeholder'">{{ moldInfo.id ? t('moldCheck.reSelectMold') : t('moldCheck.selectMold') }}</text>
<uni-icons type="right" size="18" color="#9ca3af"></uni-icons>
</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('moldCheck.taskInfo') }}</text>
</view>
<view class="form-field">
<text class="form-label"><text class="required-star">*</text>{{ t('moldCheck.taskName') }}</text>
<input
v-model="taskName"
class="form-input"
:placeholder="t('moldCheck.placeholderTaskName')"
/>
</view>
<view class="form-field">
<text class="form-label"><text class="required-star">*</text>{{ t('moldCheck.templateSelection') }}</text>
<view class="template-select" @click="openTemplatePicker">
<text :class="['template-text', selectedTemplate ? '' : 'placeholder']">{{ selectedTemplate?.planName || t('moldCheck.selectTemplate') }}</text>
<uni-icons type="right" size="18" color="#9ca3af"></uni-icons>
</view>
</view>
</view>
<!-- 点检项列表 -->
<view v-if="!inspectionItems.length" class="hint">{{ t('moldCheck.noItems') }}</view>
<view v-else 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>
<text class="inspection-title">{{ textValue(item.subjectName) }}</text>
</view>
<view class="inspection-fields">
<view class="field-row">
<text class="field-label">{{ t('moldCheck.inspectionMethod') }}</text>
<text class="field-value">{{ textValue(item.inspectionMethod) }}</text>
</view>
<view class="field-row">
<text class="field-label">{{ t('moldCheck.judgmentCriteria') }}</text>
<text class="field-value">{{ textValue(item.judgmentCriteria) }}</text>
</view>
</view>
</view>
</view>
</view>
</scroll-view>
<view class="action-bar">
<view class="action-btn back-btn" @click="goBack">{{ t('dashboard.back') }}</view>
<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">
<text class="picker-title">{{ t('moldCheck.selectMold') }}</text>
<view class="picker-close" @click="closeMoldPicker">
<uni-icons type="close" size="24" color="#6b7280"></uni-icons>
</view>
</view>
<view class="picker-search">
<input v-model="moldSearchKeyword" class="picker-search-input" :placeholder="t('moldCheck.searchMold')" confirm-type="search" @confirm="searchMold" />
</view>
<scroll-view scroll-y class="picker-content">
<view
v-for="mold in moldList"
:key="mold.id"
:class="['picker-item', moldInfo.id === String(mold.id) ? 'picker-item-active' : '']"
@click="selectMold(mold)"
>
<view class="picker-item-main">
<text class="picker-item-name">{{ mold.name || mold.code || '-' }}</text>
<text class="picker-item-code">{{ mold.code || '' }}</text>
</view>
<text v-if="mold.productName" class="picker-item-desc">{{ mold.productName }}</text>
</view>
<view v-if="moldLoading" class="picker-loading">{{ t('functionCommon.loading') }}</view>
<view v-else-if="!moldList.length" class="picker-empty">{{ t('moldCheck.noMoldData') }}</view>
<view v-else-if="moldFinished" class="picker-loading">{{ t('functionCommon.noMoreData') }}</view>
</scroll-view>
</view>
</view>
<!-- 模板选择弹窗 -->
<view v-if="showTemplatePicker" class="picker-mask" @click="closeTemplatePicker">
<view class="picker-popup" @click.stop>
<view class="picker-header">
<text class="picker-title">{{ t('moldCheck.selectTemplate') }}</text>
<view class="picker-close" @click="closeTemplatePicker">
<uni-icons type="close" size="24" color="#6b7280"></uni-icons>
</view>
</view>
<scroll-view scroll-y class="picker-content" @scrolltolower="loadMoreTemplates">
<view
v-for="template in templateList"
:key="template.id"
:class="['picker-item', selectedTemplate?.id === template.id ? 'picker-item-active' : '']"
@click="selectTemplate(template)"
>
<view class="picker-item-main">
<text class="picker-item-name">{{ template.planName || '-' }}</text>
<text :class="['picker-item-type', String(template.planType) === '1' ? 'type-maintain' : 'type-inspect']">
{{ String(template.planType) === '1' ? t('moldCheck.taskTypeMaintain') : t('moldCheck.taskTypeInspect') }}
</text>
</view>
<text v-if="template.description" class="picker-item-desc">{{ template.description }}</text>
</view>
<view v-if="templateLoading" class="picker-loading">{{ t('functionCommon.loading') }}</view>
<view v-else-if="!templateList.length" class="picker-empty">{{ t('moldCheck.noTemplate') }}</view>
<view v-else-if="templateFinished" class="picker-loading">{{ t('functionCommon.noMoreData') }}</view>
</scroll-view>
</view>
</view>
</view>
</template>
<script setup>
import { reactive, ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { useI18n } from 'vue-i18n'
import NavBar from '@/components/common/NavBar.vue'
import { createMoldTicketDirect, getPlanMaintenancePage, getSubjectList, getMoldBrandDetail, getMoldList } from '@/api/mes/moldCheck'
import { DICT_TYPE, getDictLabel, initAllDict } from '@/utils/dict'
const { t } = useI18n()
// 模具信息(从列表页传入或通过选择器选择)
const moldInfo = reactive({
id: '',
code: '',
name: '',
productName: '',
moldSize: '',
status: '',
statusLabel: '',
currentDevice: ''
})
const taskName = ref('')
const selectedTemplate = ref(null)
const templateList = ref([])
const showTemplatePicker = ref(false)
const templateLoading = ref(false)
const templateFinished = ref(false)
const templatePageNo = ref(1)
const templatePageSize = ref(20)
const inspectionItems = ref([])
const submitLoading = ref(false)
const statusClass = ref('')
// 模具选择器
const showMoldPicker = ref(false)
const moldList = ref([])
const moldLoading = ref(false)
const moldFinished = ref(true)
const moldSearchKeyword = ref('')
onLoad(async (query) => {
await initAllDict()
const moldId = String(query?.id || '')
if (moldId) {
// 从列表页传入了模具ID
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
moldInfo.productName = query?.productName ? decodeURIComponent(query.productName) : ''
moldInfo.moldSize = query?.moldSize ? decodeURIComponent(query.moldSize) : ''
moldInfo.status = String(query?.status || '')
moldInfo.currentDevice = query?.currentDevice ? decodeURIComponent(query.currentDevice) : ''
moldInfo.statusLabel = getDictLabel(DICT_TYPE.ERP_MOLD_STATUS, moldInfo.status, moldInfo.status)
updateStatusClass()
} else {
// 只有ID加载模具详情
await loadMoldInfo(moldId)
}
}
// 如果没有传入模具ID用户需要通过模具选择器选择
})
async function loadMoldInfo(id) {
try {
const res = await getMoldBrandDetail(id)
const data = res && res.data !== undefined ? res.data : res
moldInfo.id = String(data?.id || id)
moldInfo.code = String(data?.code || '')
moldInfo.name = String(data?.name || '')
moldInfo.productName = String(data?.productName || '')
moldInfo.moldSize = String(data?.moldSize || '')
moldInfo.status = String(data?.status || '')
moldInfo.currentDevice = String(data?.currentDevice || '')
moldInfo.statusLabel = getDictLabel(DICT_TYPE.ERP_MOLD_STATUS, moldInfo.status, moldInfo.status)
updateStatusClass()
} catch (error) {
uni.showToast({ title: t('functionCommon.loadFailed'), icon: 'none' })
}
}
function updateStatusClass() {
const status = String(moldInfo.status)
if (status === '1' || moldInfo.statusLabel.includes('正常') || moldInfo.statusLabel.includes('使用')) {
statusClass.value = 'status-success'
} else if (status === '2' || moldInfo.statusLabel.includes('维修') || moldInfo.statusLabel.includes('警')) {
statusClass.value = 'status-warning'
} else if (status === '3' || moldInfo.statusLabel.includes('报废') || moldInfo.statusLabel.includes('停')) {
statusClass.value = 'status-danger'
} else {
statusClass.value = 'status-default'
}
}
// 模具选择器
function openMoldPicker() {
showMoldPicker.value = true
if (!moldList.value.length) {
loadMoldList(true)
}
}
function closeMoldPicker() {
showMoldPicker.value = false
}
async function loadMoldList(reset) {
if (moldLoading.value) return
if (reset) {
moldList.value = []
}
moldLoading.value = true
try {
const params = {}
if (moldSearchKeyword.value.trim()) {
params.name = moldSearchKeyword.value.trim()
}
const res = await getMoldList(params)
const root = res && res.data !== undefined ? res.data : res
const list = Array.isArray(root) ? root : (root?.list || root?.rows || root?.records || root?.data?.list || root?.data?.rows || root?.data?.records || [])
moldList.value = reset ? list : [...moldList.value, ...list]
moldFinished.value = true
} catch (error) {
uni.showToast({ title: t('functionCommon.loadFailed'), icon: 'none' })
} finally {
moldLoading.value = false
}
}
function searchMold() {
loadMoldList(true)
}
function selectMold(mold) {
moldInfo.id = String(mold.id || '')
moldInfo.code = String(mold.code || '')
moldInfo.name = String(mold.name || '')
moldInfo.productName = String(mold.productName || '')
moldInfo.moldSize = String(mold.moldSize || '')
moldInfo.status = String(mold.status || '')
moldInfo.currentDevice = String(mold.currentDevice || '')
moldInfo.statusLabel = getDictLabel(DICT_TYPE.ERP_MOLD_STATUS, moldInfo.status, moldInfo.status)
updateStatusClass()
closeMoldPicker()
}
// 模板选择器
function openTemplatePicker() {
showTemplatePicker.value = true
if (!templateList.value.length) {
loadTemplates(true)
}
}
function closeTemplatePicker() {
showTemplatePicker.value = false
}
async function loadTemplates(reset) {
if (templateLoading.value) return
if (reset) {
templatePageNo.value = 1
templateFinished.value = false
}
templateLoading.value = true
try {
const res = await getPlanMaintenancePage({
pageNo: templatePageNo.value,
pageSize: templatePageSize.value
})
const root = res && res.data !== undefined ? res.data : res
const list = root?.list || root?.rows || root?.records || root?.data?.list || root?.data?.rows || root?.data?.records || []
const total = root?.total ?? root?.data?.total ?? 0
templateList.value = reset ? list : [...templateList.value, ...list]
templateFinished.value = templateList.value.length >= total || list.length < templatePageSize.value
} catch (error) {
uni.showToast({ title: t('functionCommon.loadFailed'), icon: 'none' })
} finally {
templateLoading.value = false
}
}
async function loadMoreTemplates() {
if (templateLoading.value || templateFinished.value) return
templatePageNo.value += 1
await loadTemplates(false)
}
function selectTemplate(template) {
selectedTemplate.value = template
loadInspectionItems(template.id)
closeTemplatePicker()
}
async function loadInspectionItems(planId) {
try {
const res = await getSubjectList(planId)
const root = res && res.data !== undefined ? res.data : res
const items = Array.isArray(root) ? root : (root?.list || root?.rows || root?.records || root?.data?.list || root?.data?.rows || root?.data?.records || root?.data || [])
inspectionItems.value = Array.isArray(items) ? items : []
} catch (error) {
uni.showToast({ title: t('functionCommon.loadFailed'), icon: 'none' })
}
}
function textValue(value) {
if (value === 0) return '0'
if (value === null || value === undefined) return '-'
const text = String(value).trim()
return text || '-'
}
function goBack() {
uni.navigateBack()
}
async function handleSubmit() {
if (submitLoading.value) return
if (!moldInfo.id) {
uni.showToast({ title: t('moldCheck.selectMoldError'), icon: 'none' })
return
}
if (!taskName.value.trim()) {
uni.showToast({ title: t('moldCheck.placeholderTaskName'), icon: 'none' })
return
}
if (!selectedTemplate.value) {
uni.showToast({ title: t('moldCheck.selectTemplateError'), icon: 'none' })
return
}
submitLoading.value = true
try {
await createMoldTicketDirect({
name: taskName.value.trim(),
taskType: '1',
moldList: String(moldInfo.id),
projectForm: String(selectedTemplate.value.id)
})
uni.showToast({ title: t('moldCheck.submitSuccess'), icon: 'success' })
setTimeout(() => {
uni.navigateBack()
}, 1500)
} catch (error) {
uni.showToast({ title: t('moldCheck.submitFailed'), icon: 'none' })
} finally {
submitLoading.value = false
}
}
</script>
<style lang="scss" scoped>
.page-container { min-height: 100vh; background-color: #f5f7fb; }
.detail-scroll { height: calc(100vh - 172rpx); }
.content-section { padding: 20rpx 24rpx 28rpx; }
.section-card { background: #ffffff; border-radius: 24rpx; padding: 24rpx; margin-bottom: 20rpx; box-shadow: 0 6rpx 18rpx rgba(15, 23, 42, 0.05); }
.section-header { display: flex; align-items: center; gap: 12rpx; margin-bottom: 20rpx; }
.section-icon { width: 44rpx; height: 44rpx; border-radius: 12rpx; background: #eff6ff; display: flex; align-items: center; justify-content: center; }
.section-title { font-size: 32rpx; font-weight: 600; color: #1f2937; }
.mold-info-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20rpx; }
.mold-info-item { display: flex; flex-direction: column; gap: 8rpx; }
.mold-info-label { font-size: 24rpx; color: #9ca3af; }
.mold-info-value { font-size: 28rpx; color: #374151; }
.mold-select-area { display: flex; align-items: center; justify-content: space-between; height: 88rpx; padding: 0 24rpx; background: #f8fafc; border-radius: 14rpx; }
.mold-select-placeholder { font-size: 28rpx; color: #9ca3af; }
.mold-select-change { font-size: 26rpx; color: #1f7cff; }
.status-tag { display: inline-block; padding: 6rpx 16rpx; border-radius: 8rpx; font-size: 24rpx; }
.status-success { color: #15803d; background: #dcfce7; }
.status-warning { color: #d97706; background: #fef3c7; }
.status-danger { color: #dc2626; background: #fee2e2; }
.status-default { color: #6b7280; background: #f3f4f6; }
.form-field { display: flex; flex-direction: column; gap: 12rpx; }
.form-field + .form-field { margin-top: 24rpx; }
.form-label { font-size: 26rpx; color: #6b7280; }
.required-star { color: #ef4444; font-size: 28rpx; margin-right: 4rpx; }
.form-input { width: 100%; height: 88rpx; padding: 0 24rpx; background: #f8fafc; border-radius: 14rpx; font-size: 28rpx; color: #374151; box-sizing: border-box; }
.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-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; }
.inspection-index { width: 44rpx; height: 44rpx; border-radius: 50%; background: #1f7cff; color: #ffffff; font-size: 24rpx; font-weight: 700; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.inspection-title { flex: 1; min-width: 0; font-size: 30rpx; font-weight: 600; color: #1f2937; }
.inspection-fields { margin-top: 16rpx; display: flex; flex-direction: column; gap: 14rpx; }
.field-row { display: flex; justify-content: space-between; align-items: flex-start; gap: 20rpx; }
.field-label { width: 170rpx; font-size: 27rpx; color: #9ca3af; flex-shrink: 0; }
.field-value { flex: 1; text-align: right; font-size: 28rpx; color: #374151; line-height: 1.5; }
.hint { padding: 48rpx 0; text-align: center; color: #9ca3af; font-size: 26rpx; }
.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-type { padding: 6rpx 16rpx; border-radius: 8rpx; font-size: 22rpx; flex-shrink: 0; }
.type-inspect { color: #1d4ed8; background: #dbeafe; }
.type-maintain { color: #15803d; background: #dcfce7; }
.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; }
</style>