neo-cmp-cli 1.13.16 → 1.13.17
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/README.md +2 -1
- package/dist/index2.js +1 -1
- package/dist/main2.js +1 -1
- package/dist/neo/neoLogin.js +1 -1
- package/dist/package.json.js +1 -1
- package/package.json +1 -1
- package/template/antd-custom-cmp-template/package.json +1 -1
- package/template/asset-manage-template/package.json +2 -2
- package/template/echarts-custom-cmp-template/package.json +1 -1
- package/template/empty-custom-cmp-template/package.json +2 -2
- package/template/map-custom-cmp-template/package.json +1 -1
- package/template/neo-bi-cmps/neo.config.js +7 -1
- package/template/neo-bi-cmps/package.json +8 -7
- package/template/neo-bi-cmps/public/403.html +77 -0
- package/template/neo-bi-cmps/public/demo.html +2453 -0
- package/template/neo-bi-cmps/src/assets/icon/barChart.svg +1 -0
- package/template/neo-bi-cmps/src/assets/icon/card.svg +1 -0
- package/template/neo-bi-cmps/src/assets/icon/filter.svg +1 -0
- package/template/neo-bi-cmps/src/assets/icon/funnel.svg +1 -0
- package/template/neo-bi-cmps/src/assets/icon/tab.svg +1 -0
- package/template/neo-bi-cmps/src/components/filterBar__c/README.md +3 -14
- package/template/neo-bi-cmps/src/components/filterBar__c/common.scss +29 -0
- package/template/neo-bi-cmps/src/components/filterBar__c/index.tsx +668 -146
- package/template/neo-bi-cmps/src/components/filterBar__c/model.ts +26 -48
- package/template/neo-bi-cmps/src/components/filterBar__c/style.scss +46 -139
- package/template/neo-bi-cmps/src/components/targetNumber__c/customStyleConfig/index.tsx +11 -10
- package/template/neo-bi-cmps/src/components/targetNumber__c/index.tsx +9 -16
- package/template/neo-bi-cmps/src/utils/common.ts +231 -0
- package/template/neo-bi-cmps/src/utils/filter2chartFilter.ts +268 -0
- package/template/neo-bi-cmps/src/utils/filterBar.ts +140 -0
- package/template/neo-bi-cmps/src/utils/pipelineFunnel.ts +341 -0
- package/template/neo-bi-cmps/src/utils/queryByCustomSQL.ts +117 -0
- package/template/neo-bi-cmps/src/utils/requestDebounce.ts +22 -0
- package/template/neo-bi-cmps/src/utils/simpleTable.tsx +344 -0
- package/template/neo-bi-cmps/src/utils/stageSwitch.ts +15 -0
- package/template/neo-bi-cmps/src/utils/stageTimeChart.ts +90 -0
- package/template/neo-bi-cmps/src/utils/targetNumber.ts +12 -0
- package/template/neo-custom-cmp-template/package.json +2 -2
- package/template/neo-h5-cmps/package.json +2 -2
- package/template/neo-order-cmps/package.json +2 -2
- package/template/neo-pipeline-cmps/.prettierrc.js +12 -0
- package/template/neo-pipeline-cmps/@types/neo-ui-common.d.ts +36 -0
- package/template/neo-pipeline-cmps/README.md +99 -0
- package/template/neo-pipeline-cmps/commitlint.config.js +59 -0
- package/template/neo-pipeline-cmps/neo.config.js +124 -0
- package/template/neo-pipeline-cmps/package.json +66 -0
- package/template/neo-pipeline-cmps/public/403.html +77 -0
- package/template/neo-pipeline-cmps/public/css/base.css +283 -0
- package/template/neo-pipeline-cmps/public/demo.html +2453 -0
- package/template/neo-pipeline-cmps/public/scripts/app/bluebird.js +6679 -0
- package/template/neo-pipeline-cmps/public/template.html +13 -0
- package/template/neo-pipeline-cmps/src/assets/css/common.scss +127 -0
- package/template/neo-pipeline-cmps/src/assets/css/mixin.scss +47 -0
- package/template/neo-pipeline-cmps/src/assets/icon/barChart.svg +1 -0
- package/template/neo-pipeline-cmps/src/assets/icon/card.svg +1 -0
- package/template/neo-pipeline-cmps/src/assets/icon/filter.svg +1 -0
- package/template/neo-pipeline-cmps/src/assets/icon/funnel.svg +1 -0
- package/template/neo-pipeline-cmps/src/assets/icon/tab.svg +1 -0
- package/template/neo-pipeline-cmps/src/assets/img/AIBtn.gif +0 -0
- package/template/neo-pipeline-cmps/src/assets/img/NeoCRM.jpg +0 -0
- package/template/neo-pipeline-cmps/src/assets/img/aiLogo.png +0 -0
- package/template/neo-pipeline-cmps/src/assets/img/card-list.svg +1 -0
- package/template/neo-pipeline-cmps/src/assets/img/contact-form.svg +1 -0
- package/template/neo-pipeline-cmps/src/assets/img/custom-form.svg +1 -0
- package/template/neo-pipeline-cmps/src/assets/img/custom-widget.svg +1 -0
- package/template/neo-pipeline-cmps/src/assets/img/data-list.svg +1 -0
- package/template/neo-pipeline-cmps/src/assets/img/detail.svg +1 -0
- package/template/neo-pipeline-cmps/src/assets/img/favicon.png +0 -0
- package/template/neo-pipeline-cmps/src/assets/img/map.svg +1 -0
- package/template/neo-pipeline-cmps/src/assets/img/search.svg +1 -0
- package/template/neo-pipeline-cmps/src/assets/img/table.svg +1 -0
- package/template/neo-pipeline-cmps/src/components/filterBar__c/README.md +24 -0
- package/template/neo-pipeline-cmps/src/components/filterBar__c/common.scss +29 -0
- package/template/neo-pipeline-cmps/src/components/filterBar__c/index.tsx +730 -0
- package/template/neo-pipeline-cmps/src/components/filterBar__c/model.ts +50 -0
- package/template/neo-pipeline-cmps/src/components/filterBar__c/style.scss +119 -0
- package/template/neo-pipeline-cmps/src/components/pipelineFunnel__c/index.tsx +415 -0
- package/template/neo-pipeline-cmps/src/components/pipelineFunnel__c/model.ts +79 -0
- package/template/neo-pipeline-cmps/src/components/pipelineFunnel__c/style.scss +83 -0
- package/template/neo-pipeline-cmps/src/components/showHealthResult__c/index.tsx +463 -0
- package/template/neo-pipeline-cmps/src/components/showHealthResult__c/model.ts +45 -0
- package/template/neo-pipeline-cmps/src/components/showHealthResult__c/style.scss +137 -0
- package/template/neo-pipeline-cmps/src/components/simpleTable__c/README.md +90 -0
- package/template/neo-pipeline-cmps/src/components/simpleTable__c/common.scss +195 -0
- package/template/neo-pipeline-cmps/src/components/simpleTable__c/index.tsx +665 -0
- package/template/neo-pipeline-cmps/src/components/simpleTable__c/model.ts +124 -0
- package/template/neo-pipeline-cmps/src/components/simpleTable__c/style.scss +193 -0
- package/template/neo-pipeline-cmps/src/components/stageSwitch__c/index.tsx +511 -0
- package/template/neo-pipeline-cmps/src/components/stageSwitch__c/model.ts +70 -0
- package/template/{neo-bi-cmps → neo-pipeline-cmps}/src/components/stageSwitch__c/style.scss +4 -2
- package/template/neo-pipeline-cmps/src/components/stageTimeChart__c/index.tsx +455 -0
- package/template/neo-pipeline-cmps/src/components/stageTimeChart__c/model.ts +103 -0
- package/template/{neo-bi-cmps → neo-pipeline-cmps}/src/components/stageTimeChart__c/style.scss +3 -2
- package/template/neo-pipeline-cmps/src/utils/common.ts +229 -0
- package/template/neo-pipeline-cmps/src/utils/filter2chartFilter.ts +268 -0
- package/template/neo-pipeline-cmps/src/utils/filterBar.ts +140 -0
- package/template/neo-pipeline-cmps/src/utils/pipelineFunnel.ts +343 -0
- package/template/neo-pipeline-cmps/src/utils/queryByCustomSQL.ts +117 -0
- package/template/neo-pipeline-cmps/src/utils/requestDebounce.ts +22 -0
- package/template/neo-pipeline-cmps/src/utils/simpleTable.tsx +344 -0
- package/template/neo-pipeline-cmps/src/utils/stageSwitch.ts +15 -0
- package/template/neo-pipeline-cmps/src/utils/stageTimeChart.ts +90 -0
- package/template/neo-pipeline-cmps/src/utils/targetNumber.ts +12 -0
- package/template/neo-pipeline-cmps/tsconfig.json +40 -0
- package/template/neo-web-entity-grid/package.json +2 -2
- package/template/neo-web-form/package.json +2 -2
- package/template/react-custom-cmp-template/package.json +1 -1
- package/template/react-ts-custom-cmp-template/package.json +1 -1
- package/template/vue2-custom-cmp-template/package.json +1 -1
- package/template/neo-bi-cmps/.npmrc copy +0 -1
- package/template/neo-bi-cmps/src/components/aiCommitDrawer__c/README.md +0 -52
- package/template/neo-bi-cmps/src/components/aiCommitDrawer__c/index.tsx +0 -183
- package/template/neo-bi-cmps/src/components/aiCommitDrawer__c/model.ts +0 -90
- package/template/neo-bi-cmps/src/components/aiCommitDrawer__c/style.scss +0 -218
- package/template/neo-bi-cmps/src/components/forecastChart__c/README.md +0 -31
- package/template/neo-bi-cmps/src/components/forecastChart__c/index.tsx +0 -158
- package/template/neo-bi-cmps/src/components/forecastChart__c/model.ts +0 -40
- package/template/neo-bi-cmps/src/components/forecastChart__c/style.scss +0 -154
- package/template/neo-bi-cmps/src/components/forecastGrid__c/README.md +0 -36
- package/template/neo-bi-cmps/src/components/forecastGrid__c/index.tsx +0 -86
- package/template/neo-bi-cmps/src/components/forecastGrid__c/model.ts +0 -62
- package/template/neo-bi-cmps/src/components/forecastGrid__c/style.scss +0 -48
- package/template/neo-bi-cmps/src/components/gapCloser__c/README.md +0 -24
- package/template/neo-bi-cmps/src/components/gapCloser__c/index.tsx +0 -100
- package/template/neo-bi-cmps/src/components/gapCloser__c/model.ts +0 -46
- package/template/neo-bi-cmps/src/components/gapCloser__c/style.scss +0 -60
- package/template/neo-bi-cmps/src/components/kpiCards__c/README.md +0 -35
- package/template/neo-bi-cmps/src/components/kpiCards__c/index.tsx +0 -70
- package/template/neo-bi-cmps/src/components/kpiCards__c/model.ts +0 -50
- package/template/neo-bi-cmps/src/components/kpiCards__c/style.scss +0 -33
- package/template/neo-bi-cmps/src/components/oppList__c/README.md +0 -52
- package/template/neo-bi-cmps/src/components/oppList__c/index.tsx +0 -285
- package/template/neo-bi-cmps/src/components/oppList__c/model.ts +0 -86
- package/template/neo-bi-cmps/src/components/oppList__c/style.scss +0 -133
- package/template/neo-bi-cmps/src/components/pipelineFunnel__c/index.tsx +0 -130
- package/template/neo-bi-cmps/src/components/pipelineFunnel__c/model.ts +0 -66
- package/template/neo-bi-cmps/src/components/pipelineFunnel__c/style.scss +0 -133
- package/template/neo-bi-cmps/src/components/stageSwitch__c/index.tsx +0 -118
- package/template/neo-bi-cmps/src/components/stageSwitch__c/model.ts +0 -92
- package/template/neo-bi-cmps/src/components/stageTimeChart__c/index.tsx +0 -126
- package/template/neo-bi-cmps/src/components/stageTimeChart__c/model.ts +0 -57
- package/template/neo-bi-cmps/src/components/tabSwitch__c/README.md +0 -37
- package/template/neo-bi-cmps/src/components/tabSwitch__c/index.tsx +0 -80
- package/template/neo-bi-cmps/src/components/tabSwitch__c/model.ts +0 -45
- package/template/neo-bi-cmps/src/components/tabSwitch__c/style.scss +0 -37
- package/template/neo-bi-cmps/src/utils/axiosFetcher.ts +0 -37
- package/template/neo-bi-cmps/src/utils/queryObjectData.ts +0 -76
- package/template/neo-bi-cmps/src/utils/xobjects.ts +0 -162
- /package/template/{neo-bi-cmps → neo-pipeline-cmps}/src/components/pipelineFunnel__c/README.md +0 -0
- /package/template/{neo-bi-cmps → neo-pipeline-cmps}/src/components/stageSwitch__c/README.md +0 -0
- /package/template/{neo-bi-cmps → neo-pipeline-cmps}/src/components/stageTimeChart__c/README.md +0 -0
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 表格 simpleTable 专用:格式化、历史快照、趋势与单元格渲染
|
|
3
|
+
*/
|
|
4
|
+
import * as React from 'react';
|
|
5
|
+
import { Card, Popover } from 'antd';
|
|
6
|
+
import moment from 'moment';
|
|
7
|
+
|
|
8
|
+
import { toFiniteNumber } from './common';
|
|
9
|
+
|
|
10
|
+
/** 期间初商机快照(与当前行 id 关联) */
|
|
11
|
+
export interface HistoryOppSnap {
|
|
12
|
+
id: string;
|
|
13
|
+
closeDate: unknown;
|
|
14
|
+
opportunityName: string;
|
|
15
|
+
saleStageId: string;
|
|
16
|
+
money: unknown;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface FieldInfo {
|
|
20
|
+
name: string;
|
|
21
|
+
label: string;
|
|
22
|
+
apiKey: string;
|
|
23
|
+
type: string;
|
|
24
|
+
itemType: string;
|
|
25
|
+
checkitem: any[];
|
|
26
|
+
selectitem?: any[];
|
|
27
|
+
required: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function parseNumberForCompare(value: unknown): number | null {
|
|
31
|
+
if (value == null || value === '') return null;
|
|
32
|
+
const n = parseFloat(String(value).replace(/,/g, '').trim());
|
|
33
|
+
return Number.isFinite(n) ? n : null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function parseCloseDateMs(value: unknown): number | null {
|
|
37
|
+
if (value == null || value === '') return null;
|
|
38
|
+
const m = moment(value);
|
|
39
|
+
return m.isValid() ? m.valueOf() : null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
type Trend = 'up' | 'down' | 'same';
|
|
43
|
+
|
|
44
|
+
function moneyTrend(
|
|
45
|
+
current: unknown,
|
|
46
|
+
history: HistoryOppSnap | null | undefined,
|
|
47
|
+
): Trend | null {
|
|
48
|
+
if (!history) return null;
|
|
49
|
+
const cur = parseNumberForCompare(current);
|
|
50
|
+
const prev = parseNumberForCompare(history.money);
|
|
51
|
+
if (cur == null || prev == null) return null;
|
|
52
|
+
if (cur > prev) return 'up';
|
|
53
|
+
if (cur < prev) return 'down';
|
|
54
|
+
return 'same';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function closeDateTrend(
|
|
58
|
+
current: unknown,
|
|
59
|
+
history: HistoryOppSnap | null | undefined,
|
|
60
|
+
): Trend | null {
|
|
61
|
+
if (!history) return null;
|
|
62
|
+
const cur = parseCloseDateMs(current);
|
|
63
|
+
const prev = parseCloseDateMs(history.closeDate);
|
|
64
|
+
if (cur == null || prev == null) return null;
|
|
65
|
+
if (cur > prev) return 'up';
|
|
66
|
+
if (cur < prev) return 'down';
|
|
67
|
+
return 'same';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** 自定义 SQL 返回行:数组列顺序与 HISTORY_QUERY_FIELDS 一致;或对象字段 */
|
|
71
|
+
export function historyRowToSnap(row: unknown): HistoryOppSnap | null {
|
|
72
|
+
if (row == null) return null;
|
|
73
|
+
if (Array.isArray(row)) {
|
|
74
|
+
if (row.length < 5) return null;
|
|
75
|
+
return {
|
|
76
|
+
id: String(row[0] ?? '').trim(),
|
|
77
|
+
closeDate: row[1],
|
|
78
|
+
opportunityName: row[2] != null ? String(row[2]) : '',
|
|
79
|
+
saleStageId: row[3] != null ? String(row[3]) : '',
|
|
80
|
+
money: row[4],
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
if (typeof row === 'object') {
|
|
84
|
+
const o = row as Record<string, unknown>;
|
|
85
|
+
const id = o.id ?? o.opportunity_1_id;
|
|
86
|
+
if (id == null || id === '') return null;
|
|
87
|
+
return {
|
|
88
|
+
id: String(id).trim(),
|
|
89
|
+
closeDate:
|
|
90
|
+
o.opportunity_1_closeDate ?? o.closeDate ?? o.opportunity_1_close_date,
|
|
91
|
+
opportunityName: String(
|
|
92
|
+
o.opportunity_1_opportunityName ?? o.opportunityName ?? '',
|
|
93
|
+
),
|
|
94
|
+
saleStageId: String(
|
|
95
|
+
o.opportunity_1_saleStageId ?? o.saleStageId ?? '',
|
|
96
|
+
),
|
|
97
|
+
money: o.opportunity_1_money ?? o.money,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function buildHistoryMap(rows: unknown[]): Map<string, HistoryOppSnap> {
|
|
104
|
+
const map = new Map<string, HistoryOppSnap>();
|
|
105
|
+
for (const raw of rows) {
|
|
106
|
+
const snap = historyRowToSnap(raw);
|
|
107
|
+
if (snap?.id) map.set(snap.id, snap);
|
|
108
|
+
}
|
|
109
|
+
return map;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** closeDate:YYYY-MM-DD */
|
|
113
|
+
export function formatCloseDate(value: unknown): string {
|
|
114
|
+
if (value == null || value === '') return '—';
|
|
115
|
+
const m = moment(value);
|
|
116
|
+
return m.isValid() ? m.format('YYYY-MM-DD') : String(value);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** recentActivityRecordTime:YYYY-MM-DD HH:mm:ss */
|
|
120
|
+
export function formatActivityDateTime(value: unknown): string {
|
|
121
|
+
if (value == null || value === '') return '—';
|
|
122
|
+
const m = moment(value);
|
|
123
|
+
return m.isValid() ? m.format('YYYY-MM-DD HH:mm:ss') : String(value);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** customItem246/247:变更记录时间(毫秒/秒时间戳或可被 moment 解析的值)→ YYYY-MM-DD */
|
|
127
|
+
export function formatChangeRecordedAt(value: unknown): string {
|
|
128
|
+
if (value == null || value === '') return '—';
|
|
129
|
+
const n = toFiniteNumber(value);
|
|
130
|
+
if (n != null) {
|
|
131
|
+
const ms = n < 1e12 ? n * 1000 : n;
|
|
132
|
+
const m = moment(ms);
|
|
133
|
+
if (m.isValid()) return m.format('YYYY-MM-DD');
|
|
134
|
+
}
|
|
135
|
+
const m = moment(value);
|
|
136
|
+
return m.isValid() ? m.format('YYYY-MM-DD') : String(value);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** 金额列:$ 前缀 + 千分位(不缩写) */
|
|
140
|
+
export function formatMoneyCell(value: unknown): string {
|
|
141
|
+
if (value == null || value === '') return '—';
|
|
142
|
+
const n = parseFloat(String(value).replace(/,/g, '').trim());
|
|
143
|
+
if (!Number.isFinite(n)) return String(value);
|
|
144
|
+
const part = Number.isInteger(n)
|
|
145
|
+
? n.toLocaleString('en-US')
|
|
146
|
+
: n.toLocaleString('en-US', {
|
|
147
|
+
minimumFractionDigits: 0,
|
|
148
|
+
maximumFractionDigits: 2,
|
|
149
|
+
});
|
|
150
|
+
return `$${part}`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** oppHealthAssessmentLevel:数值 → 健康度标签(带颜色背景) */
|
|
154
|
+
export function renderOppHealthAssessmentLevel(value: unknown): React.ReactNode {
|
|
155
|
+
if (value == null || value === '') return '—';
|
|
156
|
+
const n = toFiniteNumber(value);
|
|
157
|
+
if (n === 5) {
|
|
158
|
+
return (
|
|
159
|
+
<span className="opp-health-tag opp-health-tag--green">Excellent</span>
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
if (n === 6) {
|
|
163
|
+
return <span className="opp-health-tag opp-health-tag--yellow">Health</span>;
|
|
164
|
+
}
|
|
165
|
+
if (n === 7) {
|
|
166
|
+
return <span className="opp-health-tag opp-health-tag--red">Risk</span>;
|
|
167
|
+
}
|
|
168
|
+
if (n === 8) {
|
|
169
|
+
return <span className="opp-health-tag opp-health-tag--red">Problem</span>;
|
|
170
|
+
}
|
|
171
|
+
return '—';
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* 百分比列:末尾加 %;接口常见为 0–1 小数或 0–100 数值,均按可读方式展示
|
|
176
|
+
*/
|
|
177
|
+
export function formatPercentCell(value: unknown): string {
|
|
178
|
+
if (value == null || value === '') return '—';
|
|
179
|
+
const raw = String(value).replace(/,/g, '').trim();
|
|
180
|
+
const n = parseFloat(raw.replace(/%/g, ''));
|
|
181
|
+
if (!Number.isFinite(n)) return String(value);
|
|
182
|
+
let pct = n;
|
|
183
|
+
if (n > 0 && n <= 1) {
|
|
184
|
+
pct = n * 100;
|
|
185
|
+
}
|
|
186
|
+
const text = Number.isInteger(pct)
|
|
187
|
+
? String(pct)
|
|
188
|
+
: pct.toLocaleString('en-US', {
|
|
189
|
+
minimumFractionDigits: 0,
|
|
190
|
+
maximumFractionDigits: 2,
|
|
191
|
+
});
|
|
192
|
+
return `${text}%`;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function moneyChangeTooltipTitle(
|
|
196
|
+
record: any,
|
|
197
|
+
current: unknown,
|
|
198
|
+
): React.ReactNode {
|
|
199
|
+
const preset = record?.customItem246__c;
|
|
200
|
+
const h = record?.historyData as HistoryOppSnap | undefined;
|
|
201
|
+
if (!h) return null;
|
|
202
|
+
return (
|
|
203
|
+
<div className="simpleTable-change-tip">
|
|
204
|
+
<div className="simpleTable-change-tip__title">Amount</div>
|
|
205
|
+
<div className="simpleTable-change-tip__detail">
|
|
206
|
+
{formatMoneyCell(h.money)}
|
|
207
|
+
{' → '}
|
|
208
|
+
<span className="simpleTable-change-tip__to">{formatMoneyCell(current)}</span>
|
|
209
|
+
</div>
|
|
210
|
+
<div className="simpleTable-change-tip__meta">
|
|
211
|
+
Changed on {formatChangeRecordedAt(preset)}
|
|
212
|
+
</div>
|
|
213
|
+
</div>
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export function closeDateChangeTooltipTitle(
|
|
218
|
+
record: any,
|
|
219
|
+
current: unknown,
|
|
220
|
+
): React.ReactNode {
|
|
221
|
+
const preset = record?.customItem247__c;
|
|
222
|
+
const h = record?.historyData as HistoryOppSnap | undefined;
|
|
223
|
+
if (!h) return null;
|
|
224
|
+
return (
|
|
225
|
+
<div className="simpleTable-change-tip">
|
|
226
|
+
<div className="simpleTable-change-tip__title">Close Date</div>
|
|
227
|
+
<div className="simpleTable-change-tip__detail">
|
|
228
|
+
{formatCloseDate(h.closeDate)}
|
|
229
|
+
{' → '}
|
|
230
|
+
<span className="simpleTable-change-tip__to">{formatCloseDate(current)}</span>
|
|
231
|
+
</div>
|
|
232
|
+
<div className="simpleTable-change-tip__meta">
|
|
233
|
+
Changed on {formatChangeRecordedAt(preset)}
|
|
234
|
+
</div>
|
|
235
|
+
</div>
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export function renderTrendCell(args: {
|
|
240
|
+
displayText: string;
|
|
241
|
+
trend: Trend | null;
|
|
242
|
+
tooltip: React.ReactNode;
|
|
243
|
+
}): React.ReactNode {
|
|
244
|
+
const { displayText, trend, tooltip } = args;
|
|
245
|
+
const showArrow = trend === 'up' || trend === 'down';
|
|
246
|
+
const arrow =
|
|
247
|
+
trend === 'up' ? (
|
|
248
|
+
<span className="simpleTable-trend simpleTable-trend--up">↑</span>
|
|
249
|
+
) : trend === 'down' ? (
|
|
250
|
+
<span className="simpleTable-trend simpleTable-trend--down">↓</span>
|
|
251
|
+
) : null;
|
|
252
|
+
|
|
253
|
+
return (
|
|
254
|
+
<span className="simpleTable-cell-trend-wrap">
|
|
255
|
+
<span className="simpleTable-cell-trend-wrap__text">{displayText}</span>
|
|
256
|
+
{showArrow && arrow ? (
|
|
257
|
+
<Popover
|
|
258
|
+
content={tooltip}
|
|
259
|
+
trigger="hover"
|
|
260
|
+
placement="top"
|
|
261
|
+
overlayClassName="simpleTable-change-popover"
|
|
262
|
+
mouseEnterDelay={0.08}
|
|
263
|
+
>
|
|
264
|
+
<span className="simpleTable-cell-trend-wrap__arrow">{arrow}</span>
|
|
265
|
+
</Popover>
|
|
266
|
+
) : null}
|
|
267
|
+
</span>
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** demo.html winRateTooltip 风格 */
|
|
272
|
+
export function renderWinRateHoverCard(record: any): React.ReactNode {
|
|
273
|
+
const baseline = record?.customItem244__c;
|
|
274
|
+
const positives = record?.customItem241__c;
|
|
275
|
+
const negatives = record?.customItem242__c;
|
|
276
|
+
const winRate = record?.customItem239__c;
|
|
277
|
+
|
|
278
|
+
const fmtLine = (v: unknown) =>
|
|
279
|
+
v == null || v === '' ? '—' : String(v);
|
|
280
|
+
|
|
281
|
+
return (
|
|
282
|
+
<Card
|
|
283
|
+
size="small"
|
|
284
|
+
bordered={false}
|
|
285
|
+
className="simpleTable-winrate-card"
|
|
286
|
+
>
|
|
287
|
+
<div className="simpleTable-winrate-card__baseline">
|
|
288
|
+
Baseline Probability{' '}
|
|
289
|
+
<span className="simpleTable-winrate-card__baseline-val">
|
|
290
|
+
{formatPercentCell(baseline)}
|
|
291
|
+
</span>
|
|
292
|
+
</div>
|
|
293
|
+
<hr className="simpleTable-winrate-card__hr" />
|
|
294
|
+
<div className="simpleTable-winrate-card__section-title">Positive Factors</div>
|
|
295
|
+
<div className="simpleTable-winrate-card__positives">
|
|
296
|
+
{fmtLine(positives)}
|
|
297
|
+
</div>
|
|
298
|
+
<hr className="simpleTable-winrate-card__hr" />
|
|
299
|
+
<div className="simpleTable-winrate-card__section-title">Negative Factors</div>
|
|
300
|
+
<div className="simpleTable-winrate-card__negatives">
|
|
301
|
+
{fmtLine(negatives)}
|
|
302
|
+
</div>
|
|
303
|
+
<hr className="simpleTable-winrate-card__hr" />
|
|
304
|
+
<div className="simpleTable-winrate-card__footer">
|
|
305
|
+
<span>AI Win Rate</span>
|
|
306
|
+
<span className="simpleTable-winrate-card__winrate-val">
|
|
307
|
+
{formatPercentCell(winRate)}
|
|
308
|
+
</span>
|
|
309
|
+
</div>
|
|
310
|
+
</Card>
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** 汇总金额展示(与 formatMoneyCell 数字格式一致) */
|
|
315
|
+
export function formatMoneySummaryAmount(n: number): string {
|
|
316
|
+
const part = Number.isInteger(n)
|
|
317
|
+
? n.toLocaleString('en-US')
|
|
318
|
+
: n.toLocaleString('en-US', {
|
|
319
|
+
minimumFractionDigits: 0,
|
|
320
|
+
maximumFractionDigits: 2,
|
|
321
|
+
});
|
|
322
|
+
return `$${part}`;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/** 按字段 apiKey 汇总行金额(解析规则与金额列一致) */
|
|
326
|
+
export function sumMoneyRows(rows: any[], moneyFieldKey: string): number {
|
|
327
|
+
let total = 0;
|
|
328
|
+
for (const row of rows) {
|
|
329
|
+
const v = row?.[moneyFieldKey];
|
|
330
|
+
if (v == null || v === '') continue;
|
|
331
|
+
const n = parseFloat(String(v).replace(/,/g, '').trim());
|
|
332
|
+
if (Number.isFinite(n)) total += n;
|
|
333
|
+
}
|
|
334
|
+
return total;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
export function isMoneyField(field: FieldInfo): boolean {
|
|
338
|
+
const k = field.apiKey?.toLowerCase?.() ?? '';
|
|
339
|
+
if (k === 'money' || k === 'amount') return true;
|
|
340
|
+
if (field.type === 'currency' || field.itemType === 'currency') return true;
|
|
341
|
+
return false;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export { moneyTrend, closeDateTrend };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 阶段切换 stageSwitch 专用(商机行字段解析)
|
|
3
|
+
*/
|
|
4
|
+
import { parseNumberOrZero } from './common';
|
|
5
|
+
|
|
6
|
+
export function rowOpportunityStage(row: Record<string, unknown>): string {
|
|
7
|
+
return String(row.customItem248__c ?? '');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function rowMoney(row: Record<string, unknown>): number {
|
|
11
|
+
if (row.money !== undefined && row.money !== null && row.money !== '') {
|
|
12
|
+
return parseNumberOrZero(row.money);
|
|
13
|
+
}
|
|
14
|
+
return parseNumberOrZero(row.amount);
|
|
15
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 阶段停留时间 stageTimeChart 专用
|
|
3
|
+
*/
|
|
4
|
+
import { extractStageKeyFromStageName } from './common';
|
|
5
|
+
|
|
6
|
+
const COLOR_GREEN = '#22c55e';
|
|
7
|
+
const COLOR_YELLOW = '#eab308';
|
|
8
|
+
const COLOR_RED = '#ef4444';
|
|
9
|
+
const COLOR_NEUTRAL = '#94a3b8';
|
|
10
|
+
|
|
11
|
+
export interface StageTimeRow {
|
|
12
|
+
stageName: string;
|
|
13
|
+
actualTime: number;
|
|
14
|
+
actualTimeDisplay: string;
|
|
15
|
+
targetPercent: number;
|
|
16
|
+
limitPercent: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface StageTimeItem {
|
|
20
|
+
stageName: string;
|
|
21
|
+
actualTime: string;
|
|
22
|
+
actualPercent: number;
|
|
23
|
+
actualColor: string;
|
|
24
|
+
barActualWidthPct: number;
|
|
25
|
+
barTargetLeftPct: number;
|
|
26
|
+
barLimitLeftPct: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 将「16天11小时」「8天6时」等文案转为天数(浮点),如 16 + 11/24
|
|
31
|
+
*/
|
|
32
|
+
export function parseDurationToDays(text: unknown): number {
|
|
33
|
+
const s = String(text ?? '').trim();
|
|
34
|
+
if (!s) return 0;
|
|
35
|
+
let days = 0;
|
|
36
|
+
let hours = 0;
|
|
37
|
+
const dayMatch = s.match(/(\d+(?:\.\d+)?)\s*(天|Day)/);
|
|
38
|
+
if (dayMatch) days = parseFloat(dayMatch[1]) || 0;
|
|
39
|
+
const hourMatch = s.match(/(\d+(?:\.\d+)?)\s*(?:小时|时|Hour)/);
|
|
40
|
+
if (hourMatch) hours = parseFloat(hourMatch[1]) || 0;
|
|
41
|
+
return days + hours / 24;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** 终局阶段不参与「阶段停留时间」展示 */
|
|
45
|
+
export function isClosedOutcomeStage(stageName: string): boolean {
|
|
46
|
+
const key = extractStageKeyFromStageName(stageName).toLowerCase();
|
|
47
|
+
return key === 'closed won' || key === 'closed lost';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** 配色:实际天数与 customItem3(目标)/customItem4(上限)比较 */
|
|
51
|
+
export function resolveActualColor(
|
|
52
|
+
actualDays: number,
|
|
53
|
+
target: number,
|
|
54
|
+
limit: number,
|
|
55
|
+
): string {
|
|
56
|
+
if (actualDays <= target) return COLOR_GREEN;
|
|
57
|
+
if (actualDays > limit) return COLOR_RED;
|
|
58
|
+
return COLOR_YELLOW;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** 将解析后的行转为图表条数据(配色与条形比例) */
|
|
62
|
+
export function buildStageTimeItems(rows: StageTimeRow[]): StageTimeItem[] {
|
|
63
|
+
return rows.map((row) => {
|
|
64
|
+
const actualPercent = row.actualTime;
|
|
65
|
+
const hasTh =
|
|
66
|
+
Number.isFinite(row.targetPercent) && Number.isFinite(row.limitPercent);
|
|
67
|
+
let targetNum = row.targetPercent;
|
|
68
|
+
let limitNum = row.limitPercent;
|
|
69
|
+
let actualColor: string;
|
|
70
|
+
const scaleMax = 60;
|
|
71
|
+
|
|
72
|
+
if (hasTh) {
|
|
73
|
+
actualColor = resolveActualColor(actualPercent, targetNum, limitNum);
|
|
74
|
+
} else {
|
|
75
|
+
targetNum = 0;
|
|
76
|
+
limitNum = 0;
|
|
77
|
+
actualColor = COLOR_NEUTRAL;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
stageName: row.stageName,
|
|
82
|
+
actualTime: row.actualTimeDisplay,
|
|
83
|
+
actualPercent,
|
|
84
|
+
actualColor,
|
|
85
|
+
barActualWidthPct: Math.min(100, (actualPercent / scaleMax) * 100),
|
|
86
|
+
barTargetLeftPct: hasTh ? (targetNum / scaleMax) * 100 : 0,
|
|
87
|
+
barLimitLeftPct: hasTh ? (limitNum / scaleMax) * 100 : 0,
|
|
88
|
+
};
|
|
89
|
+
});
|
|
90
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 数值指标 targetNumber:样式辅助
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/** margin/padding 字符串:无单位时补上 quantity,已有 px/rem等则原样返回 */
|
|
6
|
+
export function formatCssSpacing(value: string, unit: string): string {
|
|
7
|
+
if (!value || value === '0') return '0';
|
|
8
|
+
if (/\d+(px|rem|em|%)$/.test(value.trim())) {
|
|
9
|
+
return value;
|
|
10
|
+
}
|
|
11
|
+
return `${value}${unit}`;
|
|
12
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"experimentalDecorators": true,
|
|
4
|
+
"target": "esnext",
|
|
5
|
+
"module": "esnext",
|
|
6
|
+
"allowJs": false,
|
|
7
|
+
"jsx": "react", // preserve
|
|
8
|
+
"declaration": false,
|
|
9
|
+
"noEmit": false,
|
|
10
|
+
"importHelpers": true,
|
|
11
|
+
"isolatedModules": false,
|
|
12
|
+
"strict": false,
|
|
13
|
+
"noImplicitAny": true,
|
|
14
|
+
"strictNullChecks": true,
|
|
15
|
+
"noImplicitThis": true,
|
|
16
|
+
"noUnusedLocals": false,
|
|
17
|
+
"noImplicitReturns": true,
|
|
18
|
+
"moduleResolution": "node",
|
|
19
|
+
"baseUrl": "./",
|
|
20
|
+
"paths": {
|
|
21
|
+
"@": [
|
|
22
|
+
"./src"
|
|
23
|
+
]
|
|
24
|
+
},
|
|
25
|
+
"typeRoots": [
|
|
26
|
+
"./@types",
|
|
27
|
+
"./node_modules/@types"
|
|
28
|
+
],
|
|
29
|
+
"allowSyntheticDefaultImports": true,
|
|
30
|
+
"esModuleInterop": true,
|
|
31
|
+
"forceConsistentCasingInFileNames": true
|
|
32
|
+
},
|
|
33
|
+
"include": [
|
|
34
|
+
"src",
|
|
35
|
+
"test"
|
|
36
|
+
],
|
|
37
|
+
"exclude": [
|
|
38
|
+
"node_modules"
|
|
39
|
+
]
|
|
40
|
+
}
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"antd": "^4.9.4",
|
|
38
38
|
"axios": "^1.7.0",
|
|
39
39
|
"lodash": "^4.17.23",
|
|
40
|
-
"neo-open-api": "^1.2.
|
|
40
|
+
"neo-open-api": "^1.2.7",
|
|
41
41
|
"neo-register": "^1.2.0",
|
|
42
42
|
"react": "^16.9.0",
|
|
43
43
|
"react-dom": "^16.9.0",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"@types/react-dom": "^16.9.15",
|
|
53
53
|
"husky": "^4.2.5",
|
|
54
54
|
"lint-staged": "^10.2.9",
|
|
55
|
-
"neo-cmp-cli": "^1.13.
|
|
55
|
+
"neo-cmp-cli": "^1.13.17",
|
|
56
56
|
"prettier": "^2.0.5"
|
|
57
57
|
},
|
|
58
58
|
"overrides": {
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"antd": "^4.9.4",
|
|
38
38
|
"axios": "^1.7.0",
|
|
39
39
|
"lodash": "^4.17.23",
|
|
40
|
-
"neo-open-api": "^1.2.
|
|
40
|
+
"neo-open-api": "^1.2.7",
|
|
41
41
|
"neo-register": "^1.2.0",
|
|
42
42
|
"react": "^16.9.0",
|
|
43
43
|
"react-dom": "^16.9.0",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"@types/react-dom": "^16.9.15",
|
|
54
54
|
"husky": "^4.2.5",
|
|
55
55
|
"lint-staged": "^10.2.9",
|
|
56
|
-
"neo-cmp-cli": "^1.13.
|
|
56
|
+
"neo-cmp-cli": "^1.13.17",
|
|
57
57
|
"prettier": "^2.0.5"
|
|
58
58
|
},
|
|
59
59
|
"overrides": {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
registry = "https://registry.npmmirror.com/"
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
# AiCommitDrawer 组件
|
|
2
|
-
|
|
3
|
-
AI推荐Commit抽屉组件,展示AI推荐的Commit商机。
|
|
4
|
-
|
|
5
|
-
## 使用方式
|
|
6
|
-
|
|
7
|
-
```tsx
|
|
8
|
-
import AiCommitDrawer from './components/aiCommitDrawer__c';
|
|
9
|
-
|
|
10
|
-
<AiCommitDrawer
|
|
11
|
-
visible={true}
|
|
12
|
-
totalAmount="$6,800,000"
|
|
13
|
-
closedAmount="$3,200,000"
|
|
14
|
-
tabs={[
|
|
15
|
-
{ key: 'pipeline', label: 'From Open Pipeline', amount: '$2,500,000', count: 2 },
|
|
16
|
-
{ key: 'commit', label: 'From Commit', amount: '$1,800,000', count: 2 },
|
|
17
|
-
]}
|
|
18
|
-
pipelineDeals={[...]}
|
|
19
|
-
commitDeals={[...]}
|
|
20
|
-
onClose={() => console.log('Close')}
|
|
21
|
-
onPrioritize={(deal) => console.log('Prioritize:', deal)}
|
|
22
|
-
onDismiss={(deal) => console.log('Dismiss:', deal)}
|
|
23
|
-
/>
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
## Props
|
|
27
|
-
|
|
28
|
-
| 属性 | 说明 | 类型 | 默认值 |
|
|
29
|
-
|------|------|------|--------|
|
|
30
|
-
| visible | 是否显示 | boolean | false |
|
|
31
|
-
| totalAmount | AI预测总额 | string | '$6,800,000' |
|
|
32
|
-
| closedAmount | 已关闭金额 | string | '$3,200,000' |
|
|
33
|
-
| closedCount | 已关闭数量 | number | 5 |
|
|
34
|
-
| tabs | Tab配置 | { key, label, amount, count }[] | [] |
|
|
35
|
-
| pipelineDeals | Pipeline商机 | CommitDeal[] | [] |
|
|
36
|
-
| commitDeals | Commit商机 | CommitDeal[] | [] |
|
|
37
|
-
| onClose | 关闭回调 | () => void | - |
|
|
38
|
-
| onPrioritize | 优先处理回调 | (deal: CommitDeal) => void | - |
|
|
39
|
-
| onDismiss | 忽略回调 | (deal: CommitDeal) => void | - |
|
|
40
|
-
|
|
41
|
-
## CommitDeal
|
|
42
|
-
|
|
43
|
-
| 属性 | 说明 | 类型 |
|
|
44
|
-
|------|------|------|
|
|
45
|
-
| id | 唯一标识 | string |
|
|
46
|
-
| name | 商机名称 | string |
|
|
47
|
-
| account | 账户 | string |
|
|
48
|
-
| amount | 金额 | string |
|
|
49
|
-
| stage | 阶段 | string |
|
|
50
|
-
| closeDate | 关闭日期 | string |
|
|
51
|
-
| aiScore | AI评分 | string |
|
|
52
|
-
| description | 描述 | string |
|