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.

380 lines
13 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>
<div class="mold-operate-page">
<div class="mold-operate-page__header">
<el-button @click="emit('back')">
<Icon icon="ep:arrow-left" class="mr-5px" /> {{ t('MoldManagement.MoldBrandPage.back') }}
</el-button>
</div>
<!-- 模具信息卡片 -->
<div class="mold-operate-page__info-card" v-if="mold">
<el-image v-if="getImageList(mold.images).length" :src="getImageList(mold.images)[0]"
fit="cover" class="mold-operate-page__info-image" />
<div class="mold-operate-page__info-empty" v-else></div>
<div class="mold-operate-page__info-item">
<span class="mold-operate-page__info-label">{{ t('MoldManagement.MoldBrandPage.moldCode') }}</span>
<span class="mold-operate-page__info-value">{{ mold.code }}</span>
</div>
<div class="mold-operate-page__info-item">
<span class="mold-operate-page__info-label">{{ t('MoldManagement.MoldBrandPage.moldName') }}</span>
<span class="mold-operate-page__info-value">{{ mold.name }}</span>
</div>
<div class="mold-operate-page__info-item">
<span class="mold-operate-page__info-label">{{ t('MoldManagement.MoldBrandPage.productName') }}</span>
<span class="mold-operate-page__info-value">{{ mold.productName }}</span>
</div>
<div class="mold-operate-page__info-item">
<span class="mold-operate-page__info-label">{{ t('MoldManagement.MoldBrandPage.moldSize') }}</span>
<span class="mold-operate-page__info-value">{{ mold.moldSize }}</span>
</div>
<div class="mold-operate-page__info-item">
<span class="mold-operate-page__info-label">{{ t('MoldManagement.MoldBrandPage.status') }}</span>
<dict-tag :type="DICT_TYPE.ERP_MOLD_STATUS" :value="mold.status" />
</div>
<div class="mold-operate-page__info-item">
<span class="mold-operate-page__info-label">{{ t('MoldManagement.MoldBrandPage.currentDevice') }}</span>
<span class="mold-operate-page__info-value">{{ mold.currentDevice || '-' }}</span>
</div>
<div class="mold-operate-page__info-item">
<span class="mold-operate-page__info-label">{{ t('MoldManagement.MoldBrandPage.lastChangeTime') }}</span>
<span class="mold-operate-page__info-value">{{ mold.lastChangeTime ?
dateFormatter(mold.lastChangeTime) : '-' }}</span>
</div>
</div>
<div class="mold-operate-page__body">
<!-- 左侧表单 -->
<div class="mold-operate-page__form">
<el-form ref="operateFormRef" :model="operateFormData" :rules="operateFormRules" label-width="100px">
<el-form-item :label="t('MoldManagement.MoldBrandPage.targetLine')">
<el-input v-model="displayTopCategory" disabled :placeholder="'选择目标设备后自动显示'" class="!w-full" />
</el-form-item>
<el-form-item :label="t('MoldManagement.MoldBrandPage.targetDevice')" prop="deviceId">
<el-select v-model="operateFormData.deviceId" filterable clearable
:placeholder="t('MoldManagement.MoldBrandPage.placeholderTargetDevice')" class="!w-full">
<el-option v-for="device in deviceOptions" :key="device.id"
:label="`${device.deviceName}${device.deviceCode}`" :value="device.id" />
</el-select>
</el-form-item>
<el-form-item :label="t('MoldManagement.MoldBrandPage.operateTime')" prop="operateTime">
<el-date-picker v-model="operateFormData.operateTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss"
:placeholder="t('MoldManagement.MoldBrandPage.placeholderOperateTime')" class="!w-full" />
</el-form-item>
<el-form-item :label="t('MoldManagement.MoldBrandPage.operator')" prop="operatorId">
<el-select v-model="operateFormData.operatorId" filterable clearable
:placeholder="t('MoldManagement.MoldBrandPage.placeholderOperator')" class="!w-full">
<el-option v-for="item in operatorOptions" :key="String(item.id)" :label="item.nickname"
:value="String(item.id)" />
</el-select>
</el-form-item>
<el-form-item :label="t('MoldManagement.MoldBrandPage.remark')" prop="remark">
<el-input v-model="operateFormData.remark"
:placeholder="t('MoldManagement.MoldBrandPage.placeholderRemark')" type="textarea" :rows="3" />
</el-form-item>
</el-form>
<div class="mold-operate-page__form-actions">
<el-button @click="emit('back')">{{ t('MoldManagement.MoldBrandPage.cancel') }}</el-button>
<el-button type="primary" @click="submitOperateForm" :loading="operateLoading">{{
t('MoldManagement.MoldBrandPage.submit') }}</el-button>
</div>
</div>
<!-- 右侧记录列表 -->
<div class="mold-operate-page__history">
<div class="mold-operate-page__history-header">
<span class="mold-operate-page__history-title">{{ t('MoldManagement.MoldBrandPage.recentRecords')
}}</span>
</div>
<el-table :data="recentOperateList" :stripe="true" :show-overflow-tooltip="true" v-loading="recentLoading">
<el-table-column :label="t('MoldManagement.MoldBrandPage.operateType')">
<template #default="scope">
<span :class="scope.row.operateType === '1' ? 'text-primary' : 'text-success'">
{{ scope.row.operateType === '1' ? t('MoldManagement.MoldBrandPage.installUp') :
t('MoldManagement.MoldBrandPage.uninstallDown') }}
</span>
</template>
</el-table-column>
<el-table-column :label="t('MoldManagement.MoldBrandPage.moldName')" prop="moldName" />
<el-table-column :label="t('MoldManagement.MoldBrandPage.lineName')" prop="lineName" />
<el-table-column :label="t('MoldManagement.MoldBrandPage.operateTime')" prop="createTime" width="160"
:formatter="dateFormatter" />
<el-table-column :label="t('MoldManagement.MoldBrandPage.operator')" prop="operatorName" width="100" />
</el-table>
<el-empty v-if="!recentOperateList.length" :description="t('MoldManagement.MoldBrandPage.noRecords')" />
<Pagination :total="recentTotal" v-model:page="recentPageNo" v-model:limit="recentPageSize"
@pagination="fetchRecentOperateList" />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { MoldBrandApi, type MoldBrandVO } from '@/api/erp/mold'
import { DeviceLedgerApi, type DeviceLedgerVO } from '@/api/mes/deviceledger'
import { MoldOperateApi, type MoldOperateVO } from '@/api/mes/moldoperate'
import { DeviceLineApi, type DeviceLineTreeVO } from '@/api/mes/deviceline'
import { getSimpleUserList, type UserVO } from '@/api/system/user'
import { DICT_TYPE } from '@/utils/dict'
import { dateFormatter } from '@/utils/formatTime'
defineOptions({ name: 'MoldOperateView' })
const props = defineProps<{
mold: MoldBrandVO | null
type: number // 1: , 2:
deviceOptions: DeviceLedgerVO[]
}>()
const emit = defineEmits<{
(e: 'back'): void
(e: 'success'): void
}>()
const { t } = useI18n()
const message = useMessage()
const operateLoading = ref(false)
const operateFormRef = ref()
// 表单数据
const operateFormData = reactive({
lineId: undefined as string | undefined,
deviceId: undefined as number | undefined,
operateTime: undefined as string | undefined,
operatorId: undefined as string | undefined,
remark: undefined as string | undefined
})
// 表单验证规则(目标产线不再必填,选择设备后自动填充)
const operateFormRules = reactive({
deviceId: [{ required: true, message: t('MoldManagement.MoldBrandPage.validatorTargetDeviceRequired'), trigger: 'blur' }],
operateTime: [{ required: true, message: t('MoldManagement.MoldBrandPage.validatorOperateTimeRequired'), trigger: 'blur' }],
operatorId: [{ required: true, message: t('MoldManagement.MoldBrandPage.validatorOperatorRequired'), trigger: 'blur' }]
})
// 下拉选项
const deviceLineTree = ref<DeviceLineTreeVO[]>([])
const displayTopCategory = ref('')
const operatorOptions = ref<UserVO[]>([])
// 最近操作记录
const recentOperateList = ref<MoldOperateVO[]>([])
const recentPageNo = ref(1)
const recentPageSize = ref(5)
const recentTotal = ref(0)
const recentLoading = ref(false)
const getImageList = (images?: string) => {
if (!images) return []
return String(images)
.split(',')
.map((item) => item.trim())
.filter(Boolean)
}
const initOperateOptions = async () => {
try {
const data = await DeviceLineApi.getDeviceLineTree()
deviceLineTree.value = Array.isArray(data) ? data : []
} catch {
deviceLineTree.value = []
}
try {
operatorOptions.value = (await getSimpleUserList()) ?? []
} catch {
operatorOptions.value = []
}
}
// 选择设备后自动匹配产线名称
watch(() => operateFormData.deviceId, async (deviceId) => {
displayTopCategory.value = ''
if (!deviceId) return
try {
const detail = await DeviceLedgerApi.getDeviceLedger(deviceId)
if (detail?.deviceLine != null) {
operateFormData.lineId = detail.deviceLine
}
// 优先用后端返回的顶级分类名称
displayTopCategory.value = detail?.topCategoryName || ''
// fallback: 用产线树匹配根节点名称
if (!displayTopCategory.value && detail?.deviceLine != null && deviceLineTree.value.length) {
const lineId = Number(detail.deviceLine)
const containsId = (nodes: DeviceLineTreeVO[] | undefined, id: number): boolean => {
if (!nodes) return false
return nodes.some((n) => n.id === id || containsId(n.children, id))
}
for (const root of deviceLineTree.value) {
if (root.id === lineId || containsId(root.children, lineId)) {
displayTopCategory.value = root.name
break
}
}
}
} catch {}
})
const fetchRecentOperateList = async () => {
if (!props.mold?.id) return
recentLoading.value = true
try {
const data = await MoldOperateApi.getMoldOperatePage({
pageNo: recentPageNo.value,
pageSize: recentPageSize.value,
moldId: props.mold.id
})
recentOperateList.value = data?.list ?? []
recentTotal.value = data?.total ?? 0
} catch {
recentOperateList.value = []
recentTotal.value = 0
} finally {
recentLoading.value = false
}
}
const submitOperateForm = async () => {
if (!operateFormRef.value) return
try {
await operateFormRef.value.validate()
operateLoading.value = true
const submitData = {
operateType: props.type,
moldId: props.mold?.id,
lineId: operateFormData.lineId,
lineName: displayTopCategory.value,
deviceId: operateFormData.deviceId,
remark: operateFormData.remark,
operateTime: operateFormData.operateTime,
operatorId: operateFormData.operatorId
}
await MoldOperateApi.createMoldOperate(submitData)
message.success(props.type === 1 ? t('MoldManagement.MoldBrandPage.moldUpSuccess') : t('MoldManagement.MoldBrandPage.moldDownSuccess'))
fetchRecentOperateList()
emit('success')
} catch (error) {
console.error(t('MoldManagement.MoldBrandPage.submitFailed'), error)
} finally {
operateLoading.value = false
}
}
/** 对外暴露 open 方法,用于初始化 */
const open = async () => {
operateFormData.lineId = undefined
operateFormData.deviceId = undefined
operateFormData.operateTime = undefined
operateFormData.operatorId = undefined
operateFormData.remark = undefined
displayTopCategory.value = ''
await initOperateOptions()
recentPageNo.value = 1
await fetchRecentOperateList()
}
defineExpose({ open })
</script>
<style scoped>
.mold-operate-page__header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.mold-operate-page__info-card {
display: flex;
align-items: center;
gap: 24px;
padding: 20px;
background: #fff;
border-radius: 12px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
margin-bottom: 20px;
}
.mold-operate-page__info-image {
width: 80px;
height: 80px;
border-radius: 8px;
border: 1px solid var(--el-border-color-lighter);
flex-shrink: 0;
}
.mold-operate-page__info-empty {
width: 80px;
height: 80px;
border-radius: 8px;
border: 1px dashed var(--el-border-color-lighter);
flex-shrink: 0;
}
.mold-operate-page__info-item {
flex: 1;
min-width: 0;
}
.mold-operate-page__info-label {
display: block;
color: var(--el-text-color-secondary);
font-size: 12px;
margin-bottom: 4px;
}
.mold-operate-page__info-value {
display: block;
color: var(--el-text-color-primary);
font-size: 14px;
font-weight: 500;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.mold-operate-page__body {
display: flex;
gap: 20px;
}
.mold-operate-page__form {
flex: 1;
background: #fff;
border-radius: 12px;
padding: 24px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
}
.mold-operate-page__form-actions {
display: flex;
justify-content: flex-end;
gap: 12px;
margin-top: 24px;
padding-top: 16px;
border-top: 1px solid var(--el-border-color-lighter);
}
.mold-operate-page__history {
width: 30vw;
background: #fff;
border-radius: 12px;
padding: 20px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
max-height: 500px;
overflow-y: auto;
}
.mold-operate-page__history-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.mold-operate-page__history-title {
font-size: 16px;
font-weight: 600;
color: var(--el-text-color-primary);
}
</style>