402 lines
11 KiB
Vue
402 lines
11 KiB
Vue
<script setup lang="ts">
|
|
import { ref, reactive, onMounted } from 'vue'
|
|
import type { PaginationInfo } from '@/types'
|
|
import { Button, Select, Space, message, Badge, Modal, Input, Form } from 'ant-design-vue'
|
|
import type { ColumnsType } from 'ant-design-vue/es/table'
|
|
import {
|
|
SearchOutlined,
|
|
ReloadOutlined,
|
|
CheckCircleOutlined,
|
|
PlayCircleOutlined,
|
|
CloseCircleOutlined,
|
|
UserOutlined
|
|
} from '@ant-design/icons-vue'
|
|
import {
|
|
getMaintenanceTasks,
|
|
acceptMaintenanceTask,
|
|
startMaintenanceTask,
|
|
completeMaintenanceTask,
|
|
cancelMaintenanceTask,
|
|
type MaintenanceTask,
|
|
type TaskStatus
|
|
} from '@/api/maintenance'
|
|
import { getProjectSelectorList } from '@/api/project'
|
|
|
|
// 任务状态映射
|
|
const statusMap: Record<TaskStatus, { text: string; color: string; status: 'default' | 'processing' | 'success' | 'error' | 'warning' | 'default' }> = {
|
|
PENDING: { text: '待接受', color: 'default', status: 'default' },
|
|
ACCEPTED: { text: '已接受', color: 'blue', status: 'processing' },
|
|
IN_PROGRESS: { text: '进行中', color: 'processing', status: 'processing' },
|
|
COMPLETED: { text: '已完成', color: 'success', status: 'success' },
|
|
CANCELLED: { text: '已取消', color: 'error', status: 'error' }
|
|
}
|
|
|
|
// 表格列定义
|
|
const columns: ColumnsType = [
|
|
{ title: '任务标题', dataIndex: 'title', key: 'title', width: 180 },
|
|
{ title: '所属项目', dataIndex: 'projectName', key: 'projectName', width: 150 },
|
|
{ title: '关联设备', dataIndex: 'equipmentName', key: 'equipmentName', width: 150 },
|
|
{ title: '负责人', dataIndex: 'assigneeName', key: 'assigneeName', width: 100 },
|
|
{ title: '计划日期', dataIndex: 'scheduledDate', key: 'scheduledDate', width: 120 },
|
|
{ title: '开始时间', dataIndex: 'startTime', key: 'startTime', width: 160 },
|
|
{ title: '完成时间', dataIndex: 'completedTime', key: 'completedTime', width: 160 },
|
|
{ title: '状态', dataIndex: 'status', key: 'status', width: 100 },
|
|
{ title: '操作', key: 'action', width: 140, fixed: 'right' as const }
|
|
]
|
|
|
|
// 项目选择选项
|
|
const projectOptions = ref<{ value: string; label: string }[]>([])
|
|
|
|
// 状态选项
|
|
const statusOptions = [
|
|
{ value: 'PENDING', label: '待接受' },
|
|
{ value: 'ACCEPTED', label: '已接受' },
|
|
{ value: 'IN_PROGRESS', label: '进行中' },
|
|
{ value: 'COMPLETED', label: '已完成' },
|
|
{ value: 'CANCELLED', label: '取消' }
|
|
]
|
|
|
|
// 查询参数
|
|
const queryParams = reactive({
|
|
projectId: '',
|
|
status: undefined as TaskStatus | undefined,
|
|
assigneeId: undefined as string | undefined
|
|
})
|
|
|
|
// 数据状态
|
|
const loading = ref(false)
|
|
const tableData = ref<MaintenanceTask[]>([])
|
|
const pagination = reactive({
|
|
current: 1,
|
|
pageSize: 10,
|
|
total: 0
|
|
})
|
|
|
|
// 取消模态框
|
|
const cancelModalVisible = ref(false)
|
|
const cancelReason = ref('')
|
|
const cancellingTaskId = ref<string | null>(null)
|
|
|
|
// 完成模态框
|
|
const completeModalVisible = ref(false)
|
|
const completionNotes = ref('')
|
|
const completingTaskId = ref<string | null>(null)
|
|
|
|
// 获取项目列表
|
|
const fetchProjects = async () => {
|
|
try {
|
|
const res = await getProjectSelectorList()
|
|
projectOptions.value = (res.data.data || []).map((item: any) => ({
|
|
value: item.id,
|
|
label: item.name
|
|
}))
|
|
} catch {
|
|
message.error('获取项目列表失败')
|
|
}
|
|
}
|
|
|
|
// 获取维保任务列表
|
|
const fetchTaskList = async () => {
|
|
if (!queryParams.projectId) {
|
|
message.warning('请先选择项目')
|
|
return
|
|
}
|
|
loading.value = true
|
|
try {
|
|
const res = await getMaintenanceTasks({
|
|
projectId: queryParams.projectId,
|
|
status: queryParams.status,
|
|
assigneeId: queryParams.assigneeId
|
|
})
|
|
const data = res.data.data || []
|
|
tableData.value = data
|
|
pagination.total = data.length
|
|
} catch {
|
|
message.error('获取维保任务列表失败')
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
// 搜索
|
|
const handleSearch = () => {
|
|
pagination.current = 1
|
|
fetchTaskList()
|
|
}
|
|
|
|
// 重置
|
|
const handleReset = () => {
|
|
queryParams.projectId = ''
|
|
queryParams.status = undefined
|
|
queryParams.assigneeId = undefined
|
|
pagination.current = 1
|
|
tableData.value = []
|
|
}
|
|
|
|
// 分页变化
|
|
const handlePageChange = (page: number, pageSize: number) => {
|
|
pagination.current = page
|
|
pagination.pageSize = pageSize
|
|
fetchTaskList()
|
|
}
|
|
|
|
// 接受任务
|
|
const handleAccept = async (id: string) => {
|
|
try {
|
|
await acceptMaintenanceTask(id, 'current-user-id')
|
|
message.success('任务已接受')
|
|
fetchTaskList()
|
|
} catch {
|
|
message.error('接受任务失败')
|
|
}
|
|
}
|
|
|
|
// 开始任务
|
|
const handleStart = async (id: string) => {
|
|
try {
|
|
await startMaintenanceTask(id)
|
|
message.success('任务已开始')
|
|
fetchTaskList()
|
|
} catch {
|
|
message.error('开始任务失败')
|
|
}
|
|
}
|
|
|
|
// 打开完成模态框
|
|
const handleOpenComplete = (id: string) => {
|
|
completingTaskId.value = id
|
|
completionNotes.value = ''
|
|
completeModalVisible.value = true
|
|
}
|
|
|
|
// 完成任务
|
|
const handleComplete = async () => {
|
|
if (!completingTaskId.value) return
|
|
try {
|
|
await completeMaintenanceTask(completingTaskId.value, { completionNotes: completionNotes.value })
|
|
message.success('任务已完成')
|
|
completeModalVisible.value = false
|
|
completingTaskId.value = null
|
|
completionNotes.value = ''
|
|
fetchTaskList()
|
|
} catch {
|
|
message.error('完成任务失败')
|
|
}
|
|
}
|
|
|
|
// 打开取消模态框
|
|
const handleOpenCancel = (id: string) => {
|
|
cancellingTaskId.value = id
|
|
cancelReason.value = ''
|
|
cancelModalVisible.value = true
|
|
}
|
|
|
|
// 取消任务
|
|
const handleCancel = async () => {
|
|
if (!cancellingTaskId.value) return
|
|
try {
|
|
await cancelMaintenanceTask(cancellingTaskId.value)
|
|
message.success('任务已取消')
|
|
cancelModalVisible.value = false
|
|
cancellingTaskId.value = null
|
|
cancelReason.value = ''
|
|
fetchTaskList()
|
|
} catch {
|
|
message.error('取消任务失败')
|
|
}
|
|
}
|
|
|
|
// 格式化日期时间
|
|
const formatDateTime = (date: string | Date | undefined) => {
|
|
if (!date) return '-'
|
|
const d = new Date(date)
|
|
const year = d.getFullYear()
|
|
const month = String(d.getMonth() + 1).padStart(2, '0')
|
|
const day = String(d.getDate()).padStart(2, '0')
|
|
const hour = String(d.getHours()).padStart(2, '0')
|
|
const minute = String(d.getMinutes()).padStart(2, '0')
|
|
return `${year}-${month}-${day} ${hour}:${minute}`
|
|
}
|
|
|
|
onMounted(() => {
|
|
fetchProjects()
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div class="page-container">
|
|
<!-- 页面标题 -->
|
|
<div class="page-header">
|
|
<h2 class="page-title">维保任务</h2>
|
|
</div>
|
|
|
|
<!-- 筛选区 -->
|
|
<div class="filter-bar">
|
|
<Space>
|
|
<Select
|
|
v-model:value="queryParams.projectId"
|
|
placeholder="请选择项目"
|
|
style="width: 240px"
|
|
allow-clear
|
|
:options="projectOptions"
|
|
/>
|
|
<Select
|
|
v-model:value="queryParams.status"
|
|
placeholder="任务状态"
|
|
style="width: 120px"
|
|
allow-clear
|
|
:options="statusOptions"
|
|
/>
|
|
<Button type="primary" @click="handleSearch">
|
|
<SearchOutlined /> 查询
|
|
</Button>
|
|
<Button @click="handleReset">
|
|
<ReloadOutlined /> 重置
|
|
</Button>
|
|
</Space>
|
|
</div>
|
|
|
|
<!-- 表格区 -->
|
|
<div class="table-card">
|
|
<a-table
|
|
:columns="columns"
|
|
:data-source="tableData"
|
|
:loading="loading"
|
|
:row-key="(record: MaintenanceTask) => record.id"
|
|
:pagination="{
|
|
current: pagination.current,
|
|
pageSize: pagination.pageSize,
|
|
total: pagination.total,
|
|
showSizeChanger: true,
|
|
showQuickJumper: true,
|
|
showTotal: (total: number) => `共 ${total} 条`
|
|
}"
|
|
@change="(pag: PaginationInfo) => handlePageChange(pag.current, pag.pageSize)"
|
|
>
|
|
<template #bodyCell="{ column, record }">
|
|
<template v-if="column.key === 'assigneeName'">
|
|
<Space>
|
|
<UserOutlined />
|
|
{{ record.assigneeName || '-' }}
|
|
</Space>
|
|
</template>
|
|
<template v-else-if="column.key === 'scheduledDate'">
|
|
{{ record.scheduledDate || '-' }}
|
|
</template>
|
|
<template v-else-if="column.key === 'startTime'">
|
|
{{ formatDateTime(record.startTime) }}
|
|
</template>
|
|
<template v-else-if="column.key === 'completedTime'">
|
|
{{ formatDateTime(record.completedTime) }}
|
|
</template>
|
|
<template v-else-if="column.key === 'status'">
|
|
<Badge
|
|
:status="statusMap[record.status as TaskStatus]?.status"
|
|
:text="statusMap[record.status as TaskStatus]?.text"
|
|
/>
|
|
</template>
|
|
<template v-else-if="column.key === 'action'">
|
|
<Space>
|
|
<!-- 待接受状态显示接受按钮 -->
|
|
<Button
|
|
v-if="record.status === 'PENDING'"
|
|
type="link"
|
|
size="small"
|
|
@click="handleAccept(record.id)"
|
|
>
|
|
<CheckCircleOutlined /> 接受
|
|
</Button>
|
|
<!-- 已接受状态显示开始按钮 -->
|
|
<Button
|
|
v-if="record.status === 'ACCEPTED'"
|
|
type="link"
|
|
size="small"
|
|
@click="handleStart(record.id)"
|
|
>
|
|
<PlayCircleOutlined /> 开始
|
|
</Button>
|
|
<!-- 进行中状态显示完成按钮 -->
|
|
<Button
|
|
v-if="record.status === 'IN_PROGRESS'"
|
|
type="link"
|
|
size="small"
|
|
@click="handleOpenComplete(record.id)"
|
|
>
|
|
<CheckCircleOutlined /> 完成
|
|
</Button>
|
|
<!-- 待接受、已接受、进行中状态显示取消按钮 -->
|
|
<Button
|
|
v-if="['PENDING', 'ACCEPTED', 'IN_PROGRESS'].includes(record.status)"
|
|
type="link"
|
|
size="small"
|
|
danger
|
|
@click="handleOpenCancel(record.id)"
|
|
>
|
|
<CloseCircleOutlined /> 取消
|
|
</Button>
|
|
</Space>
|
|
</template>
|
|
</template>
|
|
</a-table>
|
|
|
|
<!-- 未选择项目提示 -->
|
|
<a-empty v-if="!queryParams.projectId && tableData.length === 0" description="请先选择项目" />
|
|
</div>
|
|
|
|
<!-- 取消任务模态框 -->
|
|
<Modal
|
|
v-model:open="cancelModalVisible"
|
|
title="取消任务"
|
|
@ok="handleCancel"
|
|
>
|
|
<p>确定要取消该维保任务吗?</p>
|
|
</Modal>
|
|
|
|
<!-- 完成任务模态框 -->
|
|
<Modal
|
|
v-model:open="completeModalVisible"
|
|
title="完成任务"
|
|
@ok="handleComplete"
|
|
>
|
|
<Form layout="vertical">
|
|
<Form.Item label="完成备注">
|
|
<Input.TextArea
|
|
v-model:value="completionNotes"
|
|
placeholder="请输入完成备注"
|
|
:rows="4"
|
|
/>
|
|
</Form.Item>
|
|
</Form>
|
|
</Modal>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.page-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.page-title {
|
|
margin: 0;
|
|
font-size: 20px;
|
|
font-weight: 500;
|
|
color: #262626;
|
|
}
|
|
|
|
.filter-bar {
|
|
margin-bottom: 24px;
|
|
padding: 16px;
|
|
background: #fafafa;
|
|
border-radius: 8px;
|
|
}
|
|
|
|
.table-card {
|
|
background: #fff;
|
|
border-radius: 8px;
|
|
padding: 16px;
|
|
}
|
|
</style>
|