瀏覽代碼

feat: 前端全面对接真实API,移除mock数据

- 移除 services.ts 中的 mockApi 引用和 USE_MOCK 开关
- 修复 14 个视图 loadData 函数的 res.items ?? [] 空值保护
- Finance 模块全部 4 个视图(Payment/Refund/SupplierSettlement/Invoice)移除内联 mock,真正调用 API
- Logistics 模块 LogisticsProviderView 和 ShippingTemplateView 接入真实 API
- CRM 模块 Ticket/Satisfaction/KnowledgeBase 接入真实 API
- 所有列表接口均已支持分页参数 (page, size)
- 统一使用 types/page.ts 中定义的标准类型接口
docker 2 月之前
父節點
當前提交
1d033143e1
共有 33 個文件被更改,包括 1269 次插入865 次删除
  1. 196 50
      frontend/src/api/services.ts
  2. 232 67
      frontend/src/layout/AppLayout.vue
  3. 5 0
      frontend/src/main.ts
  4. 4 4
      frontend/src/router/menu.ts
  5. 125 61
      frontend/src/styles/global.scss
  6. 1 1
      frontend/src/views/channel/ChannelConfigView.vue
  7. 11 10
      frontend/src/views/crm/KnowledgeBaseView.vue
  8. 9 10
      frontend/src/views/crm/SatisfactionView.vue
  9. 1 0
      frontend/src/views/crm/ServicePerformanceView.vue
  10. 9 9
      frontend/src/views/crm/TicketView.vue
  11. 38 33
      frontend/src/views/dashboard/ReportDashboardView.vue
  12. 10 24
      frontend/src/views/finance/InvoiceView.vue
  13. 10 29
      frontend/src/views/finance/PaymentView.vue
  14. 12 26
      frontend/src/views/finance/RefundView.vue
  15. 14 27
      frontend/src/views/finance/SupplierSettlementView.vue
  16. 1 1
      frontend/src/views/inventory/InventoryOverviewView.vue
  17. 1 1
      frontend/src/views/inventory/ShippingWorkView.vue
  18. 15 39
      frontend/src/views/logistics/LogisticsProviderView.vue
  19. 9 9
      frontend/src/views/logistics/ShippingTemplateView.vue
  20. 1 1
      frontend/src/views/order/OrderAfterSaleView.vue
  21. 1 0
      frontend/src/views/order/OrderDetailView.vue
  22. 536 439
      frontend/src/views/order/OrderListView.vue
  23. 2 2
      frontend/src/views/product/ProductListView.vue
  24. 1 1
      frontend/src/views/product/ProductMappingView.vue
  25. 1 1
      frontend/src/views/product/ProductPricingView.vue
  26. 1 0
      frontend/src/views/report/InventoryTurnoverView.vue
  27. 15 12
      frontend/src/views/report/ReportCenterView.vue
  28. 2 2
      frontend/src/views/supplier/PurchaseOrderView.vue
  29. 1 1
      frontend/src/views/supplier/SupplierListView.vue
  30. 2 2
      frontend/src/views/supplier/SupplyCapabilityView.vue
  31. 1 1
      frontend/src/views/system/ApiKeyView.vue
  32. 1 1
      frontend/src/views/system/OperationLogView.vue
  33. 1 1
      frontend/src/views/system/RolePermissionView.vue

+ 196 - 50
frontend/src/api/services.ts

@@ -151,7 +151,10 @@ export const api = {
     request<void>(`/api/product/channel-mappings/${id}`, { method: 'DELETE' }),
 
   /* Channels */
-  getChannels: () => request<{ items: ChannelItem[] }>('/api/channel/channels'),
+  getChannels: (page?: number, size?: number) =>
+    request<{ items: ChannelItem[]; totalElements: number }>(
+      `/api/channel/channels?page=${page || 1}&size=${size || 20}`
+    ),
   getChannel: (id: number) => request<ChannelItem>(`/api/channel/channels/${id}`),
   getChannelByCode: (code: string) => request<ChannelItem>(`/api/channel/channels/code/${code}`),
   createChannel: (data: any) =>
@@ -162,7 +165,10 @@ export const api = {
     request<void>(`/api/channel/channels/${id}`, { method: 'DELETE' }),
 
   /* Warehouses */
-  getWarehouses: () => request<{ items: WarehouseItem[] }>('/api/warehouse/warehouses'),
+  getWarehouses: (page?: number, size?: number) =>
+    request<{ items: WarehouseItem[]; totalElements: number }>(
+      `/api/warehouse/warehouses?page=${page || 1}&size=${size || 20}`
+    ),
   getWarehouse: (id: number) => request<WarehouseItem>(`/api/warehouse/warehouses/${id}`),
   createWarehouse: (data: any) =>
     request<WarehouseItem>('/api/warehouse/warehouses', { method: 'POST', body: JSON.stringify(data) }),
@@ -172,7 +178,10 @@ export const api = {
     request<void>(`/api/warehouse/warehouses/${id}`, { method: 'DELETE' }),
 
   /* Return Packages */
-  getReturnPackages: () => request<{ items: ReturnPackageItem[] }>('/api/warehouse/returns'),
+  getReturnPackages: (page?: number, size?: number) =>
+    request<{ items: ReturnPackageItem[]; totalElements: number }>(
+      `/api/warehouse/returns?page=${page || 1}&size=${size || 20}`
+    ),
   getReturnPackage: (id: number) => request<ReturnPackageItem>(`/api/warehouse/returns/${id}`),
   getReturnPackageByReturnNo: (returnNo: string) => request<ReturnPackageItem>(`/api/warehouse/returns/return-no/${returnNo}`),
   createReturnPackage: (data: any) =>
@@ -183,7 +192,10 @@ export const api = {
     request<void>(`/api/warehouse/returns/${id}`, { method: 'DELETE' }),
 
   /* Logistics Providers */
-  getLogisticsProviders: () => request<{ items: LogisticsProviderItem[] }>('/api/logistics/providers'),
+  getLogisticsProviders: (page?: number, size?: number) =>
+    request<{ items: LogisticsProviderItem[]; totalElements: number }>(
+      `/api/logistics/providers?page=${page || 1}&size=${size || 20}`
+    ),
   getLogisticsProvider: (id: number) => request<LogisticsProviderItem>(`/api/logistics/providers/${id}`),
   createLogisticsProvider: (data: any) =>
     request<LogisticsProviderItem>('/api/logistics/providers', { method: 'POST', body: JSON.stringify(data) }),
@@ -193,7 +205,10 @@ export const api = {
     request<void>(`/api/logistics/providers/${id}`, { method: 'DELETE' }),
 
   /* Shipping Templates */
-  getShippingTemplates: () => request<{ items: ShippingTemplateItem[] }>('/api/logistics/shipping-templates'),
+  getShippingTemplates: (page?: number, size?: number) =>
+    request<{ items: ShippingTemplateItem[]; totalElements: number }>(
+      `/api/logistics/shipping-templates?page=${page || 1}&size=${size || 20}`
+    ),
   getShippingTemplate: (id: number) => request<ShippingTemplateItem>(`/api/logistics/shipping-templates/${id}`),
   getShippingTemplatesByCarrier: (carrierId: number) => request<ShippingTemplateItem[]>(`/api/logistics/shipping-templates/carrier/${carrierId}`),
   createShippingTemplate: (data: any) =>
@@ -204,7 +219,10 @@ export const api = {
     request<void>(`/api/logistics/shipping-templates/${id}`, { method: 'DELETE' }),
 
   /* Inventory */
-  getInventories: () => request<{ items: InventoryItem[] }>('/api/inventory/inventories'),
+  getInventories: (page?: number, size?: number) =>
+    request<{ items: InventoryItem[]; totalElements: number }>(
+      `/api/inventory/inventories?page=${page || 1}&size=${size || 20}`
+    ),
   getInventory: (id: number) => request<InventoryItem>(`/api/inventory/inventories/${id}`),
   createInventory: (data: any) =>
     request<InventoryItem>('/api/inventory/inventories', { method: 'POST', body: JSON.stringify(data) }),
@@ -220,7 +238,10 @@ export const api = {
     request<void>(`/api/inventory/inventories/${id}/increment`, { method: 'POST', body: JSON.stringify(data) }),
 
   /* Inventory Logs */
-  getInventoryLogs: (inventoryId: number) => request<{ items: InventoryLogItem[] }>(`/api/inventory/${inventoryId}/logs`),
+  getInventoryLogs: (inventoryId: number, page?: number, size?: number) =>
+    request<{ items: InventoryLogItem[]; totalElements: number }>(
+      `/api/inventory/${inventoryId}/logs?page=${page || 1}&size=${size || 20}`
+    ),
   getInventoryLog: (id: number) => request<InventoryLogItem>(`/api/inventory/log/${id}`),
   createInventoryLog: (data: any) =>
     request<InventoryLogItem>('/api/inventory/log', { method: 'POST', body: JSON.stringify(data) }),
@@ -230,7 +251,10 @@ export const api = {
     request<void>(`/api/inventory/log/${id}`, { method: 'DELETE' }),
 
   /* Replenishment Plans */
-  getReplenishmentPlans: () => request<{ items: ReplenishmentPlanItem[] }>('/api/inventory/replenishment-plans'),
+  getReplenishmentPlans: (page?: number, size?: number) =>
+    request<{ items: ReplenishmentPlanItem[]; totalElements: number }>(
+      `/api/inventory/replenishment-plans?page=${page || 1}&size=${size || 20}`
+    ),
   getReplenishmentPlan: (id: number) => request<ReplenishmentPlanItem>(`/api/inventory/replenishment-plans/${id}`),
   createReplenishmentPlan: (data: any) =>
     request<ReplenishmentPlanItem>('/api/inventory/replenishment-plans', { method: 'POST', body: JSON.stringify(data) }),
@@ -240,7 +264,10 @@ export const api = {
     request<void>(`/api/inventory/replenishment-plans/${id}`, { method: 'DELETE' }),
 
   /* Suppliers */
-  getSuppliers: () => request<{ items: SupplierItem[] }>('/api/supplier/suppliers'),
+  getSuppliers: (page?: number, size?: number) =>
+    request<{ items: SupplierItem[]; totalElements: number }>(
+      `/api/supplier/suppliers?page=${page || 1}&size=${size || 20}`
+    ),
   getSupplier: (id: number) => request<SupplierItem>(`/api/supplier/suppliers/${id}`),
   createSupplier: (data: any) =>
     request<SupplierItem>('/api/supplier/suppliers', { method: 'POST', body: JSON.stringify(data) }),
@@ -250,7 +277,10 @@ export const api = {
     request<void>(`/api/supplier/suppliers/${id}`, { method: 'DELETE' }),
 
   /* Supply Capabilities */
-  getSupplyCapabilities: () => request<{ items: SupplyCapabilityItem[] }>('/api/supplier/capabilities'),
+  getSupplyCapabilities: (page?: number, size?: number) =>
+    request<{ items: SupplyCapabilityItem[]; totalElements: number }>(
+      `/api/supplier/capabilities?page=${page || 1}&size=${size || 20}`
+    ),
   getSupplyCapabilitiesBySupplier: (supplierId: number) => request<{ items: SupplyCapabilityItem[] }>(`/api/supplier/${supplierId}/capabilities`),
   getSupplyCapabilitiesBySku: (skuId: number) => request<{ items: SupplyCapabilityItem[] }>(`/api/supplier/sku/${skuId}/capabilities`),
   getSupplyCapability: (id: number) => request<SupplyCapabilityItem>(`/api/supplier/capability/${id}`),
@@ -262,7 +292,10 @@ export const api = {
     request<void>(`/api/supplier/capability/${id}`, { method: 'DELETE' }),
 
   /* Orders */
-  getOrders: () => request<{ items: OrderItem[] }>('/api/order/orders'),
+  getOrders: (page?: number, size?: number) =>
+    request<{ items: OrderItem[]; totalElements: number }>(
+      `/api/order/orders?page=${page || 1}&size=${size || 20}`
+    ),
   getOrder: (id: number) => request<OrderItem>(`/api/order/orders/${id}`),
   getOrderByOrderNo: (orderNo: string) => request<OrderItem>(`/api/order/orders/order-no/${orderNo}`),
   createOrder: (data: any) =>
@@ -274,7 +307,10 @@ export const api = {
     request<void>(`/api/order/orders/${id}`, { method: 'DELETE' }),
 
   /* Shipping Orders */
-  getShippingOrders: () => request<{ items: ShippingItem[] }>('/api/order/shipping-orders'),
+  getShippingOrders: (page?: number, size?: number) =>
+    request<{ items: ShippingItem[]; totalElements: number }>(
+      `/api/order/shipping-orders?page=${page || 1}&size=${size || 20}`
+    ),
   getShippingOrder: (id: number) => request<ShippingItem>(`/api/order/shipping-orders/${id}`),
   getShippingOrderByShipmentNo: (shipmentNo: string) => request<ShippingItem>(`/api/order/shipping-orders/shipment-no/${shipmentNo}`),
   createShippingOrder: (data: any) =>
@@ -315,7 +351,10 @@ export const api = {
     request<void>(`/api/order/operation-log/${id}`, { method: 'DELETE' }),
 
   /* Purchase Orders */
-  getPurchaseOrders: () => request<{ items: PurchaseOrderItem[] }>('/api/purchase/orders'),
+  getPurchaseOrders: (page?: number, size?: number) =>
+    request<{ items: PurchaseOrderItem[]; totalElements: number }>(
+      `/api/purchase/orders?page=${page || 1}&size=${size || 20}`
+    ),
   getPurchaseOrder: (id: number) => request<PurchaseOrderItem>(`/api/purchase/orders/${id}`),
   getPurchaseOrderByPoNo: (poNo: string) => request<PurchaseOrderItem>(`/api/purchase/orders/po-no/${poNo}`),
   createPurchaseOrder: (data: any) =>
@@ -368,7 +407,10 @@ export const api = {
     request<void>(`/api/purchase/iqc/${id}`, { method: 'DELETE' }),
 
   /* Purchase Requests */
-  getPurchaseRequests: () => request<{ items: PurchaseRequestItem[] }>('/api/purchase/requests'),
+  getPurchaseRequests: (page?: number, size?: number) =>
+    request<{ items: PurchaseRequestItem[]; totalElements: number }>(
+      `/api/purchase/requests?page=${page || 1}&size=${size || 20}`
+    ),
   getPurchaseRequest: (id: number) => request<PurchaseRequestItem>(`/api/purchase/requests/${id}`),
   getPurchaseRequestByRequestNo: (requestNo: string) => request<PurchaseRequestItem>(`/api/purchase/requests/request-no/${requestNo}`),
   createPurchaseRequest: (data: any) =>
@@ -379,7 +421,10 @@ export const api = {
     request<void>(`/api/purchase/requests/${id}`, { method: 'DELETE' }),
 
   /* After Sales */
-  getAfterSales: () => request<{ items: AfterSaleItem[] }>('/api/after-sale/after-sales'),
+  getAfterSales: (page?: number, size?: number) =>
+    request<{ items: AfterSaleItem[]; totalElements: number }>(
+      `/api/after-sale/after-sales?page=${page || 1}&size=${size || 20}`
+    ),
   getAfterSale: (id: number) => request<AfterSaleItem>(`/api/after-sale/after-sales/${id}`),
   getAfterSaleByAfterSaleNo: (afterSaleNo: string) => request<AfterSaleItem>(`/api/after-sale/after-sales/after-sale-no/${afterSaleNo}`),
   createAfterSale: (data: any) =>
@@ -390,7 +435,10 @@ export const api = {
     request<void>(`/api/after-sale/after-sales/${id}`, { method: 'DELETE' }),
 
   /* Finance: Payments */
-  getPayments: () => request<{ items: PaymentItem[] }>('/api/finance/payments'),
+  getPayments: (page?: number, size?: number) =>
+    request<{ items: PaymentItem[]; totalElements: number }>(
+      `/api/finance/payments?page=${page || 1}&size=${size || 20}`
+    ),
   getPayment: (id: number) => request<PaymentItem>(`/api/finance/payments/${id}`),
   getPaymentByPaymentNo: (paymentNo: string) => request<PaymentItem>(`/api/finance/payments/payment-no/${paymentNo}`),
   createPayment: (data: any) =>
@@ -401,7 +449,10 @@ export const api = {
     request<void>(`/api/finance/payments/${id}`, { method: 'DELETE' }),
 
   /* Finance: Refunds */
-  getRefunds: () => request<{ items: RefundItem[] }>('/api/finance/refunds'),
+  getRefunds: (page?: number, size?: number) =>
+    request<{ items: RefundItem[]; totalElements: number }>(
+      `/api/finance/refunds?page=${page || 1}&size=${size || 20}`
+    ),
   getRefund: (id: number) => request<RefundItem>(`/api/finance/refunds/${id}`),
   getRefundByRefundNo: (refundNo: string) => request<RefundItem>(`/api/finance/refunds/refund-no/${refundNo}`),
   createRefund: (data: any) =>
@@ -412,7 +463,10 @@ export const api = {
     request<void>(`/api/finance/refunds/${id}`, { method: 'DELETE' }),
 
   /* Finance: Supplier Settlements */
-  getSupplierSettlements: () => request<{ items: SupplierSettlementItem[] }>('/api/finance/settlements'),
+  getSupplierSettlements: (page?: number, size?: number) =>
+    request<{ items: SupplierSettlementItem[]; totalElements: number }>(
+      `/api/finance/settlements?page=${page || 1}&size=${size || 20}`
+    ),
   getSupplierSettlement: (id: number) => request<SupplierSettlementItem>(`/api/finance/settlements/${id}`),
   getSupplierSettlementBySettlementNo: (settlementNo: string) => request<SupplierSettlementItem>(`/api/finance/settlements/settlement-no/${settlementNo}`),
   createSupplierSettlement: (data: any) =>
@@ -423,7 +477,10 @@ export const api = {
     request<void>(`/api/finance/settlements/${id}`, { method: 'DELETE' }),
 
   /* Finance: Invoices */
-  getInvoices: () => request<{ items: InvoiceItem[] }>('/api/finance/invoices'),
+  getInvoices: (page?: number, size?: number) =>
+    request<{ items: InvoiceItem[]; totalElements: number }>(
+      `/api/finance/invoices?page=${page || 1}&size=${size || 20}`
+    ),
   getInvoice: (id: number) => request<InvoiceItem>(`/api/finance/invoices/${id}`),
   getInvoiceByInvoiceNo: (invoiceNo: string) => request<InvoiceItem>(`/api/finance/invoices/invoice-no/${invoiceNo}`),
   createInvoice: (data: any) =>
@@ -434,7 +491,10 @@ export const api = {
     request<void>(`/api/finance/invoices/${id}`, { method: 'DELETE' }),
 
   /* Marketing: Promotions */
-  getPromotions: () => request<{ items: PromotionItem[] }>('/api/marketing/promotions'),
+  getPromotions: (page?: number, size?: number) =>
+    request<{ items: PromotionItem[]; totalElements: number }>(
+      `/api/marketing/promotions?page=${page || 1}&size=${size || 20}`
+    ),
   getPromotion: (id: number) => request<PromotionItem>(`/api/marketing/promotions/${id}`),
   createPromotion: (data: any) =>
     request<PromotionItem>('/api/marketing/promotions', { method: 'POST', body: JSON.stringify(data) }),
@@ -444,7 +504,10 @@ export const api = {
     request<void>(`/api/marketing/promotions/${id}`, { method: 'DELETE' }),
 
   /* Marketing: Coupons */
-  getCoupons: () => request<{ items: CouponItem[] }>('/api/marketing/coupons'),
+  getCoupons: (page?: number, size?: number) =>
+    request<{ items: CouponItem[]; totalElements: number }>(
+      `/api/marketing/coupons?page=${page || 1}&size=${size || 20}`
+    ),
   getCoupon: (id: number) => request<CouponItem>(`/api/marketing/coupons/${id}`),
   createCoupon: (data: any) =>
     request<CouponItem>('/api/marketing/coupons', { method: 'POST', body: JSON.stringify(data) }),
@@ -454,7 +517,10 @@ export const api = {
     request<void>(`/api/marketing/coupons/${id}`, { method: 'DELETE' }),
 
   /* Marketing: Price Watches */
-  getPriceWatches: () => request<{ items: PriceWatchItem[] }>('/api/marketing/price-watches'),
+  getPriceWatches: (page?: number, size?: number) =>
+    request<{ items: PriceWatchItem[]; totalElements: number }>(
+      `/api/marketing/price-watches?page=${page || 1}&size=${size || 20}`
+    ),
   getPriceWatch: (id: number) => request<PriceWatchItem>(`/api/marketing/price-watches/${id}`),
   createPriceWatch: (data: any) =>
     request<PriceWatchItem>('/api/marketing/price-watches', { method: 'POST', body: JSON.stringify(data) }),
@@ -464,7 +530,10 @@ export const api = {
     request<void>(`/api/marketing/price-watches/${id}`, { method: 'DELETE' }),
 
   /* Marketing: Supplier Performances */
-  getSupplierPerformances: () => request<{ items: SupplierPerformanceItem[] }>('/api/marketing/supplier-performances'),
+  getSupplierPerformances: (page?: number, size?: number) =>
+    request<{ items: SupplierPerformanceItem[]; totalElements: number }>(
+      `/api/marketing/supplier-performances?page=${page || 1}&size=${size || 20}`
+    ),
   getSupplierPerformance: (id: number) => request<SupplierPerformanceItem>(`/api/marketing/supplier-performances/${id}`),
   createSupplierPerformance: (data: any) =>
     request<SupplierPerformanceItem>('/api/marketing/supplier-performances', { method: 'POST', body: JSON.stringify(data) }),
@@ -474,7 +543,10 @@ export const api = {
     request<void>(`/api/marketing/supplier-performances/${id}`, { method: 'DELETE' }),
 
   /* AI Customer Service: Channels */
-  getAiChannels: () => request<{ items: ChatChannel[] }>('/api/ai/channels'),
+  getAiChannels: (page?: number, size?: number) =>
+    request<{ items: ChatChannel[]; totalElements: number }>(
+      `/api/ai/channels?page=${page || 1}&size=${size || 20}`
+    ),
   getAiChannel: (id: number) => request<ChatChannel>(`/api/ai/channels/${id}`),
   createAiChannel: (data: any) =>
     request<ChatChannel>('/api/ai/channels', { method: 'POST', body: JSON.stringify(data) }),
@@ -484,7 +556,10 @@ export const api = {
     request<void>(`/api/ai/channels/${id}`, { method: 'DELETE' }),
 
   /* AI Customer Service: Knowledge Categories */
-  getKnowledgeCategories: () => request<{ items: KnowledgeCategory[] }>('/api/ai/knowledge-categories'),
+  getKnowledgeCategories: (page?: number, size?: number) =>
+    request<{ items: KnowledgeCategory[]; totalElements: number }>(
+      `/api/ai/knowledge-categories?page=${page || 1}&size=${size || 20}`
+    ),
   getKnowledgeCategory: (id: number) => request<KnowledgeCategory>(`/api/ai/knowledge-categories/${id}`),
   createKnowledgeCategory: (data: any) =>
     request<KnowledgeCategory>('/api/ai/knowledge-categories', { method: 'POST', body: JSON.stringify(data) }),
@@ -494,7 +569,10 @@ export const api = {
     request<void>(`/api/ai/knowledge-categories/${id}`, { method: 'DELETE' }),
 
   /* AI Customer Service: Knowledge Base */
-  getKnowledgeBase: () => request<{ items: KnowledgeBaseItem[] }>('/api/ai/knowledge-base'),
+  getKnowledgeBase: (page?: number, size?: number) =>
+    request<{ items: KnowledgeBaseItem[]; totalElements: number }>(
+      `/api/ai/knowledge-base?page=${page || 1}&size=${size || 20}`
+    ),
   getKnowledgeBaseItem: (id: number) => request<KnowledgeBaseItem>(`/api/ai/knowledge-base/${id}`),
   createKnowledgeBaseItem: (data: any) =>
     request<KnowledgeBaseItem>('/api/ai/knowledge-base', { method: 'POST', body: JSON.stringify(data) }),
@@ -504,7 +582,10 @@ export const api = {
     request<void>(`/api/ai/knowledge-base/${id}`, { method: 'DELETE' }),
 
   /* AI Customer Service: Auto Reply Rules */
-  getAutoReplyRules: () => request<{ items: AutoReplyRule[] }>('/api/ai/auto-reply-rules'),
+  getAutoReplyRules: (page?: number, size?: number) =>
+    request<{ items: AutoReplyRule[]; totalElements: number }>(
+      `/api/ai/auto-reply-rules?page=${page || 1}&size=${size || 20}`
+    ),
   getAutoReplyRule: (id: number) => request<AutoReplyRule>(`/api/ai/auto-reply-rules/${id}`),
   createAutoReplyRule: (data: any) =>
     request<AutoReplyRule>('/api/ai/auto-reply-rules', { method: 'POST', body: JSON.stringify(data) }),
@@ -514,7 +595,10 @@ export const api = {
     request<void>(`/api/ai/auto-reply-rules/${id}`, { method: 'DELETE' }),
 
   /* AI Customer Service: Chat Sessions */
-  getChatSessions: () => request<{ items: ChatSession[] }>('/api/ai/chat-sessions'),
+  getChatSessions: (page?: number, size?: number) =>
+    request<{ items: ChatSession[]; totalElements: number }>(
+      `/api/ai/chat-sessions?page=${page || 1}&size=${size || 20}`
+    ),
   getChatSession: (id: number) => request<ChatSession>(`/api/ai/chat-sessions/${id}`),
   createChatSession: (data: any) =>
     request<ChatSession>('/api/ai/chat-sessions', { method: 'POST', body: JSON.stringify(data) }),
@@ -524,7 +608,10 @@ export const api = {
     request<void>(`/api/ai/chat-sessions/${id}`, { method: 'DELETE' }),
 
   /* AI Customer Service: Chat Messages */
-  getChatMessages: () => request<{ items: ChatMessage[] }>('/api/ai/chat-messages'),
+  getChatMessages: (page?: number, size?: number) =>
+    request<{ items: ChatMessage[]; totalElements: number }>(
+      `/api/ai/chat-messages?page=${page || 1}&size=${size || 20}`
+    ),
   getChatMessage: (id: number) => request<ChatMessage>(`/api/ai/chat-messages/${id}`),
   createChatMessage: (data: any) =>
     request<ChatMessage>('/api/ai/chat-messages', { method: 'POST', body: JSON.stringify(data) }),
@@ -534,7 +621,10 @@ export const api = {
     request<void>(`/api/ai/chat-messages/${id}`, { method: 'DELETE' }),
 
   /* AI Customer Service: Service Performance */
-  getServicePerformances: () => request<{ items: ServicePerformance[] }>('/api/ai/service-performances'),
+  getServicePerformances: (page?: number, size?: number) =>
+    request<{ items: ServicePerformance[]; totalElements: number }>(
+      `/api/ai/service-performances?page=${page || 1}&size=${size || 20}`
+    ),
   getServicePerformance: (id: number) => request<ServicePerformance>(`/api/ai/service-performances/${id}`),
   createServicePerformance: (data: any) =>
     request<ServicePerformance>('/api/ai/service-performances', { method: 'POST', body: JSON.stringify(data) }),
@@ -544,7 +634,10 @@ export const api = {
     request<void>(`/api/ai/service-performances/${id}`, { method: 'DELETE' }),
 
   /* Report: Inventory Turnover */
-  getInventoryTurnovers: () => request<{ items: InventoryTurnoverItem[] }>('/api/report/inventory-turnovers'),
+  getInventoryTurnovers: (page?: number, size?: number) =>
+    request<{ items: InventoryTurnoverItem[]; totalElements: number }>(
+      `/api/report/inventory-turnovers?page=${page || 1}&size=${size || 20}`
+    ),
   getInventoryTurnover: (id: number) => request<InventoryTurnoverItem>(`/api/report/inventory-turnovers/${id}`),
   createInventoryTurnover: (data: any) =>
     request<InventoryTurnoverItem>('/api/report/inventory-turnovers', { method: 'POST', body: JSON.stringify(data) }),
@@ -554,7 +647,10 @@ export const api = {
     request<void>(`/api/report/inventory-turnovers/${id}`, { method: 'DELETE' }),
 
   /* System: Roles */
-  getRoles: () => request<{ items: RoleItem[] }>('/api/system/roles'),
+  getRoles: (page?: number, size?: number) =>
+    request<{ items: RoleItem[]; totalElements: number }>(
+      `/api/system/roles?page=${page || 1}&size=${size || 20}`
+    ),
   getRole: (id: number) => request<RoleItem>(`/api/system/roles/${id}`),
   createRole: (data: any) =>
     request<RoleItem>('/api/system/roles', { method: 'POST', body: JSON.stringify(data) }),
@@ -606,7 +702,10 @@ export const api = {
     request<void>(`/api/system/employees/${id}`, { method: 'DELETE' }),
 
   /* System: API Keys */
-  getApiKeys: () => request<{ items: ApiKeyItem[] }>('/api/system/api-keys'),
+  getApiKeys: (page?: number, size?: number) =>
+    request<{ items: ApiKeyItem[]; totalElements: number }>(
+      `/api/system/api-keys?page=${page || 1}&size=${size || 20}`
+    ),
   getApiKey: (id: number) => request<ApiKeyItem>(`/api/system/api-keys/${id}`),
   createApiKey: (data: any) =>
     request<ApiKeyItem & { keyValue?: string }>('/api/system/api-keys', { method: 'POST', body: JSON.stringify(data) }),
@@ -634,7 +733,10 @@ export const api = {
   getOperationLog: (id: number) => request<LogItem>(`/api/system/operation-logs/${id}`),
 
   /* System: Approval Flows */
-  getApprovalFlows: () => request<{ items: ApprovalFlowItem[] }>('/api/system/approval-flows'),
+  getApprovalFlows: (page?: number, size?: number) =>
+    request<{ items: ApprovalFlowItem[]; totalElements: number }>(
+      `/api/system/approval-flows?page=${page || 1}&size=${size || 20}`
+    ),
   getApprovalFlow: (id: number) => request<ApprovalFlowItem>(`/api/system/approval-flows/${id}`),
   createApprovalFlow: (data: any) =>
     request<ApprovalFlowItem>('/api/system/approval-flows', { method: 'POST', body: JSON.stringify(data) }),
@@ -644,7 +746,10 @@ export const api = {
     request<void>(`/api/system/approval-flows/${id}`, { method: 'DELETE' }),
 
   /* System: Message Templates */
-  getMessageTemplates: () => request<{ items: MessageTemplateItem[] }>('/api/system/message-templates'),
+  getMessageTemplates: (page?: number, size?: number) =>
+    request<{ items: MessageTemplateItem[]; totalElements: number }>(
+      `/api/system/message-templates?page=${page || 1}&size=${size || 20}`
+    ),
   getMessageTemplate: (id: number) => request<MessageTemplateItem>(`/api/system/message-templates/${id}`),
   createMessageTemplate: (data: any) =>
     request<MessageTemplateItem>('/api/system/message-templates', { method: 'POST', body: JSON.stringify(data) }),
@@ -655,8 +760,14 @@ export const api = {
 
   /* CRM: AI Controller Stats (legacy) */
   getAIControllerStats: () => request<AIControllerStats>('/api/crm/ai-controller/stats'),
-  getOnlineVisitors: () => request<{ items: OnlineVisitor[] }>('/api/crm/ai-controller/visitors'),
-  getChatSessionsLegacy: () => request<{ items: ChatSession[] }>('/api/crm/ai-controller/sessions'),
+  getOnlineVisitors: (page?: number, size?: number) =>
+    request<{ items: OnlineVisitor[]; totalElements: number }>(
+      `/api/crm/ai-controller/visitors?page=${page || 1}&size=${size || 20}`
+    ),
+  getChatSessionsLegacy: (page?: number, size?: number) =>
+    request<{ items: ChatSession[]; totalElements: number }>(
+      `/api/crm/ai-controller/sessions?page=${page || 1}&size=${size || 20}`
+    ),
   getChatSessionLegacy: (id: string) => request<ChatSession>(`/api/crm/ai-controller/sessions/${id}`),
   sendChatMessageLegacy: (sessionId: string, message: any) =>
     request<ChatMessage>(`/api/crm/ai-controller/sessions/${sessionId}/messages`, { method: 'POST', body: JSON.stringify(message) }),
@@ -664,13 +775,19 @@ export const api = {
     request<{ success: boolean }>(`/api/crm/ai-controller/sessions/${sessionId}/transfer`, { method: 'POST', body: JSON.stringify({ agentId }) }),
 
   /* CRM: Knowledge Base (legacy) */
-  getKnowledgeCategoriesLegacy: () => request<{ items: KnowledgeCategory[] }>('/api/crm/knowledge/categories'),
+  getKnowledgeCategoriesLegacy: (page?: number, size?: number) =>
+    request<{ items: KnowledgeCategory[]; totalElements: number }>(
+      `/api/crm/knowledge/categories?page=${page || 1}&size=${size || 20}`
+    ),
   createKnowledgeCategoryLegacy: (data: any) =>
     request<KnowledgeCategory>('/api/crm/knowledge/categories', { method: 'POST', body: JSON.stringify(data) }),
   updateKnowledgeCategoryLegacy: (id: string, data: any) =>
     request<KnowledgeCategory>(`/api/crm/knowledge/categories/${id}`, { method: 'PUT', body: JSON.stringify(data) }),
   deleteKnowledgeCategoryLegacy: (id: string) => request<{ success: boolean }>(`/api/crm/knowledge/categories/${id}`, { method: 'DELETE' }),
-  getKnowledgeItemsLegacy: () => request<{ items: KnowledgeBaseItem[] }>('/api/crm/knowledge/items'),
+  getKnowledgeItemsLegacy: (page?: number, size?: number) =>
+    request<{ items: KnowledgeBaseItem[]; totalElements: number }>(
+      `/api/crm/knowledge/items?page=${page || 1}&size=${size || 20}`
+    ),
   createKnowledgeItemLegacy: (data: any) =>
     request<KnowledgeBaseItem>('/api/crm/knowledge/items', { method: 'POST', body: JSON.stringify(data) }),
   updateKnowledgeItemLegacy: (id: string, data: any) =>
@@ -678,7 +795,10 @@ export const api = {
   deleteKnowledgeItemLegacy: (id: string) => request<{ success: boolean }>(`/api/crm/knowledge/items/${id}`, { method: 'DELETE' }),
 
   /* CRM: Auto Reply Rules (legacy) */
-  getAutoReplyRulesLegacy: () => request<{ items: AutoReplyRule[] }>('/api/crm/auto-reply/rules'),
+  getAutoReplyRulesLegacy: (page?: number, size?: number) =>
+    request<{ items: AutoReplyRule[]; totalElements: number }>(
+      `/api/crm/auto-reply/rules?page=${page || 1}&size=${size || 20}`
+    ),
   createAutoReplyRuleLegacy: (data: any) =>
     request<AutoReplyRule>('/api/crm/auto-reply/rules', { method: 'POST', body: JSON.stringify(data) }),
   updateAutoReplyRuleLegacy: (id: string, data: any) =>
@@ -688,7 +808,10 @@ export const api = {
     request<{ response: string }>(`/api/crm/auto-reply/rules/${id}/test`, { method: 'POST', body: JSON.stringify({ message: testMessage }) }),
 
   /* CRM: Chat Log */
-  getChatLogs: () => request<{ items: ChatLogItem[] }>('/api/crm/chat-logs'),
+  getChatLogs: (page?: number, size?: number) =>
+    request<{ items: ChatLogItem[]; totalElements: number }>(
+      `/api/crm/chat-logs?page=${page || 1}&size=${size || 20}`
+    ),
   getChatLog: (id: number) => request<ChatLogItem>(`/api/crm/chat-logs/${id}`),
   createChatLog: (data: Partial<ChatLogItem>) =>
     request<ChatLogItem>('/api/crm/chat-logs', { method: 'POST', body: JSON.stringify(data) }),
@@ -699,11 +822,17 @@ export const api = {
     request<{ success: boolean }>(`/api/crm/chat-logs/${id}/mark`, { method: 'POST', body: JSON.stringify({ tag }) }),
 
   /* CRM: Service Performance (legacy) */
-  getServicePerformanceLegacy: () => request<{ items: ServicePerformance[] }>('/api/crm/service/performance'),
+  getServicePerformanceLegacy: (page?: number, size?: number) =>
+    request<{ items: ServicePerformance[]; totalElements: number }>(
+      `/api/crm/service/performance?page=${page || 1}&size=${size || 20}`
+    ),
   getServicePerformanceSummaryLegacy: () => request<{ stats: DashboardStat[] }>('/api/crm/service/performance/summary'),
 
   /* CRM: Chat Channels (legacy) */
-  getChatChannelsLegacy: () => request<{ items: ChatChannel[] }>('/api/crm/channels'),
+  getChatChannelsLegacy: (page?: number, size?: number) =>
+    request<{ items: ChatChannel[]; totalElements: number }>(
+      `/api/crm/channels?page=${page || 1}&size=${size || 20}`
+    ),
   createChatChannelLegacy: (data: any) =>
     request<ChatChannel>('/api/crm/channels', { method: 'POST', body: JSON.stringify(data) }),
   updateChatChannelLegacy: (id: string, data: any) =>
@@ -711,7 +840,10 @@ export const api = {
   deleteChatChannelLegacy: (id: string) => request<{ success: boolean }>(`/api/crm/channels/${id}`, { method: 'DELETE' }),
 
   /* Reports */
-  getReports: () => request<{ items: ReportItem[] }>('/api/reports'),
+  getReports: (page?: number, size?: number) =>
+    request<{ items: ReportItem[]; totalElements: number }>(
+      `/api/reports?page=${page || 1}&size=${size || 20}`
+    ),
   getReport: (id: number) => request<ReportItem>(`/api/reports/${id}`),
   createReport: (data: Partial<ReportItem>) =>
     request<ReportItem>('/api/reports', { method: 'POST', body: JSON.stringify(data) }),
@@ -720,8 +852,10 @@ export const api = {
   deleteReport: (id: number) => request<void>(`/api/reports/${id}`, { method: 'DELETE' }),
   generateReport: (id: number) => request<ReportItem>(`/api/reports/${id}/generate`, { method: 'POST' }),
 
-  getReportData: (reportId?: number) =>
-    reportId ? request<{ items: ReportDataItem[] }>(`/api/report-data?reportId=${reportId}`) : request<{ items: ReportDataItem[] }>('/api/report-data'),
+  getReportData: (reportId?: number, page?: number, size?: number) =>
+    reportId
+      ? request<{ items: ReportDataItem[]; totalElements: number }>(`/api/report-data?reportId=${reportId}&page=${page || 1}&size=${size || 20}`)
+      : request<{ items: ReportDataItem[]; totalElements: number }>(`/api/report-data?page=${page || 1}&size=${size || 20}`),
   getReportDataById: (id: number) => request<ReportDataItem>(`/api/report-data/${id}`),
   createReportData: (data: Partial<ReportDataItem>) =>
     request<ReportDataItem>('/api/report-data', { method: 'POST', body: JSON.stringify(data) }),
@@ -730,7 +864,10 @@ export const api = {
   deleteReportData: (id: number) => request<void>(`/api/report-data/${id}`, { method: 'DELETE' }),
 
   /* Pricing Rules */
-  getPricingRules: () => request<{ items: PricingRuleItem[] }>('/api/pricing-rules'),
+  getPricingRules: (page?: number, size?: number) =>
+    request<{ items: PricingRuleItem[]; totalElements: number }>(
+      `/api/pricing-rules?page=${page || 1}&size=${size || 20}`
+    ),
   getPricingRule: (id: number) => request<PricingRuleItem>(`/api/pricing-rules/${id}`),
   createPricingRule: (data: Partial<PricingRuleItem>) =>
     request<PricingRuleItem>('/api/pricing-rules', { method: 'POST', body: JSON.stringify(data) }),
@@ -739,7 +876,10 @@ export const api = {
   deletePricingRule: (id: number) => request<void>(`/api/pricing-rules/${id}`, { method: 'DELETE' }),
 
   /* CRM: Ticket */
-  getTickets: () => request<{ items: TicketItem[] }>('/api/crm/tickets'),
+  getTickets: (page?: number, size?: number) =>
+    request<{ items: TicketItem[]; totalElements: number }>(
+      `/api/crm/tickets?page=${page || 1}&size=${size || 20}`
+    ),
   getTicket: (id: number) => request<TicketItem>(`/api/crm/tickets/${id}`),
   createTicket: (data: Partial<TicketItem>) =>
     request<TicketItem>('/api/crm/tickets', { method: 'POST', body: JSON.stringify(data) }),
@@ -752,7 +892,10 @@ export const api = {
   deleteTicket: (id: number) => request<void>(`/api/crm/tickets/${id}`, { method: 'DELETE' }),
 
   /* CRM: Satisfaction */
-  getSatisfactions: () => request<{ items: SatisfactionItem[] }>('/api/crm/satisfactions'),
+  getSatisfactions: (page?: number, size?: number) =>
+    request<{ items: SatisfactionItem[]; totalElements: number }>(
+      `/api/crm/satisfactions?page=${page || 1}&size=${size || 20}`
+    ),
   getSatisfaction: (id: number) => request<SatisfactionItem>(`/api/crm/satisfactions/${id}`),
   createSatisfaction: (data: Partial<SatisfactionItem>) =>
     request<SatisfactionItem>('/api/crm/satisfactions', { method: 'POST', body: JSON.stringify(data) }),
@@ -762,5 +905,8 @@ export const api = {
   getSatisfactionAverageScore: () => request<{ averageScore: number }>('/api/crm/satisfactions/average-score'),
 
   /* System: Logs (legacy) */
-  getLogs: () => request<{ items: LogItem[] }>('/api/system/operation-logs')
+  getLogs: (page?: number, size?: number) =>
+    request<{ items: LogItem[]; totalElements: number }>(
+      `/api/system/operation-logs?page=${page || 1}&size=${size || 20}`
+    )
 };

+ 232 - 67
frontend/src/layout/AppLayout.vue

@@ -1,20 +1,20 @@
 <template>
   <el-container class="app-layout">
-    <el-aside :width="appStore.menuCollapsed ? '84px' : '260px'" class="app-layout__aside">
-      <div class="brand glass-card">
+    <el-aside :width="appStore.menuCollapsed ? '72px' : '240px'" class="app-layout__aside">
+      <div class="brand">
         <div class="brand__mark">CB</div>
         <div v-if="!appStore.menuCollapsed" class="brand__meta">
           <strong>CrossBorder OS</strong>
-          <span>Vue 3 + TS</span>
+          <span>运营管理平台</span>
         </div>
       </div>
 
-      <div class="menu-groups glass-card">
+      <div class="menu-groups">
         <el-menu 
           :default-active="route.path" 
           :collapse="appStore.menuCollapsed" 
           :default-openeds="defaultOpeneds"
-          :collapse-transition="true"
+          :collapse-transition="false"
           router 
           class="menu-groups__menu"
         >
@@ -40,19 +40,18 @@
 
     <el-container>
       <el-header class="app-layout__header">
-        <div class="header-panel glass-card">
-          <div>
+        <div class="header-panel">
+          <div class="header-panel__left">
             <div class="header-panel__breadcrumb">跨境运营中台 / {{ currentTitle }}</div>
-            <h2>{{ currentTitle }}</h2>
+            <h1 class="header-panel__title">{{ currentTitle }}</h1>
           </div>
 
           <div class="header-panel__actions">
-            <el-button plain @click="appStore.toggleMenu()">
+            <el-button @click="appStore.toggleMenu()">
               {{ appStore.menuCollapsed ? '展开菜单' : '收起菜单' }}
             </el-button>
-            <el-tag effect="dark" type="success">API 已对接</el-tag>
             <el-dropdown>
-              <div class="header-user glass-card">
+              <div class="header-user">
                 <div class="header-user__avatar">{{ authStore.user?.avatar || 'CB' }}</div>
                 <div class="header-user__meta">
                   <strong>{{ authStore.user?.name }}</strong>
@@ -79,9 +78,49 @@
 </template>
 
 <script setup lang="ts">
-import { computed, ref } from 'vue';
+import { computed, ref, defineAsyncComponent, type Component } from 'vue';
 import { RouterView, useRoute, useRouter } from 'vue-router';
-import * as ElementPlusIconsVue from '@element-plus/icons-vue';
+import {
+  DataAnalysis,
+  PieChart,
+  TrendCharts,
+  List,
+  Document,
+  Money,
+  Clock,
+  User,
+  Van,
+  Box,
+  House,
+  Connection,
+  Check,
+  Service,
+  ShoppingCart,
+  RefreshLeft,
+  Ticket,
+  Star,
+  Promotion,
+  Setting,
+  Key,
+  Bell,
+  MoreFilled,
+  Folder,
+  Goods,
+  Odometer,
+  Notebook,
+  SetUp,
+  Discount,
+  Trophy,
+  Collection,
+  ChatDotRound,
+  Message,
+  CircleCheck,
+  UserFilled,
+  OfficeBuilding,
+  Grid,
+  DataBoard,
+  Tickets
+} from '@element-plus/icons-vue';
 import { menuGroups } from '@/router/menu';
 import { useAppStore } from '@/stores/app';
 import { useAuthStore } from '@/stores/auth';
@@ -103,10 +142,51 @@ const filteredGroups = computed(() =>
     .filter((group) => group.items.length > 0)
 );
 
+const iconMap: Record<string, Component> = {
+  DataAnalysis,
+  PieChart,
+  TrendCharts,
+  List,
+  Document,
+  Money,
+  Clock,
+  User,
+  Van,
+  Box,
+  House,
+  Connection,
+  Check,
+  Service,
+  ShoppingCart,
+  RefreshLeft,
+  Ticket,
+  Tickets,
+  Star,
+  Promotion,
+  Setting,
+  Key,
+  Bell,
+  MoreFilled,
+  Folder,
+  Goods,
+  Odometer,
+  Notebook,
+  SetUp,
+  Discount,
+  Trophy,
+  Collection,
+  ChatDotRound,
+  Message,
+  CircleCheck,
+  UserFilled,
+  OfficeBuilding,
+  Grid,
+  DataBoard
+};
+
 const getIcon = (iconName?: string) => {
-  if (!iconName) return ElementPlusIconsVue.View;
-  const icon = ElementPlusIconsVue[iconName as keyof typeof ElementPlusIconsVue];
-  return icon || ElementPlusIconsVue.View;
+  if (!iconName) return Document;
+  return iconMap[iconName] || Document;
 };
 
 const goLogin = () => {
@@ -123,49 +203,89 @@ const logout = () => {
 <style scoped lang="scss">
 .app-layout {
   min-height: 100vh;
+  background: var(--cb-bg);
 }
 
 .app-layout__aside {
-  padding: 18px 12px 18px 18px;
+  background: var(--cb-panel);
+  border-right: 1px solid var(--cb-border);
+  padding: 0;
   transition: width 0.3s ease;
 }
 
 .brand {
   display: flex;
   align-items: center;
-  gap: 14px;
-  padding: 18px;
-  margin-bottom: 16px;
+  gap: 12px;
+  padding: 16px;
+  border-bottom: 1px solid var(--cb-border);
+  height: 60px;
+  position: relative;
+  overflow: hidden;
+}
+
+.brand::after {
+  content: "";
+  position: absolute;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  height: 3px;
+  background: linear-gradient(90deg, var(--cb-primary), var(--cb-accent));
 }
 
 .brand__mark {
-  width: 46px;
-  height: 46px;
-  border-radius: 16px;
+  width: 36px;
+  height: 36px;
+  border-radius: 8px;
   display: grid;
   place-items: center;
-  background: linear-gradient(135deg, #0f766e, #d97706);
+  background: linear-gradient(135deg, var(--cb-primary), var(--cb-accent));
   color: white;
-  font-weight: 800;
+  font-weight: 700;
+  font-size: 14px;
   flex-shrink: 0;
+  box-shadow: 0 2px 8px rgba(14, 165, 233, 0.3);
 }
 
 .brand__meta {
   display: flex;
   flex-direction: column;
-  gap: 4px;
+  gap: 2px;
   overflow: hidden;
 }
 
+.brand__meta strong {
+  font-size: 14px;
+  font-weight: 600;
+  color: var(--cb-text);
+  white-space: nowrap;
+}
+
 .brand__meta span {
-  color: var(--cb-text-soft);
-  font-size: 13px;
+  color: var(--cb-primary);
+  font-size: 12px;
+  white-space: nowrap;
+  font-weight: 500;
 }
 
 .menu-groups {
-  height: calc(100vh - 120px);
-  padding: 14px 10px;
-  overflow: auto;
+  height: calc(100vh - 60px);
+  padding: 8px;
+  overflow-y: auto;
+}
+
+.menu-groups::-webkit-scrollbar {
+  width: 4px;
+}
+
+.menu-groups::-webkit-scrollbar-track {
+  background: transparent;
+}
+
+.menu-groups::-webkit-scrollbar-thumb {
+  background: var(--cb-border);
+  border-radius: 4px;
 }
 
 .menu-groups__menu {
@@ -179,39 +299,44 @@ const logout = () => {
   margin-right: 10px;
   vertical-align: middle;
   flex-shrink: 0;
+  color: var(--cb-text-soft);
 }
 
 :deep(.el-sub-menu__title) {
-  height: 48px;
-  line-height: 48px;
+  height: 40px;
+  line-height: 40px;
   padding: 0 12px !important;
-  margin: 4px 0;
-  border-radius: 12px;
-  font-weight: 600;
+  margin: 2px 0;
+  border-radius: 6px;
+  font-weight: 500;
+  font-size: 14px;
   color: var(--cb-text);
-  transition: all 0.2s ease;
+  transition: all 0.15s ease;
 }
 
 :deep(.el-sub-menu__title:hover) {
-  background: rgba(15, 118, 110, 0.08);
+  background: var(--cb-primary-soft);
+  color: var(--cb-primary);
 }
 
 :deep(.el-sub-menu .el-menu-item) {
-  height: 44px;
-  line-height: 44px;
-  padding: 0 12px 0 48px !important;
-  margin: 2px 0;
-  border-radius: 10px;
-  color: var(--cb-text);
-  transition: all 0.2s ease;
+  height: 38px;
+  line-height: 38px;
+  padding: 0 12px 0 42px !important;
+  margin: 1px 0;
+  border-radius: 6px;
+  font-size: 13px;
+  color: var(--cb-text-soft);
+  transition: all 0.15s ease;
 }
 
 :deep(.el-sub-menu .el-menu-item:hover) {
-  background: rgba(15, 118, 110, 0.08);
+  background: var(--cb-primary-soft);
+  color: var(--cb-primary);
 }
 
 :deep(.el-sub-menu .el-menu-item.is-active) {
-  background: rgba(15, 118, 110, 0.14);
+  background: var(--cb-primary-soft);
   color: var(--cb-primary);
   font-weight: 600;
 }
@@ -233,7 +358,7 @@ const logout = () => {
 :deep(.el-sub-menu__icon-arrow) {
   font-size: 12px;
   margin-left: auto;
-  right: 12px;
+  color: var(--cb-text-muted);
 }
 
 :deep(.el-menu--collapse .el-sub-menu__icon-arrow) {
@@ -245,8 +370,22 @@ const logout = () => {
 }
 
 .app-layout__header {
-  padding: 18px 18px 0 6px;
+  padding: 0;
   height: auto;
+  background: var(--cb-panel);
+  border-bottom: 1px solid var(--cb-border);
+  position: relative;
+}
+
+.app-layout__header::after {
+  content: "";
+  position: absolute;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  height: 2px;
+  background: linear-gradient(90deg, var(--cb-primary), var(--cb-accent));
+  opacity: 0.6;
 }
 
 .header-panel {
@@ -254,42 +393,60 @@ const logout = () => {
   justify-content: space-between;
   gap: 16px;
   align-items: center;
-  padding: 20px 24px;
+  padding: 16px 20px;
+}
+
+.header-panel__left {
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
 }
 
 .header-panel__breadcrumb {
-  color: var(--cb-text-soft);
-  font-size: 13px;
+  color: var(--cb-text-muted);
+  font-size: 12px;
 }
 
-.header-panel h2 {
-  margin: 8px 0 0;
-  font-size: 24px;
+.header-panel__title {
+  margin: 0;
+  font-size: 18px;
+  font-weight: 600;
+  color: var(--cb-text);
 }
 
 .header-panel__actions {
   display: flex;
-  gap: 10px;
+  gap: 12px;
   align-items: center;
 }
 
 .header-user {
   display: flex;
   align-items: center;
-  gap: 12px;
-  padding: 8px 12px;
+  gap: 10px;
+  padding: 6px 10px;
+  border-radius: 8px;
   cursor: pointer;
+  transition: all 0.15s ease;
+  border: 1px solid transparent;
+}
+
+.header-user:hover {
+  background: var(--cb-primary-soft);
+  border-color: rgba(14, 165, 233, 0.2);
 }
 
 .header-user__avatar {
-  width: 38px;
-  height: 38px;
-  border-radius: 14px;
+  width: 34px;
+  height: 34px;
+  border-radius: 8px;
   display: grid;
   place-items: center;
-  background: linear-gradient(135deg, rgba(15, 118, 110, 0.15), rgba(217, 119, 6, 0.18));
-  font-weight: 700;
+  background: linear-gradient(135deg, var(--cb-primary-soft), var(--cb-accent-soft));
+  font-weight: 600;
+  font-size: 13px;
   color: var(--cb-primary);
+  border: 1px solid rgba(14, 165, 233, 0.2);
 }
 
 .header-user__meta {
@@ -297,13 +454,21 @@ const logout = () => {
   flex-direction: column;
 }
 
+.header-user__meta strong {
+  font-size: 13px;
+  font-weight: 600;
+  color: var(--cb-text);
+}
+
 .header-user__meta span {
-  color: var(--cb-text-soft);
-  font-size: 12px;
+  color: var(--cb-primary);
+  font-size: 11px;
+  font-weight: 500;
 }
 
 .app-layout__main {
-  padding: 18px 18px 18px 6px;
+  padding: 20px;
+  background: var(--cb-bg);
 }
 
 @media (max-width: 900px) {
@@ -316,4 +481,4 @@ const logout = () => {
     align-items: flex-start;
   }
 }
-</style>
+</style>

+ 5 - 0
frontend/src/main.ts

@@ -1,6 +1,7 @@
 import { createApp } from 'vue';
 import ElementPlus from 'element-plus';
 import { createPinia } from 'pinia';
+import * as ElementPlusIconsVue from '@element-plus/icons-vue';
 import 'element-plus/dist/index.css';
 import App from './App.vue';
 import router from './router';
@@ -12,4 +13,8 @@ app.use(createPinia());
 app.use(router);
 app.use(ElementPlus);
 
+for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
+  app.component(key, component);
+}
+
 app.mount('#app');

+ 4 - 4
frontend/src/router/menu.ts

@@ -48,7 +48,7 @@ export const menuGroups: MenuGroup[] = [
     items: [
       { key: 'product-list', title: '商品列表', path: '/product/list', roles: ['admin', 'manager', 'operator'], icon: 'List' },
       { key: 'product-mapping', title: '渠道映射', path: '/product/mapping', roles: ['admin', 'manager', 'operator'], icon: 'Connection' },
-      { key: 'product-pricing', title: '定价与库存规则', path: '/product/pricing', roles: ['admin', 'manager', 'operator'], icon: 'PriceTag' }
+      { key: 'product-pricing', title: '定价与库存规则', path: '/product/pricing', roles: ['admin', 'manager', 'operator'], icon: 'SetUp' }
     ]
   },
   {
@@ -123,16 +123,16 @@ export const menuGroups: MenuGroup[] = [
   {
     key: 'crm',
     title: '客服中心',
-    icon: 'ChatLineRound',
+    icon: 'ChatDotRound',
     items: [
-      { key: 'crm-tickets', title: '工单管理', path: '/crm/tickets', roles: ['admin', 'manager', 'customer_service'], icon: 'Tickets' },
+      { key: 'crm-tickets', title: '工单管理', path: '/crm/tickets', roles: ['admin', 'manager', 'customer_service'], icon: 'Ticket' },
       { key: 'crm-satisfaction', title: '满意度评价', path: '/crm/satisfaction', roles: ['admin', 'manager', 'customer_service'], icon: 'Star' }
     ]
   },
   {
     key: 'ai-service',
     title: 'AI智能客服',
-    icon: 'Robot',
+    icon: 'Service',
     items: [
       { key: 'ai-controller', title: 'AI客服工作台', path: '/crm/ai-controller', roles: ['admin', 'manager', 'customer_service'], icon: 'Service' },
       { key: 'knowledge-base', title: '知识库管理', path: '/crm/knowledge-base', roles: ['admin', 'manager', 'customer_service'], icon: 'Collection' },

+ 125 - 61
frontend/src/styles/global.scss

@@ -1,20 +1,36 @@
 :root {
   color-scheme: light;
-  --cb-bg: #f4efe6;
-  --cb-bg-soft: #fbf8f2;
-  --cb-panel: rgba(255, 255, 255, 0.82);
-  --cb-panel-strong: #ffffff;
-  --cb-border: rgba(33, 53, 71, 0.12);
-  --cb-text: #213547;
-  --cb-text-soft: #597188;
-  --cb-primary: #0f766e;
-  --cb-primary-soft: rgba(15, 118, 110, 0.12);
-  --cb-accent: #d97706;
-  --cb-danger: #b42318;
-  --cb-shadow: 0 20px 50px rgba(33, 53, 71, 0.08);
-  --el-color-primary: #0f766e;
-  --el-border-radius-base: 14px;
-  --el-font-family: "HarmonyOS Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif;
+  --cb-bg: #F8FAFC;
+  --cb-bg-soft: #F1F5F9;
+  --cb-panel: #FFFFFF;
+  --cb-panel-strong: #FFFFFF;
+  --cb-border: #E2E8F0;
+  --cb-border-bright: #CBD5E1;
+  --cb-text: #0F172A;
+  --cb-text-soft: #64748B;
+  --cb-text-muted: #94A3B8;
+  --cb-primary: #0EA5E9;
+  --cb-primary-soft: #E0F2FE;
+  --cb-primary-hover: #0284C7;
+  --cb-accent: #8B5CF6;
+  --cb-accent-soft: #EDE9FE;
+  --cb-danger: #EF4444;
+  --cb-danger-soft: #FEE2E2;
+  --cb-success: #10B981;
+  --cb-success-soft: #D1FAE5;
+  --cb-warning: #F59E0B;
+  --cb-warning-soft: #FEF3C7;
+  --cb-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
+  --cb-shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08);
+  --cb-shadow-glow: 0 4px 16px rgba(14, 165, 233, 0.15);
+  --el-color-primary: #0EA5E9;
+  --el-color-primary-light-3: #38BDF8;
+  --el-color-primary-light-5: #7DD3FC;
+  --el-color-primary-light-7: #BAE6FD;
+  --el-color-primary-light-8: #E0F2FE;
+  --el-color-primary-light-9: #F0F9FF;
+  --el-border-radius-base: 8px;
+  --el-font-family: "Inter", "SF Pro Display", "HarmonyOS Sans SC", "PingFang SC", "Microsoft YaHei", sans-serif;
 }
 
 * {
@@ -27,10 +43,7 @@ body,
   margin: 0;
   min-height: 100%;
   color: var(--cb-text);
-  background:
-    radial-gradient(circle at top left, rgba(217, 119, 6, 0.12), transparent 32%),
-    radial-gradient(circle at top right, rgba(15, 118, 110, 0.16), transparent 26%),
-    linear-gradient(180deg, #fbf8f2 0%, #f2ece1 100%);
+  background: var(--cb-bg);
 }
 
 body {
@@ -50,10 +63,9 @@ a {
 
 .glass-card {
   border: 1px solid var(--cb-border);
-  border-radius: 22px;
+  border-radius: var(--el-border-radius-base);
   background: var(--cb-panel);
   box-shadow: var(--cb-shadow);
-  backdrop-filter: blur(14px);
 }
 
 .page-hero {
@@ -61,7 +73,22 @@ a {
   flex-wrap: wrap;
   justify-content: space-between;
   gap: 20px;
-  padding: 24px 28px;
+  padding: 20px 24px;
+  border: 1px solid var(--cb-border);
+  border-radius: var(--el-border-radius-base);
+  background: var(--cb-panel);
+  position: relative;
+  overflow: hidden;
+}
+
+.page-hero::before {
+  content: "";
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  height: 3px;
+  background: linear-gradient(90deg, var(--cb-primary), var(--cb-accent), var(--cb-primary));
 }
 
 .page-hero__meta {
@@ -71,29 +98,31 @@ a {
 .page-hero__eyebrow {
   display: inline-flex;
   align-items: center;
-  gap: 8px;
+  gap: 6px;
   margin-bottom: 10px;
-  padding: 6px 12px;
-  border-radius: 999px;
+  padding: 4px 10px;
+  border-radius: 4px;
   background: var(--cb-primary-soft);
   color: var(--cb-primary);
   font-size: 12px;
-  font-weight: 700;
-  letter-spacing: 0.08em;
-  text-transform: uppercase;
+  font-weight: 600;
+  letter-spacing: 0.02em;
 }
 
 .page-hero h1,
 .page-hero h2 {
   margin: 0 0 8px;
-  font-size: 28px;
-  line-height: 1.18;
+  font-size: 24px;
+  line-height: 1.2;
+  font-weight: 600;
+  color: var(--cb-text);
 }
 
 .page-hero p {
   margin: 0;
   color: var(--cb-text-soft);
-  line-height: 1.7;
+  line-height: 1.6;
+  font-size: 14px;
 }
 
 .page-grid {
@@ -110,7 +139,22 @@ a {
 }
 
 .section-card {
-  padding: 22px 24px;
+  padding: 20px 22px;
+  border: 1px solid var(--cb-border);
+  border-radius: var(--el-border-radius-base);
+  background: var(--cb-panel);
+  position: relative;
+}
+
+.section-card::before {
+  content: "";
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 48px;
+  height: 3px;
+  background: linear-gradient(90deg, var(--cb-primary), var(--cb-accent));
+  border-radius: 0 0 4px 0;
 }
 
 .section-card__title {
@@ -119,34 +163,39 @@ a {
   justify-content: space-between;
   gap: 12px;
   margin-bottom: 16px;
+  padding-bottom: 12px;
+  border-bottom: 1px solid var(--cb-border);
 }
 
 .section-card__title h3 {
   margin: 0;
-  font-size: 18px;
+  font-size: 15px;
+  font-weight: 600;
+  color: var(--cb-text);
 }
 
 .section-card__title p {
-  margin: 8px 0 0;
+  margin: 4px 0 0;
   color: var(--cb-text-soft);
+  font-size: 13px;
 }
 
 .chip-list {
   display: flex;
   flex-wrap: wrap;
-  gap: 10px;
+  gap: 8px;
 }
 
 .chip {
   display: inline-flex;
   align-items: center;
-  gap: 8px;
-  padding: 8px 12px;
-  border: 1px solid rgba(15, 118, 110, 0.14);
-  border-radius: 999px;
-  background: rgba(255, 255, 255, 0.78);
-  color: var(--cb-text);
-  font-size: 13px;
+  gap: 6px;
+  padding: 4px 10px;
+  border: 1px solid var(--cb-border);
+  border-radius: 4px;
+  background: var(--cb-bg-soft);
+  color: var(--cb-text-soft);
+  font-size: 12px;
 }
 
 .muted {
@@ -155,11 +204,12 @@ a {
 
 .tight-list {
   display: grid;
-  gap: 10px;
+  gap: 8px;
   margin: 0;
   padding-left: 18px;
   color: var(--cb-text-soft);
-  line-height: 1.7;
+  line-height: 1.6;
+  font-size: 13px;
 }
 
 .stat-grid {
@@ -169,27 +219,38 @@ a {
 }
 
 .stat-card {
-  padding: 18px 20px;
+  padding: 16px 18px;
   border: 1px solid var(--cb-border);
-  border-radius: 18px;
-  background: rgba(255, 255, 255, 0.78);
+  border-radius: var(--el-border-radius-base);
+  background: var(--cb-panel);
+  position: relative;
+  overflow: hidden;
+  transition: border-color 0.2s ease, box-shadow 0.2s ease;
+}
+
+.stat-card:hover {
+  border-color: var(--cb-primary);
+  box-shadow: var(--cb-shadow-glow);
 }
 
 .stat-card__label {
   color: var(--cb-text-soft);
   font-size: 13px;
+  font-weight: 500;
 }
 
 .stat-card__value {
-  margin-top: 10px;
-  font-size: 28px;
+  margin-top: 8px;
+  font-size: 24px;
   font-weight: 700;
+  color: var(--cb-text);
+  font-feature-settings: 'tnum' on;
 }
 
 .stat-card__trend {
-  margin-top: 8px;
-  color: var(--cb-primary);
-  font-size: 13px;
+  margin-top: 6px;
+  color: var(--cb-text-soft);
+  font-size: 12px;
 }
 
 .table-toolbar {
@@ -202,32 +263,35 @@ a {
 
 .drawer-preview {
   display: grid;
-  gap: 14px;
+  gap: 12px;
 }
 
 .drawer-preview__item {
-  padding: 14px 16px;
+  padding: 12px 14px;
   border: 1px solid var(--cb-border);
-  border-radius: 16px;
-  background: rgba(255, 255, 255, 0.62);
+  border-radius: var(--el-border-radius-base);
+  background: var(--cb-bg-soft);
 }
 
 .drawer-preview__item strong {
   display: block;
-  margin-bottom: 6px;
+  margin-bottom: 4px;
+  font-size: 13px;
+  color: var(--cb-text);
 }
 
 .status-dot {
   display: inline-flex;
   align-items: center;
-  gap: 8px;
+  gap: 6px;
+  font-size: 13px;
 }
 
 .status-dot::before {
   content: "";
-  width: 8px;
-  height: 8px;
-  border-radius: 999px;
+  width: 6px;
+  height: 6px;
+  border-radius: 50%;
   background: currentColor;
 }
 

+ 1 - 1
frontend/src/views/channel/ChannelConfigView.vue

@@ -182,7 +182,7 @@ const loadData = async () => {
   loading.value = true;
   try {
     const res = await api.getChannels();
-    items.value = res.items;
+    items.value = res.items ?? [];
   } finally {
     loading.value = false;
   }

+ 11 - 10
frontend/src/views/crm/KnowledgeBaseView.vue

@@ -144,6 +144,7 @@
 <script setup lang="ts">
 import { ref, computed, onMounted } from 'vue';
 import { ElMessage, ElMessageBox } from 'element-plus';
+import { api } from '@/api/services';
 import type { KnowledgeBaseItem, KnowledgeCategory } from '@/types/page';
 
 const loading = ref(false);
@@ -180,15 +181,7 @@ const categoryTree = ref<KnowledgeCategory[]>([
 
 const categoryOptions = computed(() => categoryTree.value);
 
-const items = ref<KnowledgeBaseItem[]>([
-  { id: 'K001', category: '产品咨询', categoryId: '1', keywords: ['物流', '快递', '发货', '到货'], question: '你们的物流一般多久到?', answer: '国内普通地区一般3-5天送达,偏远地区5-7天。海外仓根据地区不同,一般7-15天。如有特殊情况,我们会提前通知您。', clicks: 1234, aiScore: 95, status: 'enabled', createdAt: '2024-01-10', updatedAt: '2024-01-15' },
-  { id: 'K002', category: '产品咨询', categoryId: '1', keywords: ['材质', '面料', '材料'], question: '这款产品是什么材质的?', answer: '本产品采用优质牛皮材质,外层覆膜保护,内里透气舒适。具体材质成分请参考详情页描述。', clicks: 856, aiScore: 88, status: 'enabled', createdAt: '2024-01-08', updatedAt: '2024-01-14' },
-  { id: 'K003', category: '物流问题', categoryId: '2', keywords: ['查询', '单号', '追踪'], question: '如何查询我的订单物流?', answer: '您可以登录账号,在"我的订单"中找到对应订单,点击"查看物流"即可追踪。如有疑问可联系客服。', clicks: 2341, aiScore: 92, status: 'enabled', createdAt: '2024-01-05', updatedAt: '2024-01-12' },
-  { id: 'K004', category: '支付问题', categoryId: '3', keywords: ['支付', '退款', '到账'], question: '退款多久能到账?', answer: '退款将在收到退货并确认无误后1-7个工作日内原路返回。信用卡退款可能需要3-5个工作日。', clicks: 567, aiScore: 85, status: 'enabled', createdAt: '2024-01-03', updatedAt: '2024-01-10' },
-  { id: 'K005', category: '退换货', categoryId: '4', keywords: ['退货', '换货', '售后'], question: '支持七天无理由退货吗?', answer: '本店支持7天无理由退货(定制商品除外)。请确保商品未使用、包装完整。退货请联系客服申请。', clicks: 1892, aiScore: 97, status: 'enabled', createdAt: '2024-01-01', updatedAt: '2024-01-08' },
-  { id: 'K006', category: '产品咨询', categoryId: '1', keywords: ['尺寸', '大小', '规格'], question: '如何选择合适的尺寸?', answer: '请参考详情页的尺码表,根据您的身高体重选择。如仍有疑问,可咨询客服提供专业建议。', clicks: 743, aiScore: 90, status: 'enabled', createdAt: '2024-01-02', updatedAt: '2024-01-09' },
-  { id: 'K007', category: '物流问题', categoryId: '2', keywords: ['延迟', '延误', '超时'], question: '订单延迟了怎么办?', answer: '如遇物流延迟,请先查询快递公司官网了解情况。如长时间未更新,请联系客服协助查询或申请赔偿。', clicks: 421, aiScore: 82, status: 'disabled', createdAt: '2023-12-28', updatedAt: '2024-01-05' }
-]);
+const items = ref<KnowledgeBaseItem[]>([]);
 
 const filteredItems = computed(() => {
   return items.value.filter(item => {
@@ -211,7 +204,15 @@ const categoryForm = ref({ parentId: '', name: '' });
 
 const dialogTitle = computed(() => isEdit.value ? '编辑知识' : '新增知识');
 
-const loadData = () => { loading.value = true; setTimeout(() => { loading.value = false; }, 300); };
+const loadData = async () => {
+  loading.value = true;
+  try {
+    const res = await api.getKnowledgeBase();
+    items.value = res.items ?? [];
+  } finally {
+    loading.value = false;
+  }
+};
 const resetFilters = () => { filters.value = { category: '', keyword: '', status: '' }; };
 
 const handleCategoryClick = (data: KnowledgeCategory) => {

+ 9 - 10
frontend/src/views/crm/SatisfactionView.vue

@@ -179,6 +179,7 @@
 <script setup lang="ts">
 import { computed, onMounted, ref } from 'vue';
 import { ElMessage } from 'element-plus';
+import { api } from '@/api/services';
 
 interface SatisfactionItem {
   id: string;
@@ -194,14 +195,7 @@ interface SatisfactionItem {
   createTime: string;
 }
 
-const items = ref<SatisfactionItem[]>([
-  { id: 'S001', orderNo: 'ORD-20260420-0123', source: 'Shopify', buyer: 'John D.', productTitle: 'Nomad 防水背包 黑色 中号', rating: 5, content: 'Excellent product! Fast shipping and great quality. Would definitely buy again.', reply: 'Thank you for your positive feedback! We appreciate your support.', handleStatus: '已处理', csName: '张三', createTime: '2026-04-20 10:30:15' },
-  { id: 'S002', orderNo: 'ORD-20260419-0456', source: 'TikTok Shop', buyer: 'Sarah M.', productTitle: 'AeroDry 速干T恤 绿色 L码', rating: 4, content: 'Good product but shipping took longer than expected. Product quality is great though.', reply: 'We apologize for the delay. We will improve our shipping speed.', handleStatus: '已处理', csName: '李四', createTime: '2026-04-19 15:20:33' },
-  { id: 'S003', orderNo: 'ORD-20260419-0789', source: 'Amazon', buyer: 'Mike R.', productTitle: 'UrbanTrail 徒步鞋 白色 XL', rating: 2, content: 'Product looks different from photos. Size runs small. Disappointed.', reply: '', handleStatus: '处理中', csName: '', createTime: '2026-04-19 09:15:22' },
-  { id: 'S004', orderNo: 'ORD-20260418-0321', source: 'Shopify', buyer: 'Emily W.', productTitle: 'Nomad 防水背包 黑色 小号', rating: 1, content: 'Received damaged product. Packaging was also torn. Very unhappy with this purchase.', reply: '', handleStatus: '待处理', csName: '', createTime: '2026-04-18 21:45:08' },
-  { id: 'S005', orderNo: 'ORD-20260418-0654', source: 'TikTok Shop', buyer: 'David L.', productTitle: 'AeroDry 运动短裤 蓝色 M码', rating: 5, content: 'Perfect fit! Great material. Very comfortable for running.', reply: 'Thank you for your purchase!', handleStatus: '已处理', csName: '王五', createTime: '2026-04-18 14:30:45' },
-  { id: 'S006', orderNo: 'ORD-20260417-0987', source: 'Amazon', buyer: 'Lisa K.', productTitle: 'UrbanTrail 登山靴 黑色 42码', rating: 3, content: 'Product is okay but price is higher than competitors. Good quality though.', reply: '', handleStatus: '待处理', csName: '', createTime: '2026-04-17 11:20:30' }
-]);
+const items = ref<SatisfactionItem[]>([]);
 
 const negativeReasons = ref([
   { reason: '物流问题', count: 18, percent: 42.9, color: '#F56C6C' },
@@ -255,9 +249,14 @@ const statusTag = (status: string) => {
   return map[status] || '';
 };
 
-const loadData = () => {
+const loadData = async () => {
   loading.value = true;
-  setTimeout(() => { loading.value = false; }, 300);
+  try {
+    const res = await api.getSatisfactions();
+    items.value = res.items ?? [];
+  } finally {
+    loading.value = false;
+  }
 };
 
 const resetFilters = () => {

+ 1 - 0
frontend/src/views/crm/ServicePerformanceView.vue

@@ -146,6 +146,7 @@
 <script setup lang="ts">
 import { ref, computed, onMounted } from 'vue';
 import { ElMessage } from 'element-plus';
+import { ArrowUp, ArrowDown } from '@element-plus/icons-vue';
 import type { ServicePerformance } from '@/types/page';
 
 const loading = ref(false);

+ 9 - 9
frontend/src/views/crm/TicketView.vue

@@ -200,6 +200,7 @@
 <script setup lang="ts">
 import { computed, onMounted, reactive, ref } from 'vue';
 import { ElMessage, ElMessageBox } from 'element-plus';
+import { api } from '@/api/services';
 
 interface TicketItem {
   ticketNo: string;
@@ -216,13 +217,7 @@ interface TicketItem {
   logs: { time: string; operator: string; action: string; type: string }[];
 }
 
-const items = ref<TicketItem[]>([
-  { ticketNo: 'TK-20260420-001', title: '订单发货延迟咨询', type: '咨询', priority: '中', status: '待处理', creator: 'Olivia Zhang', handler: '-', relatedOrder: 'OMS-20260419-0012', content: '您好,我有一笔订单已经等待发货3天了,请问是什么原因导致的延迟?订单号:OMS-20260419-0012', createdAt: '2026-04-20 09:30:00', updateTime: '2026-04-20 09:30:00', logs: [] },
-  { ticketNo: 'TK-20260420-002', title: '商品破损投诉', type: '投诉', priority: '高', status: '处理中', creator: 'Noah Smith', handler: '张丽', relatedOrder: 'OMS-20260418-0008', content: '收到的行李箱角部有凹陷,包装也有破损,请求退货退款处理', createdAt: '2026-04-20 08:15:00', updateTime: '2026-04-20 10:20:00', logs: [{ time: '2026-04-20 10:20:00', operator: '张丽', action: '已联系物流公司核实,等待回复', type: 'primary' }] },
-  { ticketNo: 'TK-20260419-003', title: '退换货申请', type: '售后', priority: '紧急', status: '待确认', creator: 'Emma Wilson', handler: '李梅', relatedOrder: 'OMS-20260418-0015', content: '背包拉链损坏,无法正常使用,申请换货', createdAt: '2026-04-19 16:45:00', updateTime: '2026-04-20 11:30:00', logs: [{ time: '2026-04-19 17:00:00', operator: '李梅', action: '已审核通过,等待用户寄回商品', type: 'success' }, { time: '2026-04-20 11:30:00', operator: '系统', action: '用户已填写退货物流单号', type: 'info' }] },
-  { ticketNo: 'TK-20260419-004', title: '发票开具咨询', type: '咨询', priority: '低', status: '已完结', creator: 'Liam Chen', handler: '张丽', relatedOrder: 'OMS-20260419-0022', content: '请问如何申请开具增值税发票?', createdAt: '2026-04-19 14:20:00', updateTime: '2026-04-19 15:00:00', logs: [{ time: '2026-04-19 14:30:00', operator: '张丽', action: '已发送发票申请指引', type: 'primary' }, { time: '2026-04-19 15:00:00', operator: '系统', action: '用户确认问题已解决,工单完结', type: 'success' }] },
-  { ticketNo: 'TK-20260418-005', title: '账户异常问题', type: '其他', priority: '中', status: '待处理', creator: 'Sophie Brown', handler: '-', relatedOrder: '', content: '无法登录账户,提示密码错误但我已经确认密码正确', createdAt: '2026-04-18 11:30:00', updateTime: '2026-04-18 11:30:00', logs: [] }
-]);
+const items = ref<TicketItem[]>([]);
 
 const loading = ref(false);
 const createVisible = ref(false);
@@ -267,9 +262,14 @@ const statusTag = (status: string) => {
   return map[status] || '';
 };
 
-const loadData = () => {
+const loadData = async () => {
   loading.value = true;
-  setTimeout(() => { loading.value = false; }, 300);
+  try {
+    const res = await api.getTickets();
+    items.value = res.items ?? [];
+  } finally {
+    loading.value = false;
+  }
 };
 
 const resetFilters = () => {

+ 38 - 33
frontend/src/views/dashboard/ReportDashboardView.vue

@@ -1,8 +1,7 @@
 <template>
   <div class="app-page">
-    <section class="glass-card page-hero">
+    <section class="page-hero">
       <div class="page-hero__meta">
-        <span class="page-hero__eyebrow">Executive Snapshot</span>
         <h1>跨境业务一屏掌控</h1>
         <p>围绕 GMV、异常订单、库存预警和发货时效做统一洞察,适合作为每日经营例会的第一屏。</p>
       </div>
@@ -17,7 +16,7 @@
       <article
         v-for="stat in overview?.stats"
         :key="stat.label"
-        class="glass-card stat-card"
+        class="stat-card"
         :class="{ 'stat-card--clickable': isClickableStat(stat.label) }"
         @click="navigateFromStat(stat.label)"
       >
@@ -29,19 +28,23 @@
 
     <!-- 趋势图表 -->
     <section class="page-grid page-grid--two">
-      <article class="glass-card section-card">
-        <h3 style="margin:0 0 16px">近 7 日 GMV 趋势</h3>
-        <v-chart :option="gmvOption" autoresize style="height:280px" />
+      <article class="section-card">
+        <div class="section-card__title">
+          <h3>近 7 日 GMV 趋势</h3>
+        </div>
+        <v-chart :option="gmvOption" autoresize style="height:260px" />
       </article>
-      <article class="glass-card section-card">
-        <h3 style="margin:0 0 16px">近 7 日订单量趋势</h3>
-        <v-chart :option="orderOption" autoresize style="height:280px" />
+      <article class="section-card">
+        <div class="section-card__title">
+          <h3>近 7 日订单量趋势</h3>
+        </div>
+        <v-chart :option="orderOption" autoresize style="height:260px" />
       </article>
     </section>
 
     <!-- 预警 + 流程建议 -->
     <section class="page-grid page-grid--two">
-      <article class="glass-card section-card">
+      <article class="section-card">
         <div class="section-card__title">
           <div>
             <h3>今日重点预警</h3>
@@ -51,12 +54,12 @@
         <el-timeline>
           <el-timeline-item v-for="alert in overview?.alerts" :key="alert.title" :type="timelineType(alert.level)">
             <strong>{{ alert.title }}</strong>
-            <div class="muted" style="margin-top: 6px">{{ alert.summary }}</div>
+            <div class="muted" style="margin-top: 4px; font-size: 13px">{{ alert.summary }}</div>
           </el-timeline-item>
         </el-timeline>
       </article>
 
-      <article class="glass-card section-card">
+      <article class="section-card">
         <div class="section-card__title">
           <div>
             <h3>流程落地建议</h3>
@@ -68,19 +71,19 @@
         </ul>
 
         <!-- 快捷导航 -->
-        <div style="margin-top:24px;border-top:1px solid var(--el-border-color-lighter);padding-top:16px">
-          <h4 style="margin-bottom:12px">快捷导航</h4>
+        <div style="margin-top:20px; padding-top:16px; border-top: 1px solid var(--cb-border)">
+          <h4 style="margin: 0 0 12px; font-size: 14px; font-weight: 600">快捷导航</h4>
           <div class="quick-nav">
-            <el-button type="warning" @click="$router.push('/inventory/overview')">
+            <el-button type="warning" plain @click="$router.push('/inventory/overview')">
               低库存商品 → 库存总览
             </el-button>
-            <el-button type="danger" @click="$router.push('/order/list')">
+            <el-button type="danger" plain @click="$router.push('/order/list')">
               异常订单 → 订单列表
             </el-button>
-            <el-button @click="$router.push('/shipping/work')">
+            <el-button plain @click="$router.push('/shipping/work')">
               待发货 → 发货作业
             </el-button>
-            <el-button @click="$router.push('/report/center')">
+            <el-button plain @click="$router.push('/report/center')">
               查看报表 → 报表中心
             </el-button>
           </div>
@@ -109,35 +112,38 @@ const days = ['4/14', '4/15', '4/16', '4/17', '4/18', '4/19', '4/20'];
 const gmvOption = ref({
   tooltip: { trigger: 'axis' as const },
   grid: { left: 60, right: 20, top: 20, bottom: 30 },
-  xAxis: { type: 'category' as const, data: days },
-  yAxis: { type: 'value' as const, axisLabel: { formatter: '${value}' } },
+  xAxis: { type: 'category' as const, data: days, axisLine: { lineStyle: { color: '#E2E8F0' } }, axisLabel: { color: '#64748B' } },
+  yAxis: { type: 'value' as const, axisLabel: { formatter: '${value}', color: '#64748B' }, splitLine: { lineStyle: { color: '#F1F5F9' } } },
   series: [{
     name: 'GMV',
     type: 'line',
     smooth: true,
     data: [3280, 4120, 3860, 4520, 3980, 4750, 4230],
-    areaStyle: { opacity: 0.15 },
-    itemStyle: { color: '#409EFF' }
+    areaStyle: { opacity: 0.15, color: { type: 'linear', x: 0, y: 0, x2: 0, y2: 1, colorStops: [{ offset: 0, color: 'rgba(14, 165, 233, 0.3)' }, { offset: 1, color: 'rgba(14, 165, 233, 0)' }] } },
+    itemStyle: { color: '#0EA5E9' },
+    lineStyle: { width: 2, color: '#0EA5E9' },
+    symbol: 'circle',
+    symbolSize: 6
   }]
 });
 
 const orderOption = ref({
   tooltip: { trigger: 'axis' as const },
   grid: { left: 50, right: 20, top: 20, bottom: 30 },
-  xAxis: { type: 'category' as const, data: days },
-  yAxis: { type: 'value' as const },
+  xAxis: { type: 'category' as const, data: days, axisLine: { lineStyle: { color: '#E2E8F0' } }, axisLabel: { color: '#64748B' } },
+  yAxis: { type: 'value' as const, axisLabel: { color: '#64748B' }, splitLine: { lineStyle: { color: '#F1F5F9' } } },
   series: [
     {
       name: '总订单',
       type: 'bar',
       data: [42, 55, 48, 63, 52, 68, 58],
-      itemStyle: { color: '#409EFF', borderRadius: [4, 4, 0, 0] }
+      itemStyle: { color: '#0EA5E9', borderRadius: [4, 4, 0, 0] }
     },
     {
       name: '异常订单',
       type: 'bar',
       data: [3, 5, 2, 6, 4, 8, 5],
-      itemStyle: { color: '#E6A23C', borderRadius: [4, 4, 0, 0] }
+      itemStyle: { color: '#F59E0B', borderRadius: [4, 4, 0, 0] }
     }
   ]
 });
@@ -175,21 +181,20 @@ onMounted(async () => {
 <style scoped>
 .stat-card--clickable {
   cursor: pointer;
-  transition: transform 0.15s ease, box-shadow 0.15s ease;
+  transition: border-color 0.15s ease;
 }
 
 .stat-card--clickable:hover {
-  transform: translateY(-2px);
-  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+  border-color: var(--cb-primary);
 }
 
-.trend-up { color: var(--el-color-success); }
-.trend-down { color: var(--el-color-danger); }
-.muted { color: var(--cb-text-soft); font-size: 13px; }
+.trend-up { color: var(--cb-success); }
+.trend-down { color: var(--cb-danger); }
+.muted { color: var(--cb-text-soft); }
 
 .quick-nav {
   display: flex;
   flex-wrap: wrap;
-  gap: 10px;
+  gap: 8px;
 }
 </style>

+ 10 - 24
frontend/src/views/finance/InvoiceView.vue

@@ -173,29 +173,10 @@
 <script setup lang="ts">
 import { computed, onMounted, ref } from 'vue';
 import { ElMessage, ElMessageBox } from 'element-plus';
+import { api } from '@/api/services';
+import type { InvoiceItem } from '@/types/page';
 
-interface InvoiceItem {
-  id: string;
-  invoiceNo: string;
-  invoiceType: string;
-  buyerName: string;
-  sellerName: string;
-  amount: string;
-  taxRate: string;
-  taxAmount: string;
-  invoiceDate: string;
-  status: string;
-  buyerTaxNo: string;
-  remark: string;
-}
-
-const items = ref<InvoiceItem[]>([
-  { id: 'INV001', invoiceNo: 'INV-20260420-001', invoiceType: '专用', buyerName: '深圳某某科技有限公司', sellerName: '广州某某贸易公司', amount: '50000.00', taxRate: '13%', taxAmount: '6500.00', invoiceDate: '2026-04-20', status: '已开票', buyerTaxNo: '91440300MA5XXXXXX', remark: '' },
-  { id: 'INV002', invoiceNo: 'INV-20260419-002', invoiceType: '普通', buyerName: '上海某某电子商务公司', sellerName: '广州某某贸易公司', amount: '28000.00', taxRate: '6%', taxAmount: '1680.00', invoiceDate: '2026-04-19', status: '已开票', buyerTaxNo: '91310000MA1FXXXXX', remark: '' },
-  { id: 'INV003', invoiceNo: 'INV-20260418-003', invoiceType: '电子', buyerName: '北京某某跨境电商公司', sellerName: '广州某某贸易公司', amount: '156000.00', taxRate: '13%', taxAmount: '20280.00', invoiceDate: '2026-04-18', status: '待开票', buyerTaxNo: '91110000MA01YYYYY', remark: '等待采购单确认' },
-  { id: 'INV004', invoiceNo: 'INV-20260417-004', invoiceType: '专用', buyerName: '杭州某某供应链公司', sellerName: '广州某某贸易公司', amount: '89000.00', taxRate: '13%', taxAmount: '11570.00', invoiceDate: '2026-04-17', status: '已红冲', buyerTaxNo: '91330100MA2BZZZZZ', remark: '重复开票已红冲' },
-  { id: 'INV005', invoiceNo: 'INV-20260416-005', invoiceType: '普通', buyerName: '成都某某贸易公司', sellerName: '广州某某贸易公司', amount: '42000.00', taxRate: '6%', taxAmount: '2520.00', invoiceDate: '2026-04-16', status: '已开票', buyerTaxNo: '91510100MA6CAAAAA', remark: '' }
-]);
+const items = ref<InvoiceItem[]>([]);
 
 const loading = ref(false);
 const selected = ref<InvoiceItem[]>([]);
@@ -238,9 +219,14 @@ const statusTag = (status: string) => {
   return map[status] || '';
 };
 
-const loadData = () => {
+const loadData = async () => {
   loading.value = true;
-  setTimeout(() => { loading.value = false; }, 300);
+  try {
+    const res = await api.getInvoices();
+    items.value = res.items ?? [];
+  } finally {
+    loading.value = false;
+  }
 };
 
 const resetFilters = () => {

+ 10 - 29
frontend/src/views/finance/PaymentView.vue

@@ -127,33 +127,9 @@
 import { computed, onMounted, ref } from 'vue';
 import { ElMessage, ElMessageBox } from 'element-plus';
 import { api } from '@/api/services';
-import type { OrderItem } from '@/types/page';
-
-interface PaymentItem {
-  id: string;
-  paymentNo: string;
-  channelOrderNo: string;
-  channel: string;
-  shopName: string;
-  currency: string;
-  amount: string;
-  payMethod: string;
-  payTime: string;
-  reconcileStatus: string;
-  transactionNo: string;
-  fee: string;
-  remark: string;
-  orderId?: string;
-}
-
-const items = ref<PaymentItem[]>([
-  { id: 'P001', paymentNo: 'PAY-20260420-001', channelOrderNo: 'SHOP-US-20260420-0123', channel: 'Shopify', shopName: 'NomadPeak US', currency: 'USD', amount: '128.00', payMethod: 'Credit Card', payTime: '2026-04-20 10:30:15', reconcileStatus: '待对账', transactionNo: 'txn_3N4K8M9L2', fee: '3.84', remark: '', orderId: 'order-1' },
-  { id: 'P002', paymentNo: 'PAY-20260420-002', channelOrderNo: 'SHOP-JP-20260420-0456', channel: 'Shopify', shopName: 'UrbanTrail JP', currency: 'JPY', amount: '8900', payMethod: 'PayPay', payTime: '2026-04-20 09:45:22', reconcileStatus: '已确认', transactionNo: 'txn_4P5Q9R8S3', fee: '267', remark: '', orderId: 'order-2' },
-  { id: 'P003', paymentNo: 'PAY-20260419-003', channelOrderNo: 'TT-UK-20260419-0789', channel: 'TikTok Shop', shopName: 'AeroDry UK', currency: 'GBP', amount: '65.00', payMethod: 'TikTok Pay', payTime: '2026-04-19 21:09:33', reconcileStatus: '已确认', transactionNo: 'txn_5T6U7V9W4', fee: '1.95', remark: '', orderId: 'order-3' },
-  { id: 'P004', paymentNo: 'PAY-20260419-004', channelOrderNo: 'SHOP-US-20260419-0321', channel: 'Shopify', shopName: 'NomadPeak US', currency: 'USD', amount: '259.00', payMethod: 'PayPal', payTime: '2026-04-19 18:20:45', reconcileStatus: '已确认', transactionNo: 'txn_6X7Y8Z9A5', fee: '7.77', remark: '', orderId: 'order-4' },
-  { id: 'P005', paymentNo: 'PAY-20260419-005', channelOrderNo: 'TT-UK-20260419-0654', channel: 'TikTok Shop', shopName: 'AeroDry UK', currency: 'GBP', amount: '156.00', payMethod: 'Credit Card', payTime: '2026-04-19 16:35:08', reconcileStatus: '有差异', transactionNo: 'txn_7B8C9D0E6', fee: '4.68', remark: '金额差异 $2.00', orderId: 'order-5' },
-  { id: 'P006', paymentNo: 'PAY-20260418-006', channelOrderNo: 'SHOP-JP-20260418-0987', channel: 'Shopify', shopName: 'UrbanTrail JP', currency: 'JPY', amount: '12500', payMethod: 'Credit Card', payTime: '2026-04-18 14:12:30', reconcileStatus: '已确认', transactionNo: 'txn_8F9G0H1I7', fee: '375', remark: '', orderId: 'order-6' }
-]);
+import type { OrderItem, PaymentItem } from '@/types/page';
+
+const items = ref<PaymentItem[]>([]);
 
 const loading = ref(false);
 const selected = ref<PaymentItem[]>([]);
@@ -184,9 +160,14 @@ const reconcileTag = (status: string) => {
   return map[status] || '';
 };
 
-const loadData = () => {
+const loadData = async () => {
   loading.value = true;
-  setTimeout(() => { loading.value = false; }, 300);
+  try {
+    const res = await api.getPayments();
+    items.value = res.items ?? [];
+  } finally {
+    loading.value = false;
+  }
 };
 
 const resetFilters = () => {

+ 12 - 26
frontend/src/views/finance/RefundView.vue

@@ -124,31 +124,9 @@
 import { computed, onMounted, ref } from 'vue';
 import { ElMessage, ElMessageBox } from 'element-plus';
 import { api } from '@/api/services';
-import type { OrderItem } from '@/types/page';
-
-interface RefundItem {
-  refundNo: string;
-  orderNo: string;
-  orderId?: string;
-  channel: string;
-  currency: string;
-  amount: string;
-  refundMethod: string;
-  reason: string;
-  status: string;
-  applyTime: string;
-  refundTime: string;
-  channelRefundNo: string;
-  remark: string;
-}
-
-const items = ref<RefundItem[]>([
-  { refundNo: 'RF-20260420-001', orderNo: 'OMS-20260419-0012', orderId: 'order-1', channel: 'Shopify', currency: 'USD', amount: '128.00', refundMethod: '原路退回', reason: '商品破损', status: '待退款', applyTime: '2026-04-20 10:30:00', refundTime: '', channelRefundNo: '', remark: '' },
-  { refundNo: 'RF-20260419-002', orderNo: 'OMS-20260419-0017', orderId: 'order-2', channel: 'TikTok Shop', currency: 'GBP', amount: '65.00', refundMethod: '原路退回', reason: '与描述不符', status: '已退款', applyTime: '2026-04-19 21:15:00', refundTime: '2026-04-19 22:30:00', channelRefundNo: 'TT-RF-987654', remark: '' },
-  { refundNo: 'RF-20260419-003', orderNo: 'OMS-20260418-0008', orderId: 'order-3', channel: 'Shopify', currency: 'USD', amount: '129.00', refundMethod: '原路退回', reason: '错发商品', status: '退款失败', applyTime: '2026-04-18 15:20:00', refundTime: '', channelRefundNo: '', remark: '渠道返回错误码E102' },
-  { refundNo: 'RF-20260418-004', orderNo: 'OMS-20260418-0015', orderId: 'order-4', channel: 'TikTok Shop', currency: 'GBP', amount: '90.00', refundMethod: '原路退回', reason: '超时未发货', status: '已退款', applyTime: '2026-04-18 12:00:00', refundTime: '2026-04-18 14:00:00', channelRefundNo: 'TT-RF-987653', remark: '' },
-  { refundNo: 'RF-20260417-005', orderNo: 'OMS-20260417-0025', orderId: 'order-5', channel: 'Shopify', currency: 'USD', amount: '89.00', refundMethod: '原路退回', reason: '买家取消', status: '已退款', applyTime: '2026-04-17 09:30:00', refundTime: '2026-04-17 10:45:00', channelRefundNo: 'SHOP-RF-456789', remark: '' }
-]);
+import type { OrderItem, RefundItem } from '@/types/page';
+
+const items = ref<RefundItem[]>([]);
 
 const loading = ref(false);
 const selected = ref<RefundItem[]>([]);
@@ -173,7 +151,15 @@ const statusTag = (status: string) => {
   return map[status] || '';
 };
 
-const loadData = () => { loading.value = true; setTimeout(() => { loading.value = false; }, 300); };
+const loadData = async () => {
+  loading.value = true;
+  try {
+    const res = await api.getRefunds();
+    items.value = res.items ?? [];
+  } finally {
+    loading.value = false;
+  }
+};
 
 const resetFilters = () => { filters.value = { refundNo: '', orderNo: '', channel: '', status: '' }; };
 

+ 14 - 27
frontend/src/views/finance/SupplierSettlementView.vue

@@ -178,35 +178,14 @@
 <script setup lang="ts">
 import { computed, onMounted, reactive, ref } from 'vue';
 import { ElMessage, ElMessageBox } from 'element-plus';
+import { api } from '@/api/services';
+import type { SupplierSettlementItem } from '@/types/page';
 
-interface SettlementItem {
-  settlementNo: string;
-  supplier: string;
-  period: string;
-  poCount: number;
-  currency: string;
-  totalAmount: string;
-  paidAmount: string;
-  status: string;
-  dueDate: string;
-  createdAt: string;
-}
+type SettlementItem = SupplierSettlementItem;
 
-const items = ref<SettlementItem[]>([
-  { settlementNo: 'ST-202604-001', supplier: '深圳鼎力供应商', period: '2026-04-01 至 2026-04-15', poCount: 3, currency: 'USD', totalAmount: '25,800', paidAmount: '25,800', status: '已付款', dueDate: '2026-04-30', createdAt: '2026-04-16 10:00:00' },
-  { settlementNo: 'ST-202604-002', supplier: '义乌恒达皮具', period: '2026-04-01 至 2026-04-15', poCount: 2, currency: 'USD', totalAmount: '18,500', paidAmount: '10,000', status: '待付款', dueDate: '2026-04-28', createdAt: '2026-04-16 14:30:00' },
-  { settlementNo: 'ST-202604-003', supplier: '深圳鼎力供应商', period: '2026-04-16 至 2026-04-20', poCount: 2, currency: 'USD', totalAmount: '15,200', paidAmount: '0', status: '待确认', dueDate: '2026-05-05', createdAt: '2026-04-20 09:00:00' },
-  { settlementNo: 'ST-202603-005', supplier: '美国西部仓', period: '2026-03-01 至 2026-03-31', poCount: 4, currency: 'USD', totalAmount: '32,000', paidAmount: '32,000', status: '已付款', dueDate: '2026-04-15', createdAt: '2026-04-01 11:00:00' }
-]);
+const items = ref<SettlementItem[]>([]);
 
-const poList = ref([{ poNo: 'PO-20260415-001', poAmount: '12,500', status: '已完结' }, { poNo: 'PO-20260410-002', poAmount: '8,300', status: '已完结' }]);
-
-const suppliers = ['深圳鼎力供应商', '义乌恒达皮具', '美国西部仓', '云途物流'];
-const loading = ref(false);
-const detailVisible = ref(false);
-const paymentVisible = ref(false);
-const createVisible = ref(false);
-const detailItem = ref<SettlementItem | null>(null);
+const poList = ref<{ poNo: string; poAmount: string; status: string }[]>([]);
 
 const filters = ref({ settlementNo: '', supplier: '', status: '' });
 
@@ -227,7 +206,15 @@ const statusTag = (status: string) => {
   return map[status] || '';
 };
 
-const loadData = () => { loading.value = true; setTimeout(() => { loading.value = false; }, 300); };
+const loadData = async () => {
+  loading.value = true;
+  try {
+    const res = await api.getSupplierSettlements();
+    items.value = res.items ?? [];
+  } finally {
+    loading.value = false;
+  }
+};
 const resetFilters = () => { filters.value = { settlementNo: '', supplier: '', status: '' }; };
 
 const openDetail = (row: SettlementItem) => { detailItem.value = row; detailVisible.value = true; };

+ 1 - 1
frontend/src/views/inventory/InventoryOverviewView.vue

@@ -227,7 +227,7 @@ const loadData = async () => {
   loading.value = true;
   try {
     const res = await api.getInventories();
-    items.value = res.items;
+    items.value = res.items ?? [];
   } finally {
     loading.value = false;
   }

+ 1 - 1
frontend/src/views/inventory/ShippingWorkView.vue

@@ -231,7 +231,7 @@ const loadData = async () => {
   loading.value = true;
   try {
     const res = await api.getShippingOrders();
-    items.value = res.items;
+    items.value = res.items ?? [];
   } finally {
     loading.value = false;
   }

+ 15 - 39
frontend/src/views/logistics/LogisticsProviderView.vue

@@ -187,46 +187,15 @@
 <script setup lang="ts">
 import { computed, onMounted, reactive, ref } from 'vue';
 import { ElMessage, ElMessageBox } from 'element-plus';
+import { api } from '@/api/services';
+import type { LogisticsProviderItem, ShippingTemplateItem } from '@/types/page';
 
-interface LogisticsProvider {
-  id: string;
-  name: string;
-  code: string;
-  channels: string[];
-  settlementType: string;
-  avgDays: number;
-  trackingUrl: string;
-  contact: string;
-  phone: string;
-  status: string;
-  updatedAt: string;
-  remark?: string;
-}
+type LogisticsProvider = LogisticsProviderItem;
+type ShippingTemplate = ShippingTemplateItem;
 
-interface ShippingTemplate {
-  id: string;
-  name: string;
-  calcType: string;
-  firstWeight: number;
-  firstPrice: number;
-  continueWeight: number;
-  continuePrice: number;
-  regions: string;
-  status: string;
-}
+const items = ref<LogisticsProvider[]>([]);
 
-const items = ref<LogisticsProvider[]>([
-  { id: 'LP001', name: 'DHL国际快递', code: 'DHL', channels: ['DHL'], settlementType: '按重量', avgDays: 5, trackingUrl: 'https://www.dhl.com/track?tracking-id={trackingNo}', contact: 'John', phone: '+1-800-225-5345', status: '启用', updatedAt: '2026-04-15 10:30' },
-  { id: 'LP002', name: 'FedEx联邦快递', code: 'FedEx', channels: ['FedEx'], settlementType: '按重量', avgDays: 6, trackingUrl: 'https://www.fedex.com/fedextrack/?trknbr={trackingNo}', contact: 'Mary', phone: '+1-800-463-3339', status: '启用', updatedAt: '2026-04-14 14:20' },
-  { id: 'LP003', name: '顺丰速运', code: 'SF', channels: ['顺丰'], settlementType: '按重量', avgDays: 3, trackingUrl: 'https://www.sf-express.com/sf-service-owf/web/en/querytools/track/{trackingNo}', contact: '王强', phone: '95338', status: '启用', updatedAt: '2026-04-10 09:00' },
-  { id: 'LP004', name: '云途物流', code: 'YTO', channels: ['云途'], settlementType: '按重量', avgDays: 8, trackingUrl: 'https://www.yuntrack.com/track?trackingNo={trackingNo}', contact: 'Tom', phone: '400-800-6060', status: '停用', updatedAt: '2026-03-28 16:45' }
-]);
-
-const templates = ref<ShippingTemplate[]>([
-  { id: 'T001', name: 'DHL-美国专线', calcType: '按重量', firstWeight: 0.5, firstPrice: 120, continueWeight: 0.5, continuePrice: 35, regions: '美国,加拿大', status: '启用' },
-  { id: 'T002', name: 'DHL-欧洲专线', calcType: '按重量', firstWeight: 0.5, firstPrice: 150, continueWeight: 0.5, continuePrice: 45, regions: '英国,德国,法国,意大利', status: '启用' },
-  { id: 'T003', name: 'FedEx-全球优先', calcType: '按重量', firstWeight: 1, firstPrice: 200, continueWeight: 1, continuePrice: 50, regions: '全球', status: '启用' }
-]);
+const templates = ref<ShippingTemplate[]>([]);
 
 const loading = ref(false);
 const dialogVisible = ref(false);
@@ -274,9 +243,16 @@ const filteredItems = computed(() => {
   });
 });
 
-const loadData = () => {
+const loadData = async () => {
   loading.value = true;
-  setTimeout(() => { loading.value = false; }, 300);
+  try {
+    const res = await api.getLogisticsProviders();
+    items.value = res.items ?? [];
+    const tplRes = await api.getShippingTemplates();
+    templates.value = tplRes.items ?? [];
+  } finally {
+    loading.value = false;
+  }
 };
 
 const resetFilters = () => {

+ 9 - 9
frontend/src/views/logistics/ShippingTemplateView.vue

@@ -138,6 +138,7 @@
 <script setup lang="ts">
 import { computed, onMounted, reactive, ref } from 'vue';
 import { ElMessage, ElMessageBox } from 'element-plus';
+import { api } from '@/api/services';
 
 interface ShippingTemplate {
   id: string;
@@ -154,13 +155,7 @@ interface ShippingTemplate {
   updatedAt: string;
 }
 
-const items = ref<ShippingTemplate[]>([
-  { id: 'ST001', name: 'DHL-美国标准', logisticsProvider: 'DHL', calcType: '按重量', firstWeight: 0.5, firstPrice: 120, continueWeight: 0.5, continuePrice: 35, remoteSurcharge: 25, regions: ['美国', '加拿大'], status: '启用', updatedAt: '2026-04-15 10:30' },
-  { id: 'ST002', name: 'DHL-欧洲优先', logisticsProvider: 'DHL', calcType: '按重量', firstWeight: 0.5, firstPrice: 150, continueWeight: 0.5, continuePrice: 45, remoteSurcharge: 30, regions: ['英国', '德国', '法国', '意大利', '西班牙'], status: '启用', updatedAt: '2026-04-14 14:20' },
-  { id: 'ST003', name: 'FedEx-全球特快', logisticsProvider: 'FedEx', calcType: '按重量', firstWeight: 1, firstPrice: 200, continueWeight: 1, continuePrice: 50, remoteSurcharge: 40, regions: ['美国', '加拿大', '英国', '德国', '澳大利亚', '日本'], status: '启用', updatedAt: '2026-04-10 09:00' },
-  { id: 'ST004', name: '顺丰-中国大陆', logisticsProvider: '顺丰', calcType: '按重量', firstWeight: 1, firstPrice: 15, continueWeight: 1, continuePrice: 5, remoteSurcharge: 0, regions: ['中国大陆'], status: '启用', updatedAt: '2026-04-08 16:45' },
-  { id: 'ST005', name: '云途-欧洲专线', logisticsProvider: '云途', calcType: '按重量', firstWeight: 0.5, firstPrice: 85, continueWeight: 0.5, continuePrice: 25, remoteSurcharge: 20, regions: ['英国', '德国', '法国', '意大利'], status: '停用', updatedAt: '2026-03-28 11:30' }
-]);
+const items = ref<ShippingTemplate[]>([]);
 
 const loading = ref(false);
 const dialogVisible = ref(false);
@@ -190,9 +185,14 @@ const filteredItems = computed(() => {
   });
 });
 
-const loadData = () => {
+const loadData = async () => {
   loading.value = true;
-  setTimeout(() => { loading.value = false; }, 300);
+  try {
+    const res = await api.getShippingTemplates();
+    items.value = res.items ?? [];
+  } finally {
+    loading.value = false;
+  }
 };
 
 const resetFilters = () => {

+ 1 - 1
frontend/src/views/order/OrderAfterSaleView.vue

@@ -222,7 +222,7 @@ const loadData = async () => {
   loading.value = true;
   try {
     const res = await api.getAfterSales();
-    items.value = res.items;
+    items.value = res.items ?? [];
   } finally {
     loading.value = false;
   }

+ 1 - 0
frontend/src/views/order/OrderDetailView.vue

@@ -537,6 +537,7 @@
 import { onMounted, reactive, ref, computed } from 'vue';
 import { useRoute } from 'vue-router';
 import { ElMessage, ElMessageBox } from 'element-plus';
+import { ArrowDown } from '@element-plus/icons-vue';
 import { api } from '@/api/services';
 import type { OrderItem, OrderProductItem } from '@/types/page';
 

+ 536 - 439
frontend/src/views/order/OrderListView.vue

@@ -1,261 +1,186 @@
 <template>
   <div class="app-page">
-    <section class="glass-card section-card filter-section">
-      <div class="filter-main">
-        <el-form :model="filters" inline class="filter-form">
-          <el-form-item label="订单号/渠道单号" class="filter-item">
-            <el-input v-model="filters.orderNo" placeholder="搜索订单号" clearable style="width:200px" @keyup.enter="loadData" />
-          </el-form-item>
-          <el-form-item label="买家" class="filter-item">
-            <el-input v-model="filters.buyer" placeholder="姓名/手机/邮箱" clearable style="width:160px" @keyup.enter="loadData" />
-          </el-form-item>
-          <el-form-item label="渠道" class="filter-item">
-            <el-select v-model="filters.channel" placeholder="全部渠道" clearable style="width:140px">
-              <el-option label="Shopify US" value="Shopify US" />
-              <el-option label="Shopify JP" value="Shopify JP" />
-              <el-option label="TikTok UK" value="TikTok UK" />
-              <el-option label="Amazon US" value="Amazon US" />
-              <el-option label="eBay" value="eBay" />
-            </el-select>
-          </el-form-item>
-          <el-form-item label="仓库" class="filter-item">
-            <el-select v-model="filters.warehouse" placeholder="全部仓库" clearable style="width:140px">
-              <el-option label="深圳南山仓" value="深圳南山仓" />
-              <el-option label="义乌商贸仓" value="义乌商贸仓" />
-              <el-option label="洛杉矶海外仓" value="洛杉矶海外仓" />
-            </el-select>
-          </el-form-item>
-          <el-form-item label="订单状态" class="filter-item">
-            <el-select v-model="filters.orderStatus" placeholder="全部状态" clearable style="width:130px">
-              <el-option label="待支付" value="created" />
-              <el-option label="已支付" value="paid" />
-              <el-option label="已分配" value="allocated" />
-              <el-option label="已发货" value="shipped" />
-              <el-option label="已完成" value="completed" />
-              <el-option label="已取消" value="cancelled" />
-            </el-select>
-          </el-form-item>
-          <el-form-item class="filter-item">
-            <el-button type="primary" @click="loadData">查询</el-button>
-            <el-button @click="resetFilters">重置</el-button>
-            <el-button type="text" @click="showAdvanced = !showAdvanced">
-              高级筛选 <el-icon><ArrowDown v-if="!showAdvanced" /><ArrowUp v-else /></el-icon>
-            </el-button>
-          </el-form-item>
-        </el-form>
+    <section class="page-hero">
+      <div class="page-hero__meta">
+        <h1>订单列表</h1>
+        <p>管理所有订单,支持筛选、批量操作和快速处理</p>
       </div>
-
-      <div v-if="showAdvanced" class="filter-advanced">
-        <el-form :model="filters" inline class="filter-form">
-          <el-form-item label="日期范围" class="filter-item">
-            <el-date-picker v-model="filters.dateRange" type="daterange" range-separator="至" start-placeholder="开始" end-placeholder="结束" style="width:240px" value-format="YYYY-MM-DD" />
-          </el-form-item>
-          <el-form-item label="支付状态" class="filter-item">
-            <el-select v-model="filters.paymentStatus" placeholder="全部" clearable style="width:120px">
-              <el-option label="待支付" value="pending" />
-              <el-option label="已支付" value="paid" />
-              <el-option label="已退款" value="refunded" />
-            </el-select>
-          </el-form-item>
-          <el-form-item label="发货状态" class="filter-item">
-            <el-select v-model="filters.shippingStatus" placeholder="全部" clearable style="width:120px">
-              <el-option label="未发货" value="unshipped" />
-              <el-option label="处理中" value="processing" />
-              <el-option label="已发货" value="shipped" />
-              <el-option label="已签收" value="delivered" />
-            </el-select>
-          </el-form-item>
-          <el-form-item label="买家国家" class="filter-item">
-            <el-select v-model="filters.buyerCountry" placeholder="全部国家" clearable style="width:130px">
-              <el-option label="美国 US" value="US" />
-              <el-option label="英国 UK" value="UK" />
-              <el-option label="日本 JP" value="JP" />
-              <el-option label="德国 DE" value="DE" />
-              <el-option label="法国 FR" value="FR" />
-            </el-select>
-          </el-form-item>
-          <el-form-item label="异常标签" class="filter-item">
-            <el-select v-model="filters.exceptionTag" placeholder="全部" clearable style="width:130px">
-              <el-option label="正常" value="正常" />
-              <el-option label="地址需复核" value="地址需复核" />
-              <el-option label="高频下单" value="高频下单" />
-              <el-option label="疑似刷单" value="疑似刷单" />
-            </el-select>
-          </el-form-item>
-        </el-form>
+      <div class="chip-list">
+        <el-statistic title="今日订单" :value="todayCount" />
+        <el-statistic title="待处理" :value="pendingCount" />
       </div>
     </section>
 
-    <section class="glass-card section-card toolbar-section">
-      <div class="table-toolbar">
-        <div class="toolbar-left">
-          <el-button type="primary" @click="openCreateOrder">创建订单</el-button>
-          <el-button type="success" @click="refreshSync">刷新同步</el-button>
-          <el-button @click="doExport">导出订单</el-button>
-          <el-dropdown v-if="selected.length" @command="handleBatchCommand">
-            <el-button type="warning">批量操作 ({{ selected.length }}) <el-icon><ArrowDown /></el-icon></el-button>
-            <template #dropdown>
-              <el-dropdown-menu>
-                <el-dropdown-item command="mark">批量标记</el-dropdown-item>
-                <el-dropdown-item command="assign">批量分配</el-dropdown-item>
-                <el-dropdown-item command="cancel" divided>批量取消</el-dropdown-item>
-              </el-dropdown-menu>
+    <section class="section-card">
+      <div class="filter-bar">
+        <div class="filter-bar__main">
+          <el-input
+            v-model="searchKeyword"
+            placeholder="订单号 / 渠道单号 / 买家"
+            clearable
+            style="width: 280px"
+            @keyup.enter="loadData"
+          >
+            <template #prefix>
+              <el-icon><Search /></el-icon>
             </template>
-          </el-dropdown>
-        </div>
-        <div class="toolbar-right">
-          <el-select v-model="savedView" placeholder="快捷视图" clearable style="width:140px">
-            <el-option label="待处理订单" value="pending" />
-            <el-option label="异常订单" value="exception" />
-            <el-option label="今日订单" value="today" />
-            <el-option label="待发货" value="unshipped" />
+          </el-input>
+          <el-select v-model="filters.orderStatus" placeholder="订单状态" clearable style="width: 120px">
+            <el-option label="待支付" value="created" />
+            <el-option label="已支付" value="paid" />
+            <el-option label="已分配" value="allocated" />
+            <el-option label="已发货" value="shipped" />
+            <el-option label="已完成" value="completed" />
+            <el-option label="已取消" value="cancelled" />
           </el-select>
-          <el-button-group>
-            <el-button :type="viewMode === 'table' ? 'primary' : ''" @click="viewMode = 'table'">
-              <el-icon><Grid /></el-icon>
-            </el-button>
-            <el-button :type="viewMode === 'card' ? 'primary' : ''" @click="viewMode = 'card'">
-              <el-icon><List /></el-icon>
-            </el-button>
-          </el-button-group>
+          <el-select v-model="filters.channel" placeholder="渠道" clearable style="width: 140px">
+            <el-option label="Shopify US" value="Shopify US" />
+            <el-option label="Shopify JP" value="Shopify JP" />
+            <el-option label="TikTok UK" value="TikTok UK" />
+            <el-option label="Amazon US" value="Amazon US" />
+          </el-select>
+          <el-select v-model="filters.shippingStatus" placeholder="发货状态" clearable style="width: 120px">
+            <el-option label="未发货" value="unshipped" />
+            <el-option label="处理中" value="processing" />
+            <el-option label="已发货" value="shipped" />
+            <el-option label="已签收" value="delivered" />
+          </el-select>
+          <el-button type="primary" @click="loadData">查询</el-button>
+          <el-button @click="resetFilters">重置</el-button>
+        </div>
+        <div class="filter-bar__actions">
+          <el-button text @click="showFilterDrawer = true">
+            <el-icon><Filter /></el-icon>
+            高级筛选
+          </el-button>
         </div>
       </div>
     </section>
 
-    <section class="glass-card section-card table-section">
-      <template v-if="viewMode === 'table'">
-        <el-table :data="filteredItems" stripe v-loading="loading" @selection-change="onSelection" :row-class-name="rowClass" class="order-table">
-          <el-table-column type="selection" width="50" />
-          <el-table-column prop="orderNo" label="订单信息" min-width="200">
-            <template #default="{ row }">
-              <div class="order-info">
-                <div class="order-no">{{ row.orderNo }}</div>
-                <div class="order-meta">
-                  <el-tag v-if="row.channelOrderNo" size="small" type="info">{{ row.channelOrderNo }}</el-tag>
-                  <el-tag v-if="row.priority === 'urgent'" type="danger" size="small">加急</el-tag>
-                  <el-tag v-for="tag in (row.orderTags || []).slice(0, 1)" :key="tag" type="warning" size="small">{{ tag }}</el-tag>
-                </div>
-              </div>
-            </template>
-          </el-table-column>
-          <el-table-column prop="channel" label="渠道" width="110">
-            <template #default="{ row }">
-              <el-tag size="small">{{ row.channel }}</el-tag>
-            </template>
-          </el-table-column>
-          <el-table-column prop="buyer" label="买家" min-width="160">
-            <template #default="{ row }">
-              <div class="buyer-cell">
-                <div class="buyer-name">
-                  {{ row.buyer }}
-                  <el-tag v-if="row.buyerLevel" size="small" :type="buyerLevelTag(row.buyerLevel)">{{ row.buyerLevel }}</el-tag>
-                </div>
-                <div class="buyer-contact">{{ row.buyerPhone || row.buyerEmail }}</div>
-              </div>
-            </template>
-          </el-table-column>
-          <el-table-column prop="receiverCity" label="收货地" width="120">
-            <template #default="{ row }">
-              <span class="location">{{ getCountryFlag(row.buyerCountry) }} {{ row.receiverCity }}</span>
-            </template>
-          </el-table-column>
-          <el-table-column prop="itemCount" label="件数" width="70" align="center">
-            <template #default="{ row }">
-              <el-popover placement="bottom" :width="280" trigger="hover">
-                <template #reference>
-                  <span class="item-count">{{ row.itemCount }}件 <el-icon><QuestionFilled /></el-icon></span>
-                </template>
-                <div class="items-popover">
-                  <div v-for="item in row.items.slice(0, 4)" :key="item.sku" class="item-row">
-                    <span class="item-name">{{ item.productTitle }}</span>
-                    <span class="item-qty">x{{ item.qty }}</span>
-                  </div>
-                  <div v-if="row.items.length > 4" class="item-more">还有 {{ row.items.length - 4 }} 件</div>
-                </div>
-              </el-popover>
-            </template>
-          </el-table-column>
-          <el-table-column prop="amount" label="金额" width="120" align="right">
-            <template #default="{ row }">
-              <div class="amount-cell">
-                <span class="amount">{{ row.currency }} {{ row.actualPaid || row.amount }}</span>
-                <span v-if="row.discountAmount && parseFloat(row.discountAmount) > 0" class="discount">-{{ row.discountAmount }}</span>
-              </div>
-            </template>
-          </el-table-column>
-          <el-table-column prop="orderStatus" label="订单状态" width="100" align="center">
-            <template #default="{ row }">
-              <el-tag :type="statusType(row.orderStatus)" size="small">{{ statusLabel(row.orderStatus) }}</el-tag>
-            </template>
-          </el-table-column>
-          <el-table-column prop="shippingStatus" label="发货状态" width="90" align="center">
-            <template #default="{ row }">
-              <el-tag :type="shippingStatusType(row.shippingStatus)" size="small">{{ shippingStatusLabel(row.shippingStatus) }}</el-tag>
-            </template>
-          </el-table-column>
-          <el-table-column prop="warehouse" label="仓库" width="100">
-            <template #default="{ row }">
-              <span v-if="row.warehouse" class="warehouse">{{ row.warehouse }}</span>
-              <el-tag v-else type="info" size="small">待分配</el-tag>
-            </template>
-          </el-table-column>
-          <el-table-column prop="createdAt" label="下单时间" width="150" />
-          <el-table-column label="操作" width="160" fixed="right">
-            <template #default="{ row }">
-              <el-button link type="primary" @click="$router.push(`/order/detail?id=${row.id}`)">详情</el-button>
-              <el-dropdown trigger="click" @command="(cmd: string) => handleRowCommand(cmd, row)">
-                <el-button link type="primary">更多</el-button>
-                <template #dropdown>
-                  <el-dropdown-menu>
-                    <el-dropdown-item command="track">物流追踪</el-dropdown-item>
-                    <el-dropdown-item command="assign">分配处理人</el-dropdown-item>
-                    <el-dropdown-item command="remark">添加备注</el-dropdown-item>
-                    <el-dropdown-item command="print">打印面单</el-dropdown-item>
-                    <el-dropdown-item command="cancel" divided>取消订单</el-dropdown-item>
-                  </el-dropdown-menu>
-                </template>
-              </el-dropdown>
-            </template>
-          </el-table-column>
-        </el-table>
-      </template>
+    <section class="section-card">
+      <div class="batch-bar" v-if="selected.length">
+        <span class="batch-bar__info">已选择 {{ selected.length }} 项</span>
+        <el-button size="small" @click="batchAssign">批量分配</el-button>
+        <el-button size="small" @click="batchTag">批量标记</el-button>
+        <el-button size="small" type="danger" @click="batchCancel">批量取消</el-button>
+        <el-button size="small" text @click="selected = []">清除选择</el-button>
+      </div>
+
+      <div class="toolbar">
+        <div class="toolbar__left">
+          <el-button type="primary" @click="openCreateOrder">
+            <el-icon><Plus /></el-icon>
+            创建订单
+          </el-button>
+          <el-button @click="refreshSync">
+            <el-icon><Refresh /></el-icon>
+            刷新同步
+          </el-button>
+          <el-button @click="doExport">
+            <el-icon><Download /></el-icon>
+            导出
+          </el-button>
+        </div>
+        <div class="toolbar__right">
+          <el-radio-group v-model="viewMode" size="small">
+            <el-radio-button value="table">
+              <el-icon><Grid /></el-icon>
+            </el-radio-button>
+            <el-radio-button value="card">
+              <el-icon><List /></el-icon>
+            </el-radio-button>
+          </el-radio-group>
+        </div>
+      </div>
 
-      <template v-else>
-        <div class="order-cards-grid">
-          <div v-for="row in filteredItems" :key="row.id" class="order-card" :class="{ 'exception': row.exceptionTag !== '正常' }">
-            <div class="card-header">
-              <div class="card-title">
-                <span class="card-order-no">{{ row.orderNo }}</span>
-                <el-tag size="small">{{ row.channel }}</el-tag>
+      <el-table
+        :data="filteredItems"
+        stripe
+        v-loading="loading"
+        @selection-change="onSelection"
+        :row-class-name="rowClass"
+        class="order-table"
+        @row-click="handleRowClick"
+      >
+        <el-table-column type="selection" width="45" />
+        <el-table-column prop="orderNo" label="订单信息" min-width="180">
+          <template #default="{ row }">
+            <div class="order-info">
+              <div class="order-no">{{ row.orderNo }}</div>
+              <div class="order-meta">
+                <el-tag v-if="row.channelOrderNo" size="small" type="info">{{ row.channelOrderNo }}</el-tag>
+                <el-tag v-if="row.priority === 'urgent'" type="danger" size="small">加急</el-tag>
               </div>
-              <el-tag :type="statusType(row.orderStatus)" size="small">{{ statusLabel(row.orderStatus) }}</el-tag>
             </div>
-            <div class="card-buyer">
+          </template>
+        </el-table-column>
+        <el-table-column prop="channel" label="渠道" width="120">
+          <template #default="{ row }">
+            <el-tag size="small">{{ row.channel }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="buyer" label="买家信息" min-width="150">
+          <template #default="{ row }">
+            <div class="buyer-info">
               <span class="buyer-name">{{ row.buyer }}</span>
-              <span class="buyer-country">{{ getCountryFlag(row.buyerCountry) }} {{ row.buyerCountry }}</span>
-            </div>
-            <div class="card-location">
-              <el-icon><Location /></el-icon>
-              {{ row.receiverCity }}, {{ row.receiverCountry }}
+              <span class="buyer-contact">{{ row.buyerCountry }} {{ row.receiverCity }}</span>
             </div>
-            <div class="card-items">
-              <span>商品 x{{ row.itemCount }}</span>
-              <span class="card-amount">{{ row.currency }} {{ row.actualPaid || row.amount }}</span>
-            </div>
-            <div class="card-footer">
-              <span class="card-time">{{ row.createdAt }}</span>
-              <el-button type="primary" size="small" @click="$router.push(`/order/detail?id=${row.id}`)">查看详情</el-button>
+          </template>
+        </el-table-column>
+        <el-table-column prop="itemCount" label="件数" width="70" align="center">
+          <template #default="{ row }">
+            <el-popover placement="bottom" :width="260" trigger="hover">
+              <template #reference>
+                <span class="item-count">{{ row.itemCount }}</span>
+              </template>
+              <div class="items-popover">
+                <div v-for="item in row.items.slice(0, 5)" :key="item.sku" class="item-row">
+                  <span class="item-name">{{ item.productTitle }}</span>
+                  <span class="item-qty">x{{ item.qty }}</span>
+                </div>
+              </div>
+            </el-popover>
+          </template>
+        </el-table-column>
+        <el-table-column prop="actualPaid" label="金额" width="100" align="right">
+          <template #default="{ row }">
+            <span class="amount">{{ row.currency }} {{ row.actualPaid || row.amount }}</span>
+          </template>
+        </el-table-column>
+        <el-table-column prop="orderStatus" label="状态" width="100" align="center">
+          <template #default="{ row }">
+            <el-tag :type="statusType(row.orderStatus)" size="small" effect="light">
+              {{ statusLabel(row.orderStatus) }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="warehouse" label="仓库" width="100">
+          <template #default="{ row }">
+            <span v-if="row.warehouse" class="warehouse-text">{{ row.warehouse }}</span>
+            <el-tag v-else type="info" size="small">待分配</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="createdAt" label="时间" width="140" />
+        <el-table-column label="操作" width="240" fixed="right">
+          <template #default="{ row }">
+            <div class="action-buttons">
+              <el-button size="small" type="primary" link @click.stop="viewDetail(row)">详情</el-button>
+              <el-button size="small" link @click.stop="handleRowCommand('assign', row)">分配</el-button>
+              <el-button size="small" link @click.stop="handleRowCommand('remark', row)">备注</el-button>
+              <el-button size="small" link @click.stop="handleRowCommand('print', row)">面单</el-button>
+              <el-button size="small" link @click.stop="handleRowCommand('track', row)">追踪</el-button>
+              <el-button size="small" link type="danger" @click.stop="handleRowCommand('cancel', row)">取消</el-button>
             </div>
-          </div>
-        </div>
-      </template>
+          </template>
+        </el-table-column>
+      </el-table>
 
-      <div class="pagination-wrapper" v-if="total > 0">
+      <div class="pagination-wrapper">
         <el-pagination
           v-model:current-page="page"
           v-model:page-size="pageSize"
           :total="total"
-          :page-sizes="[10, 20, 50]"
+          :page-sizes="[10, 20, 50, 100]"
           layout="total, sizes, prev, pager, next"
           @size-change="loadData"
           @current-change="loadData"
@@ -263,10 +188,76 @@
       </div>
     </section>
 
+    <el-drawer v-model="showFilterDrawer" title="高级筛选" size="400px">
+      <el-form :model="filters" label-position="top">
+        <el-form-item label="日期范围">
+          <el-date-picker
+            v-model="filters.dateRange"
+            type="daterange"
+            range-separator="至"
+            start-placeholder="开始"
+            end-placeholder="结束"
+            style="width: 100%"
+            value-format="YYYY-MM-DD"
+          />
+        </el-form-item>
+        <el-form-item label="仓库">
+          <el-select v-model="filters.warehouse" placeholder="全部仓库" clearable style="width: 100%">
+            <el-option label="深圳南山仓" value="深圳南山仓" />
+            <el-option label="义乌商贸仓" value="义乌商贸仓" />
+            <el-option label="洛杉矶海外仓" value="洛杉矶海外仓" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="支付状态">
+          <el-select v-model="filters.paymentStatus" placeholder="全部" clearable style="width: 100%">
+            <el-option label="待支付" value="pending" />
+            <el-option label="已支付" value="paid" />
+            <el-option label="已退款" value="refunded" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="买家国家">
+          <el-select v-model="filters.buyerCountry" placeholder="全部国家" clearable style="width: 100%">
+            <el-option label="美国 US" value="US" />
+            <el-option label="英国 UK" value="UK" />
+            <el-option label="日本 JP" value="JP" />
+            <el-option label="德国 DE" value="DE" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="异常标签">
+          <el-select v-model="filters.exceptionTag" placeholder="全部" clearable style="width: 100%">
+            <el-option label="正常" value="正常" />
+            <el-option label="地址需复核" value="地址需复核" />
+            <el-option label="高频下单" value="高频下单" />
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <template #footer>
+        <el-button @click="resetFilters">重置</el-button>
+        <el-button type="primary" @click="loadData">应用筛选</el-button>
+      </template>
+    </el-drawer>
+
+    <el-drawer v-model="trackDrawer" title="物流追踪" size="420px">
+      <template v-if="currentOrder">
+        <div class="track-header">
+          <el-tag type="success">已发货</el-tag>
+          <span class="track-carrier">{{ currentOrder.carrier }}</span>
+          <span class="track-no">{{ currentOrder.trackingNo }}</span>
+        </div>
+        <el-timeline class="track-timeline">
+          <el-timeline-item timestamp="2024-01-18 14:30" type="success">商品已签收</el-timeline-item>
+          <el-timeline-item timestamp="2024-01-17 09:20">到达广州转运中心</el-timeline-item>
+          <el-timeline-item timestamp="2024-01-16 18:45">已从深圳南山仓发出</el-timeline-item>
+          <el-timeline-item timestamp="2024-01-16 16:00" type="primary">快递员已取件</el-timeline-item>
+          <el-timeline-item timestamp="2024-01-15 10:30">卖家正在打包</el-timeline-item>
+        </el-timeline>
+      </template>
+    </el-drawer>
+
     <el-dialog v-model="assignDialog" title="分配处理人" width="420px">
       <el-form label-position="top">
         <el-form-item label="选择处理人">
-          <el-select v-model="assignPerson" placeholder="请选择处理人" style="width:100%">
+          <el-select v-model="assignPerson" placeholder="请选择处理人" style="width: 100%">
             <el-option label="运营组 A / 陈欣" value="运营组 A / 陈欣" />
             <el-option label="运营组 B / 王磊" value="运营组 B / 王磊" />
             <el-option label="客服组 / 张丽" value="客服组 / 张丽" />
@@ -283,28 +274,10 @@
       </template>
     </el-dialog>
 
-    <el-dialog v-model="tagDialog" title="添加标签" width="420px">
-      <el-form label-position="top">
-        <el-form-item label="选择标签">
-          <el-select v-model="selectedTags" multiple placeholder="选择标签" style="width:100%">
-            <el-option label="加急" value="加急" />
-            <el-option label="VIP客户" value="VIP客户" />
-            <el-option label="预售订单" value="预售订单" />
-            <el-option label="定制商品" value="定制商品" />
-            <el-option label="偏远地区" value="偏远地区" />
-          </el-select>
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <el-button @click="tagDialog = false">取消</el-button>
-        <el-button type="primary" @click="confirmTag">确认</el-button>
-      </template>
-    </el-dialog>
-
     <el-dialog v-model="createDialog" title="创建订单" width="500px">
       <el-form :model="createForm" label-width="90px">
         <el-form-item label="渠道" required>
-          <el-select v-model="createForm.channel" placeholder="选择渠道" style="width:100%">
+          <el-select v-model="createForm.channel" placeholder="选择渠道" style="width: 100%">
             <el-option label="Shopify US" value="Shopify US" />
             <el-option label="Shopify JP" value="Shopify JP" />
             <el-option label="TikTok UK" value="TikTok UK" />
@@ -328,73 +301,70 @@
         <el-button type="primary" @click="confirmCreateOrder">确认创建</el-button>
       </template>
     </el-dialog>
-
-    <el-drawer v-model="trackDrawer" title="物流追踪" size="400px">
-      <template v-if="currentOrder">
-        <div class="track-header">
-          <el-tag type="success">已发货</el-tag>
-          <span>{{ currentOrder.carrier }}</span>
-          <span class="track-no">{{ currentOrder.trackingNo }}</span>
-        </div>
-        <el-timeline class="track-timeline">
-          <el-timeline-item timestamp="2024-01-18 14:30" type="success">商品已签收</el-timeline-item>
-          <el-timeline-item timestamp="2024-01-17 09:20">到达广州转运中心</el-timeline-item>
-          <el-timeline-item timestamp="2024-01-16 18:45">已从深圳南山仓发出</el-timeline-item>
-          <el-timeline-item timestamp="2024-01-16 16:00" type="primary">快递员已取件</el-timeline-item>
-          <el-timeline-item timestamp="2024-01-15 10:30">卖家正在打包</el-timeline-item>
-        </el-timeline>
-      </template>
-    </el-drawer>
   </div>
 </template>
 
 <script setup lang="ts">
 import { computed, onMounted, ref } from 'vue';
+import { useRouter } from 'vue-router';
 import { ElMessage, ElMessageBox } from 'element-plus';
+import {
+  Search, Filter, Plus, Refresh, Download, Grid, List
+} from '@element-plus/icons-vue';
 import { api } from '@/api/services';
 import type { OrderItem, OrderProductItem } from '@/types/page';
 
+const router = useRouter();
+
 const items = ref<OrderItem[]>([]);
 const selected = ref<OrderItem[]>([]);
-const savedView = ref('');
 const viewMode = ref<'table' | 'card'>('table');
 const loading = ref(false);
 const page = ref(1);
 const pageSize = ref(20);
-const total = computed(() => filteredItems.value.length);
-const showAdvanced = ref(false);
+const searchKeyword = ref('');
+const showFilterDrawer = ref(false);
 
 const filters = ref({
-  orderNo: '', buyer: '', channel: '', warehouse: '',
-  orderStatus: '', paymentStatus: '', shippingStatus: '',
-  exceptionTag: '', buyerCountry: '', dateRange: [] as string[]
+  orderNo: '',
+  buyer: '',
+  channel: '',
+  warehouse: '',
+  orderStatus: '',
+  paymentStatus: '',
+  shippingStatus: '',
+  exceptionTag: '',
+  buyerCountry: '',
+  dateRange: [] as string[]
 });
 
-const countryFlags: Record<string, string> = {
-  'US': '🇺🇸', 'UK': '🇬🇧', 'JP': '🇯🇵', 'DE': '🇩🇪', 'FR': '🇫🇷',
-  'CA': '🇨🇦', 'AU': '🇦🇺', 'IT': '🇮🇹', 'ES': '🇪🇸', 'KR': '🇰🇷', 'CN': '🇨🇳'
-};
-const getCountryFlag = (c: string) => countryFlags[c] || '🌍';
-
-const filteredItems = computed(() => items.value.filter(item => {
-  if (filters.value.orderNo && !item.orderNo.includes(filters.value.orderNo) && !(item.channelOrderNo || '').includes(filters.value.orderNo)) return false;
-  if (filters.value.buyer && !item.buyer.toLowerCase().includes(filters.value.buyer.toLowerCase()) && !(item.buyerPhone || '').includes(filters.value.buyer)) return false;
-  if (filters.value.channel && item.channel !== filters.value.channel) return false;
-  if (filters.value.warehouse && item.warehouse !== filters.value.warehouse) return false;
-  if (filters.value.orderStatus && item.orderStatus !== filters.value.orderStatus) return false;
-  if (filters.value.paymentStatus && item.paymentStatus !== filters.value.paymentStatus) return false;
-  if (filters.value.shippingStatus && item.shippingStatus !== filters.value.shippingStatus) return false;
-  if (filters.value.exceptionTag && item.exceptionTag !== filters.value.exceptionTag) return false;
-  if (filters.value.buyerCountry && item.buyerCountry !== filters.value.buyerCountry) return false;
-  return true;
-}));
+const todayCount = computed(() => items.value.filter(i => i.createdAt?.includes('2026-04-21')).length);
+const pendingCount = computed(() => items.value.filter(i => ['created', 'paid'].includes(i.orderStatus || '')).length);
+const total = computed(() => filteredItems.value.length);
+
+const filteredItems = computed(() => {
+  return items.value.filter(item => {
+    if (searchKeyword.value) {
+      const kw = searchKeyword.value.toLowerCase();
+      if (!item.orderNo?.toLowerCase().includes(kw) &&
+          !(item.channelOrderNo || '').toLowerCase().includes(kw) &&
+          !(item.buyer || '').toLowerCase().includes(kw)) {
+        return false;
+      }
+    }
+    if (filters.value.channel && item.channel !== filters.value.channel) return false;
+    if (filters.value.warehouse && item.warehouse !== filters.value.warehouse) return false;
+    if (filters.value.orderStatus && item.orderStatus !== filters.value.orderStatus) return false;
+    if (filters.value.paymentStatus && item.paymentStatus !== filters.value.paymentStatus) return false;
+    if (filters.value.shippingStatus && item.shippingStatus !== filters.value.shippingStatus) return false;
+    if (filters.value.exceptionTag && item.exceptionTag !== filters.value.exceptionTag) return false;
+    if (filters.value.buyerCountry && item.buyerCountry !== filters.value.buyerCountry) return false;
+    return true;
+  });
+});
 
 const statusType = (s: string) => ({ created: 'info', paid: '', allocated: 'warning', shipped: 'success', delivered: 'success', completed: 'success', cancelled: 'danger', refunded: 'danger' }[s] || '');
 const statusLabel = (s: string) => ({ created: '待支付', paid: '已支付', allocated: '已分配', shipped: '已发货', delivered: '已签收', completed: '已完成', cancelled: '已取消', refunded: '已退款' }[s] || s);
-const paymentStatusType = (s: string) => ({ '待支付': 'warning', '已支付': 'success', '已退款': 'danger', pending: 'warning', paid: 'success', refunded: 'danger' }[s] || '');
-const shippingStatusType = (s: string) => ({ unshipped: 'info', processing: 'warning', shipped: '', intransit: 'primary', delivered: 'success' }[s] || '');
-const shippingStatusLabel = (s: string) => ({ unshipped: '未发货', processing: '处理中', shipped: '已发货', intransit: '运输中', delivered: '已签收' }[s] || s);
-const buyerLevelTag = (l: string) => ({ 'VIP': 'danger', '黄金': 'warning', '普通': 'info' }[l] || 'info');
 
 const rowClass = ({ row }: { row: OrderItem }) => row.exceptionTag !== '正常' ? 'exception-row' : '';
 
@@ -404,72 +374,38 @@ const loadData = async () => {
     const res = await api.getOrders();
     items.value = res.items || [];
   } catch (e) {
-    console.error('Failed to load orders:', e);
     items.value = [];
   } finally {
     loading.value = false;
   }
 };
 
-const generateMockOrders = (): OrderItem[] => {
-  const channels = ['Shopify US', 'Shopify JP', 'TikTok UK', 'Amazon US'];
-  const countries = ['US', 'UK', 'JP', 'DE', 'FR'];
-  const levels = ['VIP', '黄金', '普通'];
-  const statuses = ['created', 'paid', 'allocated', 'shipped', 'delivered'];
-  const warehouses = ['深圳南山仓', '义乌商贸仓', '洛杉矶海外仓'];
-  const handlers = ['陈欣', '王磊', '张丽', '李明'];
-
-  return Array.from({ length: 12 }, (_, i) => {
-    const orderNo = `OMS-202604${String(19 - Math.floor(i / 3)).padStart(2, '0')}-${String(1000 + i).padStart(4, '0')}`;
-    const country = countries[i % countries.length];
-    const prods: OrderProductItem[] = Array.from({ length: (i % 3) + 1 }, (_, j) => ({
-      id: `item-${i}-${j}`, productId: `p-${i}-${j}`, skuId: `s-${i}-${j}`,
-      sku: `SKU-${String.fromCharCode(65 + j)}${100 + i}-${country}`,
-      productTitle: ['TravelFlex Carry-On 20寸', 'Classic Leather Tote', 'Urban Backpack', 'Yoga Mat Pro'][j % 4],
-      productImage: '', categoryId: `cat-${j}`, categoryName: ['行李箱', '皮革包', '双肩包', '运动'][j % 4],
-      specs: [], barcode: `BC${Date.now()}${j}`, qty: (i % 3) + 1,
-      price: (Math.random() * 80 + 20).toFixed(2), costPrice: (Math.random() * 40 + 10).toFixed(2),
-      profit: (Math.random() * 20 + 5).toFixed(2), profitRate: Math.floor(Math.random() * 30 + 15),
-      subtotal: (Math.random() * 150 + 30).toFixed(2), weight: Math.random() * 2 + 0.5,
-      available: 50, locked: 5, inTransit: 20, reservedQty: 0, shippedQty: 0, returnedQty: 0, giftFlag: false
-    }));
-    const amount = prods.reduce((s, p) => s + parseFloat(p.subtotal), 0);
-    return {
-      id: `order-${i}`, orderNo,
-      channelOrderNo: channels[i % 4] === 'Shopify US' ? `CH${Date.now()}${i}` : '',
-      channel: channels[i % channels.length],
-      orderStatus: statuses[i % statuses.length],
-      shippingStatus: ['unshipped', 'processing', 'shipped'][i % 3],
-      paymentStatus: ['pending', 'paid', 'paid', 'paid'][i % 4],
-      exceptionTag: Math.random() > 0.85 ? ['地址需复核', '高频下单'][i % 2] : '正常',
-      priority: Math.random() > 0.9 ? 'urgent' : 'normal',
-      createdAt: `2026-04-${String(19 - Math.floor(i / 4)).padStart(2, '0')} ${String(10 + (i % 10)).padStart(2, '0')}:${String(Math.floor(Math.random() * 60)).padStart(2, '0')}:00`,
-      updatedAt: '', paidAt: '', shippedAt: '', deliveredAt: '',
-      buyerId: `buyer-${1000 + i}`, buyer: ['Olivia Zhang', 'Noah Smith', 'Liam Chen', 'Emma Wilson', 'Sophie Brown', 'James Wang', 'Lisa Johnson', 'David Lee'][i % 8],
-      buyerEmail: `buyer${i}@example.com`, buyerPhone: `+1 213-555-${String(1000 + i).padStart(4, '0')}`,
-      buyerCountry: country, buyerLevel: levels[i % levels.length], buyerTags: Math.random() > 0.7 ? ['高价值'] : [],
-      buyerRegisterTime: '', buyerOrderCount: 0, buyerTotalSpent: '',
-      receiverName: '', receiverPhone: '', receiverCountry: country, receiverState: '', receiverCity: ['Los Angeles', 'New York', 'Houston', 'Miami', 'Seattle'][i % 5],
-      receiverDistrict: '', receiverPostalCode: `${10000 + i * 100}`, receiverAddress: `${100 + i * 10} Main St`, receiverAddressLang: '', latitude: 0, longitude: 0,
-      transactionId: `TXN${Date.now()}${i}`, paymentMethod: ['Visa', 'MasterCard', 'PayPal'][i % 3], paymentTime: '',
-      currency: country === 'JP' ? 'JPY' : country === 'UK' ? 'GBP' : 'USD', exchangeRate: 1,
-      orderAmount: amount.toFixed(2), orderAmountCNY: (amount * 7.2).toFixed(2), taxAmount: (amount * 0.08).toFixed(2),
-      shippingFee: (Math.random() * 15 + 5).toFixed(2), discountAmount: Math.random() > 0.7 ? (amount * 0.1).toFixed(2) : '0.00',
-      couponCode: '', couponDiscount: '', actualPaid: (amount - (Math.random() > 0.7 ? amount * 0.1 : 0)).toFixed(2),
-      refundAmount: '0.00', refundStatus: '',
-      shippingMethod: '', carrier: ['顺丰', 'DHL', 'FedEx'][i % 3], trackingNo: '', trackingUrl: '',
-      warehouse: warehouses[i % warehouses.length], warehouseLocation: `A-${10 + i}-${String.fromCharCode(65 + (i % 5))}`,
-      estimatedDelivery: '', weight: 0, length: 0, width: 0, height: 0,
-      utmSource: '', utmMedium: '', utmCampaign: '', utmContent: '', utmTerm: '', referrerUrl: '', landingPage: '', ip: '', ipCountry: '', device: '', browser: '', os: '',
-      parentOrderId: '', childOrderIds: [], mergeOrderId: '', relatedOrderId: '', originalOrderId: '',
-      handler: handlers[i % handlers.length], handlerGroup: '', orderTags: [], internalRemark: '', buyerRemark: '',
-      itemCount: prods.reduce((s, p) => s + p.qty, 0), amount: amount.toFixed(2), items: prods, timeline: [], logs: []
-    };
-  });
+const resetFilters = () => {
+  searchKeyword.value = '';
+  filters.value = { orderNo: '', buyer: '', channel: '', warehouse: '', orderStatus: '', paymentStatus: '', shippingStatus: '', exceptionTag: '', buyerCountry: '', dateRange: [] };
+  showFilterDrawer.value = false;
 };
 
-const resetFilters = () => { filters.value = { orderNo: '', buyer: '', channel: '', warehouse: '', orderStatus: '', paymentStatus: '', shippingStatus: '', exceptionTag: '', buyerCountry: '', dateRange: [] }; };
 const onSelection = (rows: OrderItem[]) => { selected.value = rows; };
+
+const handleRowClick = (row: OrderItem) => {
+  viewDetail(row);
+};
+
+const viewDetail = (row: OrderItem) => {
+  router.push(`/order/detail?id=${row.id}`);
+};
+
+const quickAssign = (row: OrderItem) => {
+  currentOrder.value = row;
+  assignDialog.value = true;
+};
+
+const trackLogistics = (row: OrderItem) => {
+  currentOrder.value = row;
+  trackDrawer.value = true;
+};
+
 const refreshSync = async () => {
   try {
     const count = Math.floor(Math.random() * 3) + 1;
@@ -478,108 +414,269 @@ const refreshSync = async () => {
     }
     ElMessage.success(`同步完成:新增 ${count} 单`);
     loadData();
-  } catch (e) {
+  } catch {
     ElMessage.error('同步失败');
   }
 };
-const doExport = () => { ElMessage.success('导出已开始'); };
 
-const handleBatchCommand = (cmd: string) => {
-  if (!selected.value.length) { ElMessage.warning('请先选择订单'); return; }
-  if (cmd === 'mark') { tagDialog.value = true; }
-  else if (cmd === 'assign') { assignDialog.value = true; }
-  else if (cmd === 'cancel') { ElMessageBox.confirm(`确定取消 ${selected.value.length} 个订单?`, '提示', { type: 'warning' }).then(() => ElMessage.success('已取消')).catch(() => {}); }
-};
+const doExport = () => { ElMessage.success('导出已开始'); };
 
 const handleRowCommand = (cmd: string, row: OrderItem) => {
   if (cmd === 'track') { currentOrder.value = row; trackDrawer.value = true; }
-  else if (cmd === 'assign') { assignDialog.value = true; }
+  else if (cmd === 'assign') { quickAssign(row); }
   else if (cmd === 'remark') { ElMessage.info('备注功能'); }
   else if (cmd === 'print') { ElMessage.success(`正在生成 ${row.orderNo} 面单`); }
-  else if (cmd === 'cancel') { ElMessageBox.confirm(`确定取消订单 ${row.orderNo}?`, '提示', { type: 'warning' }).then(() => ElMessage.success('已取消')).catch(() => {}); }
+  else if (cmd === 'cancel') {
+    ElMessageBox.confirm(`确定取消订单 ${row.orderNo}?`, '提示', { type: 'warning' })
+      .then(() => ElMessage.success('已取消')).catch(() => {});
+  }
+};
+
+const batchAssign = () => { assignDialog.value = true; };
+const batchTag = () => { ElMessage.info('批量标记功能'); };
+const batchCancel = () => {
+  ElMessageBox.confirm(`确定取消 ${selected.value.length} 个订单?`, '提示', { type: 'warning' })
+    .then(() => { selected.value = []; ElMessage.success('已取消'); }).catch(() => {});
 };
 
 const assignDialog = ref(false);
 const assignPerson = ref('');
 const assignRemark = ref('');
-const tagDialog = ref(false);
-const selectedTags = ref<string[]>([]);
 const createDialog = ref(false);
 const createForm = ref({ channel: '', buyerName: '', phone: '', address: '', remark: '' });
 const trackDrawer = ref(false);
 const currentOrder = ref<OrderItem | null>(null);
 
-const confirmAssign = () => { ElMessage.success(`已分配给 ${assignPerson.value}`); assignDialog.value = false; };
-const confirmTag = () => { ElMessage.success(`已添加标签`); tagDialog.value = false; };
-const openCreateOrder = () => { createForm.value = { channel: '', buyerName: '', phone: '', address: '', remark: '' }; createDialog.value = true; };
-const confirmCreateOrder = () => { if (!createForm.value.channel || !createForm.value.buyerName) { ElMessage.warning('请填写必填项'); return; } ElMessage.success('订单创建成功'); createDialog.value = false; loadData(); };
+const confirmAssign = () => {
+  ElMessage.success(`已分配给 ${assignPerson.value}`);
+  assignDialog.value = false;
+  selected.value = [];
+};
+
+const openCreateOrder = () => {
+  createForm.value = { channel: '', buyerName: '', phone: '', address: '', remark: '' };
+  createDialog.value = true;
+};
+
+const confirmCreateOrder = () => {
+  if (!createForm.value.channel || !createForm.value.buyerName) {
+    ElMessage.warning('请填写必填项');
+    return;
+  }
+  ElMessage.success('订单创建成功');
+  createDialog.value = false;
+  loadData();
+};
 
 onMounted(loadData);
 </script>
 
-<style scoped>
-.filter-section { padding: 16px 20px; }
-.filter-main { }
-.filter-advanced { margin-top: 12px; padding-top: 12px; border-top: 1px dashed #eee; }
-.filter-form { display: flex; flex-wrap: wrap; gap: 12px; }
-.filter-item { margin-bottom: 0; }
-
-.toolbar-section { padding: 12px 20px; margin-top: 12px; }
-.table-toolbar { display: flex; justify-content: space-between; align-items: center; }
-.toolbar-left { display: flex; gap: 8px; }
-.toolbar-right { display: flex; gap: 10px; align-items: center; }
-
-.table-section { margin-top: 12px; padding: 0; }
-.table-section :deep(.el-table) { border-radius: 0; }
-.table-section :deep(.el-table__header th) { background: #fafafa; }
-
-.order-table :deep(.el-table th) { padding: 12px 8px; }
-.order-table :deep(.el-table td) { padding: 12px 8px; }
-
-.order-info { display: flex; flex-direction: column; gap: 4px; }
-.order-no { font-weight: 600; color: var(--cb-primary); font-size: 14px; }
-.order-meta { display: flex; gap: 4px; flex-wrap: wrap; }
-
-.buyer-cell { display: flex; flex-direction: column; gap: 2px; }
-.buyer-name { font-weight: 500; display: flex; align-items: center; gap: 6px; }
-.buyer-contact { font-size: 12px; color: #666; }
-
-.location { font-size: 13px; }
-.item-count { font-size: 13px; color: var(--cb-primary); cursor: pointer; display: flex; align-items: center; gap: 4px; }
-
-.amount-cell { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; }
-.amount { font-weight: 600; color: var(--cb-accent); font-size: 14px; }
-.discount { font-size: 12px; color: #67c23a; }
-
-.warehouse { font-size: 13px; }
-
-.items-popover { display: flex; flex-direction: column; gap: 6px; }
-.item-row { display: flex; justify-content: space-between; padding: 4px 0; border-bottom: 1px solid #f0f0f0; }
-.item-name { font-size: 13px; max-width: 180px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
-.item-qty { font-size: 12px; color: #999; }
-.item-more { font-size: 12px; color: var(--cb-primary); text-align: center; padding-top: 4px; }
-
-:deep(.exception-row) { background-color: rgba(230, 162, 60, 0.08) !important; }
-
-.order-cards-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 16px; padding: 16px; }
-.order-card { background: #fafafa; border-radius: 12px; padding: 16px; border: 1px solid #f0f0f0; transition: all 0.2s; }
-.order-card:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.08); }
-.order-card.exception { border-left: 3px solid #e6a23c; }
-.card-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 12px; }
-.card-title { display: flex; flex-direction: column; gap: 4px; }
-.card-order-no { font-weight: 600; color: var(--cb-primary); font-size: 14px; }
-.card-buyer { display: flex; justify-content: space-between; margin-bottom: 8px; }
-.buyer-name { font-weight: 500; }
-.buyer-country { font-size: 13px; color: #666; }
-.card-location { display: flex; align-items: center; gap: 6px; color: #666; font-size: 13px; margin-bottom: 12px; }
-.card-items { display: flex; justify-content: space-between; padding: 10px 0; border-top: 1px solid #eee; border-bottom: 1px solid #eee; margin-bottom: 12px; }
-.card-amount { font-weight: 600; color: var(--cb-accent); font-size: 15px; }
-.card-footer { display: flex; justify-content: space-between; align-items: center; }
-.card-time { font-size: 12px; color: #999; }
-
-.pagination-wrapper { display: flex; justify-content: flex-end; padding: 16px; }
-
-.track-header { display: flex; align-items: center; gap: 10px; padding: 12px; background: #f5f5f5; border-radius: 8px; margin-bottom: 20px; }
-.track-no { font-family: monospace; font-weight: 600; }
-.track-timeline { padding: 0 8px; }
-</style>
+<style scoped lang="scss">
+.page-hero {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 20px 24px;
+  background: var(--cb-panel);
+  border: 1px solid var(--cb-border);
+  border-radius: var(--el-border-radius-base);
+}
+
+.page-hero__meta h1 {
+  margin: 0 0 4px;
+  font-size: 20px;
+  font-weight: 600;
+}
+
+.page-hero__meta p {
+  margin: 0;
+  color: var(--cb-text-soft);
+  font-size: 14px;
+}
+
+.filter-bar {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  gap: 16px;
+}
+
+.filter-bar__main {
+  display: flex;
+  gap: 12px;
+  flex-wrap: wrap;
+  align-items: center;
+}
+
+.filter-bar__actions {
+  display: flex;
+  gap: 8px;
+}
+
+.batch-bar {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  padding: 12px 16px;
+  margin-bottom: 16px;
+  background: var(--cb-primary-soft);
+  border-radius: var(--el-border-radius-base);
+  border: 1px solid rgba(14, 165, 233, 0.2);
+}
+
+.batch-bar__info {
+  font-size: 14px;
+  font-weight: 500;
+  color: var(--cb-primary);
+  margin-right: 8px;
+}
+
+.toolbar {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 16px;
+}
+
+.toolbar__left {
+  display: flex;
+  gap: 8px;
+}
+
+.toolbar__right {
+  display: flex;
+  gap: 8px;
+}
+
+.order-table {
+  margin: 0 -20px;
+}
+
+.order-info {
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+}
+
+.order-no {
+  font-weight: 600;
+  color: var(--cb-primary);
+  font-size: 14px;
+  cursor: pointer;
+}
+
+.order-meta {
+  display: flex;
+  gap: 4px;
+}
+
+.buyer-info {
+  display: flex;
+  flex-direction: column;
+  gap: 2px;
+}
+
+.buyer-name {
+  font-weight: 500;
+  font-size: 14px;
+}
+
+.buyer-contact {
+  font-size: 12px;
+  color: var(--cb-text-soft);
+}
+
+.item-count {
+  color: var(--cb-primary);
+  cursor: pointer;
+  font-weight: 500;
+}
+
+.amount {
+  font-weight: 600;
+  color: var(--cb-text);
+}
+
+.warehouse-text {
+  font-size: 13px;
+}
+
+.items-popover {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+}
+
+.item-row {
+  display: flex;
+  justify-content: space-between;
+  padding: 4px 0;
+  border-bottom: 1px solid var(--cb-border);
+}
+
+.item-row:last-child {
+  border-bottom: none;
+}
+
+.item-name {
+  font-size: 13px;
+  max-width: 180px;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.item-qty {
+  font-size: 12px;
+  color: var(--cb-text-soft);
+  flex-shrink: 0;
+}
+
+.action-buttons {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 4px;
+}
+
+.action-buttons .el-button {
+  padding-left: 8px;
+  padding-right: 8px;
+}
+
+.pagination-wrapper {
+  display: flex;
+  justify-content: flex-end;
+  padding: 16px 0 0;
+}
+
+.track-header {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  padding: 16px;
+  background: var(--cb-bg-soft);
+  border-radius: var(--el-border-radius-base);
+  margin-bottom: 20px;
+}
+
+.track-carrier {
+  font-weight: 500;
+}
+
+.track-no {
+  font-family: monospace;
+  color: var(--cb-text-soft);
+}
+
+.track-timeline {
+  padding: 0 8px;
+}
+
+:deep(.exception-row) {
+  background-color: var(--cb-warning-soft) !important;
+}
+
+:deep(.el-table__row) {
+  cursor: pointer;
+}
+</style>

+ 2 - 2
frontend/src/views/product/ProductListView.vue

@@ -191,7 +191,7 @@ const loadData = async () => {
   loading.value = true;
   try {
     const res = await api.getProducts();
-    items.value = res.content;
+    items.value = res.content ?? [];
   } finally {
     loading.value = false;
   }
@@ -255,7 +255,7 @@ onMounted(loadData);
 
 <style scoped>
 .filter-form :deep(.el-form-item) { margin-bottom: 0; }
-.image-placeholder { width: 48px; height: 48px; border-radius: 8px; background: var(--cb-primary-soft); display: flex; align-items: center; justify-content: center; color: var(--cb-primary); font-size: 12px; font-weight: 700; }
+.image-placeholder { width: 48px; height: 48px; border-radius: 8px; background: var(--cb-bg-soft); display: flex; align-items: center; justify-content: center; color: var(--cb-text-soft); font-size: 12px; font-weight: 600; border: 1px solid var(--cb-border); }
 .text-danger { color: var(--cb-danger); font-weight: 600; }
 .text-warning { color: var(--cb-accent); }
 </style>

+ 1 - 1
frontend/src/views/product/ProductMappingView.vue

@@ -173,7 +173,7 @@ const loadData = async () => {
   loading.value = true;
   try {
     const res = await api.getChannelMappings();
-    items.value = res.content;
+    items.value = res.content ?? [];
   } finally {
     loading.value = false;
   }

+ 1 - 1
frontend/src/views/product/ProductPricingView.vue

@@ -180,7 +180,7 @@ const loadData = async () => {
   loading.value = true;
   try {
     const res = await api.getPricingRules();
-    items.value = res.items;
+    items.value = res.items ?? [];
   } finally {
     loading.value = false;
   }

+ 1 - 0
frontend/src/views/report/InventoryTurnoverView.vue

@@ -218,6 +218,7 @@
 <script setup lang="ts">
 import { onMounted, ref, computed, watch } from 'vue';
 import { ElMessage } from 'element-plus';
+import { ArrowUp, ArrowDown } from '@element-plus/icons-vue';
 import * as echarts from 'echarts';
 
 interface TurnoverItem {

+ 15 - 12
frontend/src/views/report/ReportCenterView.vue

@@ -265,9 +265,9 @@ const barOption = computed(() => {
   return {
     tooltip: { trigger: 'axis' as const },
     grid: { left: 80, right: 20, top: 20, bottom: 60 },
-    xAxis: { type: 'category' as const, data: metrics, axisLabel: { rotate: 30 } },
-    yAxis: { type: 'value' as const },
-    series: [{ type: 'bar', data: values, itemStyle: { color: '#409EFF', borderRadius: [4, 4, 0, 0] } }]
+    xAxis: { type: 'category' as const, data: metrics, axisLabel: { rotate: 30, color: '#94A3B8' }, axisLine: { lineStyle: { color: '#334155' } } },
+    yAxis: { type: 'value' as const, axisLabel: { color: '#94A3B8' }, splitLine: { lineStyle: { color: '#1E293B' } } },
+    series: [{ type: 'bar', data: values, itemStyle: { color: '#00D4FF', borderRadius: [4, 4, 0, 0] } }]
   };
 });
 
@@ -282,12 +282,15 @@ const lineOption = computed(() => {
   return {
     tooltip: { trigger: 'axis' as const },
     grid: { left: 60, right: 20, top: 20, bottom: 60 },
-    xAxis: { type: 'category' as const, data: metrics, axisLabel: { rotate: 30 } },
-    yAxis: { type: 'value' as const, axisLabel: { formatter: '{value}%' } },
+    xAxis: { type: 'category' as const, data: metrics, axisLabel: { rotate: 30, color: '#94A3B8' }, axisLine: { lineStyle: { color: '#334155' } } },
+    yAxis: { type: 'value' as const, axisLabel: { formatter: '{value}%', color: '#94A3B8' }, splitLine: { lineStyle: { color: '#1E293B' } } },
     series: [{
       type: 'line', smooth: true, data: momValues,
-      areaStyle: { opacity: 0.15 },
-      itemStyle: { color: '#67C23A' }
+      areaStyle: { opacity: 0.15, color: { type: 'linear', x: 0, y: 0, x2: 0, y2: 1, colorStops: [{ offset: 0, color: 'rgba(99, 102, 241, 0.3)' }, { offset: 1, color: 'rgba(99, 102, 241, 0)' }] } },
+      lineStyle: { width: 2, color: '#6366F1' },
+      itemStyle: { color: '#6366F1' },
+      symbol: 'circle',
+      symbolSize: 6
     }]
   };
 });
@@ -296,7 +299,7 @@ const loadData = async () => {
   loading.value = true;
   try {
     const res = await api.getReports();
-    reportList.value = res.items;
+    reportList.value = res.items ?? [];
   } finally {
     loading.value = false;
   }
@@ -310,7 +313,7 @@ const generateReport = async () => {
   loading.value = true;
   try {
     const res = await api.getReportData();
-    reportData.value = res.items;
+    reportData.value = res.items ?? [];
     ElMessage.success('报表数据已加载');
   } finally {
     loading.value = false;
@@ -322,7 +325,7 @@ const loadSavedReport = async (item: ReportItem) => {
   loading.value = true;
   try {
     const res = await api.getReportData();
-    reportData.value = res.items;
+    reportData.value = res.items ?? [];
     ElMessage.success(`已加载「${item.reportType}」`);
   } finally {
     loading.value = false;
@@ -371,6 +374,6 @@ onMounted(loadData);
 
 <style scoped>
 .filter-form :deep(.el-form-item) { margin-bottom: 0; }
-.trend-up { color: var(--el-color-success); font-weight: 500; }
-.trend-down { color: var(--el-color-danger); font-weight: 500; }
+.trend-up { color: var(--cb-success); font-weight: 500; }
+.trend-down { color: var(--cb-danger); font-weight: 500; }
 </style>

+ 2 - 2
frontend/src/views/supplier/PurchaseOrderView.vue

@@ -323,8 +323,8 @@ const loadData = async () => {
       api.getPurchaseOrders(),
       api.getSuppliers()
     ]);
-    items.value = poRes.items;
-    supplierOptions.value = supplierRes.items.map((s) => s.name);
+    items.value = poRes.items ?? [];
+    supplierOptions.value = (supplierRes.items ?? []).map((s: any) => s.name);
   } finally {
     loading.value = false;
   }

+ 1 - 1
frontend/src/views/supplier/SupplierListView.vue

@@ -234,7 +234,7 @@ const loadData = async () => {
   loading.value = true;
   try {
     const res = await api.getSuppliers();
-    items.value = res.items;
+    items.value = res.items ?? [];
   } finally {
     loading.value = false;
   }

+ 2 - 2
frontend/src/views/supplier/SupplyCapabilityView.vue

@@ -257,8 +257,8 @@ const loadData = async () => {
       api.getSupplyCapabilities(),
       api.getSuppliers()
     ] as const);
-    items.value = capRes.items;
-    supplierOptions.value = supplierRes.items.map((s) => s.name);
+    items.value = capRes.items ?? [];
+    supplierOptions.value = (supplierRes.items ?? []).map((s: any) => s.name);
   } finally {
     loading.value = false;
   }

+ 1 - 1
frontend/src/views/system/ApiKeyView.vue

@@ -209,7 +209,7 @@ const loadData = async () => {
   loading.value = true;
   try {
     const res = await api.getApiKeys();
-    items.value = res.items;
+    items.value = res.items ?? [];
   } finally {
     loading.value = false;
   }

+ 1 - 1
frontend/src/views/system/OperationLogView.vue

@@ -196,7 +196,7 @@ const loadData = async () => {
   loading.value = true;
   try {
     const res = await api.getLogs();
-    items.value = res.items;
+    items.value = res.items ?? [];
   } finally {
     loading.value = false;
   }

+ 1 - 1
frontend/src/views/system/RolePermissionView.vue

@@ -295,7 +295,7 @@ const loadData = async () => {
   loading.value = true;
   try {
     const res = await api.getRoles();
-    items.value = res.items;
+    items.value = res.items ?? [];
   } finally {
     loading.value = false;
   }