|
@@ -4,7 +4,36 @@ import { useForm } from 'react-hook-form';
|
|
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
|
import { format } from 'date-fns';
|
|
import { format } from 'date-fns';
|
|
|
import { toast } from 'sonner';
|
|
import { toast } from 'sonner';
|
|
|
-import { Search, Edit, Eye, Package, Truck, Check, Printer } from 'lucide-react';
|
|
|
|
|
|
|
+import { Search, Edit, Eye, Package, Truck, Check, Printer, Play } from 'lucide-react';
|
|
|
|
|
+
|
|
|
|
|
+// 获取认证token的工具函数
|
|
|
|
|
+const getAuthToken = (): string | null => {
|
|
|
|
|
+ if (typeof window !== 'undefined') {
|
|
|
|
|
+ return localStorage.getItem('token');
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// 创建带认证头的fetch选项
|
|
|
|
|
+const createAuthFetchOptions = (options: RequestInit = {}): RequestInit => {
|
|
|
|
|
+ const token = getAuthToken();
|
|
|
|
|
+ const headers = new Headers(options.headers);
|
|
|
|
|
+
|
|
|
|
|
+ if (token) {
|
|
|
|
|
+ headers.set('Authorization', `Bearer ${token}`);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 只有POST/PUT/PATCH请求才需要设置Content-Type
|
|
|
|
|
+ const method = options.method?.toUpperCase() || 'GET';
|
|
|
|
|
+ if (['POST', 'PUT', 'PATCH'].includes(method) && !headers.has('Content-Type')) {
|
|
|
|
|
+ headers.set('Content-Type', 'application/json');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return {
|
|
|
|
|
+ ...options,
|
|
|
|
|
+ headers
|
|
|
|
|
+ };
|
|
|
|
|
+};
|
|
|
|
|
|
|
|
|
|
|
|
|
// 使用共享UI组件包的具体路径导入
|
|
// 使用共享UI组件包的具体路径导入
|
|
@@ -90,6 +119,14 @@ interface SubmitPrintTaskResponse {
|
|
|
scheduledAt?: string;
|
|
scheduledAt?: string;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// 触发支付成功事件响应类型
|
|
|
|
|
+interface TriggerPaymentSuccessResponse {
|
|
|
|
|
+ success: boolean;
|
|
|
|
|
+ message: string;
|
|
|
|
|
+ orderId: number;
|
|
|
|
|
+ tenantId: number;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
interface ApiResponse<T> {
|
|
interface ApiResponse<T> {
|
|
|
success: boolean;
|
|
success: boolean;
|
|
|
data?: T;
|
|
data?: T;
|
|
@@ -145,11 +182,8 @@ const sendWechatServiceMessage = async (config: WechatServiceMessageConfig): Pro
|
|
|
console.debug('准备发送微信服务消息:', config);
|
|
console.debug('准备发送微信服务消息:', config);
|
|
|
|
|
|
|
|
// 调用后端微信API
|
|
// 调用后端微信API
|
|
|
- const response = await fetch('/api/v1/auth/send-template-message', {
|
|
|
|
|
|
|
+ const response = await fetch('/api/v1/auth/send-template-message', createAuthFetchOptions({
|
|
|
method: 'POST',
|
|
method: 'POST',
|
|
|
- headers: {
|
|
|
|
|
- 'Content-Type': 'application/json',
|
|
|
|
|
- },
|
|
|
|
|
body: JSON.stringify({
|
|
body: JSON.stringify({
|
|
|
openid: config.openid,
|
|
openid: config.openid,
|
|
|
templateId: config.templateId,
|
|
templateId: config.templateId,
|
|
@@ -157,8 +191,8 @@ const sendWechatServiceMessage = async (config: WechatServiceMessageConfig): Pro
|
|
|
page: config.page || 'pages/index/index',
|
|
page: config.page || 'pages/index/index',
|
|
|
miniprogramState: config.miniprogramState || 'formal',
|
|
miniprogramState: config.miniprogramState || 'formal',
|
|
|
tenantId: config.tenantId
|
|
tenantId: config.tenantId
|
|
|
- }),
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ })
|
|
|
|
|
+ }));
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
if (!response.ok) {
|
|
|
const errorText = await response.text();
|
|
const errorText = await response.text();
|
|
@@ -228,15 +262,12 @@ const getWechatDeliveryCompanies = async (tenantId?: number): Promise<{ success:
|
|
|
console.debug('准备获取微信小店快递公司列表:', { tenantId });
|
|
console.debug('准备获取微信小店快递公司列表:', { tenantId });
|
|
|
|
|
|
|
|
// 调用后端获取快递公司列表API
|
|
// 调用后端获取快递公司列表API
|
|
|
- const response = await fetch('/api/v1/auth/get-delivery-companies', {
|
|
|
|
|
|
|
+ const response = await fetch('/api/v1/auth/get-delivery-companies', createAuthFetchOptions({
|
|
|
method: 'POST',
|
|
method: 'POST',
|
|
|
- headers: {
|
|
|
|
|
- 'Content-Type': 'application/json',
|
|
|
|
|
- },
|
|
|
|
|
body: JSON.stringify({
|
|
body: JSON.stringify({
|
|
|
tenantId
|
|
tenantId
|
|
|
- }),
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ })
|
|
|
|
|
+ }));
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
if (!response.ok) {
|
|
|
const errorText = await response.text();
|
|
const errorText = await response.text();
|
|
@@ -369,11 +400,8 @@ const uploadShippingInfoToWechat = async (
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 调用后端发货信息录入API
|
|
// 调用后端发货信息录入API
|
|
|
- const response = await fetch('/api/v1/auth/upload-shipping-info', {
|
|
|
|
|
|
|
+ const response = await fetch('/api/v1/auth/upload-shipping-info', createAuthFetchOptions({
|
|
|
method: 'POST',
|
|
method: 'POST',
|
|
|
- headers: {
|
|
|
|
|
- 'Content-Type': 'application/json',
|
|
|
|
|
- },
|
|
|
|
|
body: JSON.stringify({
|
|
body: JSON.stringify({
|
|
|
orderId: order.orderNo, // 使用订单号作为微信小程序订单ID
|
|
orderId: order.orderNo, // 使用订单号作为微信小程序订单ID
|
|
|
deliveryType: deliveryData.deliveryType, // 直接使用前端的deliveryType值
|
|
deliveryType: deliveryData.deliveryType, // 直接使用前端的deliveryType值
|
|
@@ -382,8 +410,8 @@ const uploadShippingInfoToWechat = async (
|
|
|
isAllDelivered: true,
|
|
isAllDelivered: true,
|
|
|
itemDesc, // 商品描述,最多110个字
|
|
itemDesc, // 商品描述,最多110个字
|
|
|
tenantId: tenantId || order.tenantId
|
|
tenantId: tenantId || order.tenantId
|
|
|
- }),
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ })
|
|
|
|
|
+ }));
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
if (!response.ok) {
|
|
|
const errorText = await response.text();
|
|
const errorText = await response.text();
|
|
@@ -468,6 +496,8 @@ export const OrderManagement = () => {
|
|
|
const [loadingCompanies, setLoadingCompanies] = useState(false);
|
|
const [loadingCompanies, setLoadingCompanies] = useState(false);
|
|
|
const [printingOrder, setPrintingOrder] = useState<OrderResponse | null>(null);
|
|
const [printingOrder, setPrintingOrder] = useState<OrderResponse | null>(null);
|
|
|
const [isPrinting, setIsPrinting] = useState(false);
|
|
const [isPrinting, setIsPrinting] = useState(false);
|
|
|
|
|
+ const [triggeringOrder, setTriggeringOrder] = useState<OrderResponse | null>(null);
|
|
|
|
|
+ const [isTriggering, setIsTriggering] = useState(false);
|
|
|
|
|
|
|
|
|
|
|
|
|
// 表单实例
|
|
// 表单实例
|
|
@@ -531,15 +561,12 @@ export const OrderManagement = () => {
|
|
|
// 或者使用默认值,这里先使用默认值
|
|
// 或者使用默认值,这里先使用默认值
|
|
|
const tenantId = data?.data?.[0]?.tenantId || undefined;
|
|
const tenantId = data?.data?.[0]?.tenantId || undefined;
|
|
|
|
|
|
|
|
- const response = await fetch('/api/v1/auth/get-is-trade-managed', {
|
|
|
|
|
|
|
+ const response = await fetch('/api/v1/auth/get-is-trade-managed', createAuthFetchOptions({
|
|
|
method: 'POST',
|
|
method: 'POST',
|
|
|
- headers: {
|
|
|
|
|
- 'Content-Type': 'application/json',
|
|
|
|
|
- },
|
|
|
|
|
body: JSON.stringify({
|
|
body: JSON.stringify({
|
|
|
tenantId
|
|
tenantId
|
|
|
- }),
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ })
|
|
|
|
|
+ }));
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
if (!response.ok) {
|
|
|
const errorText = await response.text();
|
|
const errorText = await response.text();
|
|
@@ -896,12 +923,9 @@ const sendDeliverySuccessNotification = async (order: OrderResponse, deliveryDat
|
|
|
try {
|
|
try {
|
|
|
console.debug('获取默认打印机,租户ID:', tenantId);
|
|
console.debug('获取默认打印机,租户ID:', tenantId);
|
|
|
|
|
|
|
|
- const response = await fetch('/api/v1/feie/printers?isDefault=true&pageSize=1', {
|
|
|
|
|
- method: 'GET',
|
|
|
|
|
- headers: {
|
|
|
|
|
- 'Content-Type': 'application/json',
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ const response = await fetch('/api/v1/feie/printers?isDefault=true&pageSize=1', createAuthFetchOptions({
|
|
|
|
|
+ method: 'GET'
|
|
|
|
|
+ }));
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
if (!response.ok) {
|
|
|
const errorText = await response.text();
|
|
const errorText = await response.text();
|
|
@@ -936,12 +960,9 @@ const sendDeliverySuccessNotification = async (order: OrderResponse, deliveryDat
|
|
|
try {
|
|
try {
|
|
|
console.debug('获取打印模板,租户ID:', tenantId);
|
|
console.debug('获取打印模板,租户ID:', tenantId);
|
|
|
|
|
|
|
|
- const response = await fetch('/api/v1/feie/config', {
|
|
|
|
|
- method: 'GET',
|
|
|
|
|
- headers: {
|
|
|
|
|
- 'Content-Type': 'application/json',
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ const response = await fetch('/api/v1/feie/config', createAuthFetchOptions({
|
|
|
|
|
+ method: 'GET'
|
|
|
|
|
+ }));
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
if (!response.ok) {
|
|
|
const errorText = await response.text();
|
|
const errorText = await response.text();
|
|
@@ -969,6 +990,97 @@ const sendDeliverySuccessNotification = async (order: OrderResponse, deliveryDat
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+ // 触发支付成功事件(测试延迟打印)
|
|
|
|
|
+ const handleTriggerPaymentSuccess = async (order: OrderResponse) => {
|
|
|
|
|
+ setTriggeringOrder(order);
|
|
|
|
|
+ setIsTriggering(true);
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ console.debug('触发支付成功事件,订单ID:', order.id, '租户ID:', order.tenantId);
|
|
|
|
|
+
|
|
|
|
|
+ // 尝试不同的API路径
|
|
|
|
|
+ const apiPaths = [
|
|
|
|
|
+ '/api/v1/payments/payment/trigger-success', // 如果路由注册在 /api/v1/payments
|
|
|
|
|
+ '/api/v1/payment/trigger-success' // 如果路由注册在 /api/v1
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
|
|
+ let response: Response | null = null;
|
|
|
|
|
+ let lastError: Error | null = null;
|
|
|
|
|
+ let successfulPath: string | null = null;
|
|
|
|
|
+
|
|
|
|
|
+ for (const apiPath of apiPaths) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ console.debug('尝试API路径:', apiPath);
|
|
|
|
|
+ response = await fetch(apiPath, createAuthFetchOptions({
|
|
|
|
|
+ method: 'POST',
|
|
|
|
|
+ body: JSON.stringify({
|
|
|
|
|
+ orderId: order.id
|
|
|
|
|
+ })
|
|
|
|
|
+ }));
|
|
|
|
|
+
|
|
|
|
|
+ // 如果响应不是404,跳出循环
|
|
|
|
|
+ if (response.status !== 404) {
|
|
|
|
|
+ successfulPath = apiPath;
|
|
|
|
|
+ console.debug(`找到有效API路径: ${apiPath}, 状态码: ${response.status}`);
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ console.debug(`路径 ${apiPath} 返回404,尝试下一个路径`);
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ lastError = error as Error;
|
|
|
|
|
+ console.debug(`路径 ${apiPath} 请求失败:`, error);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!response) {
|
|
|
|
|
+ throw new Error('所有API路径尝试失败');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!response.ok) {
|
|
|
|
|
+ const errorText = await response.text();
|
|
|
|
|
+ console.error('触发支付成功事件失败:', {
|
|
|
|
|
+ status: response.status,
|
|
|
|
|
+ statusText: response.statusText,
|
|
|
|
|
+ error: errorText
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ let errorMessage = `触发失败: ${response.status}`;
|
|
|
|
|
+ try {
|
|
|
|
|
+ const errorData = JSON.parse(errorText);
|
|
|
|
|
+ if (errorData.message) {
|
|
|
|
|
+ errorMessage = errorData.message;
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (e) {
|
|
|
|
|
+ // 如果无法解析为JSON,使用原始文本
|
|
|
|
|
+ if (errorText) {
|
|
|
|
|
+ errorMessage = errorText.substring(0, 200);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ throw new Error(errorMessage);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const result: TriggerPaymentSuccessResponse = await response.json();
|
|
|
|
|
+
|
|
|
|
|
+ if (!result.success) {
|
|
|
|
|
+ throw new Error(result.message || '触发支付成功事件失败');
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ console.debug('触发支付成功事件成功:', {
|
|
|
|
|
+ path: successfulPath,
|
|
|
|
|
+ result
|
|
|
|
|
+ });
|
|
|
|
|
+ toast.success(`支付成功事件已触发: ${result.message} (路径: ${successfulPath})`);
|
|
|
|
|
+
|
|
|
|
|
+ } catch (error: any) {
|
|
|
|
|
+ console.error('触发支付成功事件失败:', error);
|
|
|
|
|
+ toast.error(`触发失败: ${error.message || '未知错误'}`);
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ setIsTriggering(false);
|
|
|
|
|
+ setTriggeringOrder(null);
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
// 处理打印订单
|
|
// 处理打印订单
|
|
|
const handlePrintOrder = async (order: OrderResponse) => {
|
|
const handlePrintOrder = async (order: OrderResponse) => {
|
|
|
setPrintingOrder(order);
|
|
setPrintingOrder(order);
|
|
@@ -1004,13 +1116,10 @@ const sendDeliverySuccessNotification = async (order: OrderResponse, deliveryDat
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
// 使用fetch API提交打印任务
|
|
// 使用fetch API提交打印任务
|
|
|
- const response = await fetch('/api/v1/feie/tasks', {
|
|
|
|
|
|
|
+ const response = await fetch('/api/v1/feie/tasks', createAuthFetchOptions({
|
|
|
method: 'POST',
|
|
method: 'POST',
|
|
|
- headers: {
|
|
|
|
|
- 'Content-Type': 'application/json',
|
|
|
|
|
- },
|
|
|
|
|
body: JSON.stringify(printRequest)
|
|
body: JSON.stringify(printRequest)
|
|
|
- });
|
|
|
|
|
|
|
+ }));
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
if (!response.ok) {
|
|
|
const errorText = await response.text();
|
|
const errorText = await response.text();
|
|
@@ -1410,6 +1519,34 @@ const sendDeliverySuccessNotification = async (order: OrderResponse, deliveryDat
|
|
|
</TableCell>
|
|
</TableCell>
|
|
|
<TableCell className="text-right">
|
|
<TableCell className="text-right">
|
|
|
<div className="flex justify-end gap-2">
|
|
<div className="flex justify-end gap-2">
|
|
|
|
|
+ <Button
|
|
|
|
|
+ variant="ghost"
|
|
|
|
|
+ size="icon"
|
|
|
|
|
+ onClick={() => handlePrintOrder(order)}
|
|
|
|
|
+ disabled={isPrinting && printingOrder?.id === order.id}
|
|
|
|
|
+ data-testid="order-print-button"
|
|
|
|
|
+ >
|
|
|
|
|
+ {isPrinting && printingOrder?.id === order.id ? (
|
|
|
|
|
+ <div className="h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent" />
|
|
|
|
|
+ ) : (
|
|
|
|
|
+ <Printer className="h-4 w-4" />
|
|
|
|
|
+ )}
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ {/* <Button
|
|
|
|
|
+ variant="ghost"
|
|
|
|
|
+ size="icon"
|
|
|
|
|
+ onClick={() => handleTriggerPaymentSuccess(order)}
|
|
|
|
|
+ disabled={isTriggering && triggeringOrder?.id === order.id}
|
|
|
|
|
+ data-testid="order-trigger-payment-button"
|
|
|
|
|
+ title="触发支付成功事件(测试延迟打印)"
|
|
|
|
|
+ >
|
|
|
|
|
+ {isTriggering && triggeringOrder?.id === order.id ? (
|
|
|
|
|
+ <div className="h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent" />
|
|
|
|
|
+ ) : (
|
|
|
|
|
+ <Play className="h-4 w-4" />
|
|
|
|
|
+ )}
|
|
|
|
|
+ </Button> */}
|
|
|
|
|
+
|
|
|
<Button
|
|
<Button
|
|
|
variant="ghost"
|
|
variant="ghost"
|
|
|
size="icon"
|
|
size="icon"
|
|
@@ -1428,19 +1565,7 @@ const sendDeliverySuccessNotification = async (order: OrderResponse, deliveryDat
|
|
|
<Truck className="h-4 w-4" />
|
|
<Truck className="h-4 w-4" />
|
|
|
</Button>
|
|
</Button>
|
|
|
)}
|
|
)}
|
|
|
- <Button
|
|
|
|
|
- variant="ghost"
|
|
|
|
|
- size="icon"
|
|
|
|
|
- onClick={() => handlePrintOrder(order)}
|
|
|
|
|
- disabled={isPrinting && printingOrder?.id === order.id}
|
|
|
|
|
- data-testid="order-print-button"
|
|
|
|
|
- >
|
|
|
|
|
- {isPrinting && printingOrder?.id === order.id ? (
|
|
|
|
|
- <div className="h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent" />
|
|
|
|
|
- ) : (
|
|
|
|
|
- <Printer className="h-4 w-4" />
|
|
|
|
|
- )}
|
|
|
|
|
- </Button>
|
|
|
|
|
|
|
+
|
|
|
<Button
|
|
<Button
|
|
|
variant="ghost"
|
|
variant="ghost"
|
|
|
size="icon"
|
|
size="icon"
|