|
|
<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, formatDate } 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()
|
|
|
if (props.type === 1) {
|
|
|
await new Promise((resolve) => setTimeout(resolve, 2000))
|
|
|
}
|
|
|
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 = formatDate(new Date())
|
|
|
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>
|