Просмотр исходного кода

♻️ refactor(order): 优化订单取消逻辑和测试数据

- 移除订单详情查询缓存失效,减少不必要的网络请求
- 更新测试中API路径从`cancelOrder`改为`cancel-order`以匹配后端接口
- 扩展测试订单对象,包含完整的订单字段以支持更全面的测试场景

✨ feat(printer): 增强飞鹅打印功能集成

- 添加控制台日志输出以调试打印机列表和任务查询
- 重构打印配置管理组件,移除基础配置和即时保存模式
- 新增从JWT token解析租户ID的功能,支持自动租户识别
- 优化配置数据获取逻辑,支持多种响应格式处理
- 改进配置更新后的数据同步机制,确保UI状态一致性

🔧 chore(printer): 更新打印任务查询组件

- 优化租户ID获取逻辑,支持从token自动解析
- 调整打印任务查询参数处理,确保搜索功能正常工作
- 更新任务重试和取消接口调用方式,适配后端API变更
- 改进任务详情显示,适配新的字段命名

🐛 fix(printer): 修复飞鹅API集成问题

- 更新飞鹅API基础URL为HTTPS协议
- 增强API响应解析逻辑,支持JSON和PHP var_dump格式
- 改进打印机配置验证流程,提供更详细的错误信息
- 修复打印机添加和状态查询的API调用参数

♻️ refactor(printer): 重构打印配置和任务管理

- 移除未使用的打印配置项,简化配置界面
- 优化打印机管理组件,改进租户ID处理逻辑
- 重构打印任务服务,简化重试和超时配置管理
- 改进延迟调度器服务,支持租户特定的任务处理

✨ feat(printer): 增强打印触发服务

- 集成订单模块,支持从订单获取完整信息生成打印内容
- 新增打印模板支持,可配置化小票打印格式
- 改进支付成功事件处理,自动触发延迟打印任务
- 添加手动触发打印功能,支持立即打印订单收据

🔧 chore(payment): 更新支付服务集成

- 简化打印任务触发逻辑,移除冗余的订单信息获取
- 改进错误处理,优雅处理飞鹅打印模块未安装的情况
- 在支付成功回调中自动触发打印任务创建

✨ feat(order-ui): 添加订单打印功能

- 在订单管理界面新增打印按钮
- 集成飞鹅打印API,支持一键打印订单收据
- 实现打印模板渲染和变量替换
- 添加打印状态反馈和错误处理

🔧 chore(server): 初始化飞鹅打印调度器

- 在服务器启动时自动初始化防退款延迟打印调度器
- 支持多租户配置,为每个有飞鹅配置的租户启动独立调度器
- 改进错误处理,确保调度器初始化失败不影响主流程

♻️ refactor(shared-crud): 优化租户ID提取逻辑

- 改进调试信息,明确租户ID提取逻辑
- 确保当数据中已包含tenantId字段时不影响正常功能
yourname 1 месяц назад
Родитель
Сommit
f341e075d7
20 измененных файлов с 1812 добавлено и 843 удалено
  1. 2 2
      mini/src/components/order/OrderButtonBar/index.tsx
  2. 42 11
      mini/tests/unit/components/order/OrderButtonBar.test.tsx
  3. 2 0
      packages/feie-printer-management-ui-mt/src/api/feiePrinterClient.ts
  4. 158 468
      packages/feie-printer-management-ui-mt/src/components/PrintConfigManagement.tsx
  5. 57 31
      packages/feie-printer-management-ui-mt/src/components/PrintTaskQuery.tsx
  6. 11 6
      packages/feie-printer-management-ui-mt/src/components/PrinterManagement.tsx
  7. 1 7
      packages/feie-printer-management-ui-mt/src/types/feiePrinter.ts
  8. 362 56
      packages/feie-printer-module-mt/src/routes/feie.routes.ts
  9. 9 16
      packages/feie-printer-module-mt/src/services/config.service.ts
  10. 90 35
      packages/feie-printer-module-mt/src/services/delay-scheduler.service.ts
  11. 283 21
      packages/feie-printer-module-mt/src/services/feie-api.service.ts
  12. 78 64
      packages/feie-printer-module-mt/src/services/print-task.service.ts
  13. 294 54
      packages/feie-printer-module-mt/src/services/print-trigger.service.ts
  14. 57 17
      packages/feie-printer-module-mt/src/services/printer.service.ts
  15. 12 6
      packages/feie-printer-module-mt/tests/unit/delay-scheduler.service.test.ts
  16. 8 0
      packages/feie-printer-module-mt/tests/unit/printer.service.test.ts
  17. 22 47
      packages/mini-payment-mt/src/services/payment.mt.service.ts
  18. 261 1
      packages/order-management-ui-mt/src/components/OrderManagement.tsx
  19. 61 0
      packages/server/src/index.ts
  20. 2 1
      packages/shared-crud/src/services/generic-crud.service.ts

+ 2 - 2
mini/src/components/order/OrderButtonBar/index.tsx

@@ -155,9 +155,9 @@ export default function OrderButtonBar({ order, onViewDetail, onCancelOrder, hid
       return response.json()
     },
     onSuccess: () => {
-      // 刷新订单列表数据
+      // // 刷新订单列表数据
       queryClient.invalidateQueries({ queryKey: ['orders'] })
-      queryClient.invalidateQueries({ queryKey: ['order', order.id] })
+     // queryClient.invalidateQueries({ queryKey: ['order', order.id] })
 
       // 显示成功信息
       Taro.showToast({

+ 42 - 11
mini/tests/unit/components/order/OrderButtonBar.test.tsx

@@ -7,7 +7,7 @@ import OrderButtonBar from '@/components/order/OrderButtonBar'
 // Mock API client
 jest.mock('@/api', () => ({
   orderClient: {
-    cancelOrder: {
+    'cancel-order': {
       $post: jest.fn()
     }
   }
@@ -91,20 +91,51 @@ jest.mock('@/components/common/CancelReasonDialog', () => {
 
 const mockOrder = {
   id: 1,
+  tenantId: 1,
   orderNo: 'ORDER001',
-  payState: 0, // 未支付
-  state: 0, // 未发货
+  userId: 1,
+  authCode: null,
+  cardNo: null,
+  sjtCardNo: null,
   amount: 100,
-  payAmount: 100,
+  costAmount: 80,
   freightAmount: 0,
   discountAmount: 0,
-  goodsDetail: JSON.stringify([
-    { id: 1, name: '商品1', price: 50, num: 2, image: '', spec: '默认规格' }
-  ]),
+  payAmount: 100,
+  orderType: 1,
+  payType: 1,
+  payState: 0, // 未支付
+  state: 0, // 未发货
+  deliveryType: 1,
+  deliveryCompany: null,
+  deliveryNo: null,
+  deliveryTime: null,
+  confirmTime: null,
+  cancelTime: null,
+  cancelReason: null,
+  remark: null,
   recevierName: '张三',
   receiverMobile: '13800138000',
   address: '北京市朝阳区',
-  createdAt: '2025-01-01T00:00:00Z'
+  recevierProvince: 110000,
+  recevierCity: 110100,
+  recevierDistrict: 110105,
+  recevierTown: 0,
+  addressId: 1,
+  merchantId: 1,
+  supplierId: 1,
+  createdBy: 1,
+  updatedBy: 1,
+  createdAt: '2025-01-01T00:00:00Z',
+  updatedAt: '2025-01-01T00:00:00Z',
+  deletedAt: null,
+  goodsDetail: JSON.stringify([
+    { id: 1, name: '商品1', price: 50, num: 2, image: '', spec: '默认规格' }
+  ]),
+  merchant: null,
+  supplier: null,
+  deliveryAddress: null,
+  orderGoods: []
 }
 
 const createTestQueryClient = () => new QueryClient({
@@ -163,7 +194,7 @@ describe('OrderButtonBar', () => {
   })
 
   it('should call API when cancel order is confirmed', async () => {
-    const mockApiCall = require('@/api').orderClient.cancelOrder.$post as jest.Mock
+    const mockApiCall = require('@/api').orderClient['cancel-order'].$post as jest.Mock
 
     mockShowModal.mockResolvedValueOnce({ confirm: true }) // 确认取消
 
@@ -237,7 +268,7 @@ describe('OrderButtonBar', () => {
   })
 
   it('should handle network error gracefully', async () => {
-    const mockApiCall = require('@/api').orderClient.cancelOrder.$post as jest.Mock
+    const mockApiCall = require('@/api').orderClient['cancel-order'].$post as jest.Mock
 
     mockShowModal.mockResolvedValueOnce({ confirm: true })
 
@@ -288,7 +319,7 @@ describe('OrderButtonBar', () => {
 
   it('should disable cancel button during mutation', async () => {
     // 模拟mutation正在进行中
-    const mockApiCall = require('@/api').orderClient.cancelOrder.$post as jest.Mock
+    const mockApiCall = require('@/api').orderClient['cancel-order'].$post as jest.Mock
     mockApiCall.mockImplementation(() => new Promise(() => {})) // 永不resolve的promise
 
     const { getByText, getByPlaceholderText, getByTestId } = render(

+ 2 - 0
packages/feie-printer-management-ui-mt/src/api/feiePrinterClient.ts

@@ -74,6 +74,7 @@ export class FeiePrinterClient {
     const response = await this.client.get<ApiResponse<PrinterListResponse>>(apiUrl('/printers'), {
       params
     });
+    console.log("getPrintersresponse:",response);
     if (!response.data.success) {
       throw new Error(response.data.message || '获取打印机列表失败');
     }
@@ -158,6 +159,7 @@ export class FeiePrinterClient {
     const response = await this.client.get<ApiResponse<PrintTaskListResponse>>(apiUrl('/tasks'), {
       params
     });
+    console.log("taskresponse:",response);
     if (!response.data.success) {
       throw new Error(response.data.message || '获取打印任务列表失败');
     }

+ 158 - 468
packages/feie-printer-management-ui-mt/src/components/PrintConfigManagement.tsx

@@ -5,13 +5,11 @@ import { zodResolver } from '@hookform/resolvers/zod';
 import { useForm } from 'react-hook-form';
 import { z } from 'zod';
 import {
-  Settings,
   RefreshCw,
   AlertCircle,
   CheckCircle,
   XCircle,
   Clock,
-  Repeat,
   Timer,
   FileText
 } from 'lucide-react';
@@ -20,7 +18,8 @@ import {
   FeieConfig,
   ConfigKey,
   ConfigType,
-  UpdateConfigRequest
+  UpdateConfigRequest,
+  ConfigListResponse
 } from '../types/feiePrinter';
 import { createFeiePrinterClient } from '../api/feiePrinterClient';
 import {
@@ -30,12 +29,6 @@ import {
   CardDescription,
   CardHeader,
   CardTitle,
-  Table,
-  TableBody,
-  TableCell,
-  TableHead,
-  TableHeader,
-  TableRow,
   Badge,
   Dialog,
   DialogContent,
@@ -61,7 +54,6 @@ import {
 
 // 配置分组
 enum ConfigGroup {
-  BASIC = 'basic',
   PRINT_POLICY = 'print_policy',
   TEMPLATE = 'template'
 }
@@ -84,42 +76,6 @@ interface ConfigItemDefinition {
 
 // 配置项定义映射
 const configDefinitions: ConfigItemDefinition[] = [
-  {
-    key: ConfigKey.ENABLED,
-    label: '启用飞鹅打印',
-    description: '是否启用飞鹅打印功能',
-    type: ConfigType.BOOLEAN,
-    defaultValue: 'true',
-    group: ConfigGroup.BASIC,
-    component: 'switch'
-  },
-  {
-    key: ConfigKey.DEFAULT_PRINTER_SN,
-    label: '默认打印机序列号',
-    description: '默认使用的打印机序列号',
-    type: ConfigType.STRING,
-    defaultValue: '',
-    group: ConfigGroup.BASIC,
-    component: 'input'
-  },
-  {
-    key: ConfigKey.AUTO_PRINT_ON_PAYMENT,
-    label: '支付成功时自动打印',
-    description: '订单支付成功后是否自动打印小票',
-    type: ConfigType.BOOLEAN,
-    defaultValue: 'true',
-    group: ConfigGroup.PRINT_POLICY,
-    component: 'switch'
-  },
-  {
-    key: ConfigKey.AUTO_PRINT_ON_SHIPPING,
-    label: '发货时自动打印',
-    description: '订单发货时是否自动打印发货单',
-    type: ConfigType.BOOLEAN,
-    defaultValue: 'true',
-    group: ConfigGroup.PRINT_POLICY,
-    component: 'switch'
-  },
   {
     key: ConfigKey.ANTI_REFUND_DELAY,
     label: '防退款延迟时间(秒)',
@@ -132,42 +88,6 @@ const configDefinitions: ConfigItemDefinition[] = [
     max: 600,
     step: 10
   },
-  {
-    key: ConfigKey.RETRY_MAX_COUNT,
-    label: '最大重试次数',
-    description: '打印失败时的最大重试次数',
-    type: ConfigType.NUMBER,
-    defaultValue: '3',
-    group: ConfigGroup.PRINT_POLICY,
-    component: 'number',
-    min: 0,
-    max: 10,
-    step: 1
-  },
-  {
-    key: ConfigKey.RETRY_INTERVAL,
-    label: '重试间隔(秒)',
-    description: '打印失败后重试的间隔时间',
-    type: ConfigType.NUMBER,
-    defaultValue: '30',
-    group: ConfigGroup.PRINT_POLICY,
-    component: 'number',
-    min: 5,
-    max: 300,
-    step: 5
-  },
-  {
-    key: ConfigKey.TASK_TIMEOUT,
-    label: '任务超时时间(秒)',
-    description: '打印任务的最大执行时间,超时后自动取消',
-    type: ConfigType.NUMBER,
-    defaultValue: '300',
-    group: ConfigGroup.PRINT_POLICY,
-    component: 'number',
-    min: 30,
-    max: 1800,
-    step: 30
-  },
   {
     key: ConfigKey.RECEIPT_TEMPLATE,
     label: '小票模板',
@@ -209,6 +129,27 @@ const typeLabelMap: Record<ConfigType, string> = {
   [ConfigType.NUMBER]: '数字'
 };
 
+// 从JWT token中解析租户ID
+const parseTenantIdFromToken = (token: string | null | undefined): number | undefined => {
+  if (!token) return undefined;
+
+  try {
+    // JWT token格式: header.payload.signature
+    const parts = token.split('.');
+    if (parts.length !== 3) return undefined;
+
+    // 解码payload部分
+    const payload = JSON.parse(atob(parts[1].replace(/-/g, '+').replace(/_/g, '/')));
+
+    // 从payload中获取tenantId
+    // 可能是tenantId、tenant_id或tenant
+    return payload.tenantId || payload.tenant_id || payload.tenant;
+  } catch (error) {
+    console.error('解析token失败:', error);
+    return undefined;
+  }
+};
+
 // 配置类型颜色映射
 const typeColorMap: Record<ConfigType, string> = {
   [ConfigType.STRING]: 'bg-blue-100 text-blue-800 border-blue-200',
@@ -246,14 +187,19 @@ interface PrintConfigManagementProps {
  */
 export const PrintConfigManagement: React.FC<PrintConfigManagementProps> = ({
   baseURL = '/api/v1/feie',
-  tenantId,
-  authToken
+  tenantId: propTenantId,
+  authToken: propAuthToken
 }) => {
-  const [activeGroup, setActiveGroup] = useState<ConfigGroup>(ConfigGroup.BASIC);
+  // 从token中解析租户ID
+  const token = propAuthToken || localStorage.getItem('token');
+  const parsedTenantId = parseTenantIdFromToken(token);
+  const tenantId = propTenantId || parsedTenantId;
+  const authToken = propAuthToken || token;
+
+  const [activeGroup, setActiveGroup] = useState<ConfigGroup>(ConfigGroup.PRINT_POLICY);
   const [isEditDialogOpen, setIsEditDialogOpen] = useState(false);
   const [selectedConfig, setSelectedConfig] = useState<FeieConfig | null>(null);
   const [configMap, setConfigMap] = useState<Record<string, FeieConfig>>({});
-  const [tableEdits, setTableEdits] = useState<Record<string, string>>({});
   const [groupEdits, setGroupEdits] = useState<Record<string, string>>({});
 
   const queryClient = useQueryClient();
@@ -269,26 +215,71 @@ export const PrintConfigManagement: React.FC<PrintConfigManagementProps> = ({
     }
   }, [authToken, tenantId, feieClient]);
 
+  // 当租户ID变化时,重新获取配置数据
+  useEffect(() => {
+    if (tenantId) {
+      queryClient.invalidateQueries({ queryKey: ['printConfigs', tenantId] });
+    }
+  }, [tenantId, queryClient]);
+
   // 查询配置列表
   const {
     data: configList,
     isLoading,
     isError,
     refetch
-  } = useQuery({
+  } = useQuery<ConfigListResponse>({
     queryKey: ['printConfigs', tenantId],
     queryFn: () => feieClient.getPrintConfigs(),
-    enabled: !!tenantId
+    enabled: !!tenantId,
+    staleTime: 0, // 设置为0,每次重新进入页面都重新获取
+    gcTime: 0, // 设置为0,不缓存数据
+    refetchOnMount: true, // 组件挂载时重新获取
+    refetchOnWindowFocus: true // 窗口获得焦点时重新获取
   });
 
+  // 当租户ID准备好时,立即重新获取数据
+  useEffect(() => {
+    if (tenantId) {
+      // 使用 setTimeout 确保在下一个事件循环中执行,避免渲染阻塞
+      const timer = setTimeout(() => {
+        refetch();
+      }, 0);
+      return () => clearTimeout(timer);
+    }
+  }, [tenantId, refetch]);
+
   // 当配置数据加载成功后,更新配置映射
   useEffect(() => {
-    if (configList?.data) {
+    console.debug('configList 数据:', configList);
+
+    // 处理 configList 数据:可能是 ConfigListResponse 或直接是 FeieConfig[]
+    let configArray: FeieConfig[] = [];
+
+    if (configList) {
+      // 检查是否是 ConfigListResponse 类型(有 data 属性)
+      if ('data' in configList && Array.isArray(configList.data)) {
+        console.debug('configList 是 ConfigListResponse 类型');
+        configArray = configList.data;
+      } else if (Array.isArray(configList)) {
+        // 直接是 FeieConfig[] 数组
+        console.debug('configList 是 FeieConfig[] 数组类型');
+        configArray = configList;
+      }
+    }
+
+    console.debug('configArray:', configArray);
+
+    if (configArray.length > 0) {
       const map: Record<string, FeieConfig> = {};
-      configList.data.forEach((config: FeieConfig) => {
+      configArray.forEach((config: FeieConfig) => {
+        console.debug(`映射配置: key=${config.configKey}, value=${config.configValue}`);
         map[config.configKey] = config;
       });
+      console.debug('生成的 configMap:', map);
       setConfigMap(map);
+    } else {
+      console.debug('没有配置数据');
     }
   }, [configList]);
 
@@ -309,28 +300,31 @@ export const PrintConfigManagement: React.FC<PrintConfigManagementProps> = ({
   const updateConfigMutation = useMutation({
     mutationFn: ({ configKey, data }: { configKey: string; data: UpdateConfigRequest }) =>
       feieClient.updatePrintConfig(configKey, data),
-    onSuccess: () => {
+    onSuccess: (savedConfig) => {
       toast.success('配置更新成功');
       setIsEditDialogOpen(false);
       setSelectedConfig(null);
-      queryClient.invalidateQueries({ queryKey: ['printConfigs'] });
+
+      // 立即更新本地配置映射
+      if (savedConfig && savedConfig.configKey) {
+        setConfigMap(prev => ({
+          ...prev,
+          [savedConfig.configKey]: savedConfig
+        }));
+      }
+
+      // 强制重新获取数据并等待完成
+      refetch().then(() => {
+        console.debug('配置更新后数据重新获取完成');
+      }).catch(error => {
+        console.error('配置更新后重新获取数据失败:', error);
+      });
     },
     onError: (error: Error) => {
       toast.error(`更新配置失败: ${error.message}`);
     }
   });
 
-  // 即时保存配置Mutation(用于直接编辑)
-  const instantSaveConfigMutation = useMutation({
-    mutationFn: ({ configKey, configValue }: { configKey: string; configValue: string }) =>
-      feieClient.updatePrintConfig(configKey, { configValue }),
-    onSuccess: () => {
-      queryClient.invalidateQueries({ queryKey: ['printConfigs'] });
-    },
-    onError: (error: Error) => {
-      toast.error(`保存配置失败: ${error.message}`);
-    }
-  });
 
 
   // 重置配置Mutation
@@ -343,9 +337,23 @@ export const PrintConfigManagement: React.FC<PrintConfigManagementProps> = ({
         configValue: definition.defaultValue
       });
     },
-    onSuccess: () => {
+    onSuccess: (savedConfig) => {
       toast.success('配置重置成功');
-      queryClient.invalidateQueries({ queryKey: ['printConfigs'] });
+
+      // 立即更新本地配置映射
+      if (savedConfig && savedConfig.configKey) {
+        setConfigMap(prev => ({
+          ...prev,
+          [savedConfig.configKey]: savedConfig
+        }));
+      }
+
+      // 强制重新获取数据并等待完成
+      refetch().then(() => {
+        console.debug('配置重置后数据重新获取完成');
+      }).catch(error => {
+        console.error('配置重置后重新获取数据失败:', error);
+      });
     },
     onError: (error: Error) => {
       toast.error(`重置配置失败: ${error.message}`);
@@ -361,14 +369,14 @@ export const PrintConfigManagement: React.FC<PrintConfigManagementProps> = ({
     });
   };
 
-  // 打开编辑对话框
-  const openEditDialog = (config: FeieConfig) => {
-    setSelectedConfig(config);
-    updateForm.reset({
-      configValue: config.configValue
-    });
-    setIsEditDialogOpen(true);
-  };
+  // 打开编辑对话框(暂时注释掉未使用的函数)
+  // const openEditDialog = (config: FeieConfig) => {
+  //   setSelectedConfig(config);
+  //   updateForm.reset({
+  //     configValue: config.configValue
+  //   });
+  //   setIsEditDialogOpen(true);
+  // };
 
   // 批量保存配置Mutation
   const batchSaveConfigMutation = useMutation({
@@ -378,33 +386,34 @@ export const PrintConfigManagement: React.FC<PrintConfigManagementProps> = ({
       );
       return Promise.all(promises);
     },
-    onSuccess: () => {
+    onSuccess: (savedConfigs) => {
       toast.success('批量保存成功');
       // 清空编辑记录
-      setTableEdits({});
       setGroupEdits({});
-      queryClient.invalidateQueries({ queryKey: ['printConfigs'] });
+
+      // 立即更新本地配置映射
+      if (savedConfigs && Array.isArray(savedConfigs)) {
+        const updatedMap = { ...configMap };
+        savedConfigs.forEach((config: FeieConfig) => {
+          if (config && config.configKey) {
+            updatedMap[config.configKey] = config;
+          }
+        });
+        setConfigMap(updatedMap);
+      }
+
+      // 强制重新获取数据并等待完成
+      refetch().then(() => {
+        console.debug('批量保存后数据重新获取完成');
+      }).catch(error => {
+        console.error('批量保存后重新获取数据失败:', error);
+      });
     },
     onError: (error: Error) => {
       toast.error(`批量保存失败: ${error.message}`);
     }
   });
 
-  // 处理表格批量保存
-  const handleTableBatchSave = () => {
-    const editsToSave = Object.entries(tableEdits).filter(([key, value]) => {
-      const currentValue = getConfigValue(key);
-      return value !== currentValue;
-    });
-
-    if (editsToSave.length === 0) {
-      toast.info('没有需要保存的修改');
-      return;
-    }
-
-    const editsObject = Object.fromEntries(editsToSave);
-    batchSaveConfigMutation.mutate(editsObject);
-  };
 
   // 处理分组批量保存
   const handleGroupBatchSave = () => {
@@ -427,38 +436,35 @@ export const PrintConfigManagement: React.FC<PrintConfigManagementProps> = ({
     resetConfigMutation.mutate(configKey);
   };
 
-  // 获取配置值显示
-  const getConfigValueDisplay = (config: FeieConfig): string => {
-    if (config.configType === ConfigType.BOOLEAN) {
-      return config.configValue === 'true' ? '是' : '否';
-    }
-    if (config.configType === ConfigType.JSON) {
-      try {
-        const parsed = JSON.parse(config.configValue);
-        return JSON.stringify(parsed, null, 2);
-      } catch {
-        return config.configValue;
-      }
-    }
-    return config.configValue;
-  };
-
-
   // 获取配置值
   const getConfigValue = (configKey: string): string => {
     const config = configMap[configKey];
-    if (config) return config.configValue;
+    console.debug(`getConfigValue: key=${configKey}, config=`, config);
 
-    // 如果配置不存在,返回默认值
+    // 如果配置存在且有值,返回配置值
+    if (config && config.configValue !== undefined && config.configValue !== null) {
+      console.debug(`返回数据库值: ${config.configValue}`);
+      return config.configValue;
+    }
+
+    // 如果配置不存在且数据正在加载中,返回空字符串
+    if (isLoading) {
+      console.debug('数据加载中,返回空字符串');
+      return '';
+    }
+
+    // 如果配置不存在且数据加载完成,返回默认值
     const definition = configDefinitions.find(def => def.key === configKey);
-    return definition?.defaultValue || '';
+    const defaultValue = definition?.defaultValue || '';
+    console.debug(`配置不存在,返回默认值: ${defaultValue}`);
+    return defaultValue;
   };
 
   // 渲染配置项组件(批量保存模式)
   const renderBatchConfigItem = (definition: ConfigItemDefinition) => {
-    const config = configMap[definition.key];
     const value = getConfigValue(definition.key);
     const editedValue = groupEdits[definition.key];
+    console.debug(`renderBatchConfigItem: key=${definition.key}, value=${value}, editedValue=${editedValue}`);
 
     return (
       <div key={definition.key} className="space-y-2">
@@ -562,175 +568,10 @@ export const PrintConfigManagement: React.FC<PrintConfigManagementProps> = ({
             )}
           </div>
         )}
-
-        {definition.key === ConfigKey.DEFAULT_PRINTER_SN && value && (
-          <p className="text-sm text-muted-foreground">
-            当前默认打印机: <span className="font-mono">{editedValue !== undefined ? editedValue : value}</span>
-          </p>
-        )}
       </div>
     );
   };
 
-  // 渲染配置项组件(即时保存模式)
-  const renderConfigItem = (definition: ConfigItemDefinition) => {
-    const config = configMap[definition.key];
-    const value = getConfigValue(definition.key);
-
-    return (
-      <div key={definition.key} className="space-y-2">
-        <div className="flex items-center justify-between">
-          <div>
-            <h4 className="font-medium">{definition.label}</h4>
-            <p className="text-sm text-muted-foreground">{definition.description}</p>
-          </div>
-          <div className="flex items-center space-x-2">
-            <Badge variant="outline" className={typeColorMap[definition.type]}>
-              {typeLabelMap[definition.type]}
-            </Badge>
-            <Button
-              variant="ghost"
-              size="sm"
-              onClick={() => handleResetConfig(definition.key)}
-              disabled={resetConfigMutation.isPending}
-              title="重置为默认值"
-            >
-              <RefreshCw className="h-4 w-4" />
-            </Button>
-          </div>
-        </div>
-
-        {definition.component === 'switch' ? (
-          <div className="flex items-center space-x-2">
-            <Switch
-              checked={value === 'true'}
-              onCheckedChange={(checked) => {
-                const newValue = checked.toString();
-                // 更新本地状态
-                const newConfig = { ...config, configValue: newValue };
-                setConfigMap(prev => ({ ...prev, [definition.key]: newConfig }));
-                // 即时保存到服务器
-                instantSaveConfigMutation.mutate({
-                  configKey: definition.key,
-                  configValue: newValue
-                });
-              }}
-              disabled={instantSaveConfigMutation.isPending}
-            />
-            <span className="text-sm">{value === 'true' ? '已启用' : '已禁用'}</span>
-            {instantSaveConfigMutation.isPending && (
-              <RefreshCw className="h-3 w-3 animate-spin text-muted-foreground" />
-            )}
-          </div>
-        ) : definition.component === 'textarea' ? (
-          <div className="relative">
-            <Textarea
-              value={value}
-              onChange={(e) => {
-                const newValue = e.target.value;
-                // 更新本地状态
-                const newConfig = { ...config, configValue: newValue };
-                setConfigMap(prev => ({ ...prev, [definition.key]: newConfig }));
-              }}
-              onBlur={(e) => {
-                // 失去焦点时保存
-                const newValue = e.target.value;
-                if (newValue !== value) {
-                  instantSaveConfigMutation.mutate({
-                    configKey: definition.key,
-                    configValue: newValue
-                  });
-                }
-              }}
-              rows={6}
-              className="font-mono text-sm"
-              disabled={instantSaveConfigMutation.isPending}
-            />
-            {instantSaveConfigMutation.isPending && (
-              <div className="absolute right-2 top-2">
-                <RefreshCw className="h-3 w-3 animate-spin text-muted-foreground" />
-              </div>
-            )}
-          </div>
-        ) : definition.component === 'number' ? (
-          <div className="flex items-center space-x-2">
-            <div className="relative">
-              <Input
-                type="number"
-                value={value}
-                onChange={(e) => {
-                  const newValue = e.target.value;
-                  // 更新本地状态
-                  const newConfig = { ...config, configValue: newValue };
-                  setConfigMap(prev => ({ ...prev, [definition.key]: newConfig }));
-                }}
-                onBlur={(e) => {
-                  // 失去焦点时保存
-                  const newValue = e.target.value;
-                  if (newValue !== value) {
-                    instantSaveConfigMutation.mutate({
-                      configKey: definition.key,
-                      configValue: newValue
-                    });
-                  }
-                }}
-                min={definition.min}
-                max={definition.max}
-                step={definition.step}
-                className="w-32"
-                disabled={instantSaveConfigMutation.isPending}
-              />
-              {instantSaveConfigMutation.isPending && (
-                <div className="absolute right-2 top-2">
-                  <RefreshCw className="h-3 w-3 animate-spin text-muted-foreground" />
-                </div>
-              )}
-            </div>
-            {definition.key === ConfigKey.ANTI_REFUND_DELAY && (
-              <span className="text-sm text-muted-foreground">
-                ({Math.floor(parseInt(value) / 60)}分{parseInt(value) % 60}秒)
-              </span>
-            )}
-          </div>
-        ) : (
-          <div className="relative">
-            <Input
-              value={value}
-              onChange={(e) => {
-                const newValue = e.target.value;
-                // 更新本地状态
-                const newConfig = { ...config, configValue: newValue };
-                setConfigMap(prev => ({ ...prev, [definition.key]: newConfig }));
-              }}
-              onBlur={(e) => {
-                // 失去焦点时保存
-                const newValue = e.target.value;
-                if (newValue !== value) {
-                  instantSaveConfigMutation.mutate({
-                    configKey: definition.key,
-                    configValue: newValue
-                  });
-                }
-              }}
-              placeholder={`请输入${definition.label}`}
-              disabled={instantSaveConfigMutation.isPending}
-            />
-            {instantSaveConfigMutation.isPending && (
-              <div className="absolute right-2 top-2">
-                <RefreshCw className="h-3 w-3 animate-spin text-muted-foreground" />
-              </div>
-            )}
-          </div>
-        )}
-
-        {definition.key === ConfigKey.DEFAULT_PRINTER_SN && value && (
-          <p className="text-sm text-muted-foreground">
-            当前默认打印机: <span className="font-mono">{value}</span>
-          </p>
-        )}
-      </div>
-    );
-  };
 
   return (
     <div className="space-y-6">
@@ -756,14 +597,6 @@ export const PrintConfigManagement: React.FC<PrintConfigManagementProps> = ({
 
       {/* 配置分组按钮 */}
       <div className="flex space-x-2 mb-4">
-        <Button
-          variant={activeGroup === ConfigGroup.BASIC ? "default" : "outline"}
-          onClick={() => setActiveGroup(ConfigGroup.BASIC)}
-          className="flex items-center"
-        >
-          <Settings className="mr-2 h-4 w-4" />
-          基础配置
-        </Button>
         <Button
           variant={activeGroup === ConfigGroup.PRINT_POLICY ? "default" : "outline"}
           onClick={() => setActiveGroup(ConfigGroup.PRINT_POLICY)}
@@ -815,22 +648,6 @@ export const PrintConfigManagement: React.FC<PrintConfigManagementProps> = ({
         </Card>
       ) : (
         <>
-          {/* 基础配置 */}
-          {activeGroup === ConfigGroup.BASIC && (
-            <Card>
-              <CardHeader>
-                <CardTitle>基础配置</CardTitle>
-                <CardDescription>
-                  配置飞鹅打印的基础功能,包括启用状态和默认打印机
-                </CardDescription>
-              </CardHeader>
-              <CardContent className="space-y-6">
-                {configDefinitions
-                  .filter(def => def.group === ConfigGroup.BASIC)
-                  .map(renderConfigItem)}
-              </CardContent>
-            </Card>
-          )}
 
           {/* 打印策略 */}
           {activeGroup === ConfigGroup.PRINT_POLICY && (
@@ -888,18 +705,6 @@ export const PrintConfigManagement: React.FC<PrintConfigManagementProps> = ({
                         <strong>防退款延迟</strong>: 支付成功后等待指定时间确认无退款再打印,避免无效打印
                       </span>
                     </li>
-                    <li className="flex items-start">
-                      <Repeat className="h-4 w-4 mr-2 mt-0.5 flex-shrink-0" />
-                      <span>
-                        <strong>重试机制</strong>: 打印失败时自动重试,最多重试指定次数,每次间隔指定时间
-                      </span>
-                    </li>
-                    <li className="flex items-start">
-                      <Timer className="h-4 w-4 mr-2 mt-0.5 flex-shrink-0" />
-                      <span>
-                        <strong>超时取消</strong>: 打印任务执行超过指定时间自动取消,避免任务阻塞
-                      </span>
-                    </li>
                   </ul>
                 </div>
               </CardContent>
@@ -991,121 +796,6 @@ export const PrintConfigManagement: React.FC<PrintConfigManagementProps> = ({
         </>
       )}
 
-      {/* 配置列表表格视图(备用) */}
-      <Card>
-        <CardHeader>
-          <CardTitle>所有配置项</CardTitle>
-          <CardDescription>
-            以表格形式查看所有配置项的当前值
-          </CardDescription>
-        </CardHeader>
-        <CardContent>
-          {configList?.data.length === 0 ? (
-            <div className="flex flex-col items-center justify-center py-12 text-center">
-              <Settings className="h-12 w-12 text-muted-foreground mb-4" />
-              <h3 className="text-lg font-medium">暂无配置</h3>
-              <p className="text-sm text-muted-foreground mt-2">
-                还没有配置信息,系统将使用默认配置
-              </p>
-            </div>
-          ) : (
-            <div className="rounded-md border">
-              <Table>
-                <TableHeader>
-                  <TableRow>
-                    <TableHead>配置键</TableHead>
-                    <TableHead>描述</TableHead>
-                    <TableHead>类型</TableHead>
-                    <TableHead>当前值</TableHead>
-                    <TableHead>默认值</TableHead>
-                    <TableHead className="text-right">操作</TableHead>
-                  </TableRow>
-                </TableHeader>
-                <TableBody>
-                  {configDefinitions.map((definition) => {
-                    const config = configMap[definition.key];
-                    const value = getConfigValue(definition.key);
-                    const isDefault = !config || config.configValue === definition.defaultValue;
-
-                    return (
-                      <TableRow key={definition.key}>
-                        <TableCell className="font-mono text-sm">{definition.key}</TableCell>
-                        <TableCell>
-                          <div>
-                            <div className="font-medium">{definition.label}</div>
-                            <div className="text-sm text-muted-foreground">
-                              {definition.description}
-                            </div>
-                          </div>
-                        </TableCell>
-                        <TableCell>
-                          <Badge variant="outline" className={typeColorMap[definition.type]}>
-                            {typeLabelMap[definition.type]}
-                          </Badge>
-                        </TableCell>
-                        <TableCell>
-                          <div className="max-w-xs truncate" title={getConfigValueDisplay(config || {
-                            ...definition,
-                            configValue: value
-                          } as any)}>
-                            {definition.type === ConfigType.BOOLEAN ? (
-                              <div className="flex items-center">
-                                {value === 'true' ? (
-                                  <CheckCircle className="h-4 w-4 text-green-500 mr-2" />
-                                ) : (
-                                  <XCircle className="h-4 w-4 text-red-500 mr-2" />
-                                )}
-                                <span>{value === 'true' ? '是' : '否'}</span>
-                              </div>
-                            ) : (
-                              <span className="font-mono text-sm">{value}</span>
-                            )}
-                          </div>
-                        </TableCell>
-                        <TableCell>
-                          <span className="font-mono text-sm">{definition.defaultValue}</span>
-                        </TableCell>
-                        <TableCell className="text-right">
-                          <div className="flex items-center justify-end space-x-2">
-                            {!isDefault && (
-                              <Button
-                                variant="ghost"
-                                size="sm"
-                                onClick={() => handleResetConfig(definition.key)}
-                                disabled={resetConfigMutation.isPending}
-                                title="重置为默认值"
-                              >
-                                <RefreshCw className="h-4 w-4" />
-                              </Button>
-                            )}
-                            <Button
-                              variant="ghost"
-                              size="sm"
-                              onClick={() => {
-                                // 如果config不存在,创建一个临时的配置对象
-                                const configToEdit = config || {
-                                  configKey: definition.key,
-                                  configValue: definition.defaultValue,
-                                  configType: definition.type,
-                                  description: definition.description
-                                };
-                                openEditDialog(configToEdit);
-                              }}
-                              title="编辑"
-                            >
-                              编辑
-                            </Button>
-                          </div>
-                        </TableCell>
-                      </TableRow>
-                    );
-                  })}
-                </TableBody>
-              </Table>
-            </div>
-          )}
-        </CardContent>
-      </Card>
 
       {/* 编辑配置对话框 */}
       <Dialog open={isEditDialogOpen} onOpenChange={setIsEditDialogOpen}>

+ 57 - 31
packages/feie-printer-management-ui-mt/src/components/PrintTaskQuery.tsx

@@ -12,7 +12,6 @@ import {
   Calendar,
   Filter,
   Printer,
-  FileText,
   Download
 } from 'lucide-react';
 import { format } from 'date-fns';
@@ -22,10 +21,32 @@ import {
   FeiePrintTask,
   PrintTaskStatus,
   PrintTaskQueryParams,
-  PrintTaskListResponse,
   CancelReason
 } from '../types/feiePrinter';
 import { createFeiePrinterClient } from '../api/feiePrinterClient';
+
+/**
+ * 从JWT token中解析租户ID
+ */
+const parseTenantIdFromToken = (token?: string | null): number | undefined => {
+  if (!token) return undefined;
+
+  try {
+    // JWT token格式: header.payload.signature
+    const parts = token.split('.');
+    if (parts.length !== 3) return undefined;
+
+    // 解码payload部分
+    const payload = JSON.parse(atob(parts[1].replace(/-/g, '+').replace(/_/g, '/')));
+
+    // 从payload中获取tenantId
+    // 可能是tenantId、tenant_id或tenant
+    return payload.tenantId || payload.tenant_id || payload.tenant;
+  } catch (error) {
+    console.error('解析token失败:', error);
+    return undefined;
+  }
+};
 import {
   Button,
   Card,
@@ -47,7 +68,6 @@ import {
   DialogFooter,
   DialogHeader,
   DialogTitle,
-  DialogTrigger,
   Select,
   SelectContent,
   SelectItem,
@@ -122,10 +142,16 @@ interface PrintTaskQueryProps {
  */
 export const PrintTaskQuery: React.FC<PrintTaskQueryProps> = ({
   baseURL = '/api/v1/feie',
-  tenantId,
-  authToken,
+  tenantId: propTenantId,
+  authToken: propAuthToken,
   pageSize = 10
 }) => {
+  // 从token中解析租户ID
+  const token = propAuthToken || localStorage.getItem('token');
+  const parsedTenantId = parseTenantIdFromToken(token);
+  const tenantId = propTenantId || parsedTenantId;
+  const authToken = propAuthToken || token;
+
   const [searchQuery, setSearchQuery] = useState('');
   const [statusFilter, setStatusFilter] = useState<PrintTaskStatus | 'ALL'>('ALL');
   const [startDate, setStartDate] = useState<Date | undefined>();
@@ -153,10 +179,10 @@ export const PrintTaskQuery: React.FC<PrintTaskQueryProps> = ({
   const queryParams: PrintTaskQueryParams = {
     page: currentPage,
     pageSize,
-    search: searchQuery || undefined,
     status: statusFilter !== 'ALL' ? statusFilter : undefined,
     startDate: startDate ? format(startDate, 'yyyy-MM-dd') : undefined,
-    endDate: endDate ? format(endDate, 'yyyy-MM-dd') : undefined
+    endDate: endDate ? format(endDate, 'yyyy-MM-dd') : undefined,
+    search: searchQuery.trim() || undefined
   };
 
   // 查询打印任务列表
@@ -168,12 +194,12 @@ export const PrintTaskQuery: React.FC<PrintTaskQueryProps> = ({
   } = useQuery({
     queryKey: ['printTasks', queryParams],
     queryFn: () => feieClient.getPrintTasks(queryParams),
-    enabled: !!tenantId
+    enabled: true // 总是启用查询,后端会从认证信息中获取租户ID
   });
 
   // 重试打印任务Mutation
   const retryTaskMutation = useMutation({
-    mutationFn: (taskId: number) => feieClient.retryPrintTask(taskId),
+    mutationFn: (taskId: string) => feieClient.retryPrintTask({ taskId }),
     onSuccess: () => {
       toast.success('任务重试成功');
       setIsRetryDialogOpen(false);
@@ -186,8 +212,8 @@ export const PrintTaskQuery: React.FC<PrintTaskQueryProps> = ({
 
   // 取消打印任务Mutation
   const cancelTaskMutation = useMutation({
-    mutationFn: ({ taskId, reason }: { taskId: number; reason: CancelReason }) =>
-      feieClient.cancelPrintTask(taskId, reason),
+    mutationFn: ({ taskId, reason }: { taskId: string; reason: CancelReason }) =>
+      feieClient.cancelPrintTask({ taskId, reason }),
     onSuccess: () => {
       toast.success('任务取消成功');
       setIsCancelDialogOpen(false);
@@ -235,25 +261,25 @@ export const PrintTaskQuery: React.FC<PrintTaskQueryProps> = ({
   // 处理重试任务
   const handleRetryTask = () => {
     if (!selectedTask) return;
-    retryTaskMutation.mutate(selectedTask.id);
+    retryTaskMutation.mutate(selectedTask.taskId);
   };
 
   // 处理取消任务
   const handleCancelTask = (reason: CancelReason) => {
     if (!selectedTask) return;
     cancelTaskMutation.mutate({
-      taskId: selectedTask.id,
+      taskId: selectedTask.taskId,
       reason
     });
   };
 
   // 下载打印内容
   const handleDownloadContent = (task: FeiePrintTask) => {
-    const blob = new Blob([task.printContent || ''], { type: 'text/plain' });
+    const blob = new Blob([task.content || ''], { type: 'text/plain' });
     const url = URL.createObjectURL(blob);
     const a = document.createElement('a');
     a.href = url;
-    a.download = `print-task-${task.id}.txt`;
+    a.download = `print-task-${task.taskId}.txt`;
     document.body.appendChild(a);
     a.click();
     document.body.removeChild(a);
@@ -434,17 +460,17 @@ export const PrintTaskQuery: React.FC<PrintTaskQueryProps> = ({
                   <TableBody>
                     {taskList?.data.map((task) => (
                       <TableRow key={task.id}>
-                        <TableCell className="font-mono">{task.id}</TableCell>
+                        <TableCell className="font-mono">{task.taskId}</TableCell>
                         <TableCell>{task.orderId || '-'}</TableCell>
                         <TableCell className="font-mono">{task.printerSn}</TableCell>
                         <TableCell>
                           <div className="flex items-center space-x-2">
-                            {taskStatusIconMap[task.status]}
+                            {taskStatusIconMap[task.printStatus]}
                             <Badge
                               variant="outline"
-                              className={taskStatusColorMap[task.status]}
+                              className={taskStatusColorMap[task.printStatus]}
                             >
-                              {taskStatusLabelMap[task.status]}
+                              {taskStatusLabelMap[task.printStatus]}
                             </Badge>
                           </div>
                         </TableCell>
@@ -452,8 +478,8 @@ export const PrintTaskQuery: React.FC<PrintTaskQueryProps> = ({
                           {format(new Date(task.createdAt), 'yyyy-MM-dd HH:mm:ss')}
                         </TableCell>
                         <TableCell>
-                          {task.completedAt
-                            ? format(new Date(task.completedAt), 'yyyy-MM-dd HH:mm:ss')
+                          {task.printedAt || task.cancelledAt
+                            ? format(new Date(task.printedAt || task.cancelledAt!), 'yyyy-MM-dd HH:mm:ss')
                             : '-'}
                         </TableCell>
                         <TableCell className="text-right">
@@ -474,7 +500,7 @@ export const PrintTaskQuery: React.FC<PrintTaskQueryProps> = ({
                             >
                               <Download className="h-4 w-4" />
                             </Button>
-                            {task.status === PrintTaskStatus.FAILED && (
+                            {task.printStatus === PrintTaskStatus.FAILED && (
                               <Button
                                 variant="ghost"
                                 size="sm"
@@ -485,7 +511,7 @@ export const PrintTaskQuery: React.FC<PrintTaskQueryProps> = ({
                                 <RefreshCw className="h-4 w-4" />
                               </Button>
                             )}
-                            {(task.status === PrintTaskStatus.PENDING || task.status === PrintTaskStatus.DELAYED) && (
+                            {(task.printStatus === PrintTaskStatus.PENDING || task.printStatus === PrintTaskStatus.DELAYED) && (
                               <Button
                                 variant="ghost"
                                 size="sm"
@@ -568,7 +594,7 @@ export const PrintTaskQuery: React.FC<PrintTaskQueryProps> = ({
           <DialogHeader>
             <DialogTitle>打印任务详情</DialogTitle>
             <DialogDescription>
-              任务ID: {selectedTask?.id}
+              任务ID: {selectedTask?.taskId}
             </DialogDescription>
           </DialogHeader>
           {selectedTask && (
@@ -585,12 +611,12 @@ export const PrintTaskQuery: React.FC<PrintTaskQueryProps> = ({
                 <div>
                   <h4 className="text-sm font-medium text-muted-foreground">状态</h4>
                   <div className="flex items-center space-x-2">
-                    {taskStatusIconMap[selectedTask.status]}
+                    {taskStatusIconMap[selectedTask.printStatus]}
                     <Badge
                       variant="outline"
-                      className={taskStatusColorMap[selectedTask.status]}
+                      className={taskStatusColorMap[selectedTask.printStatus]}
                     >
-                      {taskStatusLabelMap[selectedTask.status]}
+                      {taskStatusLabelMap[selectedTask.printStatus]}
                     </Badge>
                   </div>
                 </div>
@@ -604,7 +630,7 @@ export const PrintTaskQuery: React.FC<PrintTaskQueryProps> = ({
                 </div>
                 <div>
                   <h4 className="text-sm font-medium text-muted-foreground">完成时间</h4>
-                  <p>{selectedTask.completedAt ? format(new Date(selectedTask.completedAt), 'yyyy-MM-dd HH:mm:ss') : '-'}</p>
+                  <p>{selectedTask.printedAt || selectedTask.cancelledAt ? format(new Date(selectedTask.printedAt || selectedTask.cancelledAt!), 'yyyy-MM-dd HH:mm:ss') : '-'}</p>
                 </div>
               </div>
               {selectedTask.errorMessage && (
@@ -618,7 +644,7 @@ export const PrintTaskQuery: React.FC<PrintTaskQueryProps> = ({
               <div>
                 <h4 className="text-sm font-medium text-muted-foreground">打印内容</h4>
                 <div className="mt-1 p-3 bg-gray-50 border border-gray-200 rounded-md font-mono text-sm whitespace-pre-wrap max-h-60 overflow-y-auto">
-                  {selectedTask.printContent || '无内容'}
+                  {selectedTask.content || '无内容'}
                 </div>
               </div>
             </div>
@@ -635,7 +661,7 @@ export const PrintTaskQuery: React.FC<PrintTaskQueryProps> = ({
           <DialogHeader>
             <DialogTitle>确认重试</DialogTitle>
             <DialogDescription>
-              确定要重试任务 "{selectedTask?.id}" 吗?
+              确定要重试任务 "{selectedTask?.taskId}" 吗?
             </DialogDescription>
           </DialogHeader>
           <DialogFooter>
@@ -664,7 +690,7 @@ export const PrintTaskQuery: React.FC<PrintTaskQueryProps> = ({
           <DialogHeader>
             <DialogTitle>取消打印任务</DialogTitle>
             <DialogDescription>
-              确定要取消任务 "{selectedTask?.id}" 吗?请选择取消原因。
+              确定要取消任务 "{selectedTask?.taskId}" 吗?请选择取消原因。
             </DialogDescription>
           </DialogHeader>
           <div className="space-y-4">

+ 11 - 6
packages/feie-printer-management-ui-mt/src/components/PrinterManagement.tsx

@@ -113,15 +113,15 @@ const printerTypeLabelMap: Record<PrinterType, string> = {
 interface PrinterManagementProps {
   /**
    * API基础URL
-   * @default '/api'
+   * @default '/api/v1/feie'
    */
   baseURL?: string;
   /**
-   * 租户ID
+   * 租户ID(可选,后端会从认证信息中获取)
    */
   tenantId?: number;
   /**
-   * 认证token
+   * 认证token(可选,会自动从localStorage获取)
    */
   authToken?: string;
   /**
@@ -155,9 +155,14 @@ export const PrinterManagement: React.FC<PrinterManagementProps> = ({
 
   // 设置认证和租户信息
   useEffect(() => {
-    if (authToken) {
-      feieClient.setAuthToken(authToken);
+    // 优先使用传入的authToken,否则尝试从localStorage获取
+    const token = authToken || localStorage.getItem('token');
+    if (token) {
+      feieClient.setAuthToken(token);
     }
+
+    // 租户ID由后端从认证信息中获取,这里不需要设置
+    // 如果传入了tenantId,可以设置,但不是必需的
     if (tenantId) {
       feieClient.setTenantId(tenantId);
     }
@@ -181,7 +186,7 @@ export const PrinterManagement: React.FC<PrinterManagementProps> = ({
   } = useQuery({
     queryKey: ['printers', queryParams],
     queryFn: () => feieClient.getPrinters(queryParams),
-    enabled: !!tenantId
+    enabled: true // 总是启用查询,后端会从认证信息中获取租户ID
   });
 
   // 创建打印机表单

+ 1 - 7
packages/feie-printer-management-ui-mt/src/types/feiePrinter.ts

@@ -133,6 +133,7 @@ export interface PrintTaskQueryParams {
   status?: PrintTaskStatus;
   startDate?: string;
   endDate?: string;
+  search?: string;
 }
 
 /**
@@ -149,14 +150,7 @@ export interface PrintTaskListResponse {
  * 打印配置键枚举
  */
 export enum ConfigKey {
-  ENABLED = 'feie.enabled',
-  DEFAULT_PRINTER_SN = 'feie.default_printer_sn',
-  AUTO_PRINT_ON_PAYMENT = 'feie.auto_print_on_payment',
-  AUTO_PRINT_ON_SHIPPING = 'feie.auto_print_on_shipping',
   ANTI_REFUND_DELAY = 'feie.anti_refund_delay',
-  RETRY_MAX_COUNT = 'feie.retry_max_count',
-  RETRY_INTERVAL = 'feie.retry_interval',
-  TASK_TIMEOUT = 'feie.task_timeout',
   RECEIPT_TEMPLATE = 'feie.receipt_template',
   SHIPPING_TEMPLATE = 'feie.shipping_template'
 }

+ 362 - 56
packages/feie-printer-module-mt/src/routes/feie.routes.ts

@@ -12,6 +12,8 @@ import { FeieApiConfig } from '../types/feie.types';
  */
 async function getFeieApiConfig(tenantId: number, dataSource: DataSource): Promise<FeieApiConfig | null> {
   try {
+    console.debug(`开始获取租户 ${tenantId} 的飞鹅API配置`);
+
     // 从 system_config_mt 表获取飞鹅API配置
     const configKeys = [
       'feie.api.user',      // 飞鹅API用户
@@ -23,6 +25,8 @@ async function getFeieApiConfig(tenantId: number, dataSource: DataSource): Promi
 
     // 直接查询数据库
     const configRepository = dataSource.getRepository('system_config_mt');
+    console.debug(`查询数据库,租户ID: ${tenantId}, 配置键: ${configKeys.join(', ')}`);
+
     const configs = await configRepository.find({
       where: {
         tenantId,
@@ -30,10 +34,13 @@ async function getFeieApiConfig(tenantId: number, dataSource: DataSource): Promi
       }
     });
 
+    console.debug(`找到 ${configs.length} 条配置记录`);
+
     // 转换为键值对
     const configMap: Record<string, string> = {};
     configs.forEach((config: any) => {
       configMap[config.configKey] = config.configValue;
+      console.debug(`配置键: ${config.configKey}, 值: ${config.configValue}`);
     });
 
     // 检查必要的配置项
@@ -42,17 +49,21 @@ async function getFeieApiConfig(tenantId: number, dataSource: DataSource): Promi
 
     if (!user || !ukey) {
       console.warn(`[租户${tenantId}] 飞鹅API配置不完整,缺少user或ukey`);
+      console.debug(`当前配置映射:`, JSON.stringify(configMap, null, 2));
       return null;
     }
 
     // 返回配置结构
-    return {
+    const feieConfig = {
       user,
       ukey,
-      baseUrl: configMap['feie.api.base_url'] || 'http://api.feieyun.cn/Api/Open/',
+      baseUrl: configMap['feie.api.base_url'] || 'https://api.feieyun.cn/Api/Open/',
       timeout: parseInt(configMap['feie.api.timeout'] || '10000', 10),
       maxRetries: parseInt(configMap['feie.api.max_retries'] || '3', 10)
     };
+
+    console.debug(`租户 ${tenantId} 的飞鹅API配置获取成功:`, JSON.stringify(feieConfig, null, 2));
+    return feieConfig;
   } catch (error) {
     console.warn(`[租户${tenantId}] 获取飞鹅API配置失败:`, error);
     return null;
@@ -63,22 +74,8 @@ export function createFeieRoutes(dataSource: DataSource) {
   const app = new OpenAPIHono<AuthContext>();
 
   // 初始化服务(使用空配置,实际配置在路由处理中动态获取)
-  const printerService = new PrinterService(dataSource, {
-    baseUrl: 'http://api.feieyun.cn/Api/Open/',
-    user: '',
-    ukey: '',
-    timeout: 10000,
-    maxRetries: 3
-  });
   const printTaskService = new PrintTaskService(dataSource, {
-    baseUrl: 'http://api.feieyun.cn/Api/Open/',
-    user: '',
-    ukey: '',
-    timeout: 10000,
-    maxRetries: 3
-  });
-  const delaySchedulerService = new DelaySchedulerService(dataSource, {
-    baseUrl: 'http://api.feieyun.cn/Api/Open/',
+    baseUrl: 'https://api.feieyun.cn/Api/Open/',
     user: '',
     ukey: '',
     timeout: 10000,
@@ -89,34 +86,116 @@ export function createFeieRoutes(dataSource: DataSource) {
   // 打印机管理路由
   app.get('/printers', async (c) => {
     const user = c.get('user');
+    console.debug('获取打印机列表 - 用户对象:', JSON.stringify(user, null, 2));
     const tenantId = user?.tenantId || 1; // 从认证中间件获取
+    console.debug(`获取打印机列表 - 租户ID: ${tenantId}`);
+
+    // 获取查询参数
+    const query = c.req.query();
+    const page = query.page ? parseInt(query.page, 10) : 1;
+    const pageSize = query.pageSize ? parseInt(query.pageSize, 10) : 10;
+    const search = query.search;
+    const status = query.status as any;
+    const printerType = query.printerType as any;
+    const isDefault = query.isDefault === 'true';
 
     // 获取飞鹅API配置
     const feieConfig = await getFeieApiConfig(tenantId, dataSource);
     if (!feieConfig) {
+      console.error(`租户 ${tenantId} 的飞鹅API配置未找到或配置不完整`);
       return c.json({ success: false, message: '飞鹅API配置未找到或配置不完整' }, 400);
     }
 
+    console.debug(`租户 ${tenantId} 的飞鹅API配置获取成功`);
+
     // 创建带配置的服务实例
     const tenantPrinterService = new PrinterService(dataSource, feieConfig);
     const printers = await tenantPrinterService.getPrinters(tenantId);
-    return c.json({ success: true, data: printers });
+
+    // 应用筛选逻辑
+    let filteredPrinters = printers;
+
+    // 搜索筛选
+    if (search) {
+      filteredPrinters = filteredPrinters.filter(p =>
+        (p.printerName && p.printerName.includes(search)) ||
+        p.printerSn.includes(search)
+      );
+    }
+
+    // 状态筛选
+    if (status && status !== 'ALL') {
+      filteredPrinters = filteredPrinters.filter(p => p.printerStatus === status);
+    }
+
+    // 类型筛选
+    if (printerType && printerType !== 'ALL') {
+      filteredPrinters = filteredPrinters.filter(p => p.printerType === printerType);
+    }
+
+    // 默认打印机筛选
+    if (isDefault) {
+      filteredPrinters = filteredPrinters.filter(p => p.isDefault === 1);
+    }
+
+    // 分页逻辑
+    const total = filteredPrinters.length;
+    const startIndex = (page - 1) * pageSize;
+    const endIndex = Math.min(startIndex + pageSize, total);
+    const paginatedPrinters = filteredPrinters.slice(startIndex, endIndex);
+
+    console.debug(`返回打印机列表,总数: ${total}, 分页: ${page}/${Math.ceil(total/pageSize)}, 数量: ${paginatedPrinters.length}`);
+
+    return c.json({
+      success: true,
+      data: {
+        data: paginatedPrinters,
+        total,
+        page,
+        pageSize
+      }
+    });
   });
 
   app.post('/printers', async (c) => {
     const user = c.get('user');
     const tenantId = user?.tenantId || 1;
-    const body = await c.req.json();
-    const printer = await printerService.addPrinter(tenantId, body);
-    return c.json({ success: true, data: printer });
+
+    try {
+      const body = await c.req.json();
+
+      // 获取飞鹅API配置
+      const feieConfig = await getFeieApiConfig(tenantId, dataSource);
+      if (!feieConfig) {
+        return c.json({ success: false, message: '飞鹅API配置未找到或配置不完整' }, 400);
+      }
+
+      // 创建带配置的服务实例
+      const tenantPrinterService = new PrinterService(dataSource, feieConfig);
+      const printer = await tenantPrinterService.addPrinter(tenantId, body);
+      return c.json({ success: true, data: printer });
+    } catch (error) {
+      console.error(`[租户${tenantId}] 添加打印机失败:`, error);
+      const errorMessage = error instanceof Error ? error.message : '添加打印机失败';
+      return c.json({ success: false, message: errorMessage }, 500);
+    }
   });
 
   app.get('/printers/:printerSn', async (c) => {
     const user = c.get('user');
     const tenantId = user?.tenantId || 1;
     const printerSn = c.req.param('printerSn');
-    const printer = await (printerService as any).findOne({
-      where: { tenantId, printerSn }
+
+    // 获取飞鹅API配置
+    const feieConfig = await getFeieApiConfig(tenantId, dataSource);
+    if (!feieConfig) {
+      return c.json({ success: false, message: '飞鹅API配置未找到或配置不完整' }, 400);
+    }
+
+    // 创建带配置的服务实例
+    const tenantPrinterService = new PrinterService(dataSource, feieConfig);
+    const printer = await tenantPrinterService.repository.findOne({
+      where: { tenantId, printerSn } as any
     });
 
     if (!printer) {
@@ -130,69 +209,170 @@ export function createFeieRoutes(dataSource: DataSource) {
     const user = c.get('user');
     const tenantId = user?.tenantId || 1;
     const printerSn = c.req.param('printerSn');
-    const body = await c.req.json();
-    const printer = await printerService.updatePrinter(tenantId, printerSn, body);
-    return c.json({ success: true, data: printer });
+
+    try {
+      const body = await c.req.json();
+
+      // 获取飞鹅API配置
+      const feieConfig = await getFeieApiConfig(tenantId, dataSource);
+      if (!feieConfig) {
+        return c.json({ success: false, message: '飞鹅API配置未找到或配置不完整' }, 400);
+      }
+
+      // 创建带配置的服务实例
+      const tenantPrinterService = new PrinterService(dataSource, feieConfig);
+      const printer = await tenantPrinterService.updatePrinter(tenantId, printerSn, body);
+      return c.json({ success: true, data: printer });
+    } catch (error) {
+      console.error(`[租户${tenantId}] 更新打印机失败,打印机SN: ${printerSn}:`, error);
+      const errorMessage = error instanceof Error ? error.message : '更新打印机失败';
+      return c.json({ success: false, message: errorMessage }, 500);
+    }
   });
 
   app.delete('/printers/:printerSn', async (c) => {
     const user = c.get('user');
     const tenantId = user?.tenantId || 1;
     const printerSn = c.req.param('printerSn');
-    await printerService.deletePrinter(tenantId, printerSn);
-    return c.json({ success: true, message: '打印机删除成功' });
+
+    try {
+      // 获取飞鹅API配置
+      const feieConfig = await getFeieApiConfig(tenantId, dataSource);
+      if (!feieConfig) {
+        return c.json({ success: false, message: '飞鹅API配置未找到或配置不完整' }, 400);
+      }
+
+      // 创建带配置的服务实例
+      const tenantPrinterService = new PrinterService(dataSource, feieConfig);
+      await tenantPrinterService.deletePrinter(tenantId, printerSn);
+      return c.json({ success: true, message: '打印机删除成功' });
+    } catch (error) {
+      console.error(`[租户${tenantId}] 删除打印机失败,打印机SN: ${printerSn}:`, error);
+      const errorMessage = error instanceof Error ? error.message : '删除打印机失败';
+      return c.json({ success: false, message: errorMessage }, 500);
+    }
   });
 
   app.get('/printers/:printerSn/status', async (c) => {
     const user = c.get('user');
     const tenantId = user?.tenantId || 1;
     const printerSn = c.req.param('printerSn');
-    const status = await printerService.getPrinterStatus(tenantId, printerSn);
-    return c.json({ success: true, data: status });
+
+    try {
+      // 获取飞鹅API配置
+      const feieConfig = await getFeieApiConfig(tenantId, dataSource);
+      if (!feieConfig) {
+        return c.json({ success: false, message: '飞鹅API配置未找到或配置不完整' }, 400);
+      }
+
+      // 创建带配置的服务实例
+      const tenantPrinterService = new PrinterService(dataSource, feieConfig);
+      const status = await tenantPrinterService.getPrinterStatus(tenantId, printerSn);
+      return c.json({ success: true, data: status });
+    } catch (error) {
+      console.error(`[租户${tenantId}] 查询打印机状态失败,打印机SN: ${printerSn}:`, error);
+      const errorMessage = error instanceof Error ? error.message : '查询打印机状态失败';
+      return c.json({ success: false, message: errorMessage }, 500);
+    }
   });
 
   app.post('/printers/:printerSn/set-default', async (c) => {
     const user = c.get('user');
     const tenantId = user?.tenantId || 1;
     const printerSn = c.req.param('printerSn');
-    const printer = await printerService.setDefaultPrinter(tenantId, printerSn);
-    return c.json({ success: true, data: printer });
+
+    try {
+      // 获取飞鹅API配置
+      const feieConfig = await getFeieApiConfig(tenantId, dataSource);
+      if (!feieConfig) {
+        return c.json({ success: false, message: '飞鹅API配置未找到或配置不完整' }, 400);
+      }
+
+      // 创建带配置的服务实例
+      const tenantPrinterService = new PrinterService(dataSource, feieConfig);
+      const printer = await tenantPrinterService.setDefaultPrinter(tenantId, printerSn);
+      return c.json({ success: true, data: printer });
+    } catch (error) {
+      console.error(`[租户${tenantId}] 设置默认打印机失败,打印机SN: ${printerSn}:`, error);
+      const errorMessage = error instanceof Error ? error.message : '设置默认打印机失败';
+      return c.json({ success: false, message: errorMessage }, 500);
+    }
   });
 
   // 打印任务管理路由
   app.post('/tasks', async (c) => {
     const user = c.get('user');
     const tenantId = user?.tenantId || 1;
-    const body = await c.req.json();
-    const task = await printTaskService.createPrintTask(tenantId, body);
-    return c.json({ success: true, data: task });
+
+    try {
+      const body = await c.req.json();
+
+      // 获取飞鹅API配置
+      const feieConfig = await getFeieApiConfig(tenantId, dataSource);
+      if (!feieConfig) {
+        return c.json({ success: false, message: '飞鹅API配置未找到或配置不完整' }, 400);
+      }
+
+      // 创建带配置的打印任务服务实例
+      const tenantPrintTaskService = new PrintTaskService(dataSource, feieConfig);
+      const task = await tenantPrintTaskService.createPrintTask(tenantId, body);
+      return c.json({ success: true, data: task });
+    } catch (error) {
+      console.error(`[租户${tenantId}] 创建打印任务失败:`, error);
+      const errorMessage = error instanceof Error ? error.message : '创建打印任务失败';
+      return c.json({ success: false, message: errorMessage }, 500);
+    }
   });
 
   app.get('/tasks', async (c) => {
     const user = c.get('user');
     const tenantId = user?.tenantId || 1;
-    const query = c.req.query();
-    const filters = {
-      orderId: query.orderId ? parseInt(query.orderId, 10) : undefined,
-      printerSn: query.printerSn,
-      printType: query.printType as any,
-      printStatus: query.printStatus as any,
-      startDate: query.startDate ? new Date(query.startDate) : undefined,
-      endDate: query.endDate ? new Date(query.endDate) : undefined
-    };
-    const page = query.page ? parseInt(query.page, 10) : 1;
-    const limit = query.limit ? parseInt(query.limit, 10) : 20;
 
-    const result = await printTaskService.getPrintTasks(tenantId, filters, page, limit);
-    return c.json({ success: true, data: result.tasks, total: result.total, page, limit });
+    try {
+      const query = c.req.query();
+      const filters = {
+        orderId: query.orderId ? parseInt(query.orderId, 10) : undefined,
+        printerSn: query.printerSn,
+        printType: query.printType as any,
+        printStatus: query.status as any, // 前端传递的是status参数
+        startDate: query.startDate ? new Date(query.startDate) : undefined,
+        endDate: query.endDate ? new Date(query.endDate) : undefined,
+        search: query.search
+      };
+      const page = query.page ? parseInt(query.page, 10) : 1;
+      const limit = query.pageSize ? parseInt(query.pageSize, 10) : 20; // 前端传递的是pageSize参数
+
+      // 获取飞鹅API配置
+      const feieConfig = await getFeieApiConfig(tenantId, dataSource);
+      if (!feieConfig) {
+        return c.json({ success: false, message: '飞鹅API配置未找到或配置不完整' }, 400);
+      }
+
+      // 创建带配置的打印任务服务实例
+      const tenantPrintTaskService = new PrintTaskService(dataSource, feieConfig);
+      const result = await tenantPrintTaskService.getPrintTasks(tenantId, filters, page, limit);
+      return c.json({
+        success: true,
+        data: {
+          data: result.tasks,
+          total: result.total,
+          page,
+          pageSize: limit
+        }
+      });
+    } catch (error) {
+      console.error(`[租户${tenantId}] 获取打印任务列表失败:`, error);
+      const errorMessage = error instanceof Error ? error.message : '获取打印任务列表失败';
+      return c.json({ success: false, message: errorMessage }, 500);
+    }
   });
 
   app.get('/tasks/:taskId', async (c) => {
     const user = c.get('user');
     const tenantId = user?.tenantId || 1;
     const taskId = c.req.param('taskId');
-    const task = await (printTaskService as any).findOne({
-      where: { tenantId, taskId }
+    const task = await printTaskService.repository.findOne({
+      where: { tenantId, taskId } as any
     });
 
     if (!task) {
@@ -206,8 +386,23 @@ export function createFeieRoutes(dataSource: DataSource) {
     const user = c.get('user');
     const tenantId = user?.tenantId || 1;
     const taskId = c.req.param('taskId');
-    const status = await printTaskService.getPrintTaskStatus(tenantId, taskId);
-    return c.json({ success: true, data: status });
+
+    try {
+      // 获取飞鹅API配置
+      const feieConfig = await getFeieApiConfig(tenantId, dataSource);
+      if (!feieConfig) {
+        return c.json({ success: false, message: '飞鹅API配置未找到或配置不完整' }, 400);
+      }
+
+      // 创建带配置的打印任务服务实例
+      const tenantPrintTaskService = new PrintTaskService(dataSource, feieConfig);
+      const status = await tenantPrintTaskService.getPrintTaskStatus(tenantId, taskId);
+      return c.json({ success: true, data: status });
+    } catch (error) {
+      console.error(`[租户${tenantId}] 获取打印任务状态失败,任务ID: ${taskId}:`, error);
+      const errorMessage = error instanceof Error ? error.message : '获取打印任务状态失败';
+      return c.json({ success: false, message: errorMessage }, 500);
+    }
   });
 
   app.post('/tasks/:taskId/cancel', async (c) => {
@@ -223,31 +418,116 @@ export function createFeieRoutes(dataSource: DataSource) {
     const user = c.get('user');
     const tenantId = user?.tenantId || 1;
     const taskId = c.req.param('taskId');
-    const task = await printTaskService.retryPrintTask(tenantId, taskId);
-    return c.json({ success: true, data: task });
+
+    try {
+      // 获取飞鹅API配置
+      const feieConfig = await getFeieApiConfig(tenantId, dataSource);
+      if (!feieConfig) {
+        return c.json({ success: false, message: '飞鹅API配置未找到或配置不完整' }, 400);
+      }
+
+      // 创建带配置的打印任务服务实例
+      const tenantPrintTaskService = new PrintTaskService(dataSource, feieConfig);
+      const task = await tenantPrintTaskService.retryPrintTask(tenantId, taskId);
+      return c.json({ success: true, data: task });
+    } catch (error) {
+      console.error(`[租户${tenantId}] 重试打印任务失败,任务ID: ${taskId}:`, error);
+      const errorMessage = error instanceof Error ? error.message : '重试打印任务失败';
+      return c.json({ success: false, message: errorMessage }, 500);
+    }
   });
 
   // 调度器管理路由
   app.get('/scheduler/status', async (c) => {
+    const user = c.get('user');
+    console.debug('获取调度器状态 - 用户对象:', JSON.stringify(user, null, 2));
+    const tenantId = user?.tenantId || 1;
+
+    // 创建租户特定的调度器实例
+    const delaySchedulerService = new DelaySchedulerService(dataSource, {
+      baseUrl: 'https://api.feieyun.cn/Api/Open/',
+      user: '',
+      ukey: '',
+      timeout: 10000,
+      maxRetries: 3
+    }, tenantId);
+
     const status = delaySchedulerService.getStatus();
     return c.json({ success: true, data: status });
   });
 
   app.post('/scheduler/start', async (c) => {
+    const user = c.get('user');
+    console.debug('启动调度器 - 用户对象:', JSON.stringify(user, null, 2));
+    const tenantId = user?.tenantId || 1;
+
+    // 创建租户特定的调度器实例
+    const delaySchedulerService = new DelaySchedulerService(dataSource, {
+      baseUrl: 'https://api.feieyun.cn/Api/Open/',
+      user: '',
+      ukey: '',
+      timeout: 10000,
+      maxRetries: 3
+    }, tenantId);
+
     await delaySchedulerService.start();
     return c.json({ success: true, message: '调度器已启动' });
   });
 
   app.post('/scheduler/stop', async (c) => {
+    const user = c.get('user');
+    console.debug('停止调度器 - 用户对象:', JSON.stringify(user, null, 2));
+    const tenantId = user?.tenantId || 1;
+
+    // 创建租户特定的调度器实例
+    const delaySchedulerService = new DelaySchedulerService(dataSource, {
+      baseUrl: 'https://api.feieyun.cn/Api/Open/',
+      user: '',
+      ukey: '',
+      timeout: 10000,
+      maxRetries: 3
+    }, tenantId);
+
     await delaySchedulerService.stop();
     return c.json({ success: true, message: '调度器已停止' });
   });
 
   app.get('/scheduler/health', async (c) => {
+    const user = c.get('user');
+    console.debug('调度器健康检查 - 用户对象:', JSON.stringify(user, null, 2));
+    const tenantId = user?.tenantId || 1;
+
+    // 创建租户特定的调度器实例
+    const delaySchedulerService = new DelaySchedulerService(dataSource, {
+      baseUrl: 'https://api.feieyun.cn/Api/Open/',
+      user: '',
+      ukey: '',
+      timeout: 10000,
+      maxRetries: 3
+    }, tenantId);
+
     const health = await delaySchedulerService.healthCheck();
     return c.json({ success: true, data: health });
   });
 
+  app.post('/scheduler/trigger', async (c) => {
+    const user = c.get('user');
+    console.debug('手动触发调度器处理 - 用户对象:', JSON.stringify(user, null, 2));
+    const tenantId = user?.tenantId || 1;
+
+    // 创建租户特定的调度器实例
+    const delaySchedulerService = new DelaySchedulerService(dataSource, {
+      baseUrl: 'https://api.feieyun.cn/Api/Open/',
+      user: '',
+      ukey: '',
+      timeout: 10000,
+      maxRetries: 3
+    }, tenantId);
+
+    const result = await delaySchedulerService.triggerManualProcess();
+    return c.json({ success: result.success, data: result });
+  });
+
   // 打印配置管理路由
   app.get('/config', async (c) => {
     const user = c.get('user');
@@ -255,7 +535,20 @@ export function createFeieRoutes(dataSource: DataSource) {
 
     try {
       const configs = await configService.getPrintConfigs(tenantId);
-      return c.json({ success: true, data: configs });
+
+      // 将 FeieConfigMt 转换为前端需要的 FeieConfig 格式
+      const transformedConfigs = configs.map(config => ({
+        id: config.id,
+        tenantId: config.tenantId,
+        configKey: config.configKey as any, // 字符串转换为 ConfigKey 枚举
+        configValue: config.configValue || '', // 处理null值
+        configType: config.configType as any, // 字符串转换为 ConfigType 枚举
+        description: config.description || undefined,
+        createdAt: config.createdAt.toISOString(),
+        updatedAt: config.updatedAt.toISOString()
+      }));
+
+      return c.json({ success: true, data: { data: transformedConfigs } });
     } catch (error) {
       console.error(`[租户${tenantId}] 获取打印配置失败:`, error);
       return c.json({ success: false, message: '获取打印配置失败' }, 500);
@@ -274,7 +567,20 @@ export function createFeieRoutes(dataSource: DataSource) {
 
     try {
       const config = await configService.updatePrintConfig(tenantId, configKey, body.configValue);
-      return c.json({ success: true, data: config });
+
+      // 将 FeieConfigMt 转换为前端需要的 FeieConfig 格式
+      const transformedConfig = {
+        id: config.id,
+        tenantId: config.tenantId,
+        configKey: config.configKey as any, // 字符串转换为 ConfigKey 枚举
+        configValue: config.configValue || '', // 处理null值
+        configType: config.configType as any, // 字符串转换为 ConfigType 枚举
+        description: config.description || undefined,
+        createdAt: config.createdAt.toISOString(),
+        updatedAt: config.updatedAt.toISOString()
+      };
+
+      return c.json({ success: true, data: transformedConfig });
     } catch (error) {
       console.error(`[租户${tenantId}] 更新配置失败,key: ${configKey}:`, error);
       return c.json({ success: false, message: '更新配置失败' }, 500);

+ 9 - 16
packages/feie-printer-module-mt/src/services/config.service.ts

@@ -87,12 +87,9 @@ export class ConfigService {
    * 根据配置值猜测配置类型
    */
   private guessConfigType(configValue: string): string {
-    // 尝试解析为JSON
-    try {
-      JSON.parse(configValue);
-      return 'JSON';
-    } catch {
-      // 不是JSON
+    // 首先检查是否为数字(纯数字,不是JSON数字)
+    if (!isNaN(Number(configValue)) && configValue.trim() !== '' && !configValue.includes('{') && !configValue.includes('[')) {
+      return 'NUMBER';
     }
 
     // 检查是否为布尔值
@@ -100,9 +97,12 @@ export class ConfigService {
       return 'BOOLEAN';
     }
 
-    // 检查是否为数字
-    if (!isNaN(Number(configValue)) && configValue.trim() !== '') {
-      return 'NUMBER';
+    // 尝试解析为JSON
+    try {
+      JSON.parse(configValue);
+      return 'JSON';
+    } catch {
+      // 不是JSON
     }
 
     // 默认为字符串
@@ -114,14 +114,7 @@ export class ConfigService {
    */
   private getConfigDescription(configKey: string): string {
     const descriptions: Record<string, string> = {
-      'feie.enabled': '是否启用飞鹅打印功能',
-      'feie.default_printer_sn': '默认使用的打印机序列号',
-      'feie.auto_print_on_payment': '订单支付成功后是否自动打印小票',
-      'feie.auto_print_on_shipping': '订单发货时是否自动打印发货单',
       'feie.anti_refund_delay': '支付成功后等待确认无退款的时间(秒)',
-      'feie.retry_max_count': '打印失败时的最大重试次数',
-      'feie.retry_interval': '打印失败后重试的间隔时间(秒)',
-      'feie.task_timeout': '打印任务的最大执行时间(秒)',
       'feie.receipt_template': '小票打印的模板内容',
       'feie.shipping_template': '发货单打印的模板内容'
     };

+ 90 - 35
packages/feie-printer-module-mt/src/services/delay-scheduler.service.ts

@@ -1,7 +1,7 @@
 import { DataSource } from 'typeorm';
 import * as cron from 'node-cron';
 import { PrintTaskService } from './print-task.service';
-import { FeieApiConfig, PrintStatus, CancelReason } from '../types/feie.types';
+import { FeieApiConfig, CancelReason } from '../types/feie.types';
 import { OrderMt } from '@d8d/orders-module-mt';
 
 export class DelaySchedulerService {
@@ -9,14 +9,23 @@ export class DelaySchedulerService {
   private isRunning: boolean = false;
   private cronJob: cron.ScheduledTask | null = null;
   private defaultDelaySeconds: number = 120; // 默认2分钟延迟
+  private tenantId: number | null = null; // 租户ID,null表示处理所有租户
 
   private dataSource: DataSource;
-  private orderRepository: any;
+  private orderRepository: any | null;
 
-  constructor(dataSource: DataSource, feieConfig: FeieApiConfig) {
+  constructor(dataSource: DataSource, feieConfig: FeieApiConfig, tenantId?: number) {
     this.dataSource = dataSource;
     this.printTaskService = new PrintTaskService(dataSource, feieConfig);
-    this.orderRepository = dataSource.getRepository(OrderMt);
+    this.tenantId = tenantId || null;
+
+    // 尝试获取订单仓库,如果不可用则设置为null
+    try {
+      this.orderRepository = dataSource.getRepository(OrderMt);
+    } catch (error) {
+      console.warn('无法获取订单仓库,订单退款检查功能将不可用:', error);
+      this.orderRepository = null;
+    }
   }
 
   /**
@@ -64,36 +73,33 @@ export class DelaySchedulerService {
     try {
       console.log('开始检查延迟打印任务...');
 
-      // 由于我们无法直接获取所有租户列表,我们需要通过查询有延迟任务的租户来处理
-      // 在实际应用中,应该从租户服务获取所有活跃租户
-      // 这里简化处理:直接处理所有到期的延迟任务
+      if (this.tenantId !== null) {
+        // 只处理指定租户的延迟任务
+        console.log(`处理租户 ${this.tenantId} 的延迟任务`);
+        await this.processTenantDelayedTasks(this.tenantId);
+      } else {
+        // 处理所有租户的延迟任务(向后兼容)
+        const tenantIds = await this.getTenantsWithDelayedTasks();
 
-      // 获取所有租户的延迟任务(通过打印任务服务)
-      // 注意:这里假设打印任务服务可以处理跨租户查询
-      // 在实际实现中,可能需要更复杂的租户管理逻辑
+        if (tenantIds.length === 0) {
+          console.log('没有找到有延迟任务的租户');
+          return;
+        }
 
-      // 由于技术限制,我们暂时记录日志
-      // 实际实现应该调用 processAllTenantsDelayedTasks()
-      console.log('延迟任务检查完成(实际处理逻辑需要租户管理支持)');
+        console.log(`找到 ${tenantIds.length} 个有延迟任务的租户`);
 
+        // 处理每个租户的延迟任务
+        for (const tenantId of tenantIds) {
+          await this.processTenantDelayedTasks(tenantId);
+        }
+      }
+
+      console.log('延迟任务检查完成');
     } catch (error) {
       console.error('处理延迟打印任务失败:', error);
     }
   }
 
-  /**
-   * 处理所有租户的延迟打印任务
-   */
-  private async processAllTenantsDelayedTasks(): Promise<void> {
-    try {
-      // 这里应该从租户服务获取所有活跃租户ID
-      // 由于没有租户服务,我们暂时无法实现完整的跨租户处理
-      // 在实际项目中,应该注入租户服务
-      console.warn('processAllTenantsDelayedTasks: 需要租户服务支持');
-    } catch (error) {
-      console.error('处理所有租户延迟任务失败:', error);
-    }
-  }
 
   /**
    * 处理指定租户的延迟打印任务
@@ -158,6 +164,12 @@ export class DelaySchedulerService {
    */
   private async shouldCancelDueToRefund(tenantId: number, orderId: number): Promise<boolean> {
     try {
+      // 如果订单仓库不可用,跳过检查
+      if (!this.orderRepository) {
+        console.debug('订单仓库不可用,跳过退款检查');
+        return false;
+      }
+
       // 查询订单状态
       const order = await this.orderRepository.findOne({
         where: { id: orderId, tenantId }
@@ -213,29 +225,42 @@ export class DelaySchedulerService {
   getStatus(): {
     isRunning: boolean;
     defaultDelaySeconds: number;
+    tenantId: number | null;
     lastProcessTime?: Date;
   } {
     return {
       isRunning: this.isRunning,
-      defaultDelaySeconds: this.defaultDelaySeconds
+      defaultDelaySeconds: this.defaultDelaySeconds,
+      tenantId: this.tenantId
     };
   }
 
   /**
    * 手动触发任务处理
    */
-  async triggerManualProcess(tenantId: number): Promise<{
+  async triggerManualProcess(tenantId?: number): Promise<{
     success: boolean;
     processedTasks: number;
     message: string;
   }> {
     try {
-      const pendingTasks = await this.printTaskService.getPendingDelayedTasks(tenantId);
+      // 使用指定的tenantId或调度器的tenantId
+      const targetTenantId = tenantId !== undefined ? tenantId : this.tenantId;
+
+      if (targetTenantId === null) {
+        return {
+          success: false,
+          processedTasks: 0,
+          message: '未指定租户ID,无法手动处理任务'
+        };
+      }
+
+      const pendingTasks = await this.printTaskService.getPendingDelayedTasks(targetTenantId);
       let processedCount = 0;
 
       for (const task of pendingTasks) {
         try {
-          await this.printTaskService.executePrintTask(tenantId, task.taskId);
+          await this.printTaskService.executePrintTask(targetTenantId, task.taskId);
           processedCount++;
         } catch (error) {
           console.error(`手动处理任务失败 ${task.taskId}:`, error);
@@ -283,11 +308,41 @@ export class DelaySchedulerService {
   }
 
   /**
-   * 获取活跃租户列表(简化实现)
-   * 在实际应用中,应该从租户服务获取
+   * 获取有延迟打印任务的租户ID列表
    */
-  private async getActiveTenants(): Promise<Array<{ id: number; name: string }>> {
-    // 这里返回一个空数组,实际实现应该查询租户表
-    return [];
+  private async getTenantsWithDelayedTasks(): Promise<number[]> {
+    try {
+      let query: string;
+      let params: any[] = [];
+
+      if (this.tenantId !== null) {
+        // 只查询指定租户
+        query = `
+          SELECT DISTINCT tenant_id
+          FROM feie_print_task_mt
+          WHERE tenant_id = $1
+            AND print_status = 'DELAYED'
+            AND scheduled_at <= NOW()
+          ORDER BY tenant_id
+        `;
+        params = [this.tenantId];
+      } else {
+        // 查询所有租户
+        query = `
+          SELECT DISTINCT tenant_id
+          FROM feie_print_task_mt
+          WHERE print_status = 'DELAYED'
+            AND scheduled_at <= NOW()
+          ORDER BY tenant_id
+        `;
+      }
+
+      const result = await this.dataSource.query(query, params);
+      return result.map((row: any) => row.tenant_id);
+    } catch (error) {
+      console.error('获取有延迟任务的租户失败:', error);
+      return [];
+    }
   }
+
 }

+ 283 - 21
packages/feie-printer-module-mt/src/services/feie-api.service.ts

@@ -1,4 +1,4 @@
-import axios, { AxiosInstance, AxiosError } from 'axios';
+import axios, { AxiosInstance } from 'axios';
 import { createHash } from 'crypto';
 import { FeieApiConfig, FeiePrinterInfo, FeiePrintRequest, FeiePrintResponse, FeiePrinterStatusResponse, FeieOrderStatusResponse, FeieAddPrinterResponse, FeieDeletePrinterResponse } from '../types/feie.types';
 
@@ -13,7 +13,7 @@ export class FeieApiService {
 
     this.config = {
       baseUrl: defaultBaseUrl,
-      timeout: 10000,
+      timeout: 30000, // 增加到30秒,飞鹅API有时响应较慢
       maxRetries: 3,
       ...config
     };
@@ -37,6 +37,112 @@ export class FeieApiService {
     return createHash('sha1').update(content).digest('hex');
   }
 
+  /**
+   * 解析飞鹅API返回的响应(支持JSON和PHP var_dump格式)
+   */
+  private parseFeieResponse(responseString: string): any {
+    try {
+      console.debug('开始解析飞鹅API响应');
+
+      // 移除多余的空白字符和换行
+      const cleaned = responseString.trim();
+      console.debug('清理后的响应:', cleaned.substring(0, 200) + '...');
+
+      // 首先尝试解析为JSON
+      try {
+        const jsonResult = JSON.parse(cleaned);
+        console.debug('成功解析为JSON格式');
+        return jsonResult;
+      } catch (jsonError) {
+        console.debug('不是JSON格式,尝试解析PHP var_dump格式');
+      }
+
+      // 检查是否是var_dump格式
+      if (!cleaned.startsWith('array(')) {
+        console.debug('不是PHP var_dump格式');
+        return null;
+      }
+
+      // 解析PHP var_dump格式
+      const result: any = {};
+
+      // 提取ret字段 - 支持多种格式
+      let retMatch = cleaned.match(/\["ret"\]\s*=>\s*int\((\d+)\)/);
+      if (!retMatch) {
+        retMatch = cleaned.match(/\["ret"\]\s*=>\s*string\((\d+)\)\s*"(\d+)"/);
+        if (retMatch) {
+          result.ret = parseInt(retMatch[2], 10);
+        }
+      } else {
+        result.ret = parseInt(retMatch[1], 10);
+      }
+
+      // 提取msg字段
+      const msgMatch = cleaned.match(/\["msg"\]\s*=>\s*string\((\d+)\)\s*"([^"]*)"/);
+      if (msgMatch) {
+        result.msg = msgMatch[2];
+      }
+
+      // 提取data字段中的信息
+      const dataMatch = cleaned.match(/\["data"\]\s*=>\s*array\(\d+\)\s*{([\s\S]*?)}\s*}/);
+      if (dataMatch) {
+        const dataContent = dataMatch[1];
+        result.data = {};
+
+        // 提取ok数组
+        const okMatch = dataContent.match(/\["ok"\]\s*=>\s*array\(\d+\)\s*{([\s\S]*?)}/);
+        if (okMatch) {
+          result.data.ok = [];
+          const okContent = okMatch[1];
+          const okItems = okContent.match(/\[(\d+)\]\s*=>\s*string\(\d+\)\s*"([^"]*)"/g);
+          if (okItems) {
+            okItems.forEach(item => {
+              const itemMatch = item.match(/string\(\d+\)\s*"([^"]*)"/);
+              if (itemMatch) {
+                result.data.ok.push(itemMatch[1]);
+              }
+            });
+          }
+        }
+
+        // 提取no数组
+        const noMatch = dataContent.match(/\["no"\]\s*=>\s*array\(\d+\)\s*{([\s\S]*?)}/);
+        if (noMatch) {
+          result.data.no = [];
+          const noContent = noMatch[1];
+          const noItems = noContent.match(/\[(\d+)\]\s*=>\s*string\(\d+\)\s*"([^"]*)"/g);
+          if (noItems) {
+            noItems.forEach(item => {
+              const itemMatch = item.match(/string\(\d+\)\s*"([^"]*)"/);
+              if (itemMatch) {
+                result.data.no.push(itemMatch[1]);
+              }
+            });
+          }
+        }
+      }
+
+      // 如果没有解析到ret字段,尝试其他方式
+      if (result.ret === undefined) {
+        console.debug('未解析到ret字段,尝试其他方式');
+        // 检查是否有错误信息
+        if (cleaned.includes('错误:')) {
+          result.ret = -1; // 假设是参数错误
+          result.msg = '打印机添加失败';
+        } else if (cleaned.includes('"ok"')) {
+          result.ret = 0;
+          result.msg = 'ok';
+        }
+      }
+
+      console.debug('解析结果:', JSON.stringify(result, null, 2));
+      return result;
+    } catch (error) {
+      console.debug('解析飞鹅API响应失败:', error);
+      return null;
+    }
+  }
+
   /**
    * 执行API请求,支持重试
    */
@@ -48,6 +154,8 @@ export class FeieApiService {
       user: this.config.user,
       stime: timestamp,
       sig: signature,
+      apiname: endpoint,  // 飞鹅API要求将接口名作为apiname参数传递
+      debug: 1,  // 添加debug参数以便查看更多错误信息
       ...params
     };
 
@@ -58,11 +166,65 @@ export class FeieApiService {
 
     for (let attempt = 1; attempt <= this.maxRetries; attempt++) {
       try {
-        const response = await this.client.post(endpoint, requestParams);
+        // 飞鹅API所有请求都发送到根路径,接口名通过apiname参数指定
+        const response = await this.client.post('', requestParams);
+
+        // 调试日志:记录完整的API响应
+        console.debug(`飞鹅API响应:`, {
+          status: response.status,
+          statusText: response.statusText,
+          data: response.data,
+          headers: response.headers
+        });
+
+        // 检查响应数据结构
+        if (!response.data) {
+          throw new Error(`飞鹅API响应格式错误: 响应数据为空`);
+        }
 
-        if (response.data.ret !== 0) {
-          const errorMsg = response.data.msg || '未知错误';
-          const errorCode = response.data.ret;
+        // 尝试解析响应数据,可能是字符串或对象
+        let responseData = response.data;
+
+        // 如果是字符串,使用统一的解析函数
+        if (typeof responseData === 'string') {
+          const parsedResponse = this.parseFeieResponse(responseData);
+          if (parsedResponse) {
+            responseData = parsedResponse;
+          } else {
+            // 检查是否是HTML错误页面
+            if (responseData.includes('<!DOCTYPE') || responseData.includes('<html')) {
+              throw new Error(`飞鹅API返回HTML错误页面,可能是网络或配置问题: ${responseData.substring(0, 200)}...`);
+            }
+            throw new Error(`飞鹅API响应格式错误: 无法解析响应数据: ${responseData.substring(0, 200)}...`);
+          }
+        }
+
+        // 检查是否为对象
+        if (typeof responseData !== 'object' || responseData === null) {
+          throw new Error(`飞鹅API响应格式错误: 响应数据不是有效的对象: ${JSON.stringify(responseData)}`);
+        }
+
+        // 检查ret字段是否存在
+        if (responseData.ret === undefined) {
+          // 飞鹅API有时会返回不同的字段名,检查常见字段
+          if (responseData.code !== undefined) {
+            // 如果有code字段,将其映射到ret
+            responseData.ret = responseData.code;
+          } else if (responseData.error !== undefined) {
+            // 如果有error字段,将其作为错误信息
+            throw new Error(`飞鹅API错误: ${responseData.error}`);
+          } else if (responseData.msg !== undefined) {
+            // 如果只有msg字段,将其作为错误信息,并设置ret为-1(参数错误)
+            responseData.ret = -1;
+            console.debug('飞鹅API响应缺少ret字段,但包含msg字段,设置为参数错误:', responseData.msg);
+          } else {
+            throw new Error(`飞鹅API响应格式错误: 缺少ret字段,响应数据: ${JSON.stringify(responseData)}`);
+          }
+        }
+
+        if (responseData.ret !== 0) {
+          const errorMsg = responseData.msg || '未知错误';
+          const errorCode = responseData.ret;
 
           // 根据错误代码提供更详细的错误信息
           let detailedMessage = `飞鹅API错误: ${errorMsg} (错误代码: ${errorCode})`;
@@ -112,7 +274,7 @@ export class FeieApiService {
           throw new Error(detailedMessage);
         }
 
-        return response.data;
+        return responseData;
       } catch (error) {
         lastError = error as Error;
 
@@ -121,7 +283,7 @@ export class FeieApiService {
           const status = error.response?.status;
           const data = error.response?.data;
 
-          let axiosErrorMsg = `HTTP错误: ${status}`;
+          let axiosErrorMsg = `HTTP错误: ${status || '未知'}`;
 
           if (status === 400) {
             axiosErrorMsg += ' - 请求参数错误';
@@ -136,7 +298,7 @@ export class FeieApiService {
             axiosErrorMsg += ' - API端点不存在';
           } else if (status === 429) {
             axiosErrorMsg += ' - 请求频率过高';
-          } else if (status >= 500) {
+          } else if (status && status >= 500) {
             axiosErrorMsg += ' - 服务器内部错误';
           }
 
@@ -160,6 +322,7 @@ export class FeieApiService {
    */
   async addPrinter(printerInfo: FeiePrinterInfo): Promise<FeieAddPrinterResponse> {
     const { sn, key, name = '' } = printerInfo;
+    // 飞鹅API要求格式:sn#key#remark,其中remark是备注名称
     const snlist = `${sn}#${key}#${name}`;
 
     return this.executeRequest<FeieAddPrinterResponse>('Open_printerAddlist', {
@@ -182,10 +345,8 @@ export class FeieApiService {
    * 查询打印机状态
    */
   async queryPrinterStatus(sn: string): Promise<FeiePrinterStatusResponse> {
-    const snlist = sn;
-
     return this.executeRequest<FeiePrinterStatusResponse>('Open_queryPrinterStatus', {
-      snlist
+      sn
     });
   }
 
@@ -256,18 +417,119 @@ export class FeieApiService {
    */
   async validatePrinterConfig(sn: string, key: string): Promise<boolean> {
     try {
-      // 临时创建一个配置来测试打印机
-      const tempConfig: FeieApiConfig = {
-        baseUrl: this.config.baseUrl,
-        user: this.config.user,
-        ukey: this.config.ukey
-      };
+      console.debug(`开始验证打印机配置,SN: ${sn}, 用户: ${this.config.user}`);
+
+      // 首先尝试查询打印机状态,检查打印机是否已存在
+      try {
+        await this.executeRequest('Open_queryPrinterStatus', {
+          sn
+        });
+        console.debug(`打印机 ${sn} 已存在,配置验证通过`);
+        return true;
+      } catch (queryError: any) {
+        console.debug(`查询打印机状态失败:`, queryError.message);
+
+        // 如果错误代码是-4(打印机不存在),尝试添加打印机来验证密钥
+        // 或者错误代码是undefined(可能是响应格式错误),也尝试添加验证
+        // 或者错误信息包含"响应格式错误",也尝试添加验证
+        if (queryError.message.includes('错误代码: -4') ||
+            queryError.message.includes('错误代码: undefined') ||
+            queryError.message.includes('响应格式错误')) {
+          console.debug(`打印机 ${sn} 不存在或响应格式错误,尝试添加验证。错误信息: ${queryError.message}`);
+
+          try {
+            // 尝试添加打印机
+            const addResponse = await this.executeRequest<any>('Open_printerAddlist', {
+              printerContent: `${sn}#${key}#验证配置`
+            });
+
+            console.debug(`打印机添加API响应:`, JSON.stringify(addResponse, null, 2));
+
+            // 检查响应结果
+            if (addResponse.ret === 0) {
+              // ret为0表示API调用成功
+              // 检查data.no数组中是否包含当前打印机
+              const isInNoArray = addResponse.data?.no?.some((item: string) =>
+                item.includes(sn) && item.includes(key)
+              );
+
+              if (isInNoArray) {
+                // 打印机在no数组中,检查具体错误
+                const errorItem = addResponse.data.no.find((item: string) =>
+                  item.includes(sn) && item.includes(key)
+                );
+
+                if (errorItem && errorItem.includes('已被添加过')) {
+                  console.debug(`打印机 ${sn} 已被添加过,配置验证通过`);
+                  return true; // 打印机已存在,配置正确
+                } else if (errorItem && errorItem.includes('设备编号和KEY不正确')) {
+                  console.debug(`打印机 ${sn} 设备编号和KEY不正确,配置验证失败`);
+                  return false; // 配置错误
+                } else {
+                  console.debug(`打印机 ${sn} 添加失败,未知错误: ${errorItem}`);
+                  return false;
+                }
+              } else {
+                // 打印机添加成功或在ok数组中
+                console.debug(`打印机 ${sn} 添加成功,配置验证通过,开始删除测试打印机`);
+
+                // 删除刚刚添加的测试打印机
+                try {
+                  await this.executeRequest('Open_printerDelList', {
+                    snlist: sn
+                  });
+                  console.debug('删除测试打印机成功');
+                } catch (deleteError: any) {
+                  console.debug('删除测试打印机失败,但配置验证已通过:', deleteError.message);
+                }
+
+                return true;
+              }
+            } else {
+              // ret不为0,API调用失败
+              console.debug(`打印机 ${sn} 添加失败,ret: ${addResponse.ret}, msg: ${addResponse.msg}`);
+              return false;
+            }
+          } catch (addError: any) {
+            console.debug(`打印机 ${sn} 添加失败:`, addError.message);
+
+            // 如果返回-1(参数错误)、-2(签名错误)或-3(用户或密钥错误),说明配置有问题
+            // 其他错误(如格式错误等)也认为配置有问题
+            const isParamError = addError.message.includes('错误代码: -1') ||
+                                addError.message.includes('错误代码: -2') ||
+                                addError.message.includes('错误代码: -3');
+
+            // 检查是否是响应格式错误
+            const isFormatError = addError.message.includes('响应格式错误') ||
+                                 addError.message.includes('无法解析响应数据') ||
+                                 addError.message.includes('HTML错误页面');
+
+            // 检查是否是超时错误
+            const isTimeoutError = addError.message.includes('timeout') ||
+                                  addError.message.includes('超时') ||
+                                  addError.message.includes('Timeout');
+
+            const isValid = !isParamError && !isFormatError && !isTimeoutError;
+            console.debug(`配置验证结果: ${isValid ? '通过' : '失败'}, 参数错误: ${isParamError}, 格式错误: ${isFormatError}, 超时错误: ${isTimeoutError}`);
+
+            // 如果是超时错误,可能是网络问题,建议用户检查网络连接
+            if (isTimeoutError) {
+              console.debug('飞鹅API请求超时,请检查网络连接或联系管理员检查飞鹅API服务状态');
+            }
 
-      const tempService = new FeieApiService(tempConfig);
-      const response = await tempService.queryPrinterStatus(sn);
+            return isValid;
+          }
+        }
 
-      return response.ret === 0;
+        // 其他错误情况
+        console.debug(`查询打印机状态失败:`, queryError.message);
+        return false;
+      }
     } catch (error) {
+      console.error('验证打印机配置失败:', error);
+      if (error instanceof Error) {
+        console.error('错误详情:', error.message);
+      }
       return false;
     }
   }

+ 78 - 64
packages/feie-printer-module-mt/src/services/print-task.service.ts

@@ -1,5 +1,5 @@
 import { GenericCrudService } from '@d8d/shared-crud';
-import { DataSource, Repository } from 'typeorm';
+import { DataSource, Repository, LessThanOrEqual } from 'typeorm';
 import { FeiePrintTaskMt } from '../entities/feie-print-task.mt.entity';
 import { FeiePrinterMt } from '../entities/feie-printer.mt.entity';
 import { FeieConfigMt } from '../entities/feie-config.mt.entity';
@@ -98,62 +98,24 @@ export class PrintTaskService extends GenericCrudService<FeiePrintTaskMt> {
    * 获取重试间隔(毫秒)
    */
   private async getRetryInterval(tenantId: number): Promise<number> {
-    try {
-      const intervalValue = await this.getTenantConfigValue(tenantId, 'feie.retry_interval', '5000');
-      const intervalMs = parseInt(intervalValue, 10);
-
-      if (isNaN(intervalMs) || intervalMs < 1000) {
-        console.warn(`[租户${tenantId}] 无效的重试间隔配置: ${intervalValue},使用默认值5000ms`);
-        return 5000;
-      }
-
-      // 限制最大重试间隔(例如60秒)
-      const maxInterval = 60 * 1000;
-      if (intervalMs > maxInterval) {
-        console.warn(`[租户${tenantId}] 重试间隔超过最大值: ${intervalMs}ms,限制为${maxInterval}ms`);
-        return maxInterval;
-      }
-
-      return intervalMs;
-    } catch (error) {
-      console.warn(`[租户${tenantId}] 获取重试间隔失败,使用默认值5000ms:`, error);
-      return 5000;
-    }
+    // 使用固定值5000毫秒(5秒)
+    return 5000;
   }
 
   /**
    * 获取最大重试次数
    */
   private async getMaxRetries(tenantId: number): Promise<number> {
-    try {
-      const maxRetriesValue = await this.getTenantConfigValue(tenantId, 'feie.retry_max_count', '3');
-      const maxRetries = parseInt(maxRetriesValue, 10);
-
-      if (isNaN(maxRetries) || maxRetries < 0) {
-        console.warn(`[租户${tenantId}] 无效的最大重试次数配置: ${maxRetriesValue},使用默认值3`);
-        return 3;
-      }
-
-      // 限制最大重试次数(例如10次)
-      const maxAllowed = 10;
-      if (maxRetries > maxAllowed) {
-        console.warn(`[租户${tenantId}] 最大重试次数超过限制: ${maxRetries},限制为${maxAllowed}`);
-        return maxAllowed;
-      }
-
-      return maxRetries;
-    } catch (error) {
-      console.warn(`[租户${tenantId}] 获取最大重试次数失败,使用默认值3:`, error);
-      return 3;
-    }
+    // 使用固定值3次
+    return 3;
   }
 
   /**
    * 执行打印任务
    */
   async executePrintTask(tenantId: number, taskId: string): Promise<FeiePrintTaskMt> {
-    const task = await this.findOne({
-      where: { tenantId, taskId }
+    const task = await this.repository.findOne({
+      where: { tenantId, taskId } as any
     });
 
     if (!task) {
@@ -190,6 +152,10 @@ export class PrintTaskService extends GenericCrudService<FeiePrintTaskMt> {
         errorMessage: null
       });
 
+      if (!updatedTask) {
+        throw new Error('更新打印任务状态失败');
+      }
+
       return updatedTask;
     } catch (error) {
       // 处理打印失败
@@ -241,8 +207,8 @@ export class PrintTaskService extends GenericCrudService<FeiePrintTaskMt> {
     taskId: string,
     reason: CancelReason = CancelReason.MANUAL
   ): Promise<FeiePrintTaskMt> {
-    const task = await this.findOne({
-      where: { tenantId, taskId }
+    const task = await this.repository.findOne({
+      where: { tenantId, taskId } as any
     });
 
     if (!task) {
@@ -255,19 +221,25 @@ export class PrintTaskService extends GenericCrudService<FeiePrintTaskMt> {
     }
 
     // 更新任务状态
-    return this.update(task.id, {
+    const updatedTask = await this.update(task.id, {
       printStatus: PrintStatus.CANCELLED,
       cancelledAt: new Date(),
       cancelReason: reason
     });
+
+    if (!updatedTask) {
+      throw new Error('更新打印任务状态失败');
+    }
+
+    return updatedTask;
   }
 
   /**
    * 重试打印任务
    */
   async retryPrintTask(tenantId: number, taskId: string): Promise<FeiePrintTaskMt> {
-    const task = await this.findOne({
-      where: { tenantId, taskId }
+    const task = await this.repository.findOne({
+      where: { tenantId, taskId } as any
     });
 
     if (!task) {
@@ -286,6 +258,10 @@ export class PrintTaskService extends GenericCrudService<FeiePrintTaskMt> {
       retryCount: 0
     });
 
+    if (!updatedTask) {
+      throw new Error('更新打印任务状态失败');
+    }
+
     // 立即执行重试
     await this.executePrintTask(tenantId, taskId);
 
@@ -296,8 +272,8 @@ export class PrintTaskService extends GenericCrudService<FeiePrintTaskMt> {
    * 查询打印任务状态
    */
   async getPrintTaskStatus(tenantId: number, taskId: string): Promise<any> {
-    const task = await this.findOne({
-      where: { tenantId, taskId }
+    const task = await this.repository.findOne({
+      where: { tenantId, taskId } as any
     });
 
     if (!task) {
@@ -371,6 +347,7 @@ export class PrintTaskService extends GenericCrudService<FeiePrintTaskMt> {
       printStatus?: PrintStatus;
       startDate?: Date;
       endDate?: Date;
+      search?: string;
     } = {},
     page: number = 1,
     limit: number = 20
@@ -403,12 +380,49 @@ export class PrintTaskService extends GenericCrudService<FeiePrintTaskMt> {
       }
     }
 
-    const [tasks, total] = await this.repository.findAndCount({
-      where,
-      order: { createdAt: 'DESC' },
-      skip: (page - 1) * limit,
-      take: limit
-    });
+    // 构建查询
+    const queryBuilder = this.repository.createQueryBuilder('task')
+      .where('task.tenantId = :tenantId', { tenantId })
+      .orderBy('task.createdAt', 'DESC');
+
+    // 应用筛选条件
+    if (filters.orderId) {
+      queryBuilder.andWhere('task.orderId = :orderId', { orderId: filters.orderId });
+    }
+
+    if (filters.printerSn) {
+      queryBuilder.andWhere('task.printerSn = :printerSn', { printerSn: filters.printerSn });
+    }
+
+    if (filters.printType) {
+      queryBuilder.andWhere('task.printType = :printType', { printType: filters.printType });
+    }
+
+    if (filters.printStatus) {
+      queryBuilder.andWhere('task.printStatus = :printStatus', { printStatus: filters.printStatus });
+    }
+
+    if (filters.startDate) {
+      queryBuilder.andWhere('task.createdAt >= :startDate', { startDate: filters.startDate });
+    }
+
+    if (filters.endDate) {
+      queryBuilder.andWhere('task.createdAt <= :endDate', { endDate: filters.endDate });
+    }
+
+    // 应用搜索条件
+    if (filters.search) {
+      const searchTerm = `%${filters.search}%`;
+      queryBuilder.andWhere(
+        '(task.taskId LIKE :search OR CAST(task.orderId AS CHAR) LIKE :search OR task.printerSn LIKE :search)',
+        { search: searchTerm }
+      );
+    }
+
+    // 应用分页
+    queryBuilder.skip((page - 1) * limit).take(limit);
+
+    const [tasks, total] = await queryBuilder.getManyAndCount();
 
     return { tasks, total };
   }
@@ -417,13 +431,13 @@ export class PrintTaskService extends GenericCrudService<FeiePrintTaskMt> {
    * 获取待处理的延迟打印任务
    */
   async getPendingDelayedTasks(tenantId: number): Promise<FeiePrintTaskMt[]> {
-    return this.findMany({
+    return this.repository.find({
       where: {
         tenantId,
         printStatus: PrintStatus.DELAYED,
-        scheduledAt: { $lte: new Date() }
-      },
-      order: { scheduledAt: 'ASC' }
+        scheduledAt: LessThanOrEqual(new Date())
+      } as any,
+      order: { scheduledAt: 'ASC' } as any
     });
   }
 
@@ -431,12 +445,12 @@ export class PrintTaskService extends GenericCrudService<FeiePrintTaskMt> {
    * 处理退款事件,取消相关打印任务
    */
   async handleRefundEvent(tenantId: number, orderId: number): Promise<void> {
-    const tasks = await this.findMany({
+    const tasks = await this.repository.find({
       where: {
         tenantId,
         orderId,
         printStatus: PrintStatus.DELAYED
-      }
+      } as any
     });
 
     for (const task of tasks) {

+ 294 - 54
packages/feie-printer-module-mt/src/services/print-trigger.service.ts

@@ -1,8 +1,10 @@
 import { DataSource } from 'typeorm';
 import { PrintTaskService } from './print-task.service';
 import { PrinterService } from './printer.service';
+import { DelaySchedulerService } from './delay-scheduler.service';
 import { FeieApiConfig, PrintType, PrintStatus, CancelReason } from '../types/feie.types';
 import { FeieConfigMt } from '../entities/feie-config.mt.entity';
+import { OrderMt } from '@d8d/orders-module-mt';
 
 /**
  * 打印触发服务
@@ -11,12 +13,18 @@ import { FeieConfigMt } from '../entities/feie-config.mt.entity';
 export class PrintTriggerService {
   private printTaskService: PrintTaskService;
   private printerService: PrinterService;
+  private dataSource: DataSource;
+  private feieConfig: FeieApiConfig;
   private configRepository: any;
+  private orderRepository: any;
 
   constructor(dataSource: DataSource, feieConfig: FeieApiConfig) {
+    this.dataSource = dataSource;
+    this.feieConfig = feieConfig;
     this.printTaskService = new PrintTaskService(dataSource, feieConfig);
     this.printerService = new PrinterService(dataSource, feieConfig);
     this.configRepository = dataSource.getRepository(FeieConfigMt);
+    this.orderRepository = dataSource.getRepository(OrderMt);
   }
 
   /**
@@ -27,25 +35,15 @@ export class PrintTriggerService {
    */
   async handleOrderPaymentSuccess(
     tenantId: number,
-    orderId: number,
-    orderInfo: {
-      orderNo: string;
-      amount: number;
-      userId: number;
-      items?: Array<{
-        name: string | null;
-        quantity: number;
-        price: number;
-      }>;
-    }
+    orderId: number
   ): Promise<void> {
     try {
       console.debug(`[租户${tenantId}] 处理订单支付成功事件,订单ID: ${orderId}`);
 
-      // 1. 检查是否启用自动打印
-      const autoPrintEnabled = await this.getConfigValue(tenantId, 'feie.auto_print_on_payment', 'true');
-      if (autoPrintEnabled !== 'true') {
-        console.debug(`[租户${tenantId}] 自动打印未启用,跳过打印任务`);
+      // 1. 获取完整的订单信息
+      const fullOrderInfo = await this.getFullOrderInfo(tenantId, orderId);
+      if (!fullOrderInfo) {
+        console.warn(`[租户${tenantId}] 未找到订单信息,订单ID: ${orderId},跳过打印任务`);
         return;
       }
 
@@ -60,7 +58,7 @@ export class PrintTriggerService {
       }
 
       // 4. 生成打印内容
-      const printContent = this.generateReceiptContent(orderInfo);
+      const printContent = await this.generateReceiptContent(tenantId, fullOrderInfo);
 
       // 5. 创建延迟打印任务
       await this.printTaskService.createPrintTask(tenantId, {
@@ -72,6 +70,28 @@ export class PrintTriggerService {
       });
 
       console.debug(`[租户${tenantId}] 订单支付成功打印任务已创建,订单ID: ${orderId}, 延迟时间: ${delaySeconds}秒`);
+
+      // 6. 自动启动延迟任务检查
+      // 如果延迟时间为0或负数,立即触发打印
+      // 如果延迟时间已过(比如配置错误或系统时间问题),也立即触发
+      if (delaySeconds <= 0) {
+        console.debug(`[租户${tenantId}] 延迟时间为${delaySeconds}秒,立即触发打印任务检查`);
+
+        try {
+          // 动态创建DelaySchedulerService实例
+          const delaySchedulerService = new DelaySchedulerService(this.dataSource, this.feieConfig, tenantId);
+          const result = await delaySchedulerService.triggerManualProcess(tenantId);
+
+          if (result.success) {
+            console.debug(`[租户${tenantId}] 立即触发打印成功,处理了${result.processedTasks}个任务`);
+          } else {
+            console.warn(`[租户${tenantId}] 立即触发打印失败: ${result.message}`);
+          }
+        } catch (error) {
+          console.warn(`[租户${tenantId}] 创建延迟调度器失败:`, error);
+          // 不抛出错误,避免影响主流程
+        }
+      }
     } catch (error) {
       console.error(`[租户${tenantId}] 处理订单支付成功事件失败,订单ID: ${orderId}:`, error);
       // 不抛出错误,避免影响支付流程
@@ -142,6 +162,68 @@ export class PrintTriggerService {
     }
   }
 
+  /**
+   * 获取完整的订单信息
+   */
+  private async getFullOrderInfo(
+    tenantId: number,
+    orderId: number
+  ): Promise<{
+    orderNo: string;
+    amount: number;
+    userId: number;
+    items?: Array<{
+      name: string | null;
+      quantity: number;
+      price: number;
+    }>;
+    // 新增字段
+    payAmount: number;
+    freightAmount: number;
+    address: string | null;
+    receiverMobile: string | null;
+    recevierName: string | null;
+    state: number;
+    payState: number;
+    createdAt: Date;
+  } | null> {
+    try {
+      const order = await this.orderRepository.findOne({
+        where: { tenantId, id: orderId },
+        relations: ['orderGoods'] // 关联订单商品
+      });
+
+      if (!order) {
+        console.warn(`[租户${tenantId}] 未找到订单,订单ID: ${orderId}`);
+        return null;
+      }
+
+      // 构建完整的订单信息
+      return {
+        orderNo: order.orderNo,
+        amount: order.amount,
+        userId: order.userId,
+        items: order.orderGoods?.map((goods: any) => ({
+          name: goods.goodsName,
+          quantity: goods.num,
+          price: goods.price
+        })) || [],
+        // 新增字段
+        payAmount: order.payAmount,
+        freightAmount: order.freightAmount,
+        address: order.address,
+        receiverMobile: order.receiverMobile,
+        recevierName: order.recevierName,
+        state: order.state,
+        payState: order.payState,
+        createdAt: order.createdAt
+      };
+    } catch (error) {
+      console.error(`[租户${tenantId}] 获取完整订单信息失败,订单ID: ${orderId}:`, error);
+      return null;
+    }
+  }
+
   /**
    * 获取配置值
    */
@@ -158,46 +240,204 @@ export class PrintTriggerService {
     }
   }
 
+  /**
+   * 获取打印模板
+   */
+  private async getPrintTemplate(tenantId: number): Promise<string> {
+    try {
+      const template = await this.getConfigValue(tenantId, 'feie.receipt_template', '');
+
+      if (template) {
+        return template;
+      }
+
+      // 如果没有配置模板,使用默认模板
+      return `
+<CB>订单收据</CB>
+<BR>
+订单号: {orderNo}
+下单时间: {orderTime}
+<BR>
+<B>收货信息</B>
+收货人: {receiverName}
+联系电话: {receiverPhone}
+收货地址: {address}
+<BR>
+<B>商品信息</B>
+{goodsList}
+<BR>
+<B>费用明细</B>
+商品总额: {totalAmount}
+运费: {freightAmount}
+实付金额: {payAmount}
+<BR>
+<B>订单状态</B>
+订单状态: {orderStatus}
+支付状态: {payStatus}
+<BR>
+<C>感谢您的惠顾!</C>
+<BR>
+<QR>{orderNo}</QR>
+`;
+    } catch (error) {
+      console.warn(`[租户${tenantId}] 获取打印模板失败,使用默认模板:`, error);
+      return `
+<CB>订单收据</CB>
+<BR>
+订单号: {orderNo}
+下单时间: {orderTime}
+<BR>
+<B>收货信息</B>
+收货人: {receiverName}
+联系电话: {receiverPhone}
+收货地址: {address}
+<BR>
+<B>商品信息</B>
+{goodsList}
+<BR>
+<B>费用明细</B>
+商品总额: {totalAmount}
+运费: {freightAmount}
+实付金额: {payAmount}
+<BR>
+<B>订单状态</B>
+订单状态: {orderStatus}
+支付状态: {payStatus}
+<BR>
+<C>感谢您的惠顾!</C>
+<BR>
+<QR>{orderNo}</QR>
+`;
+    }
+  }
+
   /**
    * 生成小票打印内容
    */
-  private generateReceiptContent(orderInfo: {
-    orderNo: string;
-    amount: number;
-    userId: number;
-    items?: Array<{
-      name: string | null;
-      quantity: number;
-      price: number;
-    }>;
-  }): string {
-    const { orderNo, amount, items = [] } = orderInfo;
-
-    // 小票模板
-    const lines = [
-      '<CB>订单小票</CB><BR>',
-      '------------------------<BR>',
-      `<B>订单号:</B>${orderNo}<BR>`,
-      `<B>下单时间:</B>${new Date().toLocaleString('zh-CN')}<BR>`,
-      '------------------------<BR>',
-      '<B>商品明细:</B><BR>'
-    ];
-
-    // 添加商品明细
-    items.forEach(item => {
-      const itemTotal = item.price * item.quantity;
-      const itemName = item.name || '未命名商品';
-      lines.push(`${itemName} x${item.quantity}<BR>`);
-      lines.push(`  ¥${item.price.toFixed(2)} x ${item.quantity} = ¥${itemTotal.toFixed(2)}<BR>`);
-    });
-
-    // 添加总计
-    lines.push('------------------------<BR>');
-    lines.push(`<B>订单总额:</B>¥${amount.toFixed(2)}<BR>`);
-    lines.push('------------------------<BR>');
-    lines.push('<B>感谢您的惠顾!</B><BR>');
-    lines.push('<QR>https://example.com/order/' + orderNo + '</QR><BR>');
-
-    return lines.join('');
+  private async generateReceiptContent(
+    tenantId: number,
+    orderInfo: {
+      orderNo: string;
+      amount: number;
+      userId: number;
+      items?: Array<{
+        name: string | null;
+        quantity: number;
+        price: number;
+      }>;
+      // 新增字段
+      payAmount: number;
+      freightAmount: number;
+      address: string | null;
+      receiverMobile: string | null;
+      recevierName: string | null;
+      state: number;
+      payState: number;
+      createdAt: Date;
+    }
+  ): Promise<string> {
+    const {
+      orderNo,
+      amount,
+      items = [],
+      payAmount,
+      freightAmount,
+      address,
+      receiverMobile,
+      recevierName,
+      state,
+      payState,
+      createdAt
+    } = orderInfo;
+
+    try {
+      // 1. 获取打印模板
+      const template = await this.getPrintTemplate(tenantId);
+
+      // 2. 准备模板变量
+      // 状态映射函数
+      const getOrderStatusLabel = (state: number): string => {
+        const statusMap: Record<number, string> = {
+          0: '未发货',
+          1: '已发货',
+          2: '收货成功',
+          3: '已退货'
+        };
+        return statusMap[state] || '未知';
+      };
+
+      const getPayStatusLabel = (payState: number): string => {
+        const payStatusMap: Record<number, string> = {
+          0: '未支付',
+          1: '支付中',
+          2: '支付成功',
+          3: '已退款',
+          4: '支付失败',
+          5: '订单关闭'
+        };
+        return payStatusMap[payState] || '未知';
+      };
+
+      const variables = {
+        orderNo,
+        orderTime: new Date(createdAt).toLocaleString('zh-CN'),
+        receiverName: recevierName || '客户',
+        receiverPhone: receiverMobile || '未提供',
+        phone: receiverMobile || '未提供', // 兼容变量
+        address: address || '未提供地址',
+        goodsList: items.map(item => {
+          const itemName = item.name || '未命名商品';
+          const itemTotal = item.price * item.quantity;
+          return `${itemName} × ${item.quantity} = ¥${itemTotal.toFixed(2)}`;
+        }).join('\n') || '暂无商品信息',
+        totalAmount: `¥${amount.toFixed(2)}`,
+        freightAmount: `¥${freightAmount.toFixed(2)}`,
+        payAmount: `¥${payAmount.toFixed(2)}`,
+        orderStatus: getOrderStatusLabel(state),
+        payStatus: getPayStatusLabel(payState)
+      };
+
+      // 3. 替换模板变量
+      let content = template;
+      for (const [key, value] of Object.entries(variables)) {
+        content = content.replace(new RegExp(`{${key}}`, 'g'), value);
+      }
+
+      return content.trim();
+    } catch (error) {
+      console.warn(`[租户${tenantId}] 生成打印内容失败,使用简单模板:`, error);
+
+      // 失败时使用简单的回退模板
+      const lines = [
+        '<CB>订单小票</CB><BR>',
+        '------------------------<BR>',
+        `<B>订单号:</B>${orderNo}<BR>`,
+        `<B>下单时间:</B>${new Date(createdAt).toLocaleString('zh-CN')}<BR>`,
+        `<B>收货人:</B>${recevierName || '客户'}<BR>`,
+        `<B>联系电话:</B>${receiverMobile || '未提供'}<BR>`,
+        `<B>地址:</B>${address || '未提供地址'}<BR>`,
+        '------------------------<BR>',
+        '<B>商品明细:</B><BR>'
+      ];
+
+      // 添加商品明细
+      items.forEach(item => {
+        const itemTotal = item.price * item.quantity;
+        const itemName = item.name || '未命名商品';
+        lines.push(`${itemName} x${item.quantity}<BR>`);
+        lines.push(`  ¥${item.price.toFixed(2)} x ${item.quantity} = ¥${itemTotal.toFixed(2)}<BR>`);
+      });
+
+      // 添加总计
+      lines.push('------------------------<BR>');
+      lines.push(`<B>商品总额:</B>¥${amount.toFixed(2)}<BR>`);
+      lines.push(`<B>运费:</B>¥${freightAmount.toFixed(2)}<BR>`);
+      lines.push(`<B>实付金额:</B>¥${payAmount.toFixed(2)}<BR>`);
+      lines.push('------------------------<BR>');
+      lines.push('<B>感谢您的惠顾!</B><BR>');
+      lines.push('<QR>https://example.com/order/' + orderNo + '</QR><BR>');
+
+      return lines.join('');
+    }
   }
 }

+ 57 - 17
packages/feie-printer-module-mt/src/services/printer.service.ts

@@ -7,6 +7,7 @@ import { FeieApiConfig, PrinterDto, UpdatePrinterDto, PrinterStatus } from '../t
 export class PrinterService extends GenericCrudService<FeiePrinterMt> {
   private feieApiService: FeieApiService;
   private feieConfig: FeieApiConfig;
+  private printerRepository: Repository<FeiePrinterMt>;
 
   constructor(dataSource: DataSource, feieConfig: FeieApiConfig) {
     super(dataSource, FeiePrinterMt, {
@@ -15,12 +16,15 @@ export class PrinterService extends GenericCrudService<FeiePrinterMt> {
 
     this.feieConfig = feieConfig;
     this.feieApiService = new FeieApiService(feieConfig);
+    this.printerRepository = dataSource.getRepository(FeiePrinterMt);
   }
 
   /**
    * 添加打印机
    */
   async addPrinter(tenantId: number, printerDto: PrinterDto): Promise<FeiePrinterMt> {
+    console.debug(`开始添加打印机,租户ID: ${tenantId}, 打印机SN: ${printerDto.printerSn}`);
+
     // 验证打印机配置
     const isValid = await this.feieApiService.validatePrinterConfig(
       printerDto.printerSn,
@@ -28,24 +32,27 @@ export class PrinterService extends GenericCrudService<FeiePrinterMt> {
     );
 
     if (!isValid) {
-      throw new Error('打印机配置验证失败,请检查序列号和密钥');
+      console.error(`打印机配置验证失败,SN: ${printerDto.printerSn}, 租户ID: ${tenantId}`);
+      throw new Error('打印机配置验证失败,可能原因:\n1. 打印机序列号或密钥错误\n2. 飞鹅API服务暂时不可用\n3. 网络连接问题\n请检查配置或稍后重试,如问题持续请联系管理员检查飞鹅API配置');
     }
 
+    console.debug(`打印机配置验证通过,开始调用飞鹅API添加打印机`);
     // 调用飞鹅API添加打印机
     await this.feieApiService.addPrinter({
       sn: printerDto.printerSn,
       key: printerDto.printerKey,
-      name: printerDto.printerName,
-      type: printerDto.printerType
+      name: printerDto.printerName || ''
     });
 
     // 如果设置为默认打印机,先取消其他默认打印机
     if (printerDto.isDefault) {
+      console.debug(`设置打印机为默认打印机,清除其他默认设置`);
       await this.clearDefaultPrinter(tenantId);
     }
 
+    console.debug(`创建本地打印机记录,租户ID: ${tenantId}`);
     // 创建打印机记录
-    const printer = await this.create({
+    const createData = {
       tenantId,
       printerSn: printerDto.printerSn,
       printerKey: printerDto.printerKey,
@@ -53,8 +60,12 @@ export class PrinterService extends GenericCrudService<FeiePrinterMt> {
       printerType: printerDto.printerType || '58mm',
       printerStatus: PrinterStatus.ACTIVE,
       isDefault: printerDto.isDefault ? 1 : 0
-    });
+    };
+    console.debug('创建数据:', JSON.stringify(createData, null, 2));
+
+    const printer = await this.create(createData);
 
+    console.debug(`打印机添加成功,ID: ${printer.id}`);
     return printer;
   }
 
@@ -66,7 +77,7 @@ export class PrinterService extends GenericCrudService<FeiePrinterMt> {
     printerSn: string,
     updateDto: UpdatePrinterDto
   ): Promise<FeiePrinterMt> {
-    const printer = await this.findOne({
+    const printer = await this.printerRepository.findOne({
       where: { tenantId, printerSn }
     });
 
@@ -97,14 +108,18 @@ export class PrinterService extends GenericCrudService<FeiePrinterMt> {
       updateData.isDefault = updateDto.isDefault ? 1 : 0;
     }
 
-    return this.update(printer.id, updateData);
+    const updatedPrinter = await this.update(printer.id, updateData);
+    if (!updatedPrinter) {
+      throw new Error('更新打印机失败');
+    }
+    return updatedPrinter;
   }
 
   /**
    * 删除打印机
    */
   async deletePrinter(tenantId: number, printerSn: string): Promise<void> {
-    const printer = await this.findOne({
+    const printer = await this.printerRepository.findOne({
       where: { tenantId, printerSn }
     });
 
@@ -123,17 +138,38 @@ export class PrinterService extends GenericCrudService<FeiePrinterMt> {
    * 获取打印机列表
    */
   async getPrinters(tenantId: number): Promise<FeiePrinterMt[]> {
-    return this.findMany({
-      where: { tenantId },
-      order: { isDefault: 'DESC', createdAt: 'DESC' }
-    });
+    console.debug(`获取打印机列表,租户ID: ${tenantId}`);
+
+    try {
+      const printers = await this.printerRepository.find({
+        where: { tenantId },
+        order: { isDefault: 'DESC', createdAt: 'DESC' }
+      });
+
+      console.debug(`找到 ${printers.length} 台打印机`);
+      if (printers.length > 0) {
+        console.debug('打印机列表:', JSON.stringify(printers.map(p => ({
+          id: p.id,
+          tenantId: p.tenantId,
+          printerSn: p.printerSn,
+          printerName: p.printerName,
+          printerStatus: p.printerStatus,
+          isDefault: p.isDefault
+        })), null, 2));
+      }
+
+      return printers;
+    } catch (error) {
+      console.error(`获取打印机列表失败,租户ID: ${tenantId}`, error);
+      throw error;
+    }
   }
 
   /**
    * 获取默认打印机
    */
   async getDefaultPrinter(tenantId: number): Promise<FeiePrinterMt | null> {
-    return this.findOne({
+    return this.printerRepository.findOne({
       where: { tenantId, isDefault: 1 }
     });
   }
@@ -142,7 +178,7 @@ export class PrinterService extends GenericCrudService<FeiePrinterMt> {
    * 查询打印机状态
    */
   async getPrinterStatus(tenantId: number, printerSn: string): Promise<any> {
-    const printer = await this.findOne({
+    const printer = await this.printerRepository.findOne({
       where: { tenantId, printerSn }
     });
 
@@ -222,7 +258,7 @@ export class PrinterService extends GenericCrudService<FeiePrinterMt> {
    * 设置默认打印机
    */
   async setDefaultPrinter(tenantId: number, printerSn: string): Promise<FeiePrinterMt> {
-    const printer = await this.findOne({
+    const printer = await this.printerRepository.findOne({
       where: { tenantId, printerSn }
     });
 
@@ -234,14 +270,18 @@ export class PrinterService extends GenericCrudService<FeiePrinterMt> {
     await this.clearDefaultPrinter(tenantId);
 
     // 设置新的默认打印机
-    return this.update(printer.id, { isDefault: 1 });
+    const updatedPrinter = await this.update(printer.id, { isDefault: 1 });
+    if (!updatedPrinter) {
+      throw new Error('设置默认打印机失败');
+    }
+    return updatedPrinter;
   }
 
   /**
    * 获取可用的打印机
    */
   async getAvailablePrinters(tenantId: number): Promise<FeiePrinterMt[]> {
-    return this.findMany({
+    return this.printerRepository.find({
       where: {
         tenantId,
         printerStatus: PrinterStatus.ACTIVE

+ 12 - 6
packages/feie-printer-module-mt/tests/unit/delay-scheduler.service.test.ts

@@ -36,7 +36,12 @@ describe('DelaySchedulerService', () => {
     vi.clearAllMocks();
     vi.useFakeTimers();
 
-    mockDataSource = {} as DataSource;
+    mockDataSource = {
+      getRepository: vi.fn().mockImplementation(() => {
+        throw new Error('Repository not found');
+      }),
+      query: vi.fn().mockResolvedValue([])
+    } as any;
     delaySchedulerService = new DelaySchedulerService(mockDataSource, mockFeieConfig);
   });
 
@@ -104,7 +109,8 @@ describe('DelaySchedulerService', () => {
 
       expect(status).toEqual({
         isRunning: false,
-        defaultDelaySeconds: 120
+        defaultDelaySeconds: 120,
+        tenantId: null
       });
     });
 
@@ -202,9 +208,9 @@ describe('DelaySchedulerService', () => {
     it('应该在处理失败时记录错误', async () => {
       const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
 
-      // Mock processDelayedTasks to throw error
-      const originalProcessDelayedTasks = (delaySchedulerService as any).processDelayedTasks;
-      (delaySchedulerService as any).processDelayedTasks = vi.fn().mockRejectedValue(new Error('处理失败'));
+      // Mock getTenantsWithDelayedTasks to throw error
+      const originalGetTenantsWithDelayedTasks = (delaySchedulerService as any).getTenantsWithDelayedTasks;
+      (delaySchedulerService as any).getTenantsWithDelayedTasks = vi.fn().mockRejectedValue(new Error('处理失败'));
 
       await delaySchedulerService.start();
       const scheduleCallback = vi.mocked(cron.schedule).mock.calls[0][1];
@@ -213,7 +219,7 @@ describe('DelaySchedulerService', () => {
       expect(consoleErrorSpy).toHaveBeenCalledWith('处理延迟打印任务失败:', expect.any(Error));
 
       // Restore
-      (delaySchedulerService as any).processDelayedTasks = originalProcessDelayedTasks;
+      (delaySchedulerService as any).getTenantsWithDelayedTasks = originalGetTenantsWithDelayedTasks;
       consoleErrorSpy.mockRestore();
     });
   });

+ 8 - 0
packages/feie-printer-module-mt/tests/unit/printer.service.test.ts

@@ -46,6 +46,14 @@ describe('PrinterService', () => {
     } as unknown as DataSource;
 
     printerService = new PrinterService(mockDataSource, mockFeieConfig);
+
+    // 手动设置模拟的方法,因为PrinterService继承自GenericCrudService
+    // 而模拟可能没有正确应用到实例上
+    (printerService as any).create = vi.fn();
+    (printerService as any).findOne = vi.fn();
+    (printerService as any).findMany = vi.fn();
+    (printerService as any).update = vi.fn();
+    (printerService as any).delete = vi.fn();
   });
 
   describe('addPrinter', () => {

+ 22 - 47
packages/mini-payment-mt/src/services/payment.mt.service.ts

@@ -246,17 +246,7 @@ export class PaymentMtService extends GenericCrudService<PaymentMtEntity> {
    */
   private async triggerPrintTask(
     tenantId: number,
-    orderId: number,
-    orderInfo: {
-      orderNo: string;
-      amount: number;
-      userId: number;
-      items: Array<{
-        name: string | null;
-        quantity: number;
-        price: number;
-      }>;
-    }
+    orderId: number
   ): Promise<void> {
     try {
       // 动态导入飞鹅打印模块,避免循环依赖
@@ -269,16 +259,20 @@ export class PaymentMtService extends GenericCrudService<PaymentMtEntity> {
         return;
       }
 
-      // 创建打印触发服务
+      // 创建打印触发服务并处理订单支付成功事件
       const printTriggerService = new PrintTriggerService(this.dataSource, feieConfig);
-
-      // 处理订单支付成功事件
-      await printTriggerService.handleOrderPaymentSuccess(tenantId, orderId, orderInfo);
+      await printTriggerService.handleOrderPaymentSuccess(tenantId, orderId);
 
       console.debug(`[租户${tenantId}] 打印任务触发成功,订单ID: ${orderId}`);
 
     } catch (error) {
-      console.warn(`[租户${tenantId}] 触发打印任务失败,订单ID: ${orderId}:`, error);
+      // 检查是否是模块未找到错误
+      const errorMessage = error instanceof Error ? error.message : '未知错误';
+      if (errorMessage.includes('Cannot find package') || errorMessage.includes('ERR_MODULE_NOT_FOUND')) {
+        console.debug(`[租户${tenantId}] 飞鹅打印模块未安装,跳过打印任务,订单ID: ${orderId}`);
+      } else {
+        console.warn(`[租户${tenantId}] 触发打印任务失败,订单ID: ${orderId}:`, error);
+      }
       // 不抛出错误,打印失败不影响支付流程
     }
   }
@@ -308,7 +302,13 @@ export class PaymentMtService extends GenericCrudService<PaymentMtEntity> {
       console.debug(`[租户${tenantId}] 打印任务取消成功,订单ID: ${orderId}`);
 
     } catch (error) {
-      console.warn(`[租户${tenantId}] 取消打印任务失败,订单ID: ${orderId}:`, error);
+      // 检查是否是模块未找到错误
+      const errorMessage = error instanceof Error ? error.message : '未知错误';
+      if (errorMessage.includes('Cannot find package') || errorMessage.includes('ERR_MODULE_NOT_FOUND')) {
+        console.debug(`[租户${tenantId}] 飞鹅打印模块未安装,跳过取消打印任务,订单ID: ${orderId}`);
+      } else {
+        console.warn(`[租户${tenantId}] 取消打印任务失败,订单ID: ${orderId}:`, error);
+      }
       // 不抛出错误,打印取消失败不影响退款流程
     }
   }
@@ -322,37 +322,8 @@ export class PaymentMtService extends GenericCrudService<PaymentMtEntity> {
     try {
       console.debug(`[租户${tenantId}] 触发支付成功事件,订单ID: ${orderId}`);
 
-      // 获取订单信息
-      const orderRepository = this.dataSource.getRepository(OrderMt);
-      const order = await orderRepository.findOne({
-        where: { id: orderId, tenantId }
-      });
-
-      if (!order) {
-        console.warn(`[租户${tenantId}] 订单不存在,无法触发打印,订单ID: ${orderId}`);
-        return;
-      }
-
-      // 获取订单商品信息
-      const orderGoodsRepository = this.dataSource.getRepository(OrderGoodsMt);
-      const orderGoods = await orderGoodsRepository.find({
-        where: { orderId, tenantId }
-      });
-
-      // 准备订单信息用于打印
-      const orderInfo = {
-        orderNo: order.orderNo,
-        amount: order.amount,
-        userId: order.userId,
-        items: orderGoods.map(item => ({
-          name: item.goodsName,
-          quantity: item.num,
-          price: item.price
-        }))
-      };
-
       // 尝试触发打印(如果飞鹅打印模块可用)
-      await this.triggerPrintTask(tenantId, orderId, orderInfo);
+      await this.triggerPrintTask(tenantId, orderId);
 
     } catch (error) {
       console.error(`[租户${tenantId}] 触发支付成功事件失败,订单ID: ${orderId}:`, error);
@@ -492,6 +463,10 @@ export class PaymentMtService extends GenericCrudService<PaymentMtEntity> {
         // 支付成功,更新订单状态为已支付 (2),支付类型为微信支付 (4)
         await this.updateOrderPaymentStatus(payment.tenantId, payment.externalOrderId, PayStatus.SUCCESS, PayType.WECHAT);
         console.debug(`[租户${payment.tenantId}] 订单状态更新为已支付,订单ID: ${payment.externalOrderId}`);
+ 
+        // 触发支付成功事件,创建延迟打印任务
+        await this.triggerPaymentSuccessEvent(payment.tenantId, payment.externalOrderId);
+
       } else if (payment.paymentStatus === PaymentStatus.FAILED) {
         // 支付失败,更新订单状态为支付失败 (4),支付类型为微信支付 (4)
         await this.updateOrderPaymentStatus(payment.tenantId, payment.externalOrderId, PayStatus.FAILED, PayType.WECHAT);

+ 261 - 1
packages/order-management-ui-mt/src/components/OrderManagement.tsx

@@ -4,7 +4,7 @@ import { useForm } from 'react-hook-form';
 import { zodResolver } from '@hookform/resolvers/zod';
 import { format } from 'date-fns';
 import { toast } from 'sonner';
-import { Search, Edit, Eye, Package, Truck, Check } from 'lucide-react';
+import { Search, Edit, Eye, Package, Truck, Check, Printer } from 'lucide-react';
  
 
 // 使用共享UI组件包的具体路径导入
@@ -75,6 +75,27 @@ const DataTablePagination = ({
 import { adminOrderClient, orderClientManager } from '../api';
 import type { InferResponseType } from 'hono/client';
 import { UpdateOrderDto } from '@d8d/orders-module-mt/schemas';
+// 飞鹅打印相关类型定义
+interface SubmitPrintTaskRequest {
+  printerSn: string;
+  content: string;
+  printType: string;
+  orderId?: number;
+  delaySeconds?: number;
+}
+
+interface SubmitPrintTaskResponse {
+  taskId: string;
+  status: string;
+  scheduledAt?: string;
+}
+
+interface ApiResponse<T> {
+  success: boolean;
+  data?: T;
+  message?: string;
+  error?: string;
+}
 
 
 // 类型定义
@@ -445,6 +466,8 @@ export const OrderManagement = () => {
   const [deliveringOrder, setDeliveringOrder] = useState<OrderResponse | null>(null);
   const [deliveryCompanies, setDeliveryCompanies] = useState<Array<{ delivery_id: string; delivery_name: string }>>([]);
   const [loadingCompanies, setLoadingCompanies] = useState(false);
+  const [printingOrder, setPrintingOrder] = useState<OrderResponse | null>(null);
+  const [isPrinting, setIsPrinting] = useState(false);
 
 
   // 表单实例
@@ -868,6 +891,229 @@ const sendDeliverySuccessNotification = async (order: OrderResponse, deliveryDat
 };
 
 
+  // 获取默认打印机
+  const getDefaultPrinter = async (tenantId?: number): Promise<{ printerSn: string; printerKey: string } | null> => {
+    try {
+      console.debug('获取默认打印机,租户ID:', tenantId);
+
+      const response = await fetch('/api/v1/feie/printers?isDefault=true&pageSize=1', {
+        method: 'GET',
+        headers: {
+          'Content-Type': 'application/json',
+        }
+      });
+
+      if (!response.ok) {
+        const errorText = await response.text();
+        console.error('获取默认打印机失败:', {
+          status: response.status,
+          statusText: response.statusText,
+          error: errorText
+        });
+        return null;
+      }
+
+      const result = await response.json();
+      console.debug('获取默认打印机结果:', result);
+
+      if (result.success && result.data?.data?.length > 0) {
+        const printer = result.data.data[0];
+        return {
+          printerSn: printer.printerSn,
+          printerKey: printer.printerKey
+        };
+      }
+
+      return null;
+    } catch (error) {
+      console.error('获取默认打印机时出错:', error);
+      return null;
+    }
+  };
+
+  // 获取打印模板
+  const getPrintTemplate = async (tenantId?: number): Promise<string | null> => {
+    try {
+      console.debug('获取打印模板,租户ID:', tenantId);
+
+      const response = await fetch('/api/v1/feie/config', {
+        method: 'GET',
+        headers: {
+          'Content-Type': 'application/json',
+        }
+      });
+
+      if (!response.ok) {
+        const errorText = await response.text();
+        console.error('获取打印模板失败:', {
+          status: response.status,
+          statusText: response.statusText,
+          error: errorText
+        });
+        return null;
+      }
+
+      const result = await response.json();
+      console.debug('获取打印模板结果:', result);
+
+      if (result.success && result.data?.data) {
+        const configs = result.data.data;
+        const receiptTemplate = configs.find((config: any) => config.configKey === 'feie.receipt_template');
+        return receiptTemplate?.configValue || null;
+      }
+
+      return null;
+    } catch (error) {
+      console.error('获取打印模板时出错:', error);
+      return null;
+    }
+  };
+
+  // 处理打印订单
+  const handlePrintOrder = async (order: OrderResponse) => {
+    setPrintingOrder(order);
+    setIsPrinting(true);
+
+    try {
+      // 获取默认打印机
+      const defaultPrinter = await getDefaultPrinter(order.tenantId);
+      if (!defaultPrinter) {
+        throw new Error('未找到默认打印机,请先设置默认打印机');
+      }
+
+      // 获取打印模板
+      const template = await getPrintTemplate(order.tenantId);
+
+      // 构建打印内容
+      const printContent = generatePrintContent(order, template);
+
+      // 构建打印请求
+      const printRequest: SubmitPrintTaskRequest = {
+        printerSn: defaultPrinter.printerSn,
+        content: printContent,
+        printType: 'RECEIPT', // 收据类型
+        orderId: order.id,
+        delaySeconds: 0 // 立即打印
+      };
+
+      console.debug('提交打印任务:', {
+        orderId: order.id,
+        orderNo: order.orderNo,
+        printerSn: defaultPrinter.printerSn,
+        printRequest
+      });
+
+      // 使用fetch API提交打印任务
+      const response = await fetch('/api/v1/feie/tasks', {
+        method: 'POST',
+        headers: {
+          'Content-Type': 'application/json',
+        },
+        body: JSON.stringify(printRequest)
+      });
+
+      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: ApiResponse<SubmitPrintTaskResponse> = await response.json();
+
+      if (!result.success) {
+        throw new Error(result.message || '打印任务提交失败');
+      }
+
+      console.debug('打印任务提交成功:', result.data);
+      toast.success(`打印任务已提交,任务ID: ${result.data?.taskId}`);
+
+    } catch (error: any) {
+      console.error('打印订单失败:', error);
+      toast.error(`打印失败: ${error.message || '未知错误'}`);
+    } finally {
+      setIsPrinting(false);
+      setPrintingOrder(null);
+    }
+  };
+
+  // 生成打印内容
+  const generatePrintContent = (order: OrderResponse, template?: string | null): string => {
+    // 如果没有模板或模板为空,使用默认模板
+    if (!template) {
+      template = `
+<CB>订单收据</CB>
+<BR>
+订单号: {orderNo}
+下单时间: {orderTime}
+<BR>
+<B>收货信息</B>
+收货人: {receiverName}
+联系电话: {receiverPhone}
+收货地址: {address}
+<BR>
+<B>商品信息</B>
+{goodsList}
+<BR>
+<B>费用明细</B>
+商品总额: {totalAmount}
+运费: {freightAmount}
+实付金额: {payAmount}
+<BR>
+<B>订单状态</B>
+订单状态: {orderStatus}
+支付状态: {payStatus}
+<BR>
+<C>感谢您的惠顾!</C>
+<BR>
+<QR>{orderNo}</QR>
+`;
+    }
+
+    // 准备模板变量
+    const variables = {
+      orderNo: order.orderNo,
+      orderTime: format(new Date(order.createdAt), 'yyyy-MM-dd HH:mm:ss'),
+      receiverName: order.recevierName || '-',
+      receiverPhone: order.receiverMobile || '-',
+      phone: order.receiverMobile || '-', // 添加phone变量,兼容两种模板格式
+      address: order.address || '-',
+      goodsList: order.orderGoods?.map(item =>
+        `${item.goodsName} × ${item.num} = ¥${(item.price * item.num).toFixed(2)}`
+      ).join('\n') || '暂无商品信息',
+      totalAmount: `¥${order.amount.toFixed(2)}`,
+      freightAmount: `¥${order.freightAmount.toFixed(2)}`,
+      payAmount: `¥${order.payAmount.toFixed(2)}`,
+      orderStatus: orderStatusMap[order.state as keyof typeof orderStatusMap]?.label || '未知',
+      payStatus: payStatusMap[order.payState as keyof typeof payStatusMap]?.label || '未知'
+    };
+
+    // 替换模板变量
+    let content = template;
+    for (const [key, value] of Object.entries(variables)) {
+      content = content.replace(new RegExp(`{${key}}`, 'g'), value);
+    }
+
+    return content.trim();
+  };
+
   // 处理更新订单
   const handleUpdateSubmit = async (data: UpdateRequest) => {
     if (!editingOrder || !editingOrder.id) return;
@@ -1016,6 +1262,7 @@ const sendDeliverySuccessNotification = async (order: OrderResponse, deliveryDat
                         <div className="flex justify-end gap-2">
                           <Skeleton className="h-8 w-8" />
                           <Skeleton className="h-8 w-8" />
+                          <Skeleton className="h-8 w-8" />
                         </div>
                       </TableCell>
                     </TableRow>
@@ -1181,6 +1428,19 @@ const sendDeliverySuccessNotification = async (order: OrderResponse, deliveryDat
                             <Truck className="h-4 w-4" />
                           </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
                           variant="ghost"
                           size="icon"

+ 61 - 0
packages/server/src/index.ts

@@ -47,6 +47,67 @@ if(!AppDataSource || !AppDataSource.isInitialized) {
   console.log('数据库初始化完成')
 }
 
+// 初始化飞鹅打印防退款延迟调度器
+try {
+  const { DelaySchedulerService } = await import('@d8d/feie-printer-module-mt');
+  const { createFeieRoutes } = await import('@d8d/feie-printer-module-mt');
+
+  // 获取所有有飞鹅配置的租户
+  const systemConfigRepository = AppDataSource.getRepository('system_config_mt');
+  const configs = await systemConfigRepository.find({
+    where: {
+      configKey: 'feie.api.user'
+    }
+  });
+
+  // 为每个有配置的租户启动调度器
+  for (const config of configs) {
+    const tenantId = config.tenantId;
+
+    // 动态获取飞鹅配置
+    const configKeys = [
+      'feie.api.user',
+      'feie.api.ukey',
+      'feie.api.base_url',
+      'feie.api.timeout',
+      'feie.api.max_retries'
+    ];
+
+    const tenantConfigs = await systemConfigRepository.find({
+      where: {
+        tenantId,
+        configKey: configKeys
+      }
+    });
+
+    const configMap: Record<string, string> = {};
+    tenantConfigs.forEach((config: any) => {
+      configMap[config.configKey] = config.configValue;
+    });
+
+    const user = configMap['feie.api.user'];
+    const ukey = configMap['feie.api.ukey'];
+
+    if (user && ukey) {
+      const feieConfig = {
+        user,
+        ukey,
+        baseUrl: configMap['feie.api.base_url'] || 'https://api.feieyun.cn/Api/Open/',
+        timeout: parseInt(configMap['feie.api.timeout'] || '10000', 10),
+        maxRetries: parseInt(configMap['feie.api.max_retries'] || '3', 10)
+      };
+
+      const delaySchedulerService = new DelaySchedulerService(AppDataSource, feieConfig, tenantId);
+      await delaySchedulerService.start();
+      console.log(`[租户${tenantId}] 防退款延迟打印调度器已启动`);
+    }
+  }
+
+  console.log('飞鹅打印防退款延迟调度器初始化完成');
+} catch (error) {
+  console.warn('飞鹅打印防退款延迟调度器初始化失败,延迟打印功能可能不可用:', error);
+}
+
 const app = new Hono();
 const api = new OpenAPIHono<AuthContext>()
 

+ 2 - 1
packages/shared-crud/src/services/generic-crud.service.ts

@@ -279,7 +279,8 @@ export abstract class GenericCrudService<T extends ObjectLiteral> {
 
     // 如果用户对象包含租户ID字段,则从用户对象中提取
     // 这里需要实际的用户对象,暂时返回undefined
-    console.debug('没有找到租户ID,返回undefined');
+    // 注意:这只是一个调试信息,如果数据中已经包含tenantId字段,不会影响实际功能
+    console.debug('extractTenantId: 没有找到租户ID,返回undefined(如果数据中已包含tenantId字段,不影响功能)');
     return undefined;
   }