mes-vue/src/views/biz/stationHandover/index.vue
2026-08-08 17:22:49 +08:00

525 lines
14 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-tree-select
style="width: 400px"
cascade
checkable
:options="sectionList"
@update:value="handleUpdateValue"
/>
</n-form-item>
<n-form-item label="设备/工位">
<n-select
v-model:value="searchForm.deviceIds"
placeholder="请指定工段"
clearable
multiple
style="width: 200px"
:options="deviceList"
/>
</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-space>
</div>
<!-- 表格 -->
<n-data-table
:columns="columns"
:data="tableData"
:loading="loading"
:row-key="(row) => row.id"
:scroll-x="1200"
/>
<div class="pagination-container" style="display: flex; justify-content: flex-end; margin-top: 12px">
<n-pagination
v-model:page="pagination.page"
v-model:page-size="pagination.pageSize"
:item-count="pagination.itemCount"
:page-sizes="[10, 20, 50, 100]"
show-size-picker
show-quick-jumper
@update:page="handlePageChange"
@update:page-size="handlePageSizeChange"
@update:checked-row-keys="handleCheck"
>
<template #prefix>
共 {{ pagination.itemCount }} 条
</template>
</n-pagination>
</div>
</n-card>
<!-- 新增/编辑弹窗 -->
<n-modal v-model:show="modalVisible" preset="card" :title="modalTitle" style="width: 600px">
<n-form ref="formRef" :model="formData" :rules="formRules" label-placement="left" label-width="100px">
<n-form-item label="设备/工位" path="deviceId">
<n-select
v-model:value="formData.deviceId"
placeholder="请指定工段"
clearable
style="width: 200px"
:options="deviceList"
/>
</n-form-item>
<n-form-item label="接收人" path="receiverPerson">
<n-select
v-model:value="formData.receiverPersonId"
placeholder="请选择接收人"
clearable
style="width: 200px"
:options="userList"
/>
</n-form-item>
<n-form-item label="备注" path="remark">
<n-input v-model:value="formData.remark" type="textarea" placeholder="请输入备注"/>
</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="导入工位交接记录表" 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,
CreateOutline,
CloudUploadOutline,
DownloadOutline
} from '@vicons/ionicons5'
import {stationHandoverApi, type StationHandover} from '@/api/stationHandover'
import { Section } from '@/api/section'
import {deviceApi, Device} from '@/api/device'
import {userApi, SysUser} from '@/api/system'
import {deptApi} from "@/api/org.ts";
import {TreeNode} from "echarts/types/src/data/Tree";
const message = useMessage()
const dialog = useDialog()
//工段列表
const sectionList = ref<Section[]>([])
//工位列表
const deviceList = ref<Device[]>([])
//人员列表
const userList = ref<SysUser[]>([])
// 搜索表单
const searchForm = reactive({
deviceIds: null as number[] | null
})
// 表格数据
const tableData = ref<StationHandover[]>([])
const loading = ref(false)
const selectedIds = ref<number[]>([])
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: StationHandover = {
deviceId: undefined,
stationId: undefined,
handoverPersonId: undefined,
receiverPersonId: undefined,
handoverTime: undefined,
}
const formData = reactive<StationHandover>({...defaultFormData})
// 字典选项(下拉框/单选框/复选框关联字典时使用)
// 表单校验规则
const formRules = {}
// 表格列
const columns: DataTableColumns<StationHandover> = [
{type: 'selection'},
{title: '设备/工位', key: 'deviceName'},
{title: '上机人', key: 'handoverPerson'},
{title: '接收人', key: 'receiverPerson'},
{title: '交接时间', key: 'handoverTime'},
{title: '交接备注', key: 'remark'},
{title: '创建时间', key: 'createTime', width: 180},
{
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)}), ' 编辑']
})
])
}
}
]
// 加载数据
async function loadData() {
loading.value = true
try {
const deviceIds = searchForm.deviceIds != null ? JSON.stringify(searchForm.deviceIds) : null
const res = await stationHandoverApi.page({
page: pagination.page,
pageSize: pagination.pageSize,
deviceIds: deviceIds || undefined,
})
tableData.value = res.list
pagination.itemCount = res.total
} finally {
loading.value = false
}
}
// 搜索
function handleSearch() {
pagination.page = 1
loadData()
}
// 重置
function handleReset() {
searchForm.deviceIds = null
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 as number[]
}
// 新增
function handleAdd() {
loadDevice(null)
modalTitle.value = '新增工位交接记录表'
Object.assign(formData, defaultFormData)
modalVisible.value = true
}
// 编辑
function handleEdit(row: StationHandover) {
loadDevice(null)
modalTitle.value = '编辑工位交接记录表'
Object.assign(formData, row)
if (formData.handoverTime && typeof formData.handoverTime === 'string') {
formData.handoverTime = new Date(formData.handoverTime.replace(' ', 'T')).getTime()
}
if (formData.createTime && typeof formData.createTime === 'string') {
formData.createTime = new Date(formData.createTime.replace(' ', 'T')).getTime()
}
modalVisible.value = true
}
// 提交
async function handleSubmit() {
await formRef.value?.validate()
try {
const submitData = {...formData} as StationHandover
if (typeof submitData.handoverTime === 'number') {
submitData.handoverTime = new Date(submitData.handoverTime).toISOString().slice(0, 19).replace('T', ' ')
}
if (typeof submitData.createTime === 'number') {
submitData.createTime = new Date(submitData.createTime).toISOString().slice(0, 19).replace('T', ' ')
}
if (submitData.id) {
await stationHandoverApi.update(submitData)
message.success('修改成功')
} else {
await stationHandoverApi.create(submitData)
message.success('新增成功')
}
modalVisible.value = false
loadData()
} catch (error) {
// 错误已在拦截器处理
}
}
// 删除
function handleDelete(row: StationHandover) {
dialog.warning({
title: '提示',
content: '确定要删除该记录吗?',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: async () => {
try {
await stationHandoverApi.delete([row.id!])
message.success('删除成功')
loadData()
} catch (error) {
// 错误已在拦截器处理
}
}
})
}
// 批量删除
function handleBatchDelete() {
dialog.warning({
title: '提示',
content: `确定要删除选中的 ${selectedIds.value.length} 条记录吗?`,
positiveText: '确定',
negativeText: '取消',
onPositiveClick: async () => {
try {
await stationHandoverApi.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.id != null) params.id = searchForm.id
const blob = await stationHandoverApi.export(params)
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = '工位交接记录表数据.xlsx'
link.click()
window.URL.revokeObjectURL(url)
} catch (error) {
// 错误已在拦截器处理
}
}
// 下载导入模板
async function handleDownloadTemplate() {
try {
const blob = await stationHandoverApi.downloadTemplate()
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.download = '工位交接记录表导入模板.xlsx'
link.click()
window.URL.revokeObjectURL(url)
} catch (error) {
// 错误已在拦截器处理
}
}
// 导入上传
async function handleImportUpload({file}: UploadCustomRequestOptions) {
if (!file.file) return
try {
const result = await stationHandoverApi.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) {
// 错误已在拦截器处理
}
}
// 加载字典选项
async function loadDictOptions() {
}
//加载工段
//加载工段
async function loadSection() {
const data = await deptApi.tree()
sectionList.value = buildOptions(data);
}
function handleUpdateValue(key: any) {
console.log(key);
loadDevice(key)
}
function buildOptions(d: any[]): TreeNode[] {
return d.map(item => {
// 安全id转换0也能正常转为数字不会被||过滤
const idNum = Number(item.id);
const key = !isNaN(idNum) ? idNum : item.id;
return {
label: item.deptName,
key,
// 子节点存在且为数组才递归处理
children: Array.isArray(item.children) && item.children.length
? buildOptions(item.children)
: undefined
};
});
}
//加载设备/工位
async function loadDevice(sectionIdList: number[]) {
const sectionIds = sectionIdList != null ? JSON.stringify(sectionIdList) : null
const res = await deviceApi.getDeviceList({
sectionIds
})
deviceList.value = res.map((n: any) => {
return {
label: n.deviceName,
value: n.id
}
})
}
//加载人员
async function loadUserList() {
const res = await userApi.getPathList();
userList.value = res.map((n: any) => {
return {
label: n.username,
value: n.id
}
})
}
onMounted(() => {
loadData()
loadDictOptions()
loadSection()
loadDevice(null)
loadUserList()
})
</script>
<style scoped>
.search-form {
margin-bottom: 16px;
}
.table-toolbar {
margin-bottom: 16px;
}
</style>