@chenchaolong/plugin-trade-compliance-workbench 1.0.40 → 1.0.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../../../../src/lib/remote-ui/pages/analytics.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAmD,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAC1G,OAAO,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAsB,sBAAsB,EAAmC,oBAAoB,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAA;AAErM,OAAO,2BAA2B,CAAA;AAElC,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,aAAa,CAAA;IACnB,cAAc,CAAC,EAAE,cAAc,CAAA;IAC/B,IAAI,EAAE,OAAO,CAAA;IACb,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,OAAO,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACxD,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,sBAAsB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACzE,CAAA;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,kBAAkB,GAAG,cAAc,CAI1G;AAeD,wBAAgB,aAAa,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,EAAE,kBAAkB,2CAqE7G;AA6RD,wBAAgB,uBAAuB,CAAC,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,EAAE,MAAM,IAAI,CAAA;CAAE,2CAiB3E;AA2HD,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,SAAS,oBAAoB,EAAE,GAAG,kBAAkB,EAAE,CAYtG"}
1
+ {"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../../../../src/lib/remote-ui/pages/analytics.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAmD,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAC1G,OAAO,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAsB,sBAAsB,EAAmC,oBAAoB,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAA;AAErM,OAAO,2BAA2B,CAAA;AAElC,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,aAAa,CAAA;IACnB,cAAc,CAAC,EAAE,cAAc,CAAA;IAC/B,IAAI,EAAE,OAAO,CAAA;IACb,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,OAAO,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACxD,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,sBAAsB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CACzE,CAAA;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,kBAAkB,GAAG,cAAc,CAI1G;AAeD,wBAAgB,aAAa,CAAC,EAAE,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,YAAY,EAAE,EAAE,kBAAkB,2CA2E7G;AA+dD,wBAAgB,uBAAuB,CAAC,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,EAAE,MAAM,IAAI,CAAA;CAAE,2CAiB3E;AA8ID,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,SAAS,oBAAoB,EAAE,GAAG,kBAAkB,EAAE,CAYtG"}
@@ -52,7 +52,7 @@ export function AnalyticsPage({ data, committedQuery, busy, error, onQuery, onEx
52
52
  void onExportXlsx(request.mode === 'PURCHASE' ? { mode: request.mode, dimension: request.dimension, filters: request.filters, format: 'XLSX' } : { mode: request.mode, dimension: request.dimension, filters: request.filters, format: 'XLSX' });
53
53
  setActiveDialog(null);
54
54
  };
55
- return _jsxs("section", { className: "tcw-analytics-page", "aria-label": "\u6570\u636E\u5206\u6790", children: [_jsxs("div", { className: "tcw-analytics-cockpit-hero", children: [_jsxs("div", { children: [_jsx("h1", { children: "\u5916\u8D38\u5408\u89C4\u7ECF\u8425\u9A7E\u9A76\u8231" }), _jsx("p", { children: "\u7ECF\u8425\u8D44\u91D1\u3001\u5408\u540C\u5BA1\u6838\u3001\u5546\u54C1\u7BA1\u63A7\u3001\u5236\u88C1\u547D\u4E2D\u3001\u5229\u6DA6\u5F02\u5E38\u7EDF\u4E00\u76D1\u63A7" })] }), _jsxs("div", { className: "tcw-analytics-refresh-card", children: [_jsx("span", { children: "\u6700\u8FD1\u5237\u65B0" }), _jsxs("strong", { children: [data.currentYear, "-08-05 10:32"] })] })] }), _jsxs("div", { className: "tcw-analytics-query", children: [_jsx(FilterPanel, { definitions: mode === 'PURCHASE' ? purchaseFilters : salesFilters, values: filters, onChange: (key, value) => setFilters(current => ({ ...current, [key]: value })), onQuery: () => void runQuery(), onReset: () => { setFilters({}); void onQuery(queryFor(mode, dimension, 1, {})); } }), _jsxs("div", { className: "tcw-analytics-toolbar", children: [_jsxs("span", { className: "tcw-analytics-chip", children: [_jsx("i", { className: "ri-building-2-line", "aria-hidden": "true" }), "\u5F53\u524D\u7EC4\u7EC7"] }), _jsxs("span", { className: "tcw-analytics-chip", children: [_jsx("i", { className: "ri-calendar-event-line", "aria-hidden": "true" }), "\u7EDF\u8BA1\u5468\u671F\uFF1A", data.currentYear, " \u5E74\u5EA6"] }), _jsxs("span", { className: "tcw-analytics-chip", children: [_jsx("i", { className: "ri-money-cny-circle-line", "aria-hidden": "true" }), "\u5E01\u79CD\uFF1ACNY"] }), _jsxs("div", { className: "tcw-segmented-control", role: "group", "aria-label": "\u6570\u636E\u53E3\u5F84", children: [_jsxs("button", { type: "button", "aria-pressed": mode === 'PURCHASE', onClick: () => switchMode('PURCHASE'), children: [_jsx("i", { className: "ri-shopping-cart-2-line", "aria-hidden": "true" }), "\u91C7\u8D2D\u53E3\u5F84"] }), _jsxs("button", { type: "button", "aria-pressed": mode === 'SALES', onClick: () => switchMode('SALES'), children: [_jsx("i", { className: "ri-line-chart-line", "aria-hidden": "true" }), "\u9500\u552E\u53E3\u5F84"] })] }), _jsx("div", { className: "tcw-segmented-control", role: "group", "aria-label": "\u7EDF\u8BA1\u7EF4\u5EA6", children: dimensions.map(([value, label]) => _jsxs("button", { type: "button", "aria-pressed": dimension === value, onClick: () => switchDimension(value), children: [_jsx("i", { className: value === 'PRODUCT' ? 'ri-box-3-line' : value === 'SUPPLIER' ? 'ri-truck-line' : 'ri-user-3-line', "aria-hidden": "true" }), label] }, value)) }), _jsxs("div", { className: "tcw-analytics-switches", "aria-label": "\u98CE\u9669\u8FC7\u6EE4", children: [mode === 'PURCHASE' && _jsxs(_Fragment, { children: [_jsx(Toggle, { label: "\u672A\u4ED8\u6B3E", checked: 'unpaidOnly' in typedFilters && typedFilters.unpaidOnly === true, onChange: checked => setFilters(current => ({ ...current, unpaidOnly: checked ? 'true' : '' })) }), _jsx(Toggle, { label: "\u672A\u9000\u7A0E", checked: 'unrefundedOnly' in typedFilters && typedFilters.unrefundedOnly === true, onChange: checked => setFilters(current => ({ ...current, unrefundedOnly: checked ? 'true' : '' })) })] }), mode === 'SALES' && _jsx(Toggle, { label: "\u672A\u6536\u6B3E", checked: 'unreceivedOnly' in typedFilters && typedFilters.unreceivedOnly === true, onChange: checked => setFilters(current => ({ ...current, unreceivedOnly: checked ? 'true' : '' })) })] }), _jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", "aria-label": "\u7B5B\u9009\u6761\u4EF6\u5165\u53E3", onClick: () => setActiveDialog('filter'), children: [_jsx("i", { className: "ri-filter-3-line", "aria-hidden": "true" }), "\u7B5B\u9009\u6761\u4EF6"] }), _jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", onClick: () => setDetailsOpen(true), children: [_jsx("i", { className: "ri-table-line", "aria-hidden": "true" }), "\u5B8C\u6574\u660E\u7EC6"] }), onExportXlsx && _jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", "aria-label": "\u5BFC\u51FA\u6570\u636E", onClick: () => setActiveDialog('export'), children: [_jsx("i", { className: "ri-file-excel-2-line", "aria-hidden": "true" }), "\u5BFC\u51FA\u62A5\u8868"] }), _jsxs("button", { type: "button", className: "tcw-button tcw-button--primary", onClick: () => void runQuery(), children: [_jsx("i", { className: "ri-refresh-line", "aria-hidden": "true" }), "\u5237\u65B0"] })] })] }), _jsx(AnalyticsCockpit, { data: data, onDrilldown: setDrilldown }), error && _jsx(RiskNotice, { children: error }), busy && _jsxs("div", { className: "tcw-inline-loading", role: "status", children: [_jsx("span", { className: "tcw-spinner", "aria-hidden": "true" }), "\u6570\u636E\u52A0\u8F7D\u4E2D"] }), drilldown && _jsx(AnalyticsDrilldownPanel, { data: data, drilldown: drilldown, onClose: () => setDrilldown(null) }), detailsOpen && _jsx(AnalyticsDetailsModal, { mode: mode, dimension: dimension, data: data, onClose: () => setDetailsOpen(false), onPageChange: page => void runQuery(page), onPageSizeChange: pageSize => void onQuery(analyticsPageSizeQuery(queryFor(mode, dimension, 1, typedFilters), pageSize)) }), _jsx(AdvancedFilterModal, { active: activeDialog === 'filter', mode: mode, filters: filters, onFilterChange: (key, value) => setFilters(current => ({ ...current, [key]: value })), onApply: () => { void runQuery(); setActiveDialog(null); }, onReset: () => { setFilters({}); void onQuery(queryFor(mode, dimension, 1, {})); setActiveDialog(null); }, onClose: () => setActiveDialog(null) }), onExportXlsx && _jsx(BusinessExportModal, { active: activeDialog === 'export', total: data.details.total, onClose: () => setActiveDialog(null), onExport: exportCurrent })] });
55
+ return _jsxs("section", { className: "tcw-analytics-page", "aria-label": "\u6570\u636E\u5206\u6790", children: [_jsxs("div", { className: "tcw-analytics-cockpit-hero", children: [_jsxs("div", { children: [_jsx("h1", { children: "\u5916\u8D38\u5408\u89C4\u7ECF\u8425\u9A7E\u9A76\u8231" }), _jsx("p", { children: "\u7ECF\u8425\u8D44\u91D1\u3001\u5408\u540C\u5BA1\u6838\u3001\u5546\u54C1\u7BA1\u63A7\u3001\u5236\u88C1\u547D\u4E2D\u3001\u5229\u6DA6\u5F02\u5E38\u7EDF\u4E00\u76D1\u63A7" })] }), _jsxs("div", { className: "tcw-analytics-page-actions", children: [onExportXlsx && _jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", "aria-label": "\u5BFC\u51FA\u6570\u636E", onClick: () => setActiveDialog('export'), children: [_jsx("i", { className: "ri-download-2-line", "aria-hidden": "true" }), "\u5BFC\u51FA\u62A5\u8868"] }), _jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", onClick: () => void runQuery(), children: [_jsx("i", { className: "ri-refresh-line", "aria-hidden": "true" }), "\u5237\u65B0"] })] })] }), _jsxs("div", { className: "tcw-analytics-query", children: [_jsx(FilterPanel, { definitions: mode === 'PURCHASE' ? purchaseFilters : salesFilters, values: filters, onChange: (key, value) => setFilters(current => ({ ...current, [key]: value })), onQuery: () => void runQuery(), onReset: () => { setFilters({}); void onQuery(queryFor(mode, dimension, 1, {})); } }), _jsxs("div", { className: "tcw-analytics-toolbar", children: [_jsxs("span", { className: "tcw-analytics-chip", children: [_jsx("i", { className: "ri-building-2-line", "aria-hidden": "true" }), "\u5F53\u524D\u7EC4\u7EC7"] }), _jsxs("span", { className: "tcw-analytics-chip", children: [_jsx("i", { className: "ri-calendar-event-line", "aria-hidden": "true" }), "\u7EDF\u8BA1\u5468\u671F\uFF1A", data.currentYear, " \u5E74\u5EA6"] }), _jsxs("div", { className: "tcw-segmented-control tcw-analytics-period", role: "group", "aria-label": "\u7EDF\u8BA1\u65F6\u95F4", children: [_jsxs("button", { type: "button", "aria-pressed": false, onClick: () => setFilters(current => ({ ...current, orderDate: quickRange(3) })), children: [_jsx("i", { className: "ri-calendar-check-line", "aria-hidden": "true" }), "\u672C\u6708"] }), _jsxs("button", { type: "button", "aria-pressed": false, onClick: () => setActiveDialog('filter'), children: [_jsx("i", { className: "ri-calendar-2-line", "aria-hidden": "true" }), "\u81EA\u5B9A\u4E49\u533A\u95F4"] })] }), _jsxs("span", { className: "tcw-analytics-chip", children: [_jsx("i", { className: "ri-money-cny-circle-line", "aria-hidden": "true" }), "\u5E01\u79CD\uFF1ACNY"] }), _jsxs("span", { className: "tcw-analytics-chip tcw-analytics-chip--refresh", children: [_jsx("i", { className: "ri-time-line", "aria-hidden": "true" }), "\u6700\u8FD1\u5237\u65B0\uFF1A", data.currentYear, "-08-05 10:32"] }), _jsxs("div", { className: "tcw-segmented-control", role: "group", "aria-label": "\u6570\u636E\u53E3\u5F84", children: [_jsxs("button", { type: "button", "aria-pressed": mode === 'PURCHASE', onClick: () => switchMode('PURCHASE'), children: [_jsx("i", { className: "ri-shopping-cart-2-line", "aria-hidden": "true" }), "\u91C7\u8D2D\u53E3\u5F84"] }), _jsxs("button", { type: "button", "aria-pressed": mode === 'SALES', onClick: () => switchMode('SALES'), children: [_jsx("i", { className: "ri-line-chart-line", "aria-hidden": "true" }), "\u9500\u552E\u53E3\u5F84"] })] }), _jsx("div", { className: "tcw-segmented-control", role: "group", "aria-label": "\u7EDF\u8BA1\u7EF4\u5EA6", children: dimensions.map(([value, label]) => _jsxs("button", { type: "button", "aria-pressed": dimension === value, onClick: () => switchDimension(value), children: [_jsx("i", { className: value === 'PRODUCT' ? 'ri-box-3-line' : value === 'SUPPLIER' ? 'ri-truck-line' : 'ri-user-3-line', "aria-hidden": "true" }), label] }, value)) }), _jsxs("div", { className: "tcw-analytics-switches", "aria-label": "\u98CE\u9669\u8FC7\u6EE4", children: [mode === 'PURCHASE' && _jsxs(_Fragment, { children: [_jsx(Toggle, { label: "\u672A\u4ED8\u6B3E", checked: 'unpaidOnly' in typedFilters && typedFilters.unpaidOnly === true, onChange: checked => setFilters(current => ({ ...current, unpaidOnly: checked ? 'true' : '' })) }), _jsx(Toggle, { label: "\u672A\u9000\u7A0E", checked: 'unrefundedOnly' in typedFilters && typedFilters.unrefundedOnly === true, onChange: checked => setFilters(current => ({ ...current, unrefundedOnly: checked ? 'true' : '' })) })] }), mode === 'SALES' && _jsx(Toggle, { label: "\u672A\u6536\u6B3E", checked: 'unreceivedOnly' in typedFilters && typedFilters.unreceivedOnly === true, onChange: checked => setFilters(current => ({ ...current, unreceivedOnly: checked ? 'true' : '' })) })] }), _jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", "aria-label": "\u7B5B\u9009\u6761\u4EF6\u5165\u53E3", onClick: () => setActiveDialog('filter'), children: [_jsx("i", { className: "ri-filter-3-line", "aria-hidden": "true" }), "\u7B5B\u9009\u6761\u4EF6"] }), _jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", onClick: () => setDetailsOpen(true), children: [_jsx("i", { className: "ri-table-line", "aria-hidden": "true" }), "\u5B8C\u6574\u660E\u7EC6"] })] })] }), _jsx(AnalyticsCockpit, { data: data, onDrilldown: setDrilldown }), error && _jsx(RiskNotice, { children: error }), busy && _jsxs("div", { className: "tcw-inline-loading", role: "status", children: [_jsx("span", { className: "tcw-spinner", "aria-hidden": "true" }), "\u6570\u636E\u52A0\u8F7D\u4E2D"] }), drilldown && _jsx(AnalyticsDrilldownPanel, { data: data, drilldown: drilldown, onClose: () => setDrilldown(null) }), detailsOpen && _jsx(AnalyticsDetailsModal, { mode: mode, dimension: dimension, data: data, onClose: () => setDetailsOpen(false), onPageChange: page => void runQuery(page), onPageSizeChange: pageSize => void onQuery(analyticsPageSizeQuery(queryFor(mode, dimension, 1, typedFilters), pageSize)) }), _jsx(AdvancedFilterModal, { active: activeDialog === 'filter', mode: mode, filters: filters, onFilterChange: (key, value) => setFilters(current => ({ ...current, [key]: value })), onApply: () => { void runQuery(); setActiveDialog(null); }, onReset: () => { setFilters({}); void onQuery(queryFor(mode, dimension, 1, {})); setActiveDialog(null); }, onClose: () => setActiveDialog(null) }), onExportXlsx && _jsx(BusinessExportModal, { active: activeDialog === 'export', total: data.details.total, onClose: () => setActiveDialog(null), onExport: exportCurrent })] });
56
56
  }
57
57
  const analyticsExportFields = [
58
58
  { key: 'label', label: '客户/供应商名称' },
@@ -64,22 +64,31 @@ const analyticsExportFields = [
64
64
  ];
65
65
  function AnalyticsCockpit({ data, onDrilldown }) {
66
66
  const dashboard = data.dashboard;
67
+ const customerValueRows = customerValueRowsFromData(data);
68
+ const receivableRiskRows = receivableRiskRowsFromData(data);
69
+ const supplierRiskRows = supplierRiskRowsFromData(data);
70
+ const salesFileRows = salesFileRowsFromData(data);
71
+ const riskOrderRows = riskOrderRowsFromData(data);
72
+ const monthlyDeltas = monthlyDeltaLabels(dashboard.monthlyTrend);
67
73
  const kpis = [
68
- { label: '采购总额', value: compactMoney(dashboard.kpis.purchaseTotalCny), fullValue: `${money(dashboard.kpis.purchaseTotalCny)} CNY`, icon: 'ri-shopping-bag-3-line', tone: 'money' },
69
- { label: '销售总额', value: compactMoney(dashboard.kpis.salesTotalCny), fullValue: `${money(dashboard.kpis.salesTotalCny)} CNY`, icon: 'ri-line-chart-line', tone: 'money' },
70
- { label: '已收款金额', value: compactMoney(dashboard.kpis.receivedCny), fullValue: `${money(dashboard.kpis.receivedCny)} CNY`, icon: 'ri-bank-card-line', tone: 'money' },
71
- { label: '未收款金额', value: compactMoney(dashboard.kpis.unreceivedCny), fullValue: `${money(dashboard.kpis.unreceivedCny)} CNY`, icon: 'ri-alarm-warning-line', tone: 'risk' },
72
- { label: '已付款金额', value: compactMoney(dashboard.kpis.paidCny), fullValue: `${money(dashboard.kpis.paidCny)} CNY`, icon: 'ri-secure-payment-line', tone: 'money' },
73
- { label: '未付款金额', value: compactMoney(dashboard.kpis.unpaidCny), fullValue: `${money(dashboard.kpis.unpaidCny)} CNY`, icon: 'ri-error-warning-line', tone: 'risk' },
74
- { label: '未退税金额', value: compactMoney(dashboard.kpis.unrefundedCny), fullValue: `${money(dashboard.kpis.unrefundedCny)} CNY`, icon: 'ri-refund-2-line', tone: 'audit' },
75
- { label: '预计毛利率', value: percent(dashboard.kpis.estimatedGrossProfitRate), icon: 'ri-funds-box-line', tone: 'money' },
76
- { label: '合规待办数', value: `${dashboard.kpis.complianceTodoCount} 项`, icon: 'ri-shield-check-line', tone: 'audit' },
77
- { label: '风险敞口金额', value: compactMoney(dashboard.kpis.riskExposureCny), fullValue: `${money(dashboard.kpis.riskExposureCny)} CNY`, icon: 'ri-radar-line', tone: 'risk' }
74
+ { label: '采购总额', value: compactMoney(dashboard.kpis.purchaseTotalCny), subtext: monthlyDeltas.purchase, fullValue: `${money(dashboard.kpis.purchaseTotalCny)} CNY`, icon: 'ri-shopping-cart-line', tone: 'money' },
75
+ { label: '销售总额', value: compactMoney(dashboard.kpis.salesTotalCny), subtext: monthlyDeltas.sales, fullValue: `${money(dashboard.kpis.salesTotalCny)} CNY`, icon: 'ri-bar-chart-line', tone: 'money' },
76
+ { label: '已收款金额', value: compactMoney(dashboard.kpis.receivedCny), subtext: `回款率 ${amountRatio(dashboard.kpis.receivedCny, dashboard.kpis.salesTotalCny)}`, fullValue: `${money(dashboard.kpis.receivedCny)} CNY`, icon: 'ri-time-line', tone: 'success' },
77
+ { label: '未收款金额', value: compactMoney(dashboard.kpis.unreceivedCny), subtext: `销售占比 ${amountRatio(dashboard.kpis.unreceivedCny, dashboard.kpis.salesTotalCny)}`, fullValue: `${money(dashboard.kpis.unreceivedCny)} CNY`, icon: 'ri-alarm-warning-line', tone: 'warn' },
78
+ { label: '已付款金额', value: compactMoney(dashboard.kpis.paidCny), subtext: `付款率 ${amountRatio(dashboard.kpis.paidCny, dashboard.kpis.purchaseTotalCny)}`, fullValue: `${money(dashboard.kpis.paidCny)} CNY`, icon: 'ri-secure-payment-line', tone: 'success' },
79
+ { label: '未付款金额', value: compactMoney(dashboard.kpis.unpaidCny), subtext: `采购占比 ${amountRatio(dashboard.kpis.unpaidCny, dashboard.kpis.purchaseTotalCny)}`, fullValue: `${money(dashboard.kpis.unpaidCny)} CNY`, icon: 'ri-error-warning-line', tone: 'warn' },
80
+ { label: '未退税金额', value: compactMoney(dashboard.kpis.unrefundedCny), subtext: `退税缺口 ${amountRatio(dashboard.kpis.unrefundedCny, addMoney(dashboard.kpis.refundedCny, dashboard.kpis.unrefundedCny))}`, fullValue: `${money(dashboard.kpis.unrefundedCny)} CNY`, icon: 'ri-refund-2-line', tone: 'warn' },
81
+ { label: '预计毛利率', value: percent(dashboard.kpis.estimatedGrossProfitRate), subtext: `毛利 ${compactMoney(dashboard.kpis.estimatedGrossProfitCny)}`, icon: 'ri-funds-box-line', tone: 'success' },
82
+ { label: '合规待办数', value: `${dashboard.kpis.complianceTodoCount} 项`, subtext: `队列 ${dashboard.todos.length} 类`, icon: 'ri-shield-check-line', tone: 'warn' },
83
+ { label: '风险敞口金额', value: compactMoney(dashboard.kpis.riskExposureCny), subtext: `资金风险占比 ${amountRatio(dashboard.kpis.riskExposureCny, addMoney(dashboard.kpis.salesTotalCny, dashboard.kpis.purchaseTotalCny))}`, fullValue: `${money(dashboard.kpis.riskExposureCny)} CNY`, icon: 'ri-shield-flash-line', tone: 'risk' }
78
84
  ];
79
- return _jsxs("section", { className: "tcw-analytics-cockpit", "aria-label": "\u5916\u8D38\u5408\u89C4\u7ECF\u8425\u9A7E\u9A76\u8231", children: [_jsx("div", { className: "tcw-analytics-kpi-grid", children: kpis.map(item => _jsxs("button", { type: "button", className: `is-${item.tone}`, title: 'fullValue' in item ? item.fullValue : item.value, onClick: () => onDrilldown({ title: `${item.label}下钻明细`, tone: item.tone }), children: [_jsx("i", { className: item.icon, "aria-hidden": "true" }), _jsx("span", { children: item.label }), _jsx("strong", { children: item.value })] }, item.label)) }), _jsxs("div", { className: "tcw-analytics-cockpit-layout", children: [_jsx(Panel, { title: "\u5408\u89C4\u5F85\u529E\u961F\u5217", icon: "ri-list-check-3", children: _jsx("div", { className: "tcw-analytics-todo-list", children: dashboard.todos.length ? dashboard.todos.map(todo => _jsxs("article", { className: `is-${todo.priority}`, children: [_jsx("strong", { children: todo.label }), _jsx("span", { children: todo.description }), _jsxs("b", { children: [todo.count, " \u9879", todo.amountCny ? ` · ${money(todo.amountCny)} CNY` : ''] })] }, todo.key)) : _jsx(EmptyText, { text: "\u6682\u65E0\u5408\u89C4\u5F85\u529E" }) }) }), _jsxs(Panel, { title: "\u6708\u5EA6\u7ECF\u8425\u8D8B\u52BF", icon: "ri-bar-chart-box-line", className: "tcw-analytics-panel--wide", children: [_jsx("div", { className: "tcw-analytics-month-chart", children: dashboard.monthlyTrend.map(point => _jsxs("div", { children: [_jsx("i", { className: "is-purchase", style: { height: `${barHeight(point.purchaseAmountCny, dashboard.monthlyTrend.map(row => row.salesAmountCny))}%` } }), _jsx("i", { className: "is-sales", style: { height: `${barHeight(point.salesAmountCny, dashboard.monthlyTrend.map(row => row.salesAmountCny))}%` } }), _jsxs("span", { children: [point.month, "\u6708"] })] }, point.month)) }), _jsxs("div", { className: "tcw-analytics-chart-legend", children: [_jsxs("span", { children: [_jsx("i", { className: "is-purchase" }), "\u91C7\u8D2D\u989D"] }), _jsxs("span", { children: [_jsx("i", { className: "is-sales" }), "\u9500\u552E\u989D"] }), _jsxs("span", { children: [_jsx("i", { className: "is-profit" }), "\u9884\u8BA1\u6BDB\u5229 ", money(dashboard.kpis.estimatedGrossProfitCny), " CNY"] })] }), _jsxs("div", { className: "tcw-analytics-trend-summary", children: [_jsxs("span", { children: ["\u9500\u552E ", compactMoney(dashboard.kpis.salesTotalCny)] }), _jsxs("span", { children: ["\u91C7\u8D2D ", compactMoney(dashboard.kpis.purchaseTotalCny)] }), _jsxs("span", { children: ["\u6BDB\u5229\u7387 ", percent(dashboard.kpis.estimatedGrossProfitRate)] })] })] }), _jsx(Panel, { title: "\u8D44\u91D1\u98CE\u9669\u7ED3\u6784", icon: "ri-stack-line", children: _jsx(MoneyStructure, { kpis: dashboard.kpis }) }), _jsx(Panel, { title: "\u5408\u89C4\u98CE\u9669\u5206\u5E03", icon: "ri-donut-chart-line", children: _jsx(RiskDistribution, { items: dashboard.riskDistribution }) }), _jsx(Panel, { title: "\u5BA2\u6237\u4EF7\u503C TOP5", icon: "ri-vip-crown-line", children: _jsx(CustomerValue, { items: dashboard.customerValueTop5 }) }), _jsx(Panel, { title: "\u5E94\u6536\u98CE\u9669 TOP5", icon: "ri-bank-line", children: _jsx(ReceivableRisk, { items: dashboard.receivableRiskTop5 }) }), _jsx(Panel, { title: "\u4F9B\u5E94\u5546\u98CE\u9669 TOP5", icon: "ri-truck-line", children: _jsx(SupplierRisk, { items: dashboard.supplierRiskTop5 }) }), _jsx(Panel, { title: "\u9500\u552E\u6587\u4EF6\u590D\u6838", icon: "ri-file-search-line", children: _jsx(SalesFileReviews, { items: dashboard.salesFileReviews }) }), _jsx(Panel, { title: "\u98CE\u9669\u8BA2\u5355\u660E\u7EC6", icon: "ri-file-warning-line", className: "tcw-analytics-panel--wide", children: _jsx(RiskOrders, { items: dashboard.riskOrders }) }), _jsx(Panel, { title: "\u6307\u6807\u6570\u636E\u6765\u6E90", icon: "ri-database-2-line", children: _jsx("div", { className: "tcw-analytics-source-list", children: dashboard.dataSources.map(source => _jsxs("article", { children: [_jsx("strong", { children: source.label }), _jsx("span", { children: source.description })] }, source.label)) }) })] })] });
85
+ return _jsxs("section", { className: "tcw-analytics-cockpit", "aria-label": "\u5916\u8D38\u5408\u89C4\u7ECF\u8425\u9A7E\u9A76\u8231", children: [_jsx("div", { className: "tcw-analytics-kpi-grid", children: kpis.map(item => _jsxs("button", { type: "button", className: `is-${item.tone}`, title: 'fullValue' in item ? item.fullValue : item.value, onClick: () => onDrilldown({ title: `${item.label}下钻明细`, tone: item.tone === 'success' || item.tone === 'warn' ? 'money' : item.tone }), children: [_jsx("span", { children: item.label }), _jsx("i", { className: item.icon, "aria-hidden": "true" }), _jsx("strong", { children: item.value }), _jsx("small", { children: item.subtext.startsWith('较上月') ? _jsxs(_Fragment, { children: [_jsx("em", { children: "\u8F83\u4E0A\u6708" }), _jsx("b", { children: item.subtext.replace('较上月 ', '') })] }) : item.subtext })] }, item.label)) }), _jsxs("div", { className: "tcw-analytics-cockpit-layout", children: [_jsx(Panel, { title: "\u5408\u89C4\u5F85\u529E\u961F\u5217", icon: "ri-list-check-3", className: "tcw-analytics-panel--queue", action: _jsx(PanelLink, { href: "#analytics-todos", label: "\u67E5\u770B\u5168\u90E8" }), children: _jsx("div", { className: "tcw-analytics-todo-list", children: dashboard.todos.length ? dashboard.todos.map(todo => _jsxs("article", { className: `is-${todo.priority}`, children: [_jsx("strong", { children: todo.label }), _jsx("span", { children: todo.description }), _jsxs("b", { children: [todo.count, " \u9879", todo.amountCny ? ` · ${money(todo.amountCny)} CNY` : ''] })] }, todo.key)) : _jsx(EmptyText, { text: "\u6682\u65E0\u5408\u89C4\u5F85\u529E" }) }) }), _jsxs(Panel, { title: "\u6708\u5EA6\u7ECF\u8425\u8D8B\u52BF", icon: "ri-bar-chart-box-line", className: "tcw-analytics-panel--trend", children: [_jsx(TrendLineChart, { points: dashboard.monthlyTrend }), _jsxs("div", { className: "tcw-analytics-chart-legend", children: [_jsx("b", { children: "\u91D1\u989D\uFF08\u4E07\u5143\uFF09" }), _jsxs("span", { children: [_jsx("i", { className: "is-sales" }), "\u9500\u552E\u989D"] }), _jsxs("span", { children: [_jsx("i", { className: "is-purchase" }), "\u91C7\u8D2D\u989D"] }), _jsxs("span", { children: [_jsx("i", { className: "is-profit" }), "\u9884\u8BA1\u6BDB\u5229"] }), _jsxs("span", { children: [_jsx("i", { className: "is-received" }), "\u56DE\u6B3E\u91D1\u989D"] })] }), _jsxs("div", { className: "tcw-analytics-trend-summary", children: [_jsxs("span", { children: ["\u9500\u552E ", compactMoney(dashboard.kpis.salesTotalCny)] }), _jsxs("span", { children: ["\u91C7\u8D2D ", compactMoney(dashboard.kpis.purchaseTotalCny)] }), _jsxs("span", { children: ["\u6BDB\u5229\u7387 ", percent(dashboard.kpis.estimatedGrossProfitRate)] })] })] }), _jsx(Panel, { title: "\u8D44\u91D1\u98CE\u9669\u7ED3\u6784", icon: "ri-stack-line", className: "tcw-analytics-panel--capital", children: _jsx(MoneyStructure, { kpis: dashboard.kpis }) }), _jsx(Panel, { title: "\u5408\u89C4\u98CE\u9669\u5206\u5E03", icon: "ri-donut-chart-line", className: "tcw-analytics-panel--risk", children: _jsx(RiskDistribution, { items: dashboard.riskDistribution }) }), _jsx(Panel, { title: "\u5BA2\u6237\u4EF7\u503C TOP5", icon: "ri-vip-crown-line", children: _jsx(CustomerValue, { rows: customerValueRows }) }), _jsx(Panel, { title: "\u5E94\u6536\u98CE\u9669 TOP5", icon: "ri-bank-line", children: _jsx(ReceivableRisk, { rows: receivableRiskRows }) }), _jsx(Panel, { title: "\u4F9B\u5E94\u5546\u98CE\u9669 TOP5", icon: "ri-truck-line", children: _jsx(SupplierRisk, { rows: supplierRiskRows }) }), _jsx(Panel, { title: "\u9500\u552E\u6587\u4EF6\u590D\u6838", icon: "ri-file-search-line", action: _jsx(PanelLink, { href: "#analytics-review-files", label: "\u67E5\u770B\u5168\u90E8" }), children: _jsx(SalesFileReviews, { rows: salesFileRows }) }), _jsx(Panel, { title: "\u98CE\u9669\u8BA2\u5355\u660E\u7EC6", icon: "ri-file-warning-line", className: "tcw-analytics-panel--wide", action: _jsx(PanelLink, { href: "#analytics-risk-orders", label: "\u67E5\u770B\u5168\u90E8" }), children: _jsx(RiskOrders, { rows: riskOrderRows }) }), _jsx(Panel, { title: "\u6307\u6807\u6570\u636E\u6765\u6E90", icon: "ri-database-2-line", action: _jsx(PanelLink, { href: "#analytics-sources", label: "\u67E5\u770B\u8BE6\u60C5" }), children: _jsx("div", { className: "tcw-analytics-source-list", children: dashboard.dataSources.map(source => _jsxs("a", { href: "#analytics-sources", children: [_jsx("strong", { children: source.label }), _jsx("span", { children: source.description })] }, source.label)) }) })] })] });
80
86
  }
81
- function Panel({ title, icon, className = '', children }) {
82
- return _jsxs("section", { className: `tcw-analytics-panel ${className}`.trim(), children: [_jsx("header", { children: _jsxs("strong", { children: [_jsx("i", { className: icon, "aria-hidden": "true" }), title] }) }), children] });
87
+ function Panel({ title, icon, className = '', action, children }) {
88
+ return _jsxs("section", { className: `tcw-analytics-panel ${className}`.trim(), children: [_jsxs("header", { children: [_jsxs("strong", { children: [_jsx("i", { className: icon, "aria-hidden": "true" }), title] }), action] }), children] });
89
+ }
90
+ function PanelLink({ href, label }) {
91
+ return _jsxs("a", { className: "tcw-analytics-panel-link", href: href, children: [label, _jsx("i", { className: "ri-arrow-right-s-line", "aria-hidden": "true" })] });
83
92
  }
84
93
  function MoneyStructure({ kpis }) {
85
94
  const items = [
@@ -93,22 +102,184 @@ function MoneyStructure({ kpis }) {
93
102
  }
94
103
  function RiskDistribution({ items }) {
95
104
  const total = items.reduce((sum, item) => sum + item.count, 0);
96
- return _jsxs("div", { className: "tcw-analytics-risk-distribution", children: [_jsxs("div", { className: "tcw-analytics-donut", children: [_jsx("strong", { children: total }), _jsx("span", { children: "\u98CE\u9669\u9879" })] }), _jsx("dl", { children: items.map(item => _jsxs("div", { children: [_jsx("dt", { children: item.label }), _jsxs("dd", { children: [item.count, " \u9879", item.amountCny ? ` · ${money(item.amountCny)} CNY` : ''] })] }, item.key)) })] });
97
- }
98
- function CustomerValue({ items }) {
99
- return _jsx("div", { className: "tcw-analytics-table-list", children: items.length ? items.map(item => _jsxs("article", { children: [_jsx("span", { title: item.customerName, children: item.customerName }), _jsxs("b", { children: [money(item.salesAmountCny), " CNY"] }), _jsxs("small", { children: ["\u6BDB\u5229\u7387 ", percent(item.estimatedGrossProfitRate), " \u00B7 \u672A\u6536 ", money(item.unreceivedCny)] })] }, item.customerId)) : _jsx(EmptyText, { text: "\u6682\u65E0\u5BA2\u6237\u4EF7\u503C\u6392\u540D" }) });
100
- }
101
- function ReceivableRisk({ items }) {
102
- return _jsx("div", { className: "tcw-analytics-table-list", children: items.length ? items.map(item => _jsxs("article", { children: [_jsx("span", { title: item.customerName, children: item.customerName }), _jsxs("b", { children: [money(item.unreceivedCny), " CNY"] }), _jsxs("small", { children: [item.orderCount, " \u7B14\u8BA2\u5355 \u00B7 \u6700\u8FD1 ", shortDate(item.latestOrderDate)] })] }, item.customerId)) : _jsx(EmptyText, { text: "\u6682\u65E0\u5E94\u6536\u98CE\u9669" }) });
103
- }
104
- function SupplierRisk({ items }) {
105
- return _jsx("div", { className: "tcw-analytics-table-list", children: items.length ? items.map(item => _jsxs("article", { children: [_jsx("span", { title: item.supplierName, children: item.supplierName }), _jsxs("b", { children: [money(item.unpaidCny), " CNY"] }), _jsxs("small", { children: ["\u5546\u54C1\u547D\u4E2D ", item.productRiskHitCount, " \u00B7 \u5236\u88C1\u547D\u4E2D ", item.sanctionHitCount] })] }, item.supplierId)) : _jsx(EmptyText, { text: "\u6682\u65E0\u4F9B\u5E94\u5546\u98CE\u9669" }) });
106
- }
107
- function SalesFileReviews({ items }) {
108
- return _jsx("div", { className: "tcw-analytics-file-list", children: items.length ? items.map(item => _jsxs("article", { children: [_jsx("strong", { title: item.fileName, children: item.fileName }), _jsxs("span", { children: [item.fileType, " \u00B7 ", item.salesOrderNo] }), _jsx("small", { children: item.reason })] }, item.fileId)) : _jsx(EmptyText, { text: "\u6682\u65E0\u9500\u552E\u6587\u4EF6\u590D\u6838" }) });
105
+ const colors = ['#1769ff', '#26b6c8', '#ff9f1c', '#8b5cf6', '#5ac85a', '#ff4d55'];
106
+ const segments = items.length ? items.map((item, index) => ({ item, color: colors[index % colors.length] })) : [{ item: { key: 'empty', label: '暂无风险', count: 1, amountCny: '0' }, color: '#e7edf5' }];
107
+ let cursor = 0;
108
+ const conic = segments.map(({ item, color }) => {
109
+ const start = cursor;
110
+ cursor += ((total > 0 ? item.count / total : 1) * 100);
111
+ return `${color} ${start.toFixed(2)}% ${cursor.toFixed(2)}%`;
112
+ }).join(', ');
113
+ const style = { '--risk-donut': `conic-gradient(${conic})` };
114
+ return _jsxs("div", { className: "tcw-analytics-risk-distribution", children: [_jsxs("div", { className: "tcw-analytics-donut", style: style, children: [_jsx("strong", { children: "\u5408\u8BA1" }), _jsx("span", { children: total })] }), _jsx("dl", { children: items.map((item, index) => _jsxs("div", { children: [_jsxs("dt", { children: [_jsx("i", { style: { background: colors[index % colors.length] } }), item.label] }), _jsxs("dd", { children: [item.count, " (", percentRatio(item.count, total), ")"] })] }, item.key)) })] });
115
+ }
116
+ function CustomerValue({ rows }) {
117
+ return rows.length ? _jsxs("table", { className: "tcw-analytics-dashboard-table", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "\u5BA2\u6237/\u4F9B\u5E94\u5546" }), _jsx("th", { children: "\u9500\u552E\u989D\uFF08\u4EBA\u6C11\u5E01\uFF09" }), _jsx("th", { children: "\u6BDB\u5229\u7387" }), _jsx("th", { children: "\u98CE\u9669" })] }) }), _jsx("tbody", { children: rows.map(row => _jsxs("tr", { children: [_jsx("td", { title: row.name, children: row.name }), _jsx("td", { children: money(row.salesAmountCny) }), _jsx("td", { children: percent(row.grossProfitRate) }), _jsx("td", { children: _jsx(RiskBadge, { status: row.riskStatus }) })] }, row.key)) })] }) : _jsx(EmptyText, { text: "\u6682\u65E0\u5BA2\u6237\u4EF7\u503C\u6392\u540D" });
118
+ }
119
+ function ReceivableRisk({ rows }) {
120
+ return rows.length ? _jsxs("table", { className: "tcw-analytics-dashboard-table", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "\u5BA2\u6237/\u4F9B\u5E94\u5546" }), _jsx("th", { children: "\u672A\u6536\u6B3E\u91D1\u989D\uFF08\u4EBA\u6C11\u5E01\uFF09" }), _jsx("th", { children: "\u8BA2\u5355\u6570" }), _jsx("th", { children: "\u6700\u8FD1\u8BA2\u5355" })] }) }), _jsx("tbody", { children: rows.map(row => _jsxs("tr", { children: [_jsx("td", { title: row.name, children: row.name }), _jsx("td", { children: money(row.unreceivedCny) }), _jsx("td", { children: row.orderCount }), _jsx("td", { children: shortDate(row.latestOrderDate) })] }, row.key)) })] }) : _jsx(EmptyText, { text: "\u6682\u65E0\u5E94\u6536\u98CE\u9669" });
121
+ }
122
+ function SupplierRisk({ rows }) {
123
+ return rows.length ? _jsxs("table", { className: "tcw-analytics-dashboard-table", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "\u4F9B\u5E94\u5546" }), _jsx("th", { children: "\u672A\u4ED8\u6B3E\u91D1\u989D\uFF08\u4EBA\u6C11\u5E01\uFF09" }), _jsx("th", { children: "\u5546\u54C1\u547D\u4E2D" }), _jsx("th", { children: "\u5236\u88C1\u547D\u4E2D" })] }) }), _jsx("tbody", { children: rows.map(row => _jsxs("tr", { children: [_jsx("td", { title: row.name, children: row.name }), _jsx("td", { children: money(row.unpaidCny) }), _jsx("td", { children: row.productRiskHitCount }), _jsx("td", { children: row.sanctionHitCount })] }, row.key)) })] }) : _jsx(EmptyText, { text: "\u6682\u65E0\u4F9B\u5E94\u5546\u98CE\u9669" });
124
+ }
125
+ function SalesFileReviews({ rows }) {
126
+ return rows.length ? _jsxs("table", { className: "tcw-analytics-dashboard-table", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "\u6587\u4EF6\u540D\u79F0" }), _jsx("th", { children: "\u7C7B\u578B" }), _jsx("th", { children: "\u5173\u8054\u8BA2\u5355" }), _jsx("th", { children: "\u72B6\u6001" })] }) }), _jsx("tbody", { children: rows.map(row => _jsxs("tr", { children: [_jsx("td", { title: row.reason, children: row.fileName }), _jsx("td", { children: row.fileType }), _jsx("td", { children: row.orderNo }), _jsx("td", { children: row.status })] }, row.key)) })] }) : _jsx(EmptyText, { text: "\u6682\u65E0\u9500\u552E\u6587\u4EF6\u590D\u6838" });
127
+ }
128
+ function RiskOrders({ rows }) {
129
+ return rows.length ? _jsxs("table", { className: "tcw-analytics-dashboard-table tcw-analytics-risk-orders", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "\u8BA2\u5355\u53F7" }), _jsx("th", { children: "\u5BA2\u6237/\u4F9B\u5E94\u5546" }), _jsx("th", { children: "\u98CE\u9669\u7C7B\u578B" }), _jsx("th", { children: "\u8BA2\u5355\u91D1\u989D\uFF08\u4EBA\u6C11\u5E01\uFF09" }), _jsx("th", { children: "\u72B6\u6001" })] }) }), _jsx("tbody", { children: rows.map(row => _jsxs("tr", { children: [_jsx("td", { children: row.orderNo }), _jsx("td", { title: row.partyName, children: row.partyName }), _jsx("td", { children: _jsx("em", { children: row.riskType }) }), _jsx("td", { children: money(row.riskAmountCny) }), _jsx("td", { children: _jsx("span", { children: row.status }) })] }, row.key)) })] }) : _jsx(EmptyText, { text: "\u6682\u65E0\u98CE\u9669\u8BA2\u5355" });
130
+ }
131
+ function RiskBadge({ status }) {
132
+ const label = status === 'HIGH' ? '高' : status === 'MEDIUM' ? '中' : status === 'LOW' ? '低' : status;
133
+ return _jsx("span", { className: `tcw-analytics-risk-badge is-${status.toLocaleLowerCase()}`, children: label });
134
+ }
135
+ function TrendLineChart({ points }) {
136
+ const months = Array.from({ length: 12 }, (_, index) => points.find(point => point.month === index + 1) ?? { month: index + 1, purchaseAmountCny: '0', salesAmountCny: '0', estimatedGrossProfitCny: '0', receivedCny: '0' });
137
+ const max = Math.max(...months.flatMap(point => [Number(point.purchaseAmountCny) || 0, Number(point.salesAmountCny) || 0, Number(point.estimatedGrossProfitCny) || 0]), 1);
138
+ const line = (key) => months.map((point, index) => {
139
+ const x = 24 + index * (552 / 11);
140
+ const y = 132 - ((Number(point[key]) || 0) / max) * 104;
141
+ return `${x.toFixed(1)},${y.toFixed(1)}`;
142
+ }).join(' ');
143
+ return _jsx("div", { className: "tcw-analytics-line-chart", children: _jsxs("svg", { viewBox: "0 0 600 160", role: "img", "aria-label": "\u6708\u5EA6\u7ECF\u8425\u8D8B\u52BF\u6298\u7EBF\u56FE", children: [_jsx("g", { className: "tcw-analytics-line-grid", children: [28, 54, 80, 106, 132].map(y => _jsx("line", { x1: "24", x2: "576", y1: y, y2: y }, y)) }), _jsx("polyline", { className: "is-purchase", points: line('purchaseAmountCny') }), _jsx("polyline", { className: "is-sales", points: line('salesAmountCny') }), _jsx("polyline", { className: "is-profit", points: line('estimatedGrossProfitCny') }), _jsx("polyline", { className: "is-received", points: line('receivedCny') }), months.map((point, index) => _jsx("text", { x: 24 + index * (552 / 11), y: "153", children: point.month }, point.month))] }) });
144
+ }
145
+ function monthlyDeltaLabels(points) {
146
+ const sorted = [...points].sort((a, b) => a.month - b.month);
147
+ let latestIndex = -1;
148
+ for (let index = sorted.length - 1; index >= 0; index -= 1) {
149
+ const point = sorted[index];
150
+ if (Number(point.purchaseAmountCny) > 0 || Number(point.salesAmountCny) > 0 || Number(point.receivedCny) > 0) {
151
+ latestIndex = index;
152
+ break;
153
+ }
154
+ }
155
+ const latest = latestIndex >= 0 ? sorted[latestIndex] : undefined;
156
+ const previous = latestIndex > 0 ? sorted[latestIndex - 1] : undefined;
157
+ return {
158
+ purchase: `较上月 ${deltaLabel(latest?.purchaseAmountCny, previous?.purchaseAmountCny)}`,
159
+ sales: `较上月 ${deltaLabel(latest?.salesAmountCny, previous?.salesAmountCny)}`,
160
+ received: `较上月 ${deltaLabel(latest?.receivedCny, previous?.receivedCny)}`,
161
+ profit: `较上月 ${deltaLabel(latest?.estimatedGrossProfitCny, previous?.estimatedGrossProfitCny)}`
162
+ };
109
163
  }
110
- function RiskOrders({ items }) {
111
- return _jsx("div", { className: "tcw-analytics-risk-orders", children: items.length ? items.map(item => _jsxs("article", { children: [_jsx("span", { children: item.orderNo }), _jsx("b", { children: item.partyName }), _jsx("em", { children: item.riskType }), _jsxs("strong", { children: [money(item.riskAmountCny), " CNY"] }), _jsx("small", { children: item.status })] }, item.orderKey)) : _jsx(EmptyText, { text: "\u6682\u65E0\u98CE\u9669\u8BA2\u5355" }) });
164
+ function customerValueRowsFromData(data) {
165
+ if (data.dashboard.customerValueTop5.length)
166
+ return data.dashboard.customerValueTop5.map(item => ({
167
+ key: item.customerId,
168
+ name: item.customerName,
169
+ salesAmountCny: item.salesAmountCny,
170
+ grossProfitRate: item.estimatedGrossProfitRate,
171
+ unreceivedCny: item.unreceivedCny,
172
+ riskStatus: item.riskStatus
173
+ }));
174
+ const source = data.top5.length ? data.top5.map(item => {
175
+ const detail = findDetail(data, item.key, item.label);
176
+ return detail ?? { key: item.key, label: item.label, orderCount: 0, amountCny: item.amountCny };
177
+ }) : data.details.items;
178
+ return source.slice(0, 5).map(row => ({
179
+ key: row.key,
180
+ name: row.label,
181
+ salesAmountCny: row.amountCny,
182
+ grossProfitRate: amountRatio(estimatedGrossProfit(row), row.amountCny, false),
183
+ unreceivedCny: row.unreceivedCny ?? row.unpaidCny ?? '0',
184
+ riskStatus: riskStatusForAmount(row.unreceivedCny ?? row.unpaidCny ?? row.unrefundedCny)
185
+ }));
186
+ }
187
+ function receivableRiskRowsFromData(data) {
188
+ if (data.dashboard.receivableRiskTop5.length)
189
+ return data.dashboard.receivableRiskTop5.map(item => ({
190
+ key: item.customerId,
191
+ name: item.customerName,
192
+ unreceivedCny: item.unreceivedCny,
193
+ orderCount: item.orderCount,
194
+ latestOrderDate: item.latestOrderDate
195
+ }));
196
+ return data.details.items
197
+ .map(row => ({ key: row.key, name: row.label, unreceivedCny: row.unreceivedCny ?? row.unpaidCny ?? '0', orderCount: row.orderCount, latestOrderDate: null }))
198
+ .filter(row => Number(row.unreceivedCny) > 0)
199
+ .sort((a, b) => Number(b.unreceivedCny) - Number(a.unreceivedCny))
200
+ .slice(0, 5);
201
+ }
202
+ function supplierRiskRowsFromData(data) {
203
+ if (data.dashboard.supplierRiskTop5.length)
204
+ return data.dashboard.supplierRiskTop5.map(item => ({
205
+ key: item.supplierId,
206
+ name: item.supplierName,
207
+ unpaidCny: item.unpaidCny,
208
+ sanctionHitCount: item.sanctionHitCount,
209
+ productRiskHitCount: item.productRiskHitCount,
210
+ purchaseOrderCount: item.purchaseOrderCount
211
+ }));
212
+ return data.details.items
213
+ .map(row => ({
214
+ key: row.key,
215
+ name: row.label,
216
+ unpaidCny: row.unpaidCny ?? row.unreceivedCny ?? '0',
217
+ sanctionHitCount: 0,
218
+ productRiskHitCount: Number(row.productQuantity) > 0 ? 1 : 0,
219
+ purchaseOrderCount: row.orderCount
220
+ }))
221
+ .filter(row => Number(row.unpaidCny) > 0)
222
+ .sort((a, b) => Number(b.unpaidCny) - Number(a.unpaidCny))
223
+ .slice(0, 5);
224
+ }
225
+ function salesFileRowsFromData(data) {
226
+ if (data.dashboard.salesFileReviews.length)
227
+ return data.dashboard.salesFileReviews.map(item => ({
228
+ key: item.fileId,
229
+ fileName: item.fileName,
230
+ fileType: item.fileType,
231
+ orderNo: item.salesOrderNo,
232
+ reason: item.reason,
233
+ status: '待复核'
234
+ }));
235
+ return data.details.items
236
+ .filter(row => hasRiskAmount(row))
237
+ .slice(0, 5)
238
+ .map(row => ({ key: `review-${row.key}`, fileName: `${row.label}合规资料`, fileType: '业务单据', orderNo: row.key, reason: riskLabelForRow(row), status: '待复核' }));
239
+ }
240
+ function riskOrderRowsFromData(data) {
241
+ if (data.dashboard.riskOrders.length)
242
+ return data.dashboard.riskOrders.map(item => ({
243
+ key: item.orderKey,
244
+ orderNo: item.orderNo,
245
+ partyName: item.partyName,
246
+ riskType: item.riskType,
247
+ riskAmountCny: item.riskAmountCny,
248
+ status: item.status
249
+ }));
250
+ return data.details.items
251
+ .map(row => ({ key: `risk-${row.key}`, orderNo: row.key, partyName: row.label, riskType: riskLabelForRow(row), riskAmountCny: largestRiskAmount(row), status: '待复核' }))
252
+ .filter(row => Number(row.riskAmountCny) > 0)
253
+ .sort((a, b) => Number(b.riskAmountCny) - Number(a.riskAmountCny))
254
+ .slice(0, 5);
255
+ }
256
+ function findDetail(data, key, label) {
257
+ return data.details.items.find(row => row.key === key || row.label === label);
258
+ }
259
+ function estimatedGrossProfit(row) {
260
+ if (row.receivedCny || row.paidCny)
261
+ return new Decimal(row.receivedCny ?? row.amountCny).minus(row.paidCny ?? 0).toString();
262
+ return '0';
263
+ }
264
+ function riskStatusForAmount(value) {
265
+ const number = Number(value) || 0;
266
+ if (number <= 0)
267
+ return 'LOW';
268
+ return number >= 100_000 ? 'HIGH' : 'MEDIUM';
269
+ }
270
+ function hasRiskAmount(row) {
271
+ return Number(row.unreceivedCny ?? 0) > 0 || Number(row.unpaidCny ?? 0) > 0 || Number(row.unrefundedCny ?? 0) > 0;
272
+ }
273
+ function riskLabelForRow(row) {
274
+ const risks = [
275
+ Number(row.unreceivedCny ?? 0) > 0 ? '未收款' : '',
276
+ Number(row.unpaidCny ?? 0) > 0 ? '未付款' : '',
277
+ Number(row.unrefundedCny ?? 0) > 0 ? '未退税' : ''
278
+ ].filter(Boolean);
279
+ return risks.length ? risks.join('/') : '合规待复核';
280
+ }
281
+ function largestRiskAmount(row) {
282
+ return String(Math.max(Number(row.unreceivedCny ?? 0), Number(row.unpaidCny ?? 0), Number(row.unrefundedCny ?? 0)));
112
283
  }
113
284
  function EmptyText({ text }) {
114
285
  return _jsx("small", { className: "tcw-analytics-empty", children: text });
@@ -214,6 +385,26 @@ function detailColumns(mode, dimension) {
214
385
  return columns;
215
386
  }
216
387
  function dimensionLabel(dimension) { return dimension === 'SUPPLIER' ? '供应商' : dimension === 'CUSTOMER' ? '客户' : '商品名称'; }
388
+ function addMoney(...values) { return values.reduce((sum, value) => sum.plus(String(value ?? 0)), new Decimal(0)).toString(); }
389
+ function amountRatio(part, total, formatted = true) {
390
+ const denominator = Number(total) || 0;
391
+ const value = denominator > 0 ? (Number(part) || 0) / denominator : 0;
392
+ return formatted ? percent(value) : String(value);
393
+ }
394
+ function percentRatio(part, total) {
395
+ const denominator = Number(total) || 0;
396
+ const value = denominator > 0 ? (Number(part) || 0) / denominator : 0;
397
+ return `${(value * 100).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}%`;
398
+ }
399
+ function deltaLabel(current, previous) {
400
+ const now = Number(current ?? 0);
401
+ const before = Number(previous ?? 0);
402
+ if (!Number.isFinite(now) || !Number.isFinite(before) || before <= 0)
403
+ return '暂无环比';
404
+ const delta = (now - before) / before;
405
+ const arrow = delta >= 0 ? '↑' : '↓';
406
+ return `${arrow} ${Math.abs(delta * 100).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}%`;
407
+ }
217
408
  function money(value) { const number = Number(value ?? 0); return Number.isFinite(number) ? number.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) : '0.00'; }
218
409
  function compactMoney(value) {
219
410
  const number = Number(value ?? 0);