@chenchaolong/plugin-trade-compliance-workbench 1.0.48 → 1.0.50
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.
- package/dist/lib/entities/settings.entity.d.ts +2 -0
- package/dist/lib/entities/settings.entity.d.ts.map +1 -1
- package/dist/lib/entities/settings.entity.js +8 -0
- package/dist/lib/entities/settings.entity.js.map +1 -1
- package/dist/lib/remote-components/trade-compliance-workbench/app.css +1 -1
- package/dist/lib/remote-components/trade-compliance-workbench/app.js +4 -4
- package/dist/lib/remote-ui/analytics-contracts.d.ts +121 -145
- package/dist/lib/remote-ui/analytics-contracts.d.ts.map +1 -1
- package/dist/lib/remote-ui/analytics-contracts.js +8 -3
- package/dist/lib/remote-ui/analytics-contracts.js.map +1 -1
- package/dist/lib/remote-ui/app-source.js +130 -38
- package/dist/lib/remote-ui/app-source.js.map +1 -1
- package/dist/lib/remote-ui/model.d.ts.map +1 -1
- package/dist/lib/remote-ui/model.js +7 -43
- package/dist/lib/remote-ui/model.js.map +1 -1
- package/dist/lib/remote-ui/pages/analytics-charts.d.ts +12 -0
- package/dist/lib/remote-ui/pages/analytics-charts.d.ts.map +1 -0
- package/dist/lib/remote-ui/pages/analytics-charts.js +41 -0
- package/dist/lib/remote-ui/pages/analytics-charts.js.map +1 -0
- package/dist/lib/remote-ui/pages/analytics-dashboard.d.ts +11 -0
- package/dist/lib/remote-ui/pages/analytics-dashboard.d.ts.map +1 -0
- package/dist/lib/remote-ui/pages/analytics-dashboard.js +59 -0
- package/dist/lib/remote-ui/pages/analytics-dashboard.js.map +1 -0
- package/dist/lib/remote-ui/pages/analytics-detail-list.d.ts +13 -0
- package/dist/lib/remote-ui/pages/analytics-detail-list.d.ts.map +1 -0
- package/dist/lib/remote-ui/pages/analytics-detail-list.js +33 -0
- package/dist/lib/remote-ui/pages/analytics-detail-list.js.map +1 -0
- package/dist/lib/remote-ui/pages/analytics-format.d.ts +7 -0
- package/dist/lib/remote-ui/pages/analytics-format.d.ts.map +1 -0
- package/dist/lib/remote-ui/pages/analytics-format.js +31 -0
- package/dist/lib/remote-ui/pages/analytics-format.js.map +1 -0
- package/dist/lib/remote-ui/pages/analytics.d.ts +6 -9
- package/dist/lib/remote-ui/pages/analytics.d.ts.map +1 -1
- package/dist/lib/remote-ui/pages/analytics.js +77 -372
- package/dist/lib/remote-ui/pages/analytics.js.map +1 -1
- package/dist/lib/remote-ui/pages/catalogs.css +1 -3
- package/dist/lib/remote-ui/pages/catalogs.d.ts.map +1 -1
- package/dist/lib/remote-ui/pages/catalogs.js +0 -2
- package/dist/lib/remote-ui/pages/catalogs.js.map +1 -1
- package/dist/lib/remote-ui/pages/files-prototype.css +1 -1
- package/dist/lib/remote-ui/pages/files.d.ts.map +1 -1
- package/dist/lib/remote-ui/pages/files.js +9 -2
- package/dist/lib/remote-ui/pages/files.js.map +1 -1
- package/dist/lib/remote-ui/pages/sales-files.css +6 -0
- package/dist/lib/remote-ui/pages/sales.d.ts.map +1 -1
- package/dist/lib/remote-ui/pages/sales.js +1 -2
- package/dist/lib/remote-ui/pages/sales.js.map +1 -1
- package/dist/lib/remote-ui/pages/settings.d.ts +1 -0
- package/dist/lib/remote-ui/pages/settings.d.ts.map +1 -1
- package/dist/lib/remote-ui/pages/settings.js +5 -2
- package/dist/lib/remote-ui/pages/settings.js.map +1 -1
- package/dist/lib/remote-ui/styles.css +160 -249
- package/dist/lib/runtime-validation.d.ts.map +1 -1
- package/dist/lib/runtime-validation.js +6 -35
- package/dist/lib/runtime-validation.js.map +1 -1
- package/dist/lib/sales-file.service.js +1 -1
- package/dist/lib/sales-file.service.js.map +1 -1
- package/dist/lib/schema-maintenance.d.ts +10 -0
- package/dist/lib/schema-maintenance.d.ts.map +1 -0
- package/dist/lib/schema-maintenance.js +34 -0
- package/dist/lib/schema-maintenance.js.map +1 -0
- package/dist/lib/types.d.ts +72 -63
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/types.js +2 -3
- package/dist/lib/types.js.map +1 -1
- package/dist/lib/workbench-view.provider.d.ts +12 -62
- package/dist/lib/workbench-view.provider.d.ts.map +1 -1
- package/dist/lib/workbench-view.provider.js +20 -4
- package/dist/lib/workbench-view.provider.js.map +1 -1
- package/dist/lib/workbench.middleware.d.ts +8 -8
- package/dist/lib/workbench.plugin.d.ts +3 -3
- package/dist/lib/workbench.plugin.d.ts.map +1 -1
- package/dist/lib/workbench.plugin.js +6 -6
- package/dist/lib/workbench.plugin.js.map +1 -1
- package/dist/lib/workbench.service.d.ts +4 -33
- package/dist/lib/workbench.service.d.ts.map +1 -1
- package/dist/lib/workbench.service.js +236 -265
- package/dist/lib/workbench.service.js.map +1 -1
- package/package.json +1 -1
- package/dist/lib/legacy-schema-cleanup.d.ts +0 -9
- package/dist/lib/legacy-schema-cleanup.d.ts.map +0 -1
- package/dist/lib/legacy-schema-cleanup.js +0 -28
- package/dist/lib/legacy-schema-cleanup.js.map +0 -1
- package/dist/lib/remote-ui/pages/analytics-prototype.css +0 -307
|
@@ -1,391 +1,96 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs
|
|
2
|
-
import { Decimal } from 'decimal.js';
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
2
|
import React from 'react';
|
|
4
|
-
import {
|
|
5
|
-
import '
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
const committed = committedQuery ?? fallbackQuery;
|
|
14
|
-
const [mode, setMode] = React.useState(committed.mode);
|
|
15
|
-
const [dimension, setDimension] = React.useState(committed.dimension);
|
|
16
|
-
const [filters, setFilters] = React.useState(() => filterStateFromQuery(committed));
|
|
3
|
+
import { RiskNotice } from '../components/data-grid.js';
|
|
4
|
+
import { createLatestRequestGate } from '../model.js';
|
|
5
|
+
import { AnalyticsDashboard } from './analytics-dashboard.js';
|
|
6
|
+
import { AnalyticsDetailList } from './analytics-detail-list.js';
|
|
7
|
+
export { AnalyticsDetailList } from './analytics-detail-list.js';
|
|
8
|
+
const emptyDetailPage = () => ({ items: [], page: 1, pageSize: 20, total: 0, pageCount: 1 });
|
|
9
|
+
export function AnalyticsPage({ data, committedQuery, detailRefreshKey = 0, busy, error, onQuery, onDetailQuery, onDetailAction, onExportXlsx }) {
|
|
10
|
+
const committed = committedQuery ?? { filters: { orderDate: yearRange(data.currentYear) }, page: 1, pageSize: 20 };
|
|
11
|
+
const [filters, setFilters] = React.useState(committed.filters);
|
|
17
12
|
const [period, setPeriod] = React.useState(() => periodFromQuery(committed, data.currentYear));
|
|
18
|
-
const [
|
|
19
|
-
const [
|
|
20
|
-
const [
|
|
13
|
+
const [customVisible, setCustomVisible] = React.useState(false);
|
|
14
|
+
const [selection, setSelection] = React.useState(null);
|
|
15
|
+
const [detailPage, setDetailPage] = React.useState(emptyDetailPage);
|
|
16
|
+
const [detailBusy, setDetailBusy] = React.useState(false);
|
|
17
|
+
const [detailError, setDetailError] = React.useState(null);
|
|
18
|
+
const detailRequestGateRef = React.useRef(createLatestRequestGate());
|
|
19
|
+
const appliedDetailRefreshKeyRef = React.useRef(detailRefreshKey);
|
|
21
20
|
React.useEffect(() => {
|
|
22
|
-
|
|
23
|
-
setDimension(committed.dimension);
|
|
24
|
-
setFilters(filterStateFromQuery(committed));
|
|
21
|
+
setFilters(committed.filters);
|
|
25
22
|
setPeriod(periodFromQuery(committed, data.currentYear));
|
|
26
|
-
}, [committed]);
|
|
27
|
-
const
|
|
28
|
-
const queryFor = (nextMode, nextDimension, page, nextFilters) => nextMode === 'PURCHASE'
|
|
29
|
-
? { mode: nextMode, dimension: nextDimension === 'SUPPLIER' ? 'SUPPLIER' : 'PRODUCT', filters: purchaseAnalyticsFilters(nextFilters), page, pageSize: data.details.pageSize || 20 }
|
|
30
|
-
: { mode: nextMode, dimension: nextDimension === 'CUSTOMER' ? 'CUSTOMER' : 'PRODUCT', filters: salesAnalyticsFilters(nextFilters), page, pageSize: data.details.pageSize || 20 };
|
|
31
|
-
const runQuery = (page = 1, nextMode = mode, nextDimension = dimension) => onQuery(queryFor(nextMode, nextDimension, page, typedFilters));
|
|
23
|
+
}, [committed, data.currentYear]);
|
|
24
|
+
const queryDashboard = (nextFilters) => onQuery({ filters: nextFilters, page: 1, pageSize: committed.pageSize });
|
|
32
25
|
const applyPeriod = (nextPeriod) => {
|
|
33
|
-
const
|
|
34
|
-
const nextFilters = { ...typedFilters, orderDate };
|
|
26
|
+
const nextFilters = { orderDate: nextPeriod === 'year' ? yearRange(data.currentYear) : monthRange() };
|
|
35
27
|
setPeriod(nextPeriod);
|
|
36
|
-
|
|
37
|
-
|
|
28
|
+
setCustomVisible(false);
|
|
29
|
+
setFilters(nextFilters);
|
|
30
|
+
void queryDashboard(nextFilters);
|
|
38
31
|
};
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
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' });
|
|
44
|
-
setActiveDialog(null);
|
|
32
|
+
const applyCustom = () => {
|
|
33
|
+
setPeriod('custom');
|
|
34
|
+
setCustomVisible(false);
|
|
35
|
+
void queryDashboard(filters);
|
|
45
36
|
};
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const monthlyDeltas = monthlyDeltaLabels(dashboard.monthlyTrend);
|
|
64
|
-
const kpis = [
|
|
65
|
-
{ label: '采购总额', value: compactMoney(dashboard.kpis.purchaseTotalCny), subtext: monthlyDeltas.purchase, fullValue: `${money(dashboard.kpis.purchaseTotalCny)} CNY`, icon: 'ri-shopping-cart-line', tone: 'money' },
|
|
66
|
-
{ label: '销售总额', value: compactMoney(dashboard.kpis.salesTotalCny), subtext: monthlyDeltas.sales, fullValue: `${money(dashboard.kpis.salesTotalCny)} CNY`, icon: 'ri-bar-chart-line', tone: 'money' },
|
|
67
|
-
{ 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' },
|
|
68
|
-
{ 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' },
|
|
69
|
-
{ 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' },
|
|
70
|
-
{ 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' },
|
|
71
|
-
{ 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' },
|
|
72
|
-
{ label: '预计毛利率', value: percent(dashboard.kpis.estimatedGrossProfitRate), subtext: `毛利 ${compactMoney(dashboard.kpis.estimatedGrossProfitCny)}`, icon: 'ri-funds-box-line', tone: 'success' },
|
|
73
|
-
{ label: '合规待办数', value: `${dashboard.kpis.complianceTodoCount} 项`, subtext: `队列 ${dashboard.todos.length} 类`, icon: 'ri-shield-check-line', tone: 'warn' },
|
|
74
|
-
{ 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' }
|
|
75
|
-
];
|
|
76
|
-
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: [_jsxs("div", { className: "tcw-analytics-layout-row tcw-analytics-layout-row--charts", children: [_jsx(Panel, { title: "\u5408\u89C4\u5F85\u529E\u961F\u5217", icon: "ri-list-check-3", className: "tcw-analytics-panel--queue", children: _jsx(TodoQueue, { rows: dashboard.todos, onOpen: todo => onInfo({ title: `${todo.label}列表`, rows: todoDetailRows(todo) }) }) }), _jsxs(Panel, { title: "\u6708\u5EA6\u7ECF\u8425\u8D8B\u52BF", icon: "ri-bar-chart-box-line", className: "tcw-analytics-panel--trend", children: [_jsxs("div", { className: "tcw-analytics-chart-legend", children: [_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"] })] }), _jsx(TrendLineChart, { points: dashboard.monthlyTrend })] }), _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, onOpen: item => onInfo({ title: `${item.label}明细`, rows: [{ label: item.label, value: `${item.count} 项${item.amountCny ? ` · ${money(item.amountCny)} CNY` : ''}`, actionLabel: '查看' }] }) }) })] }), _jsxs("div", { className: "tcw-analytics-layout-row tcw-analytics-layout-row--top5", children: [_jsx(Panel, { title: "\u5BA2\u6237\u4EF7\u503C TOP5", icon: "ri-vip-crown-line", className: "tcw-analytics-panel--customer", children: _jsx(CustomerValue, { rows: customerValueRows }) }), _jsx(Panel, { title: "\u672A\u6536\u98CE\u9669 TOP5", icon: "ri-bank-line", className: "tcw-analytics-panel--receivable", children: _jsx(ReceivableRisk, { rows: receivableRiskRows }) }), _jsx(Panel, { title: "\u4F9B\u5E94\u5546\u98CE\u9669 TOP5", icon: "ri-truck-line", className: "tcw-analytics-panel--supplier", children: _jsx(SupplierRisk, { rows: supplierRiskRows }) })] }), _jsxs("div", { className: "tcw-analytics-layout-row tcw-analytics-layout-row--bottom", children: [_jsx(Panel, { title: "\u9500\u552E\u6587\u4EF6\u590D\u6838", icon: "ri-file-search-line", className: "tcw-analytics-panel--files", children: _jsx(SalesFileReviews, { rows: salesFileRows, onOpen: row => onInfo({ title: `${row.fileName}复核`, rows: [{ label: row.fileName, value: `${row.fileType} · ${row.orderNo} · ${row.status}`, actionLabel: '复核' }] }) }) }), _jsx(Panel, { title: "\u98CE\u9669\u8BA2\u5355\u660E\u7EC6", icon: "ri-file-warning-line", className: "tcw-analytics-panel--risk-orders", children: _jsx(RiskOrders, { rows: riskOrderRows, onOpen: row => onInfo({ title: `${row.orderNo}风险明细`, rows: [{ label: row.orderNo, value: `${row.partyName} · ${row.riskType} · ${money(row.riskAmountCny)} CNY`, actionLabel: '跟进' }] }) }) }), _jsx(Panel, { title: "\u6307\u6807\u6570\u636E\u6765\u6E90", icon: "ri-database-2-line", className: "tcw-analytics-panel--source", children: _jsx(DataSourceList, { rows: dashboard.dataSources, onOpen: source => onInfo({ title: source.label, rows: [{ label: source.label, value: source.description, actionLabel: '查看' }] }) }) })] })] })] });
|
|
77
|
-
}
|
|
78
|
-
function Panel({ title, icon, className = '', action, children }) {
|
|
79
|
-
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] });
|
|
80
|
-
}
|
|
81
|
-
function MoneyStructure({ kpis }) {
|
|
82
|
-
const items = [
|
|
83
|
-
{ label: '已收款', value: kpis.receivedCny, color: '#16a34a' },
|
|
84
|
-
{ label: '未收款', value: kpis.unreceivedCny, color: '#f97316' },
|
|
85
|
-
{ label: '已付款', value: kpis.paidCny, color: '#1769ff' },
|
|
86
|
-
{ label: '未付款', value: kpis.unpaidCny, color: '#ff7a00' },
|
|
87
|
-
{ label: '已退税', value: kpis.refundedCny, color: '#26b6c8' },
|
|
88
|
-
{ label: '未退税', value: kpis.unrefundedCny, color: '#ff4d55' }
|
|
89
|
-
];
|
|
90
|
-
const total = Math.max(items.reduce((sum, item) => sum + (Number(item.value) || 0), 0), 1);
|
|
91
|
-
const max = Math.max(...items.map(item => Number(item.value) || 0), 1);
|
|
92
|
-
return _jsxs("div", { className: "tcw-analytics-money-structure", children: [_jsxs("div", { className: "tcw-analytics-money-head", children: [_jsx("span", { children: "\u91D1\u989D\uFF08\u4E07\u5143\uFF09" }), _jsx("span", { children: "\u5360\u6BD4" })] }), items.map(item => _jsxs("div", { className: "tcw-analytics-money-row", children: [_jsxs("span", { children: [_jsx("i", { style: { background: item.color } }), item.label] }), _jsx("em", { children: _jsx("i", { style: { width: `${Math.max(5, (Number(item.value) || 0) / max * 100)}%`, background: item.color } }) }), _jsx("b", { children: compactNumber(Number(item.value) / 10000) }), _jsx("strong", { children: percentRatio(Number(item.value) || 0, total) })] }, item.label))] });
|
|
93
|
-
}
|
|
94
|
-
function RiskDistribution({ items, onOpen }) {
|
|
95
|
-
const total = items.reduce((sum, item) => sum + item.count, 0);
|
|
96
|
-
const colors = ['#1769ff', '#26b6c8', '#ff9f1c', '#8b5cf6', '#5ac85a', '#ff4d55'];
|
|
97
|
-
const segments = items.length ? items.map((item, index) => ({ item, color: colors[index % colors.length] })) : [{ item: { key: 'empty', label: '暂无风险', count: 1, amountCny: '0' }, color: '#e7edf5' }];
|
|
98
|
-
let cursor = 0;
|
|
99
|
-
const conic = segments.map(({ item, color }) => {
|
|
100
|
-
const start = cursor;
|
|
101
|
-
cursor += ((total > 0 ? item.count / total : 1) * 100);
|
|
102
|
-
return `${color} ${start.toFixed(2)}% ${cursor.toFixed(2)}%`;
|
|
103
|
-
}).join(', ');
|
|
104
|
-
const style = { '--risk-donut': `conic-gradient(${conic})` };
|
|
105
|
-
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("button", { type: "button", onClick: () => onOpen(item), children: [_jsxs("dt", { children: [_jsx("i", { className: "ri-checkbox-blank-circle-fill", style: { color: colors[index % colors.length] }, "aria-hidden": "true" }), item.label] }), _jsxs("dd", { children: [item.count, " (", percentRatio(item.count, total), ")"] })] }, item.key)) })] });
|
|
106
|
-
}
|
|
107
|
-
function CustomerValue({ rows }) {
|
|
108
|
-
return rows.length ? _jsxs("table", { className: "tcw-analytics-dashboard-table tcw-analytics-table--customer", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", {}), _jsx("th", { children: "\u5BA2\u6237\u540D\u79F0" }), _jsx("th", { children: "\u9500\u552E\u989D\uFF08\u4EBA\u6C11\u5E01\uFF09" }), _jsx("th", { children: "\u9884\u8BA1\u6BDB\u5229\uFF08\u4EBA\u6C11\u5E01\uFF09" }), _jsx("th", { children: "\u6BDB\u5229\u7387" }), _jsx("th", { children: "\u672A\u6536\u6B3E\u91D1\u989D\uFF08\u4EBA\u6C11\u5E01\uFF09" }), _jsx("th", { children: "\u98CE\u9669\u72B6\u6001" })] }) }), _jsx("tbody", { children: rows.map((row, index) => _jsxs("tr", { children: [_jsx("td", { children: index + 1 }), _jsx("td", { title: row.name, children: row.name }), _jsx("td", { children: money(row.salesAmountCny) }), _jsx("td", { children: money(row.estimatedGrossProfitCny) }), _jsx("td", { children: percent(row.grossProfitRate) }), _jsx("td", { children: money(row.unreceivedCny) }), _jsx("td", { children: _jsx(RiskBadge, { status: row.riskStatus }) })] }, row.key)) })] }) : _jsx(EmptyText, { text: "\u6682\u65E0\u5BA2\u6237\u4EF7\u503C\u6392\u540D" });
|
|
109
|
-
}
|
|
110
|
-
function ReceivableRisk({ rows }) {
|
|
111
|
-
return rows.length ? _jsxs("table", { className: "tcw-analytics-dashboard-table tcw-analytics-table--receivable", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "\u5BA2\u6237\u540D\u79F0" }), _jsx("th", { children: "\u672A\u6536\u6B3E\u91D1\u989D\uFF08\u4EBA\u6C11\u5E01\uFF09" }), _jsx("th", { children: "\u903E\u671F\u5929\u6570" }), _jsx("th", { children: "\u5173\u8054\u9500\u552E\u8BA2\u5355\u6570" }), _jsx("th", { children: "\u6700\u8FD1\u6536\u6B3E\u65E5\u671F" })] }) }), _jsx("tbody", { children: rows.map((row, index) => _jsxs("tr", { children: [_jsx("td", { title: row.name, children: row.name }), _jsx("td", { children: money(row.unreceivedCny) }), _jsx("td", { children: 67 - index * 9 }), _jsx("td", { children: row.orderCount }), _jsx("td", { children: shortDate(row.latestOrderDate) })] }, row.key)) })] }) : _jsx(EmptyText, { text: "\u6682\u65E0\u672A\u6536\u98CE\u9669" });
|
|
112
|
-
}
|
|
113
|
-
function SupplierRisk({ rows }) {
|
|
114
|
-
return rows.length ? _jsxs("table", { className: "tcw-analytics-dashboard-table tcw-analytics-table--supplier", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "\u4F9B\u5E94\u5546\u540D\u79F0" }), _jsx("th", { children: "\u672A\u4ED8\u6B3E\u91D1\u989D\uFF08\u4EBA\u6C11\u5E01\uFF09" }), _jsx("th", { children: "\u547D\u4E2D\u5236\u88C1\u72B6\u6001" }), _jsx("th", { children: "\u5173\u8054\u91C7\u8D2D\u8BA2\u5355\u6570" }), _jsx("th", { children: "\u5546\u54C1\u98CE\u9669\u547D\u4E2D\u6570" })] }) }), _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: _jsx("span", { className: row.sanctionHitCount > 0 ? 'tcw-analytics-status is-danger' : 'tcw-analytics-status is-ok', children: row.sanctionHitCount > 0 ? '命中制裁' : '未命中' }) }), _jsx("td", { children: row.purchaseOrderCount }), _jsx("td", { children: row.productRiskHitCount })] }, row.key)) })] }) : _jsx(EmptyText, { text: "\u6682\u65E0\u4F9B\u5E94\u5546\u98CE\u9669" });
|
|
115
|
-
}
|
|
116
|
-
function SalesFileReviews({ rows, onOpen }) {
|
|
117
|
-
return rows.length ? _jsxs("table", { className: "tcw-analytics-dashboard-table tcw-analytics-table--files", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "\u6587\u4EF6\u7F16\u53F7" }), _jsx("th", { children: "\u6587\u4EF6\u7C7B\u578B" }), _jsx("th", { children: "\u5173\u8054\u9500\u552E\u8BA2\u5355" }), _jsx("th", { children: "\u5F02\u5E38\u539F\u56E0" }), _jsx("th", { children: "\u64CD\u4F5C" })] }) }), _jsx("tbody", { children: rows.map(row => _jsxs("tr", { children: [_jsx("td", { title: row.fileName, children: row.fileName }), _jsx("td", { children: row.fileType }), _jsx("td", { children: row.orderNo }), _jsx("td", { title: row.reason, children: row.reason }), _jsx("td", { children: _jsxs("button", { type: "button", className: "tcw-analytics-row-action", onClick: () => onOpen(row), children: [_jsx("i", { className: "ri-file-search-line", "aria-hidden": "true" }), "\u590D\u6838"] }) })] }, row.key)) })] }) : _jsx(EmptyText, { text: "\u6682\u65E0\u9500\u552E\u6587\u4EF6\u590D\u6838" });
|
|
118
|
-
}
|
|
119
|
-
function RiskOrders({ rows, onOpen }) {
|
|
120
|
-
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\u7F16\u53F7" }), _jsx("th", { children: "\u5BA2\u6237" }), _jsx("th", { children: "\u98CE\u9669\u7C7B\u578B" }), _jsx("th", { children: "\u98CE\u9669\u91D1\u989D\uFF08\u4EBA\u6C11\u5E01\uFF09" }), _jsx("th", { children: "\u72B6\u6001" }), _jsx("th", { children: "\u64CD\u4F5C" })] }) }), _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", { className: "tcw-analytics-status is-warn", children: row.status }) }), _jsx("td", { children: _jsxs("button", { type: "button", className: "tcw-analytics-row-action", onClick: () => onOpen(row), children: [_jsx("i", { className: "ri-eye-line", "aria-hidden": "true" }), "\u8DDF\u8FDB"] }) })] }, row.key)) })] }) : _jsx(EmptyText, { text: "\u6682\u65E0\u98CE\u9669\u8BA2\u5355" });
|
|
121
|
-
}
|
|
122
|
-
function DataSourceList({ rows, onOpen }) {
|
|
123
|
-
const icons = ['ri-file-list-3-line', 'ri-file-chart-line', 'ri-file-shield-2-line', 'ri-file-warning-line', 'ri-file-check-line', 'ri-file-copy-2-line', 'ri-file-paper-2-line', 'ri-file-text-line', 'ri-file-list-2-line', 'ri-refund-2-line', 'ri-file-search-line', 'ri-exchange-cny-line'];
|
|
124
|
-
return _jsx("div", { className: "tcw-analytics-source-list", children: rows.map((source, index) => _jsxs("button", { type: "button", title: source.description, onClick: () => onOpen(source), children: [_jsx("i", { className: icons[index % icons.length], "aria-hidden": "true" }), _jsx("strong", { children: source.label })] }, source.label)) });
|
|
125
|
-
}
|
|
126
|
-
function TodoQueue({ rows, onOpen }) {
|
|
127
|
-
return rows.length ? _jsxs("div", { className: "tcw-analytics-todo-board", children: [_jsxs("div", { className: "tcw-analytics-todo-head", children: [_jsx("span", { children: "\u5F85\u529E\u4E8B\u9879" }), _jsx("span", { children: "\u6570\u91CF" }), _jsx("span", { children: "\u5173\u8054\u91D1\u989D" }), _jsx("span", { children: "\u4F18\u5148\u7EA7" })] }), rows.map((row, index) => _jsxs("button", { type: "button", className: "tcw-analytics-todo-row", onClick: () => onOpen(row), children: [_jsxs("span", { title: row.description, children: [_jsx("i", { className: "ri-file-list-2-line", "aria-hidden": "true" }), row.label] }), _jsx("b", { children: row.count }), _jsx("em", { children: row.amountCny ? `¥${money(row.amountCny)}` : '' }), _jsx(RiskBadge, { status: row.priority === 'high' ? 'HIGH' : row.priority === 'medium' ? 'MEDIUM' : 'LOW' })] }, row.key))] }) : _jsx(EmptyText, { text: "\u6682\u65E0\u5408\u89C4\u5F85\u529E" });
|
|
128
|
-
}
|
|
129
|
-
function RiskBadge({ status }) {
|
|
130
|
-
const label = status === 'HIGH' ? '高' : status === 'MEDIUM' ? '中' : status === 'LOW' ? '低' : status;
|
|
131
|
-
return _jsx("span", { className: `tcw-analytics-risk-badge is-${status.toLocaleLowerCase()}`, children: label });
|
|
132
|
-
}
|
|
133
|
-
function TrendLineChart({ points }) {
|
|
134
|
-
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' });
|
|
135
|
-
const max = Math.max(...months.flatMap(point => [Number(point.purchaseAmountCny) || 0, Number(point.salesAmountCny) || 0, Number(point.estimatedGrossProfitCny) || 0, Number(point.receivedCny) || 0]), 1);
|
|
136
|
-
const roundedMax = Math.max(1, Math.ceil(max / 1000 / 5000) * 5000);
|
|
137
|
-
const yTicks = Array.from({ length: 5 }, (_, index) => roundedMax - (roundedMax / 4) * index);
|
|
138
|
-
const coords = (key) => months.map((point, index) => {
|
|
139
|
-
const x = 56 + index * (516 / 11);
|
|
140
|
-
const y = 132 - ((Number(point[key]) || 0) / max) * 104;
|
|
141
|
-
return { x, y };
|
|
142
|
-
});
|
|
143
|
-
const line = (key) => coords(key).map(point => `${point.x.toFixed(1)},${point.y.toFixed(1)}`).join(' ');
|
|
144
|
-
return _jsxs("div", { className: "tcw-analytics-line-chart", children: [_jsx("span", { className: "tcw-analytics-trend-unit", children: "\u91D1\u989D\uFF08\u4E07\u5143\uFF09" }), _jsxs("svg", { viewBox: "0 0 600 170", 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, index) => _jsxs(React.Fragment, { children: [_jsx("line", { x1: "56", x2: "576", y1: y, y2: y }), _jsx("text", { className: "tcw-analytics-trend-axis", x: "42", y: y + 4, children: formatChartTick(yTicks[index]) })] }, 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') }), ['salesAmountCny', 'purchaseAmountCny', 'estimatedGrossProfitCny', 'receivedCny'].flatMap(key => coords(key).map((point, index) => _jsx("circle", { className: key === 'salesAmountCny' ? 'is-sales' : key === 'purchaseAmountCny' ? 'is-purchase' : key === 'receivedCny' ? 'is-received' : 'is-profit', cx: point.x, cy: point.y, r: "3" }, `${key}-${index}`))), months.map((point, index) => _jsxs("text", { x: 56 + index * (516 / 11), y: "156", children: [point.month, "\u6708"] }, point.month))] })] });
|
|
145
|
-
}
|
|
146
|
-
function monthlyDeltaLabels(points) {
|
|
147
|
-
const sorted = [...points].sort((a, b) => a.month - b.month);
|
|
148
|
-
let latestIndex = -1;
|
|
149
|
-
for (let index = sorted.length - 1; index >= 0; index -= 1) {
|
|
150
|
-
const point = sorted[index];
|
|
151
|
-
if (Number(point.purchaseAmountCny) > 0 || Number(point.salesAmountCny) > 0 || Number(point.receivedCny) > 0) {
|
|
152
|
-
latestIndex = index;
|
|
153
|
-
break;
|
|
37
|
+
const loadDetail = React.useCallback(async (nextSelection, page, pageSize) => {
|
|
38
|
+
const requestId = detailRequestGateRef.current.begin();
|
|
39
|
+
setDetailBusy(true);
|
|
40
|
+
setDetailError(null);
|
|
41
|
+
try {
|
|
42
|
+
let result = await onDetailQuery({ kind: nextSelection.kind, categoryKey: nextSelection.categoryKey, filters, page, pageSize });
|
|
43
|
+
if (page > result.pageCount)
|
|
44
|
+
result = await onDetailQuery({ kind: nextSelection.kind, categoryKey: nextSelection.categoryKey, filters, page: result.pageCount, pageSize });
|
|
45
|
+
if (!detailRequestGateRef.current.commit(requestId))
|
|
46
|
+
return;
|
|
47
|
+
setDetailPage(result);
|
|
48
|
+
}
|
|
49
|
+
catch (cause) {
|
|
50
|
+
if (!detailRequestGateRef.current.commit(requestId))
|
|
51
|
+
return;
|
|
52
|
+
setDetailError(cause instanceof Error ? cause.message : '明细加载失败');
|
|
53
|
+
setDetailPage({ items: [], page, pageSize, total: 0, pageCount: 1 });
|
|
154
54
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
55
|
+
finally {
|
|
56
|
+
detailRequestGateRef.current.finish(requestId);
|
|
57
|
+
if (detailRequestGateRef.current.activeCount() === 0)
|
|
58
|
+
setDetailBusy(false);
|
|
59
|
+
}
|
|
60
|
+
}, [filters, onDetailQuery]);
|
|
61
|
+
const openDetail = nextSelection => {
|
|
62
|
+
setSelection(nextSelection);
|
|
63
|
+
setDetailPage(emptyDetailPage());
|
|
64
|
+
void loadDetail(nextSelection, 1, 20);
|
|
163
65
|
};
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}
|
|
177
|
-
function
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
const total = data.details.items.reduce((sum, row) => sum.plus(row.amountCny), new Decimal(0)).toFixed(2);
|
|
182
|
-
return _jsxs("section", { className: `tcw-analytics-drilldown is-${drilldown.tone}`, "aria-label": "\u4E0B\u94BB\u660E\u7EC6", children: [_jsxs("header", { children: [_jsx("strong", { children: drilldown.title }), _jsxs("button", { type: "button", onClick: onClose, children: [_jsx("i", { className: "ri-close-line", "aria-hidden": "true" }), "\u5173\u95ED"] })] }), _jsxs("div", { className: "tcw-analytics-drilldown-metrics", children: [_jsxs("article", { children: [_jsx("span", { children: "\u4E0B\u94BB\u660E\u7EC6" }), _jsx("strong", { children: data.details.total })] }), _jsxs("article", { children: [_jsx("span", { children: "\u5F53\u524D\u9875\u8BA2\u5355\u6570" }), _jsx("strong", { children: data.details.items.reduce((sum, row) => sum + row.orderCount, 0) })] }), _jsxs("article", { children: [_jsx("span", { children: "\u5F53\u524D\u9875\u91D1\u989D\uFF08CNY\uFF09" }), _jsx("strong", { children: money(total) })] })] })] });
|
|
183
|
-
}
|
|
184
|
-
function AnalyticsDialogs({ activeDialog, mode, dimension, filters, data, chartType, exportScope, exportFormat, exportFields, onFilterChange, onApplyFilters, onResetFilters, onClose, onDialog, onChartType, onExportScope, onExportFormat, onToggleExportField, onExport }) {
|
|
185
|
-
const open = activeDialog !== null;
|
|
186
|
-
return _jsxs("div", { className: "tcw-analytics-modal-deck", hidden: !open, "aria-hidden": !open, children: [_jsx(AnalyticsModal, { title: "\u7B5B\u9009\u6761\u4EF6", active: activeDialog === 'filter', onClose: onClose, footer: _jsxs(_Fragment, { children: [_jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", onClick: onResetFilters, children: [_jsx("i", { className: "ri-restart-line", "aria-hidden": "true" }), "\u91CD\u7F6E"] }), _jsxs("button", { type: "button", className: "tcw-button tcw-button--primary", onClick: onApplyFilters, children: [_jsx("i", { className: "ri-check-line", "aria-hidden": "true" }), "\u786E\u5B9A"] })] }), children: _jsxs("div", { className: "tcw-analytics-modal-grid", children: [_jsx("div", { className: "tcw-analytics-quick-range", "aria-label": "\u7EDF\u8BA1\u65F6\u95F4", children: ['今日', '近7天', '近30天', '本月'].map((label, index) => _jsxs("button", { type: "button", className: index === 2 ? 'is-active' : undefined, onClick: () => onFilterChange('orderDate', quickRange(index)), children: [_jsx("i", { className: "ri-calendar-line", "aria-hidden": "true" }), label] }, label)) }), _jsx(ModalRange, { label: "\u4E0B\u5355\u65E5\u671F", value: filters.orderDate, onChange: value => onFilterChange('orderDate', value) }), _jsx(ModalText, { label: mode === 'PURCHASE' ? '供应商' : '客户', value: String(filters[mode === 'PURCHASE' ? 'supplier' : 'customer'] ?? ''), onChange: value => onFilterChange(mode === 'PURCHASE' ? 'supplier' : 'customer', value) }), _jsx(ModalText, { label: "\u5546\u54C1\u540D\u79F0", value: String(filters.productName ?? ''), onChange: value => onFilterChange('productName', value) }), _jsx(ModalText, { label: "\u6D77\u5173\u7F16\u7801", value: String(filters.hsCode ?? ''), onChange: value => onFilterChange('hsCode', value) }), ['客户类型', '国家/地区', '产品类别', '风险等级', '审核状态', '金额范围'].map(label => _jsxs("label", { className: "tcw-form-field", children: [_jsx("span", { children: label }), _jsx("input", { disabled: true, placeholder: "\u540E\u7AEF\u7B5B\u9009\u5B57\u6BB5\u5F85\u63A5\u5165" })] }, label))] }) }), _jsx(AnalyticsModal, { title: "\u5BFC\u51FA\u6570\u636E", active: activeDialog === 'export', onClose: onClose, footer: _jsxs(_Fragment, { children: [_jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", onClick: onClose, children: [_jsx("i", { className: "ri-close-line", "aria-hidden": "true" }), "\u53D6\u6D88"] }), _jsxs("button", { type: "button", className: "tcw-button tcw-button--primary", disabled: exportFormat === 'CSV', onClick: onExport, children: [_jsx("i", { className: "ri-download-2-line", "aria-hidden": "true" }), "\u5BFC\u51FA"] })] }), children: _jsxs("div", { className: "tcw-analytics-export-panel", children: [_jsxs("fieldset", { children: [_jsx("legend", { children: "\u5BFC\u51FA\u8303\u56F4" }), _jsxs("label", { children: [_jsx("input", { type: "radio", checked: exportScope === 'page', onChange: () => onExportScope('page') }), "\u5F53\u524D\u9875\u6570\u636E"] }), _jsxs("label", { children: [_jsx("input", { type: "radio", checked: exportScope === 'all', onChange: () => onExportScope('all') }), "\u5168\u90E8\u6570\u636E\uFF08\u5171 ", data.details.total, " \u6761\uFF09"] })] }), _jsxs("fieldset", { children: [_jsx("legend", { children: "\u5BFC\u51FA\u5B57\u6BB5" }), _jsx("div", { className: "tcw-analytics-export-fields", children: analyticsExportFields.map(field => _jsxs("label", { children: [_jsx("input", { type: "checkbox", checked: exportFields.has(field.key), onChange: () => onToggleExportField(field.key) }), field.label] }, field.key)) })] }), _jsxs("fieldset", { children: [_jsx("legend", { children: "\u5BFC\u51FA\u683C\u5F0F" }), _jsxs("label", { children: [_jsx("input", { type: "radio", checked: exportFormat === 'XLSX', onChange: () => onExportFormat('XLSX') }), "Excel\uFF08.xlsx\uFF09"] }), _jsxs("label", { children: [_jsx("input", { type: "radio", checked: exportFormat === 'CSV', onChange: () => onExportFormat('CSV') }), "CSV\uFF08.csv\uFF09"] })] })] }) }), _jsx(AnalyticsModal, { title: "\u56FE\u8868\u914D\u7F6E", active: activeDialog === 'chart', onClose: onClose, footer: _jsxs("button", { type: "button", className: "tcw-button tcw-button--primary", onClick: onClose, children: [_jsx("i", { className: "ri-check-line", "aria-hidden": "true" }), "\u786E\u5B9A"] }), children: _jsxs("div", { className: "tcw-analytics-chart-config", children: [[['bar', '柱状图', 'ri-bar-chart-fill'], ['line', '折线图', 'ri-line-chart-line'], ['donut', '饼图', 'ri-pie-chart-line']].map(([type, label, icon]) => _jsxs("button", { type: "button", className: chartType === type ? 'is-active' : undefined, onClick: () => onChartType(type), children: [_jsx("i", { className: icon, "aria-hidden": "true" }), label] }, type)), _jsxs("label", { className: "tcw-form-field", children: [_jsx("span", { children: "\u7EF4\u5EA6\uFF08X\u8F74\uFF09" }), _jsx("input", { readOnly: true, value: dimensionLabel(dimension) })] }), _jsxs("label", { className: "tcw-form-field", children: [_jsx("span", { children: "\u6307\u6807\uFF08Y\u8F74\uFF09" }), _jsx("input", { readOnly: true, value: "\u8BA2\u5355\u91D1\u989D\uFF08CNY\uFF09" })] })] }) }), _jsx(AnalyticsModal, { title: "\u4FDD\u5B58\u62A5\u8868", active: activeDialog === 'save', onClose: onClose, footer: _jsxs("button", { type: "button", className: "tcw-button tcw-button--primary", onClick: onClose, children: [_jsx("i", { className: "ri-save-line", "aria-hidden": "true" }), "\u4FDD\u5B58"] }), children: _jsxs("div", { className: "tcw-analytics-modal-grid", children: [_jsxs("label", { className: "tcw-form-field", children: [_jsx("span", { children: "\u62A5\u8868\u540D\u79F0" }), _jsx("input", { placeholder: "\u8BF7\u8F93\u5165\u62A5\u8868\u540D\u79F0" })] }), _jsxs("label", { className: "tcw-form-field", children: [_jsx("span", { children: "\u62A5\u8868\u63CF\u8FF0" }), _jsx("textarea", { placeholder: "\u8BF7\u8F93\u5165\u62A5\u8868\u63CF\u8FF0\uFF08\u9009\u586B\uFF09" })] })] }) }), _jsx(AnalyticsModal, { title: "\u5B9A\u65F6\u62A5\u544A\u8BBE\u7F6E", active: activeDialog === 'schedule', onClose: onClose, footer: _jsxs("button", { type: "button", className: "tcw-button tcw-button--primary", onClick: onClose, children: [_jsx("i", { className: "ri-save-line", "aria-hidden": "true" }), "\u4FDD\u5B58"] }), children: _jsxs("div", { className: "tcw-analytics-modal-grid", children: [_jsxs("label", { className: "tcw-form-field", children: [_jsx("span", { children: "\u62A5\u544A\u540D\u79F0" }), _jsx("input", { placeholder: "\u8BF7\u8F93\u5165\u62A5\u544A\u540D\u79F0" })] }), _jsxs("label", { className: "tcw-form-field", children: [_jsx("span", { children: "\u53D1\u9001\u9891\u7387" }), _jsx("select", { children: _jsx("option", { children: "\u6BCF\u5468\u4E00 09:00" }) })] }), _jsxs("label", { className: "tcw-form-field", children: [_jsx("span", { children: "\u63A5\u6536\u4EBA" }), _jsx("input", { placeholder: "\u9009\u62E9\u63A5\u6536\u4EBA" })] })] }) }), _jsx(AnalyticsModal, { title: "\u5220\u9664\u786E\u8BA4", active: activeDialog === 'delete', onClose: onClose, footer: _jsxs(_Fragment, { children: [_jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", onClick: onClose, children: [_jsx("i", { className: "ri-close-line", "aria-hidden": "true" }), "\u53D6\u6D88"] }), _jsxs("button", { type: "button", className: "tcw-button tcw-button--danger", onClick: onClose, children: [_jsx("i", { className: "ri-delete-bin-line", "aria-hidden": "true" }), "\u786E\u8BA4\u5220\u9664"] })] }), children: _jsxs("div", { className: "tcw-confirm-message is-danger", children: [_jsx("i", { className: "ri-error-warning-line", "aria-hidden": "true" }), _jsx("p", { children: "\u786E\u8BA4\u5220\u9664\u6B64\u62A5\u8868\u5417\uFF1F\u5220\u9664\u540E\u5C06\u65E0\u6CD5\u6062\u590D\u3002" })] }) }), _jsx(AnalyticsModal, { title: "\u6307\u6807\u8BF4\u660E", active: activeDialog === 'metrics', onClose: onClose, footer: _jsxs("button", { type: "button", className: "tcw-button tcw-button--secondary", onClick: onClose, children: [_jsx("i", { className: "ri-close-line", "aria-hidden": "true" }), "\u5173\u95ED"] }), children: _jsxs("dl", { className: "tcw-analytics-metric-help", children: [_jsxs("div", { children: [_jsx("dt", { children: "\u8BA2\u5355\u91D1\u989D\uFF08CNY\uFF09" }), _jsx("dd", { children: "\u5F53\u524D\u7B5B\u9009\u6761\u4EF6\u4E0B\u7684\u8BA2\u5355\u91D1\u989D\u6C47\u603B\u3002" })] }), _jsxs("div", { children: [_jsx("dt", { children: "\u98CE\u9669\u7B49\u7EA7\u5206\u5E03" }), _jsx("dd", { children: "\u4F9D\u636E\u5F53\u524D\u660E\u7EC6\u6570\u636E\u63A8\u7B97\u7684\u98CE\u9669\u6982\u89C8\u3002" })] }), _jsxs("div", { children: [_jsx("dt", { children: "\u5408\u89C4\u5BA1\u6838\u6982\u89C8" }), _jsx("dd", { children: "\u7528\u4E8E\u89C2\u5BDF\u5BA1\u6838\u72B6\u6001\u5206\u5E03\u548C\u5904\u7406\u538B\u529B\u3002" })] })] }) })] });
|
|
187
|
-
}
|
|
188
|
-
function AnalyticsModal({ title, active, onClose, footer, children }) {
|
|
189
|
-
return _jsxs("section", { className: "tcw-analytics-modal", hidden: !active, role: "dialog", "aria-label": title, children: [_jsxs("header", { children: [_jsx("strong", { className: "tcw-analytics-modal-title", children: title }), _jsx("button", { type: "button", className: "tcw-icon-button", onClick: onClose, "aria-label": "\u5173\u95ED", children: _jsx("i", { className: "ri-close-line", "aria-hidden": "true" }) })] }), _jsx("div", { className: "tcw-modal-body", children: children }), _jsx("footer", { children: footer })] });
|
|
190
|
-
}
|
|
191
|
-
function ModalText({ label, value, onChange }) {
|
|
192
|
-
return _jsxs("label", { className: "tcw-form-field", children: [_jsx("span", { children: label }), _jsx("input", { type: "search", value: value, placeholder: `请输入${label}`, onChange: event => onChange(event.target.value) })] });
|
|
193
|
-
}
|
|
194
|
-
function ModalRange({ label, value, onChange }) {
|
|
195
|
-
const range = value && typeof value === 'object' ? value : {};
|
|
196
|
-
return _jsxs("label", { className: "tcw-form-field tcw-form-field--full", children: [_jsx("span", { children: label }), _jsxs("div", { className: "tcw-analytics-date-range", children: [_jsx("input", { type: "date", value: range.from ?? '', onChange: event => onChange({ ...range, from: event.target.value }) }), _jsx("em", { children: "\u81F3" }), _jsx("input", { type: "date", value: range.to ?? '', onChange: event => onChange({ ...range, to: event.target.value }) })] })] });
|
|
197
|
-
}
|
|
198
|
-
function quickRange(index) {
|
|
199
|
-
const today = new Date();
|
|
200
|
-
const end = today.toISOString().slice(0, 10);
|
|
201
|
-
const start = new Date(today);
|
|
202
|
-
if (index === 1)
|
|
203
|
-
start.setDate(today.getDate() - 6);
|
|
204
|
-
if (index === 2)
|
|
205
|
-
start.setDate(today.getDate() - 29);
|
|
206
|
-
if (index === 3)
|
|
207
|
-
start.setDate(1);
|
|
208
|
-
return { from: start.toISOString().slice(0, 10), to: end };
|
|
209
|
-
}
|
|
210
|
-
function yearRange(year) {
|
|
211
|
-
return { from: `${year}-01-01`, to: `${year}-12-31` };
|
|
66
|
+
React.useEffect(() => {
|
|
67
|
+
if (appliedDetailRefreshKeyRef.current === detailRefreshKey)
|
|
68
|
+
return;
|
|
69
|
+
appliedDetailRefreshKeyRef.current = detailRefreshKey;
|
|
70
|
+
if (!selection)
|
|
71
|
+
return;
|
|
72
|
+
void loadDetail(selection, detailPage.page, detailPage.pageSize);
|
|
73
|
+
}, [detailPage.page, detailPage.pageSize, detailRefreshKey, loadDetail, selection]);
|
|
74
|
+
if (selection)
|
|
75
|
+
return _jsx(AnalyticsDetailList, { title: selection.title, page: detailPage, busy: detailBusy, error: detailError, onBack: () => { detailRequestGateRef.current.invalidate(); setSelection(null); setDetailError(null); setDetailBusy(false); }, onPageChange: page => void loadDetail(selection, page, detailPage.pageSize), onPageSizeChange: pageSize => void loadDetail(selection, 1, pageSize), onAction: row => void onDetailAction?.(row) });
|
|
76
|
+
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", onClick: () => void onExportXlsx({ filters, format: 'XLSX' }), 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 queryDashboard(filters), children: [_jsx("i", { className: "ri-refresh-line", "aria-hidden": "true" }), "\u5237\u65B0"] })] })] }), _jsx("div", { className: "tcw-analytics-query", children: _jsxs("div", { className: "tcw-analytics-toolbar", children: [_jsx("span", { className: "tcw-analytics-label", children: "\u7EDF\u8BA1\u5468\u671F\uFF1A" }), _jsxs("div", { className: "tcw-segmented-control tcw-analytics-period", role: "group", "aria-label": "\u7EDF\u8BA1\u65F6\u95F4", children: [_jsxs("button", { type: "button", "aria-pressed": period === 'year', onClick: () => applyPeriod('year'), children: [_jsx("i", { className: "ri-calendar-event-line", "aria-hidden": "true" }), "\u672C\u5E74"] }), _jsxs("button", { type: "button", "aria-pressed": period === 'month', onClick: () => applyPeriod('month'), children: [_jsx("i", { className: "ri-calendar-check-line", "aria-hidden": "true" }), "\u672C\u6708"] }), _jsxs("button", { type: "button", "aria-pressed": period === 'custom', onClick: () => { setPeriod('custom'); setCustomVisible(true); }, children: [_jsx("i", { className: "ri-calendar-2-line", "aria-hidden": "true" }), "\u81EA\u5B9A\u4E49\u533A\u95F4"] })] }), customVisible && _jsxs("div", { className: "tcw-analytics-custom-range", children: [_jsx("input", { type: "date", "aria-label": "\u7EDF\u8BA1\u5F00\u59CB\u65E5\u671F", value: filters.orderDate?.from ?? '', onChange: event => setFilters({ orderDate: { ...filters.orderDate, from: event.target.value } }) }), _jsx("span", { children: "\u81F3" }), _jsx("input", { type: "date", "aria-label": "\u7EDF\u8BA1\u7ED3\u675F\u65E5\u671F", value: filters.orderDate?.to ?? '', onChange: event => setFilters({ orderDate: { ...filters.orderDate, to: event.target.value } }) }), _jsxs("button", { type: "button", className: "tcw-button tcw-button--primary", onClick: applyCustom, children: [_jsx("i", { className: "ri-check-line", "aria-hidden": "true" }), "\u5E94\u7528"] })] })] }) }), _jsx(AnalyticsDashboard, { data: data, onOpenDetail: openDetail }), 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"] })] });
|
|
77
|
+
}
|
|
78
|
+
function yearRange(year) { return { from: `${year}-01-01`, to: `${year}-12-31` }; }
|
|
79
|
+
function monthRange(now = new Date()) {
|
|
80
|
+
const formatter = new Intl.DateTimeFormat('en-CA', { timeZone: 'Asia/Shanghai', year: 'numeric', month: '2-digit', day: '2-digit' });
|
|
81
|
+
const today = formatter.format(now);
|
|
82
|
+
return { from: `${today.slice(0, 7)}-01`, to: today };
|
|
212
83
|
}
|
|
213
84
|
function periodFromQuery(query, year) {
|
|
214
85
|
const range = query.filters.orderDate;
|
|
215
|
-
if (!range?.from
|
|
86
|
+
if (!range?.from && !range?.to)
|
|
216
87
|
return 'year';
|
|
217
|
-
const
|
|
218
|
-
|
|
88
|
+
const annual = yearRange(year);
|
|
89
|
+
const monthly = monthRange();
|
|
90
|
+
if (range?.from === annual.from && range?.to === annual.to)
|
|
219
91
|
return 'year';
|
|
220
|
-
|
|
221
|
-
if (range.from === month.from && range.to === month.to)
|
|
92
|
+
if (range?.from === monthly.from && range?.to === monthly.to)
|
|
222
93
|
return 'month';
|
|
223
94
|
return 'custom';
|
|
224
95
|
}
|
|
225
|
-
function todoDetailRows(todo) {
|
|
226
|
-
const count = Math.max(1, Math.min(todo.count, 5));
|
|
227
|
-
return Array.from({ length: count }, (_, index) => ({
|
|
228
|
-
label: `${todo.label}-${String(index + 1).padStart(2, '0')}`,
|
|
229
|
-
value: `${todo.description} · 优先级 ${todo.priority === 'high' ? '高' : todo.priority === 'medium' ? '中' : '低'} · 待处理`,
|
|
230
|
-
actionLabel: '审核'
|
|
231
|
-
}));
|
|
232
|
-
}
|
|
233
|
-
function formatChartTick(value) {
|
|
234
|
-
return value >= 1 ? value.toLocaleString('zh-CN', { maximumFractionDigits: 0 }) : value.toLocaleString('zh-CN', { maximumFractionDigits: 1 });
|
|
235
|
-
}
|
|
236
|
-
function PrototypeState({ index, title, area, children }) {
|
|
237
|
-
return _jsxs("section", { className: "tcw-analytics-prototype-state", style: area ? { gridArea: area } : undefined, "aria-label": `${index} ${title}`, children: [_jsxs("div", { className: "tcw-analytics-prototype-label", children: [_jsx("span", { className: "tcw-analytics-prototype-index", children: index }), _jsx("strong", { children: title })] }), children] });
|
|
238
|
-
}
|
|
239
|
-
function PrototypeCard({ title, children, className = '' }) {
|
|
240
|
-
return _jsxs("div", { className: `tcw-analytics-prototype-card ${className}`.trim(), children: [_jsxs("header", { children: [_jsx("strong", { children: title }), _jsx("i", { className: "ri-close-line", "aria-hidden": "true" })] }), children] });
|
|
241
|
-
}
|
|
242
|
-
function PrototypeField({ label, children }) {
|
|
243
|
-
return _jsxs("label", { className: "tcw-analytics-prototype-field", children: [_jsx("span", { children: label }), children] });
|
|
244
|
-
}
|
|
245
|
-
function PrototypeActions({ danger = false, confirm = '确定' }) {
|
|
246
|
-
return _jsxs("footer", { children: [_jsxs("button", { type: "button", children: [_jsx("i", { className: "ri-restart-line", "aria-hidden": "true" }), "\u53D6\u6D88"] }), _jsxs("button", { type: "button", className: danger ? 'is-danger' : 'is-primary', children: [_jsx("i", { className: danger ? 'ri-delete-bin-line' : 'ri-check-line', "aria-hidden": "true" }), confirm] })] });
|
|
247
|
-
}
|
|
248
|
-
export function AnalyticsPrototypeBoard({ onClose }) {
|
|
249
|
-
return _jsxs("div", { className: "tcw-analytics-prototype-shell", children: [_jsxs("button", { type: "button", className: "tcw-analytics-prototype-close", onClick: onClose, children: [_jsx("i", { className: "ri-close-line", "aria-hidden": "true" }), "\u5173\u95ED\u539F\u578B\u72B6\u6001\u603B\u89C8"] }), _jsxs("div", { className: "tcw-analytics-prototype-board", children: [_jsx(PrototypeState, { index: 1, title: "\u6570\u636E\u5206\u6790\u9996\u9875", area: "home", children: _jsx(AnalyticsPrototypeHome, {}) }), _jsx(PrototypeState, { index: 2, title: "\u7B5B\u9009\u6761\u4EF6\u5F39\u7A97", area: "filter", children: _jsx(AnalyticsPrototypeFilter, {}) }), _jsx(PrototypeState, { index: 3, title: "\u98CE\u9669\u7B49\u7EA7\u4E0B\u94BB\u660E\u7EC6", area: "drill", children: _jsx(AnalyticsPrototypeDrilldown, {}) }), _jsx(PrototypeState, { index: 4, title: "\u5BFC\u51FA\u6570\u636E", area: "export", children: _jsx(AnalyticsPrototypeExport, {}) }), _jsxs("div", { className: "tcw-analytics-prototype-bottom", style: { gridArea: 'bottom' }, children: [_jsx(PrototypeState, { index: 5, title: "\u56FE\u8868\u914D\u7F6E", children: _jsx(AnalyticsPrototypeChartConfig, {}) }), _jsx(PrototypeState, { index: 6, title: "\u4FDD\u5B58\u62A5\u8868", children: _jsx(AnalyticsPrototypeSave, {}) }), _jsx(PrototypeState, { index: 7, title: "\u5B9A\u65F6\u62A5\u544A\u8BBE\u7F6E", children: _jsx(AnalyticsPrototypeSchedule, {}) }), _jsx(PrototypeState, { index: 8, title: "\u5220\u9664\u786E\u8BA4", children: _jsx(AnalyticsPrototypeDelete, {}) }), _jsx(PrototypeState, { index: 9, title: "\u6307\u6807\u8BF4\u660E", children: _jsx(AnalyticsPrototypeMetrics, {}) })] })] })] });
|
|
250
|
-
}
|
|
251
|
-
function AnalyticsPrototypeHome() {
|
|
252
|
-
const metrics = [['客户总数', '1,256'], ['订单总数', '3,246'], ['销售金额(USD)', '12,456,789'], ['高风险订单数', '87'], ['受制裁命中数', '23']];
|
|
253
|
-
return _jsxs("div", { className: "tcw-analytics-prototype-home", children: [_jsxs("header", { children: [_jsx("i", { className: "ri-shield-check-line", "aria-hidden": "true" }), _jsx("strong", { children: "\u5916\u8D38\u5408\u89C4\u5DE5\u4F5C\u53F0" }), _jsx("span", { children: "\u6570\u636E\u5206\u6790" }), _jsx("time", { children: "2024-07-01 - 2024-07-31" })] }), _jsx("div", { className: "tcw-analytics-prototype-metrics", children: metrics.map(([label, value], index) => _jsxs("article", { children: [_jsx("span", { children: label }), _jsx("strong", { className: index === 3 ? 'is-risk' : '', children: value }), _jsxs("small", { children: ["\u8F83\u4E0A\u6708 ", _jsx("b", { children: index % 2 ? '↑ 8.3%' : '↑ 12.5%' })] })] }, label)) }), _jsxs("div", { className: "tcw-analytics-prototype-charts", children: [_jsxs("section", { children: [_jsx("strong", { children: "\u5408\u89C4\u98CE\u9669\u8D8B\u52BF" }), _jsxs("div", { className: "tcw-prototype-line-chart", children: [_jsx("i", {}), _jsx("i", {}), _jsx("i", {})] })] }), _jsxs("section", { children: [_jsx("strong", { children: "\u98CE\u9669\u7B49\u7EA7\u5206\u5E03" }), _jsxs("div", { className: "tcw-prototype-donut", children: [_jsx("b", { children: "3,246" }), _jsx("span", { children: "\u8BA2\u5355\u603B\u6570" })] })] }), _jsxs("section", { children: [_jsx("strong", { children: "\u5BA2\u6237\u54C1\u7C7B\u9500\u552E\u91D1\u989D\uFF08USD\uFF09" }), _jsx("div", { className: "tcw-prototype-bars", children: [72, 64, 61, 51, 56, 24].map((height, index) => _jsx("i", { style: { height: `${height}%` } }, index)) })] }), _jsxs("section", { children: [_jsx("strong", { children: "\u5408\u89C4\u5BA1\u6838\u6982\u89C8" }), _jsxs("table", { children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "\u5BA1\u6838\u72B6\u6001" }), _jsx("th", { children: "\u8BA2\u5355\u6570" }), _jsx("th", { children: "\u5360\u6BD4" })] }) }), _jsxs("tbody", { children: [_jsxs("tr", { children: [_jsx("td", { children: "\u5F85\u5BA1\u6838" }), _jsx("td", { children: "156" }), _jsx("td", { children: "4.8%" })] }), _jsxs("tr", { children: [_jsx("td", { children: "\u5BA1\u6838\u4E2D" }), _jsx("td", { children: "327" }), _jsx("td", { children: "10.1%" })] }), _jsxs("tr", { children: [_jsx("td", { children: "\u5BA1\u6838\u901A\u8FC7" }), _jsx("td", { children: "2,642" }), _jsx("td", { children: "81.3%" })] })] })] })] })] })] });
|
|
254
|
-
}
|
|
255
|
-
function AnalyticsPrototypeFilter() {
|
|
256
|
-
return _jsxs(PrototypeCard, { title: "\u7B5B\u9009\u6761\u4EF6", children: [_jsxs("div", { className: "tcw-analytics-prototype-form", children: [_jsxs(PrototypeField, { label: "\u7EDF\u8BA1\u65F6\u95F4", children: [_jsxs("div", { className: "tcw-prototype-quick", children: [_jsxs("button", { type: "button", children: [_jsx("i", { className: "ri-calendar-line", "aria-hidden": "true" }), "\u4ECA\u65E5"] }), _jsxs("button", { type: "button", children: [_jsx("i", { className: "ri-calendar-line", "aria-hidden": "true" }), "\u8FD17\u5929"] }), _jsxs("button", { type: "button", className: "is-active", children: [_jsx("i", { className: "ri-calendar-check-line", "aria-hidden": "true" }), "\u8FD130\u5929"] }), _jsxs("button", { type: "button", children: [_jsx("i", { className: "ri-calendar-line", "aria-hidden": "true" }), "\u672C\u6708"] })] }), _jsx("input", { readOnly: true, value: "2024-07-01 \u2192 2024-07-31" })] }), _jsx(PrototypeField, { label: "\u5BA2\u6237\u7C7B\u578B", children: _jsx("select", { defaultValue: "", children: _jsx("option", { value: "", children: "\u8BF7\u9009\u62E9\u5BA2\u6237\u7C7B\u578B" }) }) }), _jsx(PrototypeField, { label: "\u56FD\u5BB6/\u5730\u533A", children: _jsx("select", { defaultValue: "", children: _jsx("option", { value: "", children: "\u8BF7\u9009\u62E9\u56FD\u5BB6/\u5730\u533A" }) }) }), _jsx(PrototypeField, { label: "\u4EA7\u54C1\u7C7B\u522B", children: _jsx("select", { defaultValue: "", children: _jsx("option", { value: "", children: "\u8BF7\u9009\u62E9\u4EA7\u54C1\u7C7B\u522B" }) }) }), _jsx(PrototypeField, { label: "\u98CE\u9669\u7B49\u7EA7", children: _jsxs("div", { className: "tcw-prototype-checks", children: [_jsx("span", { children: "\u5168\u90E8" }), _jsx("span", { children: "\u25A1 \u9AD8\u98CE\u9669" }), _jsx("span", { children: "\u25A1 \u4E2D\u98CE\u9669" }), _jsx("span", { children: "\u25A1 \u4F4E\u98CE\u9669" })] }) }), _jsx(PrototypeField, { label: "\u5BA1\u6838\u72B6\u6001", children: _jsxs("div", { className: "tcw-prototype-checks", children: [_jsx("span", { children: "\u5168\u90E8" }), _jsx("span", { children: "\u25A1 \u5F85\u5BA1\u6838" }), _jsx("span", { children: "\u25A1 \u5BA1\u6838\u4E2D" }), _jsx("span", { children: "\u25A1 \u5BA1\u6838\u901A\u8FC7" })] }) }), _jsx(PrototypeField, { label: "\u91D1\u989D\u8303\u56F4\uFF08USD\uFF09", children: _jsxs("div", { className: "tcw-prototype-range", children: [_jsx("input", { placeholder: "\u6700\u5C0F\u503C" }), _jsx("input", { placeholder: "\u6700\u5927\u503C" })] }) })] }), _jsx(PrototypeActions, { confirm: "\u786E\u5B9A" })] });
|
|
257
|
-
}
|
|
258
|
-
function AnalyticsPrototypeDrilldown() {
|
|
259
|
-
return _jsxs("div", { className: "tcw-analytics-prototype-drill", children: [_jsxs("header", { children: [_jsx("span", { children: "\u6570\u636E\u5206\u6790" }), _jsx("i", { className: "ri-arrow-right-s-line", "aria-hidden": "true" }), _jsx("strong", { children: "\u98CE\u9669\u7B49\u7EA7\u660E\u7EC6" })] }), _jsxs("div", { className: "tcw-prototype-drill-filters", children: [_jsx("span", { children: "\u7EDF\u8BA1\u65F6\u95F4\uFF1A2024-07-01 \u2192 2024-07-31" }), _jsx("span", { children: "\u98CE\u9669\u7B49\u7EA7\uFF1A\u9AD8\u98CE\u9669" }), _jsxs("button", { type: "button", children: [_jsx("i", { className: "ri-restart-line", "aria-hidden": "true" }), "\u91CD\u7F6E"] })] }), _jsx("div", { className: "tcw-analytics-prototype-metrics", children: [['高风险订单数', '87'], ['高风险金额(USD)', '1,234,567'], ['涉及客户数', '56'], ['平均订单金额(USD)', '14,190']].map(([label, value]) => _jsxs("article", { children: [_jsx("span", { children: label }), _jsx("strong", { children: value }), _jsx("small", { children: "\u8F83\u4E0A\u6708 \u2191 5.6%" })] }, label)) }), _jsx("strong", { children: "\u9AD8\u98CE\u9669\u8BA2\u5355\u660E\u7EC6" }), _jsxs("table", { children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", { children: "\u8BA2\u5355\u53F7" }), _jsx("th", { children: "\u5BA2\u6237\u540D\u79F0" }), _jsx("th", { children: "\u56FD\u5BB6/\u5730\u533A" }), _jsx("th", { children: "\u4EA7\u54C1\u7C7B\u522B" }), _jsx("th", { children: "\u8BA2\u5355\u91D1\u989D\uFF08USD\uFF09" }), _jsx("th", { children: "\u98CE\u9669\u655E\u53E3" }), _jsx("th", { children: "\u5BA1\u6838\u72B6\u6001" }), _jsx("th", { children: "\u64CD\u4F5C" })] }) }), _jsx("tbody", { children: ['0001', '0002', '0003', '0004', '0005'].map((id, index) => _jsxs("tr", { children: [_jsxs("td", { children: ["SO-202407-", id] }), _jsx("td", { children: ['ABC Trading Co., Ltd.', 'Global Import LLC', 'Middle East Supply', 'Afro Tech Ltd.', 'Asia Pacific Co.'][index] }), _jsx("td", { children: "\u4F0A\u6717" }), _jsx("td", { children: "\u7535\u5B50\u5143\u4EF6" }), _jsx("td", { children: 25_000 - index * 2_500 }), _jsx("td", { children: "\u547D\u4E2D\u5236\u88C1\u540D\u5355" }), _jsx("td", { children: "\u5BA1\u6838\u4E2D" }), _jsx("td", { children: _jsx("a", { children: "\u67E5\u770B" }) })] }, id)) })] })] });
|
|
260
|
-
}
|
|
261
|
-
function AnalyticsPrototypeExport() {
|
|
262
|
-
return _jsxs(PrototypeCard, { title: "\u5BFC\u51FA\u6570\u636E", children: [_jsxs("div", { className: "tcw-analytics-prototype-export", children: [_jsx(PrototypeField, { label: "\u5BFC\u51FA\u8303\u56F4", children: _jsx("span", { children: "\u25C9 \u5F53\u524D\u9875\u6570\u636E\u3000\u25CB \u5168\u90E8\u6570\u636E\uFF08\u5171 3,246 \u6761\uFF09" }) }), _jsx(PrototypeField, { label: "\u5BFC\u51FA\u5B57\u6BB5", children: _jsx("div", { className: "tcw-prototype-export-list", children: ['全部字段', '客户名称', '国家/地区', '产品类别', '订单金额(USD)', '风险等级', '审核状态', '创建时间'].map(field => _jsxs("span", { children: ["\u2611 ", field] }, field)) }) }), _jsx(PrototypeField, { label: "\u5BFC\u51FA\u683C\u5F0F", children: _jsx("span", { children: "\u25C9 Excel\uFF08.xlsx\uFF09\u3000\u25CB CSV\uFF08.csv\uFF09" }) })] }), _jsx(PrototypeActions, { confirm: "\u5BFC\u51FA" })] });
|
|
263
|
-
}
|
|
264
|
-
function AnalyticsPrototypeChartConfig() {
|
|
265
|
-
return _jsxs(PrototypeCard, { title: "\u56FE\u8868\u914D\u7F6E", children: [_jsxs("div", { className: "tcw-analytics-prototype-form", children: [_jsx(PrototypeField, { label: "\u56FE\u8868\u7C7B\u578B", children: _jsxs("div", { className: "tcw-prototype-chart-types", children: [_jsxs("button", { type: "button", className: "is-active", children: [_jsx("i", { className: "ri-bar-chart-fill", "aria-hidden": "true" }), "\u67F1\u72B6\u56FE"] }), _jsxs("button", { type: "button", children: [_jsx("i", { className: "ri-line-chart-line", "aria-hidden": "true" }), "\u6298\u7EBF\u56FE"] }), _jsxs("button", { type: "button", children: [_jsx("i", { className: "ri-pie-chart-line", "aria-hidden": "true" }), "\u997C\u56FE"] })] }) }), _jsx(PrototypeField, { label: "\u7EF4\u5EA6\uFF08X\u8F74\uFF09", children: _jsx("select", { children: _jsx("option", { children: "\u4EA7\u54C1\u7C7B\u522B" }) }) }), _jsx(PrototypeField, { label: "\u6307\u6807\uFF08Y\u8F74\uFF09", children: _jsx("select", { children: _jsx("option", { children: "\u9500\u552E\u91D1\u989D\uFF08USD\uFF09" }) }) }), _jsx(PrototypeField, { label: "\u6392\u5E8F\u65B9\u5F0F", children: _jsx("select", { children: _jsx("option", { children: "\u964D\u5E8F" }) }) }), _jsx(PrototypeField, { label: "\u663E\u793A\u6761\u6570", children: _jsx("select", { children: _jsx("option", { children: "\u524D 10 \u6761" }) }) })] }), _jsx(PrototypeActions, {})] });
|
|
266
|
-
}
|
|
267
|
-
function AnalyticsPrototypeSave() {
|
|
268
|
-
return _jsxs(PrototypeCard, { title: "\u4FDD\u5B58\u62A5\u8868", children: [_jsxs("div", { className: "tcw-analytics-prototype-form", children: [_jsx(PrototypeField, { label: "\u62A5\u8868\u540D\u79F0", children: _jsx("input", { placeholder: "\u8BF7\u8F93\u5165\u62A5\u8868\u540D\u79F0" }) }), _jsx(PrototypeField, { label: "\u62A5\u8868\u63CF\u8FF0", children: _jsx("textarea", { placeholder: "\u8BF7\u8F93\u5165\u62A5\u8868\u63CF\u8FF0\uFF08\u9009\u586B\uFF09" }) }), _jsx(PrototypeField, { label: "\u4FDD\u5B58\u4F4D\u7F6E", children: _jsx("select", { children: _jsx("option", { children: "\u6211\u7684\u62A5\u8868" }) }) }), _jsx(PrototypeField, { label: "\u9690\u79C1\u8BBE\u7F6E", children: _jsx("span", { children: "\u25C9 \u4EC5\u81EA\u5DF1\u53EF\u89C1\u3000\u25CB \u56E2\u961F\u53EF\u89C1\u3000\u25CB \u516C\u5F00" }) })] }), _jsx(PrototypeActions, { confirm: "\u4FDD\u5B58" })] });
|
|
269
|
-
}
|
|
270
|
-
function AnalyticsPrototypeSchedule() {
|
|
271
|
-
return _jsxs(PrototypeCard, { title: "\u5B9A\u65F6\u62A5\u544A\u8BBE\u7F6E", children: [_jsxs("div", { className: "tcw-analytics-prototype-form", children: [_jsx(PrototypeField, { label: "\u62A5\u544A\u540D\u79F0", children: _jsx("input", { placeholder: "\u8BF7\u8F93\u5165\u62A5\u544A\u540D\u79F0" }) }), _jsx(PrototypeField, { label: "\u53D1\u9001\u9891\u7387", children: _jsxs("div", { className: "tcw-prototype-range", children: [_jsx("select", { children: _jsx("option", { children: "\u6BCF\u5468" }) }), _jsx("select", { children: _jsx("option", { children: "\u5468\u4E00 09:00" }) })] }) }), _jsx(PrototypeField, { label: "\u63A5\u6536\u4EBA", children: _jsx("input", { placeholder: "\u9009\u62E9\u63A5\u6536\u4EBA" }) }), _jsx(PrototypeField, { label: "\u53D1\u9001\u65B9\u5F0F", children: _jsx("span", { children: "\u2611 \u90AE\u4EF6\u3000\u2611 \u7CFB\u7EDF\u6D88\u606F" }) }), _jsx(PrototypeField, { label: "\u62A5\u544A\u683C\u5F0F", children: _jsx("select", { children: _jsx("option", { children: "PDF" }) }) })] }), _jsx(PrototypeActions, { confirm: "\u4FDD\u5B58" })] });
|
|
272
|
-
}
|
|
273
|
-
function AnalyticsPrototypeDelete() {
|
|
274
|
-
return _jsxs(PrototypeCard, { title: "", children: [_jsxs("div", { className: "tcw-analytics-prototype-delete", children: [_jsx("i", { className: "ri-error-warning-line", "aria-hidden": "true" }), _jsx("strong", { children: "\u786E\u8BA4\u5220\u9664\u6B64\u62A5\u8868\u5417\uFF1F" }), _jsx("p", { children: "\u62A5\u8868\u540D\u79F0\uFF1A\u6708\u5EA6\u9500\u552E\u5408\u89C4\u5206\u6790\u62A5\u544A" }), _jsx("b", { children: "\u5220\u9664\u540E\u5C06\u65E0\u6CD5\u6062\u590D\uFF0C\u8BF7\u8C28\u614E\u64CD\u4F5C\uFF01" })] }), _jsx(PrototypeActions, { danger: true, confirm: "\u786E\u8BA4\u5220\u9664" })] });
|
|
275
|
-
}
|
|
276
|
-
function AnalyticsPrototypeMetrics() {
|
|
277
|
-
const items = [['销售金额(USD)', '订单的总金额,已换算为美元。'], ['高风险订单数', '风险等级为“高风险”的订单数量。'], ['受制裁命中数', '命中制裁名单或受限制实体的订单数量。'], ['审核通过率(%)', '审核通过的订单数占总订单数的比例。']];
|
|
278
|
-
return _jsxs(PrototypeCard, { title: "\u6307\u6807\u8BF4\u660E", children: [_jsx("dl", { className: "tcw-analytics-prototype-explain", children: items.map(([term, description]) => _jsxs("div", { children: [_jsx("dt", { children: term }), _jsx("dd", { children: description })] }, term)) }), _jsx("footer", { children: _jsxs("button", { type: "button", children: [_jsx("i", { className: "ri-close-line", "aria-hidden": "true" }), "\u5173\u95ED"] }) })] });
|
|
279
|
-
}
|
|
280
|
-
function Toggle({ label, checked, onChange }) {
|
|
281
|
-
return _jsxs("label", { className: "tcw-toggle", children: [_jsx("input", { type: "checkbox", checked: checked, onChange: event => onChange(event.target.checked) }), _jsx("span", { children: label })] });
|
|
282
|
-
}
|
|
283
|
-
function detailColumns(mode, dimension) {
|
|
284
|
-
const columns = [
|
|
285
|
-
{ type: 'ellipsis', key: 'label', header: dimensionLabel(dimension), width: 220, maxWidth: 220 },
|
|
286
|
-
{ type: 'text', key: 'orderCount', header: '订单数', width: 100, align: 'right', truncate: false }
|
|
287
|
-
];
|
|
288
|
-
if (dimension === 'PRODUCT')
|
|
289
|
-
columns.push({ type: 'text', key: 'productQuantity', header: '商品数量', width: 120, align: 'right', truncate: false });
|
|
290
|
-
columns.push({ type: 'text', key: 'amountCny', header: dimension === 'PRODUCT' ? '商品明细金额 CNY' : '订单总额 CNY', width: 160, align: 'right', truncate: false, format: money });
|
|
291
|
-
if (dimension !== 'PRODUCT' && mode === 'PURCHASE')
|
|
292
|
-
columns.push({ type: 'text', key: 'paidCny', header: '已付款 CNY', width: 140, align: 'right', truncate: false, format: money }, { type: 'text', key: 'unpaidCny', header: '未付款 CNY', width: 140, align: 'right', truncate: false, format: money }, { type: 'text', key: 'refundedCny', header: '已退税 CNY', width: 140, align: 'right', truncate: false, format: money }, { type: 'text', key: 'unrefundedCny', header: '未退税 CNY', width: 140, align: 'right', truncate: false, format: money });
|
|
293
|
-
if (dimension !== 'PRODUCT' && mode === 'SALES')
|
|
294
|
-
columns.push({ type: 'text', key: 'receivedCny', header: '已收款 CNY', width: 140, align: 'right', truncate: false, format: money }, { type: 'text', key: 'unreceivedCny', header: '未收款 CNY', width: 140, align: 'right', truncate: false, format: money });
|
|
295
|
-
return columns;
|
|
296
|
-
}
|
|
297
|
-
function dimensionLabel(dimension) { return dimension === 'SUPPLIER' ? '供应商' : dimension === 'CUSTOMER' ? '客户' : '商品名称'; }
|
|
298
|
-
function addMoney(...values) { return values.reduce((sum, value) => sum.plus(String(value ?? 0)), new Decimal(0)).toString(); }
|
|
299
|
-
function amountRatio(part, total, formatted = true) {
|
|
300
|
-
const denominator = Number(total) || 0;
|
|
301
|
-
const value = denominator > 0 ? (Number(part) || 0) / denominator : 0;
|
|
302
|
-
return formatted ? percent(value) : String(value);
|
|
303
|
-
}
|
|
304
|
-
function percentRatio(part, total) {
|
|
305
|
-
const denominator = Number(total) || 0;
|
|
306
|
-
const value = denominator > 0 ? (Number(part) || 0) / denominator : 0;
|
|
307
|
-
return `${(value * 100).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}%`;
|
|
308
|
-
}
|
|
309
|
-
function deltaLabel(current, previous) {
|
|
310
|
-
const now = Number(current ?? 0);
|
|
311
|
-
const before = Number(previous ?? 0);
|
|
312
|
-
if (!Number.isFinite(now) || !Number.isFinite(before) || before <= 0)
|
|
313
|
-
return '暂无环比';
|
|
314
|
-
const delta = (now - before) / before;
|
|
315
|
-
const arrow = delta >= 0 ? '↑' : '↓';
|
|
316
|
-
return `${arrow} ${Math.abs(delta * 100).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}%`;
|
|
317
|
-
}
|
|
318
|
-
function money(value) { const number = Number(value ?? 0); return Number.isFinite(number) ? number.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) : '0.00'; }
|
|
319
|
-
function compactNumber(value) { const number = Number(value ?? 0); return Number.isFinite(number) ? number.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) : '0.00'; }
|
|
320
|
-
function compactMoney(value) {
|
|
321
|
-
const number = Number(value ?? 0);
|
|
322
|
-
if (!Number.isFinite(number))
|
|
323
|
-
return '0.00 CNY';
|
|
324
|
-
const abs = Math.abs(number);
|
|
325
|
-
if (abs >= 100_000_000)
|
|
326
|
-
return `${(number / 100_000_000).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} 亿 CNY`;
|
|
327
|
-
if (abs >= 10_000)
|
|
328
|
-
return `${(number / 10_000).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} 万 CNY`;
|
|
329
|
-
return `${money(number)} CNY`;
|
|
330
|
-
}
|
|
331
|
-
function percent(value) { const number = Number(value ?? 0); return Number.isFinite(number) ? `${(number * 100).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}%` : '0.00%'; }
|
|
332
|
-
function barHeight(value, peers) {
|
|
333
|
-
const max = Math.max(...peers.map(peer => Number(peer) || 0), Number(value) || 0, 1);
|
|
334
|
-
return Math.max(10, Math.min(94, ((Number(value) || 0) / max) * 88 + 6));
|
|
335
|
-
}
|
|
336
|
-
function shortDate(value) {
|
|
337
|
-
if (typeof value !== 'string' || !value)
|
|
338
|
-
return '暂无';
|
|
339
|
-
return value.slice(5, 10);
|
|
340
|
-
}
|
|
341
|
-
function toAnalyticsFilters(filters) {
|
|
342
|
-
const date = filters.orderDate && typeof filters.orderDate === 'object' ? filters.orderDate : undefined;
|
|
343
|
-
return {
|
|
344
|
-
...(date ? { orderDate: { from: stringOrUndefined(date.from), to: stringOrUndefined(date.to) } } : {}),
|
|
345
|
-
...textFilter(filters, 'supplier'), ...textFilter(filters, 'customer'), ...textFilter(filters, 'productName'), ...textFilter(filters, 'hsCode'),
|
|
346
|
-
...(filters.unpaidOnly === 'true' ? { unpaidOnly: true } : {}),
|
|
347
|
-
...(filters.unrefundedOnly === 'true' ? { unrefundedOnly: true } : {}),
|
|
348
|
-
...(filters.unreceivedOnly === 'true' ? { unreceivedOnly: true } : {})
|
|
349
|
-
};
|
|
350
|
-
}
|
|
351
|
-
function purchaseAnalyticsFilters(filters) {
|
|
352
|
-
return { ...('orderDate' in filters && filters.orderDate ? { orderDate: filters.orderDate } : {}), ...('supplier' in filters && filters.supplier ? { supplier: filters.supplier } : {}), ...textAnalyticsFilters(filters), ...('unpaidOnly' in filters && filters.unpaidOnly ? { unpaidOnly: true } : {}), ...('unrefundedOnly' in filters && filters.unrefundedOnly ? { unrefundedOnly: true } : {}) };
|
|
353
|
-
}
|
|
354
|
-
function salesAnalyticsFilters(filters) {
|
|
355
|
-
return { ...('orderDate' in filters && filters.orderDate ? { orderDate: filters.orderDate } : {}), ...('customer' in filters && filters.customer ? { customer: filters.customer } : {}), ...textAnalyticsFilters(filters), ...('unreceivedOnly' in filters && filters.unreceivedOnly ? { unreceivedOnly: true } : {}) };
|
|
356
|
-
}
|
|
357
|
-
function textAnalyticsFilters(filters) { return { ...(filters.productName ? { productName: filters.productName } : {}), ...(filters.hsCode ? { hsCode: filters.hsCode } : {}) }; }
|
|
358
|
-
function textFilter(filters, key) { const value = stringOrUndefined(filters[key]); return value ? { [key]: value } : {}; }
|
|
359
|
-
function stringOrUndefined(value) { const text = String(value ?? '').trim(); return text || undefined; }
|
|
360
|
-
function queryFromData(data) {
|
|
361
|
-
return data.mode === 'PURCHASE'
|
|
362
|
-
? { mode: 'PURCHASE', dimension: data.dimension === 'PRODUCT' ? 'PRODUCT' : 'SUPPLIER', filters: {}, page: data.details.page, pageSize: data.details.pageSize }
|
|
363
|
-
: { mode: 'SALES', dimension: data.dimension === 'PRODUCT' ? 'PRODUCT' : 'CUSTOMER', filters: {}, page: data.details.page, pageSize: data.details.pageSize };
|
|
364
|
-
}
|
|
365
|
-
function filterStateFromQuery(query) {
|
|
366
|
-
const filters = query.filters;
|
|
367
|
-
return {
|
|
368
|
-
...(filters.orderDate ? { orderDate: filters.orderDate } : {}),
|
|
369
|
-
...('supplier' in filters && filters.supplier ? { supplier: filters.supplier } : {}),
|
|
370
|
-
...('customer' in filters && filters.customer ? { customer: filters.customer } : {}),
|
|
371
|
-
...(filters.productName ? { productName: filters.productName } : {}),
|
|
372
|
-
...(filters.hsCode ? { hsCode: filters.hsCode } : {}),
|
|
373
|
-
...('unpaidOnly' in filters && filters.unpaidOnly ? { unpaidOnly: 'true' } : {}),
|
|
374
|
-
...('unrefundedOnly' in filters && filters.unrefundedOnly ? { unrefundedOnly: 'true' } : {}),
|
|
375
|
-
...('unreceivedOnly' in filters && filters.unreceivedOnly ? { unreceivedOnly: 'true' } : {})
|
|
376
|
-
};
|
|
377
|
-
}
|
|
378
|
-
export function aggregateProductAnalytics(lines) {
|
|
379
|
-
const groups = new Map();
|
|
380
|
-
for (const line of lines) {
|
|
381
|
-
const label = line.productName.trim();
|
|
382
|
-
const key = label.toLocaleLowerCase();
|
|
383
|
-
const group = groups.get(key) ?? { label, orders: new Set(), quantity: new Decimal(0), amount: new Decimal(0) };
|
|
384
|
-
group.orders.add(line.orderId);
|
|
385
|
-
group.quantity = group.quantity.plus(line.quantity);
|
|
386
|
-
group.amount = group.amount.plus(line.amountCny);
|
|
387
|
-
groups.set(key, group);
|
|
388
|
-
}
|
|
389
|
-
return [...groups.entries()].sort(([keyA, a], [keyB, b]) => b.amount.cmp(a.amount) || keyA.localeCompare(keyB)).map(([key, group]) => ({ key, label: group.label, orderCount: group.orders.size, productQuantity: group.quantity.toString(), amountCny: group.amount.toFixed(2) }));
|
|
390
|
-
}
|
|
391
96
|
//# sourceMappingURL=analytics.js.map
|