refactor(@vben/web-antd): 工单操作接口按业务线拆分(保洁/安保)

后端 OpsOrderController 移除了 assign/cancel/complete/create 通用接口,
改由 CleanWorkOrderController 和 SecurityOrderController 各自提供 manual-* 系列接口。

- 新增 api/ops/security/index.ts 安保工单操作 API
- cleaning/index.ts 新增 manualCreate/Dispatch/Cancel/UpgradePriority
- order-center/index.ts 移除 assignOrder/cancelOrder
- 表单组件按工单类型路由到对应 API
- cancel-form/upgrade-priority-form 支持 orderType 区分调用路径

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
lzh
2026-03-27 16:45:11 +08:00
parent c6e51a215f
commit 6ca2833df6
7 changed files with 187 additions and 52 deletions

View File

@@ -21,6 +21,29 @@ export namespace OpsCleaningApi {
reason: string; // 升级原因
}
/** 手动创建保洁工单请求 */
export interface ManualCreateReq {
title: string; // 工单标题
description?: string; // 工单描述
priority?: number; // 优先级
areaId: number; // 区域ID
cleaningType?: string; // 保洁类型
expectedDuration?: number; // 预计作业时长(分钟)
}
/** 手动派单请求 */
export interface ManualDispatchReq {
orderId: number; // 工单ID
assigneeId: number; // 目标设备ID工牌设备ID
remark?: string; // 派单备注
}
/** 手动取消工单请求 */
export interface ManualCancelReq {
orderId: number; // 工单ID
reason: string; // 取消原因
}
/** 工牌通知请求 */
export interface DeviceNotifyReq {
badgeId: number; // 工牌设备ID
@@ -143,11 +166,6 @@ export function getIotDeviceProperties(deviceId: number) {
// ==================== 保洁专属接口 ====================
/** 升级工单优先级 (P0 插队) */
export function upgradePriority(data: OpsCleaningApi.UpgradePriorityReq) {
return requestClient.post('/ops/clean/order/upgrade-priority', data);
}
/** 发送工牌通知 (语音/震动) */
export function sendDeviceNotify(data: OpsCleaningApi.DeviceNotifyReq) {
return requestClient.post('/ops/clean/device/notify', data);
@@ -189,3 +207,23 @@ export function getOrderBusinessLogs(orderId: number | string) {
`/ops/order/business-logs/${orderId}`,
);
}
/** 手动创建保洁工单 */
export function manualCreateOrder(data: OpsCleaningApi.ManualCreateReq) {
return requestClient.post<number>('/ops/clean/order/manual-create', data);
}
/** 手动派单(保洁) */
export function manualDispatchOrder(data: OpsCleaningApi.ManualDispatchReq) {
return requestClient.post('/ops/clean/order/manual-dispatch', data);
}
/** 手动取消工单(保洁) */
export function manualCancelOrder(data: OpsCleaningApi.ManualCancelReq) {
return requestClient.post('/ops/clean/order/manual-cancel', data);
}
/** 手动升级优先级(保洁,规范路径) */
export function manualUpgradePriority(data: OpsCleaningApi.UpgradePriorityReq) {
return requestClient.post('/ops/clean/order/manual-upgrade-priority', data);
}

View File

@@ -144,13 +144,6 @@ export namespace OpsOrderCenterApi {
onlineBadgeCount: number; // 在线工牌数量
}
/** 分配工单请求 */
export interface AssignOrderReq {
orderId: number; // 工单ID
assigneeId: number; // 执行人ID
remark?: string; // 备注
}
/** 暂停工单请求 */
export interface PauseOrderReq {
orderId: number; // 工单ID
@@ -164,11 +157,6 @@ export namespace OpsOrderCenterApi {
userId: number; // 操作人ID
}
/** 取消工单请求 */
export interface CancelOrderReq {
id: number; // 工单ID
reason: string; // 取消原因
}
}
// ==================== 工单查询接口 ====================
@@ -322,11 +310,9 @@ export function getTrafficTrend() {
}
// ==================== 工单操作接口 ====================
/** 重新分配/派单 */
export function assignOrder(data: OpsOrderCenterApi.AssignOrderReq) {
return requestClient.post('/ops/order/assign', data);
}
// 注意: assignOrder 和 cancelOrder 已移至各业务线控制器
// 保洁: 使用 cleaning/index.ts 中的 manualDispatchOrder / manualCancelOrder
// 安保: 使用 security/index.ts 中的 manualDispatchSecurityOrder / manualCancelSecurityOrder
/** 暂停工单 */
export function pauseOrder(data: OpsOrderCenterApi.PauseOrderReq) {
@@ -337,8 +323,3 @@ export function pauseOrder(data: OpsOrderCenterApi.PauseOrderReq) {
export function resumeOrder(data: OpsOrderCenterApi.ResumeOrderReq) {
return requestClient.post('/ops/order/resume', data);
}
/** 取消工单 */
export function cancelOrder(data: OpsOrderCenterApi.CancelOrderReq) {
return requestClient.post('/ops/order/cancel', data);
}

View File

@@ -0,0 +1,90 @@
import { requestClient } from '#/api/request';
export namespace OpsSecurityOrderApi {
/** 手动创建安保工单请求 */
export interface ManualCreateReq {
title: string; // 工单标题
description?: string; // 工单描述
priority?: number; // 优先级0=P0紧急 1=P1重要 2=P2普通
areaId: number; // 区域ID
imageUrl?: string; // 相关图片URL
sourceType?: string; // 来源类型ALARM=告警触发/MANUAL=手动创建)
// 以下字段仅用于告警自动触发场景,手动创建时不传
alarmId?: string; // 关联告警ID
alarmType?: string; // 告警类型intrusion/leave_post/fire/fence
cameraId?: string; // 摄像头ID
cameraName?: string; // 摄像头名称
roiId?: string; // ROI区域ID
}
/** 手动派单请求 */
export interface ManualDispatchReq {
orderId: number; // 工单ID
assigneeId: number; // 指定安保人员ID
remark?: string; // 派单备注
}
/** 手动取消工单请求 */
export interface ManualCancelReq {
orderId: number; // 工单ID
reason: string; // 取消原因
}
/** 手动完单请求 */
export interface ManualCompleteReq {
orderId: number; // 工单ID
result: string; // 处理结果描述
resultImgUrls?: string[]; // 处理结果图片URL列表
}
/** 手动升级优先级请求 */
export interface ManualUpgradePriorityReq {
orderId: number; // 工单ID
priority: number; // 目标优先级0=P0, 1=P1, 2=P2
}
}
// ========== 安保工单操作 API ==========
// 后端控制器: SecurityOrderController
// 后端路径前缀: /ops/security/order
/** 手动创建安保工单 */
export function manualCreateSecurityOrder(
data: OpsSecurityOrderApi.ManualCreateReq,
) {
return requestClient.post<number>(
'/ops/security/order/manual-create',
data,
);
}
/** 手动派单(安保) */
export function manualDispatchSecurityOrder(
data: OpsSecurityOrderApi.ManualDispatchReq,
) {
return requestClient.post('/ops/security/order/manual-dispatch', data);
}
/** 手动取消工单(安保) */
export function manualCancelSecurityOrder(
data: OpsSecurityOrderApi.ManualCancelReq,
) {
return requestClient.post('/ops/security/order/manual-cancel', data);
}
/** 手动完单(安保) */
export function manualCompleteSecurityOrder(
data: OpsSecurityOrderApi.ManualCompleteReq,
) {
return requestClient.post('/ops/security/order/manual-complete', data);
}
/** 手动升级优先级(安保) */
export function manualUpgradePrioritySecurityOrder(
data: OpsSecurityOrderApi.ManualUpgradePriorityReq,
) {
return requestClient.post(
'/ops/security/order/manual-upgrade-priority',
data,
);
}

View File

@@ -8,8 +8,7 @@ import { useVbenModal } from '@vben/common-ui';
import { Avatar, Badge, Card, message, Spin } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { getBadgeStatusList } from '#/api/ops/cleaning';
import { assignOrder } from '#/api/ops/order-center';
import { getBadgeStatusList, manualDispatchOrder } from '#/api/ops/cleaning';
defineOptions({ name: 'WorkOrderAssignForm' });
@@ -108,7 +107,7 @@ async function handleSubmit() {
loading.value = true;
try {
const formData = await formApi.getValues();
await assignOrder({
await manualDispatchOrder({
orderId: orderId.value!,
assigneeId: selectedBadgeId.value,
remark: formData.remark,

View File

@@ -6,7 +6,8 @@ import { IconifyIcon } from '@vben/icons';
import { Alert, Input, message } from 'ant-design-vue';
import { cancelOrder } from '#/api/ops/order-center';
import { manualCancelSecurityOrder } from '#/api/ops/security';
import { manualCancelOrder } from '#/api/ops/cleaning';
defineOptions({ name: 'CancelOrderForm' });
@@ -15,12 +16,14 @@ const emit = defineEmits<{ success: [] }>();
interface ModalData {
orderId: number;
orderCode: string;
orderType?: string; // CLEAN | SECURITY
title: string;
}
const modalData = ref<ModalData>({
orderId: 0,
orderCode: '',
orderType: undefined,
title: '',
});
const reason = ref('');
@@ -51,13 +54,25 @@ async function handleSubmit() {
return;
}
if (!modalData.value.orderType) {
message.error('工单类型未知,无法取消');
return;
}
loading.value = true;
modalApi.setState({ confirmLoading: true });
try {
await cancelOrder({
id: modalData.value.orderId,
reason: val,
});
if (modalData.value.orderType === 'SECURITY') {
await manualCancelSecurityOrder({
orderId: modalData.value.orderId,
reason: val,
});
} else {
await manualCancelOrder({
orderId: modalData.value.orderId,
reason: val,
});
}
message.success('工单已取消');
modalApi.close();
emit('success');

View File

@@ -15,7 +15,7 @@ import {
Spin,
} from 'ant-design-vue';
import { assignOrder } from '#/api/ops/order-center';
import { manualDispatchSecurityOrder } from '#/api/ops/security';
import { getUserPage } from '#/api/system/user';
defineOptions({ name: 'SecurityAssignForm' });
@@ -114,7 +114,7 @@ async function handleSubmit() {
loading.value = true;
modalApi.setState({ confirmLoading: true });
try {
await assignOrder({
await manualDispatchSecurityOrder({
orderId: modalData.value.orderId,
assigneeId: selectedUserId.value,
remark: remark.value.trim() || undefined,

View File

@@ -1,12 +1,13 @@
<script setup lang="ts">
import { ref } from 'vue';
import { computed, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { IconifyIcon } from '@vben/icons';
import { Alert, Input, message } from 'ant-design-vue';
import { upgradePriority } from '#/api/ops/cleaning';
import { manualUpgradePriority } from '#/api/ops/cleaning';
import { manualUpgradePrioritySecurityOrder } from '#/api/ops/security';
defineOptions({ name: 'UpgradePriorityForm' });
@@ -15,16 +16,19 @@ const emit = defineEmits<{ success: [] }>();
interface ModalData {
orderId: number;
orderCode: string;
orderType?: string; // CLEAN | SECURITY
currentPriority: number;
}
const modalData = ref<ModalData>({
orderId: 0,
orderCode: '',
orderType: 'CLEAN',
currentPriority: 2,
});
const reason = ref('');
const loading = ref(false);
const isSecurity = computed(() => modalData.value.orderType === 'SECURITY');
const [Modal, modalApi] = useVbenModal({
onOpenChange: (isOpen) => {
@@ -52,22 +56,30 @@ function getPriorityText(priority: number) {
/** 提交表单 */
async function handleSubmit() {
const val = reason.value.trim();
if (val.length < 5) {
message.warning('升级原因至少5个字符');
return;
}
if (val.length > 200) {
message.warning('升级原因不能超过200字符');
return;
// 保洁需要填写升级原因,安保后端不接收 reason 字段
if (!isSecurity.value) {
if (val.length < 5) {
message.warning('升级原因至少5个字符');
return;
}
if (val.length > 200) {
message.warning('升级原因不能超过200字符');
return;
}
}
loading.value = true;
modalApi.setState({ confirmLoading: true });
try {
await upgradePriority({
orderId: modalData.value.orderId,
reason: val,
});
await (modalData.value.orderType === 'SECURITY'
? manualUpgradePrioritySecurityOrder({
orderId: modalData.value.orderId,
priority: 0, // P0紧急
})
: manualUpgradePriority({
orderId: modalData.value.orderId,
reason: val,
}));
message.success('已升级为P0紧急工单');
modalApi.close();
emit('success');
@@ -115,8 +127,8 @@ async function handleSubmit() {
</div>
</div>
<!-- 升级原因 -->
<div class="uf-section">
<!-- 升级原因仅保洁需要 -->
<div v-if="!isSecurity" class="uf-section">
<div class="uf-section-title">升级原因</div>
<Input.TextArea
v-model:value="reason"