feat: 添加 SignalR 客户端与订单大厅 API 模块
All checks were successful
Build and Deploy TenantUI / build-and-deploy (push) Successful in 1m59s
All checks were successful
Build and Deploy TenantUI / build-and-deploy (push) Successful in 1m59s
- 安装 @microsoft/signalr 依赖 - 新增 useSignalR Hook(自动重连 + 断线补偿回调) - 新增订单大厅 API 模块(board/stats/pending-since + 接单/拒单/出餐/确认)
This commit is contained in:
100
apps/web-antd/src/api/order-board/index.ts
Normal file
100
apps/web-antd/src/api/order-board/index.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* 文件职责:订单大厅(看板)接口与类型定义。
|
||||
*/
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
/** 订单看板卡片。 */
|
||||
export interface OrderBoardCard {
|
||||
acceptedAt?: null | string;
|
||||
channel: number;
|
||||
createdAt: string;
|
||||
customerName?: null | string;
|
||||
customerPhone?: null | string;
|
||||
deliveryType: number;
|
||||
id: string;
|
||||
isUrged: boolean;
|
||||
itemsSummary?: null | string;
|
||||
orderNo: string;
|
||||
paidAmount: number;
|
||||
queueNumber?: null | string;
|
||||
readyAt?: null | string;
|
||||
status: number;
|
||||
storeId: string;
|
||||
tableNo?: null | string;
|
||||
urgeCount: number;
|
||||
}
|
||||
|
||||
/** 订单看板结果(四列)。 */
|
||||
export interface OrderBoardResult {
|
||||
completed: OrderBoardCard[];
|
||||
delivering: OrderBoardCard[];
|
||||
making: OrderBoardCard[];
|
||||
pending: OrderBoardCard[];
|
||||
}
|
||||
|
||||
/** 订单看板统计。 */
|
||||
export interface OrderBoardStats {
|
||||
completedCount: number;
|
||||
deliveringCount: number;
|
||||
makingCount: number;
|
||||
pendingCount: number;
|
||||
todayTotal: number;
|
||||
}
|
||||
|
||||
/** 获取完整看板数据。 */
|
||||
export async function getOrderBoardApi(params: {
|
||||
channel?: string;
|
||||
storeId: string;
|
||||
}) {
|
||||
return requestClient.get<OrderBoardResult>('/order-board/board', { params });
|
||||
}
|
||||
|
||||
/** 获取看板统计。 */
|
||||
export async function getOrderBoardStatsApi(params: { storeId: string }) {
|
||||
return requestClient.get<OrderBoardStats>('/order-board/stats', { params });
|
||||
}
|
||||
|
||||
/** 重连补偿拉取。 */
|
||||
export async function getPendingSinceApi(params: {
|
||||
since: string;
|
||||
storeId: string;
|
||||
}) {
|
||||
return requestClient.get<OrderBoardCard[]>('/order-board/pending-since', {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/** 接单。 */
|
||||
export async function acceptOrderApi(orderId: string) {
|
||||
return requestClient.post<OrderBoardCard>(
|
||||
`/order-board/${orderId}/accept`,
|
||||
{},
|
||||
);
|
||||
}
|
||||
|
||||
/** 拒单。 */
|
||||
export async function rejectOrderApi(
|
||||
orderId: string,
|
||||
data: { reason: string },
|
||||
) {
|
||||
return requestClient.post<OrderBoardCard>(
|
||||
`/order-board/${orderId}/reject`,
|
||||
data,
|
||||
);
|
||||
}
|
||||
|
||||
/** 出餐完成。 */
|
||||
export async function completePreparationApi(orderId: string) {
|
||||
return requestClient.post<OrderBoardCard>(
|
||||
`/order-board/${orderId}/complete-preparation`,
|
||||
{},
|
||||
);
|
||||
}
|
||||
|
||||
/** 确认送达/取餐。 */
|
||||
export async function confirmDeliveryApi(orderId: string) {
|
||||
return requestClient.post<OrderBoardCard>(
|
||||
`/order-board/${orderId}/confirm-delivery`,
|
||||
{},
|
||||
);
|
||||
}
|
||||
121
apps/web-antd/src/hooks/useSignalR.ts
Normal file
121
apps/web-antd/src/hooks/useSignalR.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* 文件职责:SignalR 连接管理 Hook,提供自动重连、事件订阅、断线补偿能力。
|
||||
*/
|
||||
import type { HubConnection } from '@microsoft/signalr';
|
||||
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { useAppConfig } from '@vben/hooks';
|
||||
import { useAccessStore } from '@vben/stores';
|
||||
|
||||
import {
|
||||
HubConnectionBuilder,
|
||||
HubConnectionState,
|
||||
LogLevel,
|
||||
} from '@microsoft/signalr';
|
||||
|
||||
/** SignalR Hook 配置。 */
|
||||
export interface UseSignalROptions {
|
||||
/** 重连后的补偿回调(传入断线时间戳)。 */
|
||||
onReconnected?: (lastDisconnectedAt: Date) => void;
|
||||
/** 连接关闭回调。 */
|
||||
onClose?: (error?: Error) => void;
|
||||
}
|
||||
|
||||
/** SignalR 连接管理 Hook。 */
|
||||
export function useSignalR(options?: UseSignalROptions) {
|
||||
const isConnected = ref(false);
|
||||
let connection: HubConnection | null = null;
|
||||
let lastDisconnectedAt: Date | null = null;
|
||||
|
||||
// 1. 构建 Hub URL(从 apiURL 去掉 /api/tenant/v1 后缀)
|
||||
function buildHubUrl(): string {
|
||||
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
|
||||
const base = apiURL.replace(/\/api\/tenant\/v\d+\/?$/, '');
|
||||
return `${base}/hubs/order-board`;
|
||||
}
|
||||
|
||||
// 2. 获取 JWT Token
|
||||
function getAccessToken(): string {
|
||||
const accessStore = useAccessStore();
|
||||
return accessStore.accessToken || '';
|
||||
}
|
||||
|
||||
// 3. 建立连接
|
||||
async function connect(storeId?: string): Promise<void> {
|
||||
if (connection?.state === HubConnectionState.Connected) {
|
||||
return;
|
||||
}
|
||||
|
||||
const hubUrl = storeId
|
||||
? `${buildHubUrl()}?storeId=${storeId}`
|
||||
: buildHubUrl();
|
||||
|
||||
connection = new HubConnectionBuilder()
|
||||
.withUrl(hubUrl, {
|
||||
accessTokenFactory: () => getAccessToken(),
|
||||
})
|
||||
.withAutomaticReconnect([0, 2000, 5000, 10_000, 30_000])
|
||||
.configureLogging(LogLevel.Warning)
|
||||
.build();
|
||||
|
||||
// 4. 重连成功回调
|
||||
connection.onreconnecting(() => {
|
||||
isConnected.value = false;
|
||||
lastDisconnectedAt = new Date();
|
||||
});
|
||||
|
||||
connection.onreconnected(() => {
|
||||
isConnected.value = true;
|
||||
if (lastDisconnectedAt && options?.onReconnected) {
|
||||
options.onReconnected(lastDisconnectedAt);
|
||||
}
|
||||
lastDisconnectedAt = null;
|
||||
});
|
||||
|
||||
// 5. 连接关闭回调
|
||||
connection.onclose((error) => {
|
||||
isConnected.value = false;
|
||||
lastDisconnectedAt = new Date();
|
||||
options?.onClose?.(error ?? undefined);
|
||||
});
|
||||
|
||||
await connection.start();
|
||||
isConnected.value = true;
|
||||
}
|
||||
|
||||
// 6. 断开连接
|
||||
async function disconnect(): Promise<void> {
|
||||
if (connection) {
|
||||
await connection.stop();
|
||||
connection = null;
|
||||
isConnected.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 7. 订阅事件
|
||||
function on<T = unknown>(event: string, callback: (data: T) => void): void {
|
||||
connection?.on(event, callback);
|
||||
}
|
||||
|
||||
// 8. 取消订阅
|
||||
function off(event: string): void {
|
||||
connection?.off(event);
|
||||
}
|
||||
|
||||
// 9. 调用 Hub 方法
|
||||
async function invoke(method: string, ...args: unknown[]): Promise<void> {
|
||||
if (connection?.state === HubConnectionState.Connected) {
|
||||
await connection.invoke(method, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
isConnected,
|
||||
connect,
|
||||
disconnect,
|
||||
on,
|
||||
off,
|
||||
invoke,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user