feat: 文档整理+前端修复

- 工单分页查询适配(PageResponse/服务端分页)
- 前后端枚举统一(TriggerType/TaskStatus/PlanStatus)
- Permission类型增加菜单路由属性
- SpaceNode类型增加code字段
- WorkOrder状态机补全(SUSPENDED/RETURNED)
- Dashboard适配分页查询
This commit is contained in:
chiguyong 2026-05-18 10:46:45 +08:00
parent 72f7c891f3
commit 50760cde6e
11 changed files with 193 additions and 58 deletions

View File

@ -3,7 +3,7 @@ import type { ApiResponse } from '@/types'
// ==================== 维保计划类型 ====================
export type PlanType = 'PREVENTIVE' | 'CORRECTIVE'
export type PlanStatus = 'ACTIVE' | 'INACTIVE'
export type PlanStatus = 'ACTIVE' | 'INACTIVE' | 'SUSPENDED'
export interface MaintenancePlan {
id?: string
@ -71,5 +71,6 @@ export const PLAN_TYPE_OPTIONS = [
export const PLAN_STATUS_OPTIONS = [
{ value: 'ACTIVE', label: '启用' },
{ value: 'INACTIVE', label: '停用' }
{ value: 'INACTIVE', label: '停用' },
{ value: 'SUSPENDED', label: '暂停' }
]

View File

@ -8,7 +8,7 @@ export interface MaintenancePlan {
name: string
projectId: string
projectName?: string
triggerType: 'MANUAL' | 'SCHEDULED' | 'AUTOMATIC'
triggerType: 'PLAN' | 'INSPECTION' | 'FAULT' | 'MANUAL'
equipmentId?: string
equipmentName?: string
spaceNodeId?: string
@ -25,7 +25,7 @@ export interface MaintenancePlanForm {
id?: string
name: string
projectId: string
triggerType: 'MANUAL' | 'SCHEDULED' | 'AUTOMATIC'
triggerType: 'PLAN' | 'INSPECTION' | 'FAULT' | 'MANUAL'
equipmentId?: string
spaceNodeId?: string
description?: string
@ -35,7 +35,7 @@ export interface MaintenancePlanForm {
// ==================== 维保任务相关类型 ====================
export type TaskStatus = 'PENDING' | 'ACCEPTED' | 'IN_PROGRESS' | 'COMPLETED' | 'CANCELLED'
export type TaskStatus = 'PENDING' | 'ASSIGNED' | 'IN_PROGRESS' | 'COMPLETED' | 'VERIFIED' | 'CANCELLED'
export interface MaintenanceTask {
id: string

View File

@ -5,7 +5,7 @@ import type { ApiResponse } from '@/types'
export type WorkOrderSource = 'OWNER' | 'MAINTENANCE' | 'INSPECTION' | 'FAULT' | 'REGULATORY' | 'MANUAL'
export type WorkOrderType = 'REPAIR' | 'INSPECTION' | 'SECURITY' | 'CLEANING' | 'PROPERTY' | 'CONSULTATION'
export type WorkOrderPriority = 'LOW' | 'MEDIUM' | 'HIGH' | 'URGENT'
export type WorkOrderStatus = 'PENDING' | 'ASSIGNED' | 'IN_PROGRESS' | 'COMPLETED' | 'VERIFIED' | 'CANCELLED'
export type WorkOrderStatus = 'PENDING' | 'ASSIGNED' | 'IN_PROGRESS' | 'SUSPENDED' | 'RETURNED' | 'COMPLETED' | 'VERIFIED' | 'CANCELLED'
export type TriggerType = 'PLAN' | 'INSPECTION' | 'FAULT' | 'MANUAL'
export interface WorkOrderItem {
@ -70,6 +70,8 @@ export interface WorkOrderStats {
completed: number
verified: number
cancelled: number
suspended: number
returned: number
completedToday: number
createdToday: number
overdue: number
@ -80,6 +82,16 @@ export interface WorkOrderStats {
byPriority: Record<string, number>
}
export interface PageResponse<T> {
content: T[]
page: number
size: number
totalElements: number
totalPages: number
first: boolean
last: boolean
}
// ==================== API 函数 ====================
export function getWorkOrders(params?: {
projectId?: string
@ -87,9 +99,13 @@ export function getWorkOrders(params?: {
source?: WorkOrderSource
type?: WorkOrderType
status?: WorkOrderStatus
priority?: WorkOrderPriority
assignedTo?: string
keyword?: string
page?: number
size?: number
}) {
return request.get<ApiResponse<WorkOrder[]>>('/api/wo/work-orders', { params })
return request.get<ApiResponse<PageResponse<WorkOrder>>>('/api/wo/work-orders', { params })
}
export function getWorkOrder(id: string) {
@ -128,6 +144,18 @@ export function cancelWorkOrder(id: string) {
return request.post(`/api/wo/work-orders/${id}/cancel`)
}
export function suspendWorkOrder(id: string) {
return request.post(`/api/wo/work-orders/${id}/suspend`)
}
export function resumeWorkOrder(id: string) {
return request.post(`/api/wo/work-orders/${id}/resume`)
}
export function returnWorkOrder(id: string) {
return request.post(`/api/wo/work-orders/${id}/return`)
}
export function getWorkOrderStats() {
return request.get<ApiResponse<WorkOrderStats>>('/api/wo/work-orders/stats')
}
@ -170,6 +198,8 @@ export const STATUS_OPTIONS = [
{ value: 'PENDING', label: '待分配' },
{ value: 'ASSIGNED', label: '已派单' },
{ value: 'IN_PROGRESS', label: '执行中' },
{ value: 'SUSPENDED', label: '已挂起' },
{ value: 'RETURNED', label: '已退回' },
{ value: 'COMPLETED', label: '已完成' },
{ value: 'VERIFIED', label: '已验收' },
{ value: 'CANCELLED', label: '已取消' }
@ -179,6 +209,8 @@ export const STATUS_COLOR_MAP: Record<WorkOrderStatus, string> = {
'PENDING': 'default',
'ASSIGNED': 'blue',
'IN_PROGRESS': 'orange',
'SUSPENDED': 'purple',
'RETURNED': 'volcano',
'COMPLETED': 'green',
'VERIFIED': 'cyan',
'CANCELLED': 'red'

View File

@ -81,10 +81,14 @@ export interface Permission {
code: string
name: string
type: string
path?: string
component?: string
icon?: string
resource?: string
method?: string
description?: string
sortOrder?: number
visible?: boolean
}
export interface Project {

View File

@ -87,6 +87,7 @@ export interface SpaceNodeTree extends SpaceNode {
export interface SpaceNodeCreateForm {
projectId: string
code?: string
name: string
fullName?: string
shortName?: string

View File

@ -116,9 +116,12 @@ const fetchDashboardData = async () => {
//
const workOrderRes = await getWorkOrders({
status: 'PENDING'
}).catch(() => ({ data: { data: [] } }))
pendingWorkOrders.value = (workOrderRes.data?.data || []).slice(0, 5)
status: 'PENDING',
page: 0,
size: 5
}).catch(() => ({ data: { data: { content: [], totalElements: 0 } } }))
const workOrderPageData = workOrderRes.data?.data
pendingWorkOrders.value = (workOrderPageData?.content || workOrderPageData || []).slice(0, 5)
// 7
const today = new Date()

View File

@ -54,7 +54,9 @@ const getPlanStatusLabel = (status: PlanStatus | undefined) => {
//
const getPlanStatusColor = (status: PlanStatus | undefined) => {
if (!status) return 'default'
return status === 'ACTIVE' ? 'green' : 'red'
if (status === 'ACTIVE') return 'green'
if (status === 'SUSPENDED') return 'orange'
return 'red'
}
//

View File

@ -21,9 +21,10 @@ import { getProjectSelectorList } from '@/api/project'
//
const triggerTypeMap: Record<string, { text: string; color: string }> = {
MANUAL: { text: '手动', color: 'default' },
SCHEDULED: { text: '定时', color: 'blue' },
AUTOMATIC: { text: '自动', color: 'green' }
PLAN: { text: '计划触发', color: 'blue' },
INSPECTION: { text: '巡检触发', color: 'cyan' },
FAULT: { text: '故障触发', color: 'red' },
MANUAL: { text: '手动创建', color: 'default' }
}
//
@ -43,9 +44,10 @@ const projectOptions = ref<{ value: string; label: string }[]>([])
//
const triggerTypeOptions = [
{ value: 'MANUAL', label: '手动' },
{ value: 'SCHEDULED', label: '定时' },
{ value: 'AUTOMATIC', label: '自动' }
{ value: 'PLAN', label: '计划触发' },
{ value: 'INSPECTION', label: '巡检触发' },
{ value: 'FAULT', label: '故障触发' },
{ value: 'MANUAL', label: '手动创建' }
]
//
@ -73,7 +75,7 @@ const editingPlan = ref<MaintenancePlan | null>(null)
const formState = reactive<Record<string, any>>({
name: '',
projectId: '',
triggerType: 'MANUAL',
triggerType: 'PLAN',
equipmentId: undefined,
spaceNodeId: undefined,
description: '',
@ -140,7 +142,7 @@ const handleAdd = () => {
modalTitle.value = '新建维保计划'
formState.name = ''
formState.projectId = queryParams.projectId
formState.triggerType = 'MANUAL'
formState.triggerType = 'PLAN'
formState.equipmentId = undefined
formState.spaceNodeId = undefined
formState.description = ''

View File

@ -24,10 +24,11 @@ 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' },
PENDING: { text: '待分配', color: 'default', status: 'default' },
ASSIGNED: { text: '已派单', color: 'blue', status: 'processing' },
IN_PROGRESS: { text: '进行中', color: 'processing', status: 'processing' },
COMPLETED: { text: '已完成', color: 'success', status: 'success' },
VERIFIED: { text: '已验收', color: 'success', status: 'success' },
CANCELLED: { text: '已取消', color: 'error', status: 'error' }
}
@ -49,11 +50,12 @@ const projectOptions = ref<{ value: string; label: string }[]>([])
//
const statusOptions = [
{ value: 'PENDING', label: '待接受' },
{ value: 'ACCEPTED', label: '已接受' },
{ value: 'PENDING', label: '待分配' },
{ value: 'ASSIGNED', label: '已派单' },
{ value: 'IN_PROGRESS', label: '进行中' },
{ value: 'COMPLETED', label: '已完成' },
{ value: 'CANCELLED', label: '取消' }
{ value: 'VERIFIED', label: '已验收' },
{ value: 'CANCELLED', label: '已取消' }
]
//
@ -304,11 +306,10 @@ onMounted(() => {
size="small"
@click="handleAccept(record.id)"
>
<CheckCircleOutlined /> 接受
<CheckCircleOutlined /> 派单
</Button>
<!-- 已接受状态显示开始按钮 -->
<Button
v-if="record.status === 'ACCEPTED'"
v-if="record.status === 'ASSIGNED'"
type="link"
size="small"
@click="handleStart(record.id)"
@ -324,9 +325,8 @@ onMounted(() => {
>
<CheckCircleOutlined /> 完成
</Button>
<!-- 待接受已接受进行中状态显示取消按钮 -->
<Button
v-if="['PENDING', 'ACCEPTED', 'IN_PROGRESS'].includes(record.status)"
v-if="['PENDING', 'ASSIGNED', 'IN_PROGRESS'].includes(record.status)"
type="link"
size="small"
danger

View File

@ -14,7 +14,9 @@ import {
PlayCircleOutlined,
SendOutlined,
EyeOutlined,
SafetyCertificateOutlined
SafetyCertificateOutlined,
PauseCircleOutlined,
RollbackOutlined
} from '@ant-design/icons-vue'
import {
getWorkOrders,
@ -27,6 +29,9 @@ import {
completeWorkOrder,
verifyWorkOrder,
cancelWorkOrder,
suspendWorkOrder,
resumeWorkOrder,
returnWorkOrder,
getWorkOrderStats,
getWorkOrderItems,
addWorkOrderItems,
@ -42,7 +47,8 @@ import {
type WorkOrderSource,
type WorkOrderType,
type WorkOrderPriority,
type WorkOrderStatus
type WorkOrderStatus,
type PageResponse
} from '@/api/work-order'
import { getProjectSelectorList } from '@/api/project'
import { getEquipmentList } from '@/api/equipment'
@ -120,6 +126,8 @@ const stats = reactive({
completed: 0,
verified: 0,
cancelled: 0,
suspended: 0,
returned: 0,
completedToday: 0,
createdToday: 0,
overdue: 0,
@ -141,6 +149,8 @@ const fetchStats = async () => {
stats.completed = data.completed || 0
stats.verified = data.verified || 0
stats.cancelled = data.cancelled || 0
stats.suspended = data.suspended || 0
stats.returned = data.returned || 0
stats.completedToday = data.completedToday || 0
stats.createdToday = data.createdToday || 0
stats.overdue = data.overdue || 0
@ -190,34 +200,17 @@ const fetchWorkOrders = async () => {
projectId: queryParams.projectId,
source: queryParams.source,
type: queryParams.type,
status: queryParams.status
status: queryParams.status,
priority: queryParams.priority,
keyword: queryParams.keyword || undefined,
page: pagination.current - 1,
size: pagination.pageSize
})
const apiRes = response.data
let data: WorkOrder[] = apiRes.data || apiRes || []
const pageData: PageResponse<WorkOrder> = apiRes.data || apiRes
//
if (queryParams.source) {
data = data.filter((t: WorkOrder) => t.source === queryParams.source)
}
if (queryParams.type) {
data = data.filter((t: WorkOrder) => t.type === queryParams.type)
}
if (queryParams.status) {
data = data.filter((t: WorkOrder) => t.status === queryParams.status)
}
if (queryParams.priority) {
data = data.filter((t: WorkOrder) => t.priority === queryParams.priority)
}
if (queryParams.keyword) {
const kw = queryParams.keyword.toLowerCase()
data = data.filter((t: WorkOrder) =>
t.workNo?.toLowerCase().includes(kw) ||
t.title?.toLowerCase().includes(kw)
)
}
tableData.value = data
pagination.total = data.length
tableData.value = pageData.content || []
pagination.total = pageData.totalElements || 0
} catch {
message.error('获取工单列表失败')
} finally {
@ -247,6 +240,7 @@ const handleReset = () => {
const handlePageChange = (page: number, pageSize: number) => {
pagination.current = page
pagination.pageSize = pageSize
fetchWorkOrders()
}
//
@ -614,6 +608,58 @@ const handleCancel = (record: WorkOrder) => {
})
}
// ========== ==========
const handleSuspend = (record: WorkOrder) => {
Modal.confirm({
title: '挂起工单',
content: '确定要挂起此工单吗?挂起后可随时恢复。',
onOk: async () => {
try {
await suspendWorkOrder(record.id!)
message.success('工单已挂起')
fetchWorkOrders()
} catch (error: unknown) {
message.error(getErrorMessage(error))
}
}
})
}
// ========== ==========
const handleResume = (record: WorkOrder) => {
Modal.confirm({
title: '恢复工单',
content: '确定要恢复此工单吗?',
onOk: async () => {
try {
await resumeWorkOrder(record.id!)
message.success('工单已恢复')
fetchWorkOrders()
} catch (error: unknown) {
message.error(getErrorMessage(error))
}
}
})
}
// ========== 退 ==========
const handleReturn = (record: WorkOrder) => {
Modal.confirm({
title: '退回工单',
content: '确定要退回此工单吗?退回后工单将变为待分配状态。',
okType: 'danger',
onOk: async () => {
try {
await returnWorkOrder(record.id!)
message.success('工单已退回')
fetchWorkOrders()
} catch (error: unknown) {
message.error(getErrorMessage(error))
}
}
})
}
onMounted(() => {
fetchProjects()
fetchWorkOrders()
@ -783,18 +829,32 @@ onMounted(() => {
<Button type="link" size="small" @click="handleStart(record)">
<PlayCircleOutlined /> 开始
</Button>
<Button type="link" size="small" @click="handleSuspend(record)">
<PauseCircleOutlined /> 挂起
</Button>
<Button type="link" size="small" @click="handleReturn(record)">
<RollbackOutlined /> 退回
</Button>
</template>
<template v-if="record.status === 'IN_PROGRESS'">
<Button type="link" size="small" @click="openCompleteDrawer(record)">
<CheckCircleOutlined /> 完成
</Button>
<Button type="link" size="small" @click="handleSuspend(record)">
<PauseCircleOutlined /> 挂起
</Button>
</template>
<template v-if="record.status === 'SUSPENDED'">
<Button type="link" size="small" @click="handleResume(record)">
<PlayCircleOutlined /> 恢复
</Button>
</template>
<template v-if="record.status === 'COMPLETED'">
<Button type="link" size="small" @click="openVerifyDrawer(record)">
<SafetyCertificateOutlined /> 验收
</Button>
</template>
<template v-if="['PENDING', 'ASSIGNED', 'IN_PROGRESS'].includes(record.status)">
<template v-if="['PENDING', 'ASSIGNED', 'IN_PROGRESS', 'SUSPENDED'].includes(record.status)">
<Button type="link" danger size="small" @click="handleCancel(record)">
<CloseCircleOutlined /> 取消
</Button>

View File

@ -92,9 +92,14 @@ const formState = reactive({
code: '',
name: '',
type: 'MENU',
path: '',
component: '',
icon: '',
resource: '',
method: 'GET',
description: ''
description: '',
sortOrder: 0,
visible: true
})
/**
@ -175,9 +180,14 @@ const resetForm = () => {
formState.code = ''
formState.name = ''
formState.type = 'MENU'
formState.path = ''
formState.component = ''
formState.icon = ''
formState.resource = ''
formState.method = 'GET'
formState.description = ''
formState.sortOrder = 0
formState.visible = true
editingId.value = null
}
@ -201,9 +211,14 @@ const handleEdit = (record: Permission) => {
formState.code = record.code
formState.name = record.name
formState.type = record.type
formState.path = record.path || ''
formState.component = record.component || ''
formState.icon = record.icon || ''
formState.resource = record.resource || ''
formState.method = record.method || 'GET'
formState.description = record.description || ''
formState.sortOrder = record.sortOrder ?? 0
formState.visible = record.visible ?? true
drawerTitle.value = '编辑权限'
drawerVisible.value = true
}
@ -390,6 +405,21 @@ onMounted(fetchPermissions)
<Form.Item label="类型" name="type">
<a-select v-model:value="formState.type" :options="typeOptions" />
</Form.Item>
<Form.Item v-if="formState.type === 'MENU'" label="路由路径" name="path">
<a-input v-model:value="formState.path" placeholder="如: /system/users" />
</Form.Item>
<Form.Item v-if="formState.type === 'MENU'" label="组件路径" name="component">
<a-input v-model:value="formState.component" placeholder="如: system/Users" />
</Form.Item>
<Form.Item v-if="formState.type === 'MENU'" label="菜单图标" name="icon">
<a-input v-model:value="formState.icon" placeholder="如: SettingOutlined" />
</Form.Item>
<Form.Item v-if="formState.type === 'MENU'" label="排序序号" name="sortOrder">
<a-input-number v-model:value="formState.sortOrder" :min="0" style="width: 100%" />
</Form.Item>
<Form.Item v-if="formState.type === 'MENU'" label="是否可见" name="visible">
<a-switch v-model:checked="formState.visible" />
</Form.Item>
<Form.Item label="资源路径" name="resource">
<a-input v-model:value="formState.resource" placeholder="如: /api/users" />
</Form.Item>