mes-vue/src/views/biz/maintenanceRecord/index.vue
2026-08-05 09:50:48 +08:00

442 lines
15 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="page-container">
<n-card>
<!-- 搜索表单 -->
<div class="search-form">
<n-form inline :model="searchForm" label-placement="left">
<n-form-item label="维保记录编号">
<n-input v-model:value="searchForm.recordId" placeholder="请输入维保记录编号" clearable />
</n-form-item>
<n-form-item>
<n-space>
<n-button type="primary" @click="handleSearch">
<template #icon><n-icon><SearchOutline /></n-icon></template>
搜索
</n-button>
<n-button @click="handleReset">
<template #icon><n-icon><RefreshOutline /></n-icon></template>
重置
</n-button>
</n-space>
</n-form-item>
</n-form>
</div>
<!-- 工具栏 -->
<div class="table-toolbar">
<n-space>
<n-button type="primary" @click="handleAdd">
<template #icon><n-icon><AddOutline /></n-icon></template>
新增
</n-button>
<n-button @click="importModalVisible = true">
<template #icon><n-icon><CloudUploadOutline /></n-icon></template>
导入
</n-button>
<n-button @click="handleExport">
<template #icon><n-icon><DownloadOutline /></n-icon></template>
导出{{ selectedIds.length > 0 ? `(${selectedIds.length})` : '' }}
</n-button>
<n-button type="error" :disabled="selectedIds.length === 0" @click="handleBatchDelete">
<template #icon><n-icon><TrashOutline /></n-icon></template>
删除
</n-button>
</n-space>
</div>
<!-- 表格 -->
<n-data-table
:columns="columns"
:data="tableData"
:loading="loading"
:pagination="pagination"
:row-key="(row: MaintenanceRecord) => row.recordId"
:scroll-x="1200"
@update:page="handlePageChange"
@update:page-size="handlePageSizeChange"
@update:checked-row-keys="handleCheck"
/>
</n-card>
<!-- 新增/编辑弹窗 -->
<n-modal v-model:show="modalVisible" preset="card" :title="modalTitle" style="width: 560px">
<n-form ref="formRef" :model="formData" :rules="formRules" label-placement="left" label-width="100px">
<n-form-item label="设备ID" path="equipmentId">
<n-input v-model:value="formData.equipmentId" placeholder="请输入设备ID" />
</n-form-item>
<n-form-item label="记录类型" path="recordType">
<n-select v-model:value="formData.recordType" placeholder="请选择记录类型" :options="recordTypeOptions" />
</n-form-item>
<n-form-item label="故障现象" path="itemContent">
<n-input v-model:value="formData.itemContent" type="textarea" placeholder="请输入故障现象/保养项目" :autosize="{ minRows: 2, maxRows: 4 }" />
</n-form-item>
<n-form-item label="处理措施" path="handleContent">
<n-input v-model:value="formData.handleContent" type="textarea" placeholder="请输入维修处理措施/保养执行内容" :autosize="{ minRows: 2, maxRows: 4 }" />
</n-form-item>
<n-form-item label="执行人" path="executorId">
<n-input v-model:value="formData.executorId" placeholder="请输入执行人ID" />
</n-form-item>
<n-form-item label="开始时间" path="startTime">
<n-date-picker v-model:value="formData.startTime" type="datetime" placeholder="请选择开始时间" clearable style="width: 100%" />
</n-form-item>
<n-form-item label="结束时间" path="endTime">
<n-date-picker v-model:value="formData.endTime" type="datetime" placeholder="请选择结束时间" clearable style="width: 100%" />
</n-form-item>
<n-form-item label="单据状态" path="recordStatus">
<n-select v-model:value="formData.recordStatus" placeholder="请选择单据状态" :options="recordStatusOptions" />
</n-form-item>
</n-form>
<template #footer>
<n-space justify="end">
<n-button @click="modalVisible = false">取消</n-button>
<n-button type="primary" @click="handleSubmit">确定</n-button>
</n-space>
</template>
</n-modal>
<!-- 导入弹窗 -->
<n-modal v-model:show="importModalVisible" preset="card" title="导入MES设备维修保养记录表" style="width: 500px">
<n-space vertical>
<n-alert type="info">
<template #header>导入说明</template>
<ul style="margin: 0; padding-left: 16px; line-height: 1.8">
<li>请先下载导入模板,按模板格式填写数据</li>
<li>支持 .xlsx 或 .xls 格式</li>
</ul>
</n-alert>
<n-space>
<n-button type="primary" @click="handleDownloadTemplate">
<template #icon><n-icon><DownloadOutline /></n-icon></template>
下载模板
</n-button>
</n-space>
<n-upload :max="1" accept=".xlsx,.xls" :show-file-list="true" :custom-request="handleImportUpload">
<n-upload-dragger>
<div style="margin-bottom: 12px">
<n-icon size="48" :depth="3"><CloudUploadOutline /></n-icon>
</div>
<n-text style="font-size: 16px">点击或拖拽文件到此处上传</n-text>
<n-p depth="3" style="margin: 8px 0 0 0">支持 .xlsx 或 .xls 格式</n-p>
</n-upload-dragger>
</n-upload>
</n-space>
<template #footer>
<n-button @click="importModalVisible = false">关闭</n-button>
</template>
</n-modal>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, h, onMounted } from 'vue'
import { NButton, NSpace, NIcon, NUpload, useMessage, useDialog, type DataTableColumns, type UploadCustomRequestOptions } from 'naive-ui'
import { SearchOutline, RefreshOutline, AddOutline, TrashOutline, CreateOutline, CloudUploadOutline, DownloadOutline } from '@vicons/ionicons5'
import { maintenanceRecordApi, type MaintenanceRecord } from '@/api/maintenanceRecord'
const message = useMessage()
const dialog = useDialog()
// 搜索表单
const searchForm = reactive({
recordId: '',
})
// 表格数据
const tableData = ref<MaintenanceRecord[]>([])
const loading = ref(false)
const selectedIds = ref<string[]>([])
const pagination = reactive({
page: 1,
pageSize: 10,
itemCount: 0,
showSizePicker: true,
pageSizes: [10, 20, 50]
})
// 弹窗
const modalVisible = ref(false)
const modalTitle = ref('')
const importModalVisible = ref(false)
const formRef = ref()
const defaultFormData: MaintenanceRecord = {
equipmentId: '',
recordType: '',
itemContent: '',
handleContent: '',
executorId: undefined,
startTime: undefined,
endTime: undefined,
recordStatus: '',
}
const formData = reactive<MaintenanceRecord>({ ...defaultFormData })
// 字典选项
const recordTypeOptions = ref<{ label: string; value: any }[]>([
{ label: '保养', value: 'maintain' },
{ label: '维修', value: 'repair' }
])
const recordStatusOptions = ref<{ label: string; value: any }[]>([
{ label: '待处理', value: 'pending' },
{ label: '已完成', value: 'finished' }
])
// 表单校验规则
const formRules = {
// equipmentId: { required: true, message: '请输入设备ID关联mes_equipment设备表主键', trigger: 'blur' },
// recordType: { required: true, message: '请输入记录类型maintain=保养repair=维修', trigger: 'blur' },
// itemContent: { required: true, message: '请输入故障现象/保养项目详情', trigger: 'blur' },
// handleContent: { required: true, message: '请输入维修处理措施/保养执行内容', trigger: 'blur' },
}
// 表格列
const columns: DataTableColumns<MaintenanceRecord> = [
{ type: 'selection' },
{ title: '维保记录编号', key: 'recordId', width: 160 },
{ title: '设备ID', key: 'equipmentId', width: 100 },
{ title: '记录类型', key: 'recordType', width: 100,
render(row) {
const opt = recordTypeOptions.value.find(o => o.value === row.recordType)
return opt ? opt.label : (row.recordType ?? '-')
}
},
{ title: '故障现象/保养项目', key: 'itemContent', ellipsis: { tooltip: true } },
{ title: '维修处理措施/保养执行内容', key: 'handleContent', ellipsis: { tooltip: true } },
{ title: '执行人ID', key: 'executorId', width: 100 },
{ title: '维保作业开始时间', key: 'startTime', width: 170 },
{ title: '维保作业结束时间', key: 'endTime', width: 170 },
{ title: '单据状态', key: 'recordStatus', width: 100,
render(row) {
const val = row.recordStatus
const opt = recordStatusOptions.value.find(o => o.value === val || String(o.value) === String(val))
return opt ? opt.label : (val ?? '-')
}
},
{ title: '单据创建时间', key: 'createTime', width: 170 },
{
title: '操作',
key: 'actions',
width: 140,
fixed: 'right',
render(row) {
return h('div', { style: { display: 'flex', alignItems: 'center', gap: '8px', flexWrap: 'nowrap' } }, [
h(NButton, { size: 'small', quaternary: true, onClick: () => handleEdit(row) }, {
default: () => [h(NIcon, null, { default: () => h(CreateOutline) }), ' 编辑']
}),
h(NButton, { size: 'small', quaternary: true, type: 'error', onClick: () => handleDelete(row) }, {
default: () => [h(NIcon, null, { default: () => h(TrashOutline) }), ' 删除']
})
])
}
}
]
// 加载数据
async function loadData() {
loading.value = true
try {
const res = await maintenanceRecordApi.page({
page: pagination.page,
pageSize: pagination.pageSize,
recordId: searchForm.recordId || undefined,
})
tableData.value = res.records || res.list || []
pagination.itemCount = res.total || 0
} finally {
loading.value = false
}
}
// 搜索
function handleSearch() {
pagination.page = 1
loadData()
}
// 重置
function handleReset() {
searchForm.recordId = ''
handleSearch()
}
// 分页
function handlePageChange(page: number) {
pagination.page = page
loadData()
}
function handlePageSizeChange(pageSize: number) {
pagination.pageSize = pageSize
pagination.page = 1
loadData()
}
// 选择
function handleCheck(keys: Array<string | number>) {
selectedIds.value = keys.map(String)
}
// 新增
function handleAdd() {
modalTitle.value = '新增MES设备维修保养记录表'
Object.assign(formData, defaultFormData)
modalVisible.value = true
}
// 编辑
function handleEdit(row: MaintenanceRecord) {
modalTitle.value = '编辑MES设备维修保养记录表'
Object.assign(formData, row)
if (formData.startTime && typeof formData.startTime === 'string') {
formData.startTime = new Date(formData.startTime.replace(' ', 'T')).getTime()
}
if (formData.endTime && typeof formData.endTime === 'string') {
formData.endTime = new Date(formData.endTime.replace(' ', 'T')).getTime()
}
if (formData.createTime && typeof formData.createTime === 'string') {
formData.createTime = new Date(formData.createTime.replace(' ', 'T')).getTime()
}
if (formData.updateTime && typeof formData.updateTime === 'string') {
formData.updateTime = new Date(formData.updateTime.replace(' ', 'T')).getTime()
}
modalVisible.value = true
}
// 提交
async function handleSubmit() {
await formRef.value?.validate()
try {
const submitData = { ...formData } as MaintenanceRecord
if (typeof submitData.startTime === 'number') {
submitData.startTime = new Date(submitData.startTime).toISOString().slice(0, 19).replace('T', ' ')
}
if (typeof submitData.endTime === 'number') {
submitData.endTime = new Date(submitData.endTime).toISOString().slice(0, 19).replace('T', ' ')
}
if (typeof submitData.createTime === 'number') {
submitData.createTime = new Date(submitData.createTime).toISOString().slice(0, 19).replace('T', ' ')
}
if (typeof submitData.updateTime === 'number') {
submitData.updateTime = new Date(submitData.updateTime).toISOString().slice(0, 19).replace('T', ' ')
}
if (submitData.recordId) {
await maintenanceRecordApi.update(submitData)
message.success('修改成功')
} else {
await maintenanceRecordApi.create(submitData)
message.success('新增成功')
}
modalVisible.value = false
loadData()
} catch (error) {
// 错误已在拦截器处理
}
}
// 删除
function handleDelete(row: MaintenanceRecord) {
dialog.warning({
title: '提示',
content: '确定要删除该记录吗?',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: async () => {
try {
await maintenanceRecordApi.delete([row.recordId!])
message.success('删除成功')
loadData()
} catch (error) {
// 错误已在拦截器处理
}
}
})
}
// 批量删除
function handleBatchDelete() {
dialog.warning({
title: '提示',
content: `确定要删除选中的 ${selectedIds.value.length} 条记录吗?`,
positiveText: '确定',
negativeText: '取消',
onPositiveClick: async () => {
try {
await maintenanceRecordApi.delete(selectedIds.value)
message.success('删除成功')
selectedIds.value = []
loadData()
} catch (error) {
// 错误已在拦截器处理
}
}
})
}
// 导出
async function handleExport() {
try {
const params: Record<string, any> = {}
if (selectedIds.value.length > 0) params.ids = selectedIds.value
if (searchForm.recordId) params.recordId = searchForm.recordId
const blob = await maintenanceRecordApi.export(params)
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = 'MES设备维修保养记录表数据.xlsx'
link.click()
window.URL.revokeObjectURL(url)
} catch (error) {
// 错误已在拦截器处理
}
}
// 下载导入模板
async function handleDownloadTemplate() {
try {
const blob = await maintenanceRecordApi.downloadTemplate()
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = 'MES设备维修保养记录表导入模板.xlsx'
link.click()
window.URL.revokeObjectURL(url)
} catch (error) {
// 错误已在拦截器处理
}
}
// 导入上传
async function handleImportUpload({ file }: UploadCustomRequestOptions) {
if (!file.file) return
try {
const result = await maintenanceRecordApi.importData(file.file)
if (result.fail > 0) {
dialog.warning({
title: '导入结果',
content: `成功: ${result.success} 条,失败: ${result.fail}\n错误信息: ${(result.errors || []).join('\n') || '无'}`,
positiveText: '确定'
})
} else {
message.success(`导入成功,共 ${result.success} 条数据`)
importModalVisible.value = false
}
loadData()
} catch (error) {
// 错误已在拦截器处理
}
}
onMounted(() => {
loadData()
})
</script>
<style scoped>
.search-form {
margin-bottom: 16px;
}
.table-toolbar {
margin-bottom: 16px;
}
</style>