neo-cmp-cli 1.13.16 → 1.13.18

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.
Files changed (182) hide show
  1. package/README.md +2 -1
  2. package/dist/index2.js +1 -1
  3. package/dist/main2.js +1 -1
  4. package/dist/neo/env.js +1 -1
  5. package/dist/neo/neoLogin.js +1 -1
  6. package/dist/neo/pushCmp.js +1 -1
  7. package/dist/package.json.js +1 -1
  8. package/package.json +3 -2
  9. package/template/antd-custom-cmp-template/package.json +1 -1
  10. package/template/asset-manage-template/docs/README.md +1 -232
  11. package/template/asset-manage-template/package.json +2 -2
  12. package/template/echarts-custom-cmp-template/package.json +1 -1
  13. package/template/empty-custom-cmp-template/package.json +2 -2
  14. package/template/map-custom-cmp-template/package.json +1 -1
  15. package/template/neo-bi-cmps/neo.config.js +7 -1
  16. package/template/neo-bi-cmps/package.json +8 -7
  17. package/template/neo-bi-cmps/public/403.html +77 -0
  18. package/template/neo-bi-cmps/public/demo.html +2453 -0
  19. package/template/neo-bi-cmps/src/assets/icon/barChart.svg +1 -0
  20. package/template/neo-bi-cmps/src/assets/icon/card.svg +1 -0
  21. package/template/neo-bi-cmps/src/assets/icon/filter.svg +1 -0
  22. package/template/neo-bi-cmps/src/assets/icon/funnel.svg +1 -0
  23. package/template/neo-bi-cmps/src/assets/icon/tab.svg +1 -0
  24. package/template/neo-bi-cmps/src/components/filterBar__c/README.md +3 -14
  25. package/template/neo-bi-cmps/src/components/filterBar__c/common.scss +29 -0
  26. package/template/neo-bi-cmps/src/components/filterBar__c/index.tsx +668 -146
  27. package/template/neo-bi-cmps/src/components/filterBar__c/model.ts +26 -48
  28. package/template/neo-bi-cmps/src/components/filterBar__c/style.scss +46 -139
  29. package/template/neo-bi-cmps/src/components/targetNumber__c/customStyleConfig/index.tsx +11 -10
  30. package/template/neo-bi-cmps/src/components/targetNumber__c/index.tsx +9 -16
  31. package/template/neo-bi-cmps/src/components/targetNumber__c/model.ts +1 -1
  32. package/template/neo-bi-cmps/src/utils/common.ts +231 -0
  33. package/template/neo-bi-cmps/src/utils/filter2chartFilter.ts +268 -0
  34. package/template/neo-bi-cmps/src/utils/filterBar.ts +140 -0
  35. package/template/neo-bi-cmps/src/utils/pipelineFunnel.ts +341 -0
  36. package/template/{neo-h5-cmps/src/utils/queryObjectData.ts → neo-bi-cmps/src/utils/queryByCustomSQL.ts} +18 -13
  37. package/template/neo-bi-cmps/src/utils/requestDebounce.ts +22 -0
  38. package/template/neo-bi-cmps/src/utils/simpleTable.tsx +344 -0
  39. package/template/neo-bi-cmps/src/utils/stageSwitch.ts +15 -0
  40. package/template/neo-bi-cmps/src/utils/stageTimeChart.ts +90 -0
  41. package/template/neo-bi-cmps/src/utils/targetNumber.ts +12 -0
  42. package/template/neo-custom-cmp-template/docs/README.md +0 -231
  43. package/template/neo-custom-cmp-template/package.json +2 -2
  44. package/template/neo-h5-cmps/package.json +2 -2
  45. package/template/neo-h5-cmps/src/components/entityList__c/index.tsx +1 -2
  46. package/template/neo-h5-cmps/src/components/entityTabs__c/index.tsx +1 -1
  47. package/template/neo-h5-cmps/src/components/globalSearchInput__c/index.tsx +1 -1
  48. package/template/neo-h5-cmps/src/components/openChatPageBtn__c/index.tsx +1 -2
  49. package/template/neo-order-cmps/package.json +2 -2
  50. package/template/neo-pipeline-cmps/.prettierrc.js +12 -0
  51. package/template/neo-pipeline-cmps/@types/neo-ui-common.d.ts +36 -0
  52. package/template/neo-pipeline-cmps/README.md +99 -0
  53. package/template/neo-pipeline-cmps/commitlint.config.js +59 -0
  54. package/template/neo-pipeline-cmps/neo.config.js +135 -0
  55. package/template/neo-pipeline-cmps/package.json +66 -0
  56. package/template/neo-pipeline-cmps/public/403.html +77 -0
  57. package/template/neo-pipeline-cmps/public/css/base.css +283 -0
  58. package/template/neo-pipeline-cmps/public/demo.html +2453 -0
  59. package/template/neo-pipeline-cmps/public/scripts/app/bluebird.js +6679 -0
  60. package/template/neo-pipeline-cmps/public/template.html +13 -0
  61. package/template/neo-pipeline-cmps/src/assets/css/common.scss +127 -0
  62. package/template/neo-pipeline-cmps/src/assets/css/mixin.scss +47 -0
  63. package/template/neo-pipeline-cmps/src/assets/icon/barChart.svg +1 -0
  64. package/template/neo-pipeline-cmps/src/assets/icon/card.svg +1 -0
  65. package/template/neo-pipeline-cmps/src/assets/icon/filter.svg +1 -0
  66. package/template/neo-pipeline-cmps/src/assets/icon/funnel.svg +1 -0
  67. package/template/neo-pipeline-cmps/src/assets/icon/tab.svg +1 -0
  68. package/template/neo-pipeline-cmps/src/assets/img/AIBtn.gif +0 -0
  69. package/template/neo-pipeline-cmps/src/assets/img/NeoCRM.jpg +0 -0
  70. package/template/neo-pipeline-cmps/src/assets/img/aiLogo.png +0 -0
  71. package/template/neo-pipeline-cmps/src/assets/img/card-list.svg +1 -0
  72. package/template/neo-pipeline-cmps/src/assets/img/contact-form.svg +1 -0
  73. package/template/neo-pipeline-cmps/src/assets/img/custom-form.svg +1 -0
  74. package/template/neo-pipeline-cmps/src/assets/img/custom-widget.svg +1 -0
  75. package/template/neo-pipeline-cmps/src/assets/img/data-list.svg +1 -0
  76. package/template/neo-pipeline-cmps/src/assets/img/detail.svg +1 -0
  77. package/template/neo-pipeline-cmps/src/assets/img/favicon.png +0 -0
  78. package/template/neo-pipeline-cmps/src/assets/img/map.svg +1 -0
  79. package/template/neo-pipeline-cmps/src/assets/img/search.svg +1 -0
  80. package/template/neo-pipeline-cmps/src/assets/img/table.svg +1 -0
  81. package/template/neo-pipeline-cmps/src/components/filterBar__c/README.md +24 -0
  82. package/template/neo-pipeline-cmps/src/components/filterBar__c/common.scss +29 -0
  83. package/template/neo-pipeline-cmps/src/components/filterBar__c/index.tsx +731 -0
  84. package/template/neo-pipeline-cmps/src/components/filterBar__c/model.ts +52 -0
  85. package/template/neo-pipeline-cmps/src/components/filterBar__c/style.scss +119 -0
  86. package/template/neo-pipeline-cmps/src/components/pipelineFunnel__c/README.md +39 -0
  87. package/template/neo-pipeline-cmps/src/components/pipelineFunnel__c/index.tsx +416 -0
  88. package/template/neo-pipeline-cmps/src/components/pipelineFunnel__c/model.ts +80 -0
  89. package/template/neo-pipeline-cmps/src/components/pipelineFunnel__c/style.scss +83 -0
  90. package/template/neo-pipeline-cmps/src/components/showHealthResult__c/index.tsx +470 -0
  91. package/template/neo-pipeline-cmps/src/components/showHealthResult__c/model.ts +45 -0
  92. package/template/neo-pipeline-cmps/src/components/showHealthResult__c/style.scss +137 -0
  93. package/template/neo-pipeline-cmps/src/components/simpleTable__c/README.md +89 -0
  94. package/template/neo-pipeline-cmps/src/components/simpleTable__c/common.scss +195 -0
  95. package/template/neo-pipeline-cmps/src/components/simpleTable__c/index.tsx +667 -0
  96. package/template/neo-pipeline-cmps/src/components/simpleTable__c/model.ts +124 -0
  97. package/template/neo-pipeline-cmps/src/components/simpleTable__c/style.scss +192 -0
  98. package/template/neo-pipeline-cmps/src/components/stageSwitch__c/README.md +36 -0
  99. package/template/neo-pipeline-cmps/src/components/stageSwitch__c/index.tsx +513 -0
  100. package/template/neo-pipeline-cmps/src/components/stageSwitch__c/model.ts +71 -0
  101. package/template/{neo-bi-cmps → neo-pipeline-cmps}/src/components/stageSwitch__c/style.scss +4 -2
  102. package/template/neo-pipeline-cmps/src/components/stageTimeChart__c/README.md +37 -0
  103. package/template/neo-pipeline-cmps/src/components/stageTimeChart__c/index.tsx +455 -0
  104. package/template/neo-pipeline-cmps/src/components/stageTimeChart__c/model.ts +106 -0
  105. package/template/{neo-bi-cmps → neo-pipeline-cmps}/src/components/stageTimeChart__c/style.scss +3 -2
  106. package/template/neo-pipeline-cmps/src/utils/common.ts +229 -0
  107. package/template/neo-pipeline-cmps/src/utils/filter2chartFilter.ts +266 -0
  108. package/template/neo-pipeline-cmps/src/utils/filterBar.ts +140 -0
  109. package/template/neo-pipeline-cmps/src/utils/pipelineFunnel.ts +343 -0
  110. package/template/neo-pipeline-cmps/src/utils/queryByCustomSQL.ts +121 -0
  111. package/template/neo-pipeline-cmps/src/utils/requestDebounce.ts +22 -0
  112. package/template/neo-pipeline-cmps/src/utils/simpleTable.tsx +349 -0
  113. package/template/neo-pipeline-cmps/src/utils/stageSwitch.ts +15 -0
  114. package/template/neo-pipeline-cmps/src/utils/stageTimeChart.ts +90 -0
  115. package/template/neo-pipeline-cmps/src/utils/targetNumber.ts +12 -0
  116. package/template/neo-pipeline-cmps/tsconfig.json +40 -0
  117. package/template/neo-web-entity-grid/package.json +2 -2
  118. package/template/neo-web-form/package.json +2 -2
  119. package/template/neo-web-form/src/components/batchAddTable__c/index.tsx +161 -41
  120. package/template/neo-web-form/src/components/batchAddTable__c/model.ts +4 -2
  121. package/template/react-custom-cmp-template/package.json +1 -1
  122. package/template/react-ts-custom-cmp-template/package.json +1 -1
  123. package/template/vue2-custom-cmp-template/package.json +1 -1
  124. package/template/asset-manage-template/src/utils/axiosFetcher.ts +0 -37
  125. package/template/asset-manage-template/src/utils/queryObjectData.ts +0 -112
  126. package/template/asset-manage-template/src/utils/xobjects.ts +0 -162
  127. package/template/neo-bi-cmps/.npmrc copy +0 -1
  128. package/template/neo-bi-cmps/src/components/aiCommitDrawer__c/README.md +0 -52
  129. package/template/neo-bi-cmps/src/components/aiCommitDrawer__c/index.tsx +0 -183
  130. package/template/neo-bi-cmps/src/components/aiCommitDrawer__c/model.ts +0 -90
  131. package/template/neo-bi-cmps/src/components/aiCommitDrawer__c/style.scss +0 -218
  132. package/template/neo-bi-cmps/src/components/forecastChart__c/README.md +0 -31
  133. package/template/neo-bi-cmps/src/components/forecastChart__c/index.tsx +0 -158
  134. package/template/neo-bi-cmps/src/components/forecastChart__c/model.ts +0 -40
  135. package/template/neo-bi-cmps/src/components/forecastChart__c/style.scss +0 -154
  136. package/template/neo-bi-cmps/src/components/forecastGrid__c/README.md +0 -36
  137. package/template/neo-bi-cmps/src/components/forecastGrid__c/index.tsx +0 -86
  138. package/template/neo-bi-cmps/src/components/forecastGrid__c/model.ts +0 -62
  139. package/template/neo-bi-cmps/src/components/forecastGrid__c/style.scss +0 -48
  140. package/template/neo-bi-cmps/src/components/gapCloser__c/README.md +0 -24
  141. package/template/neo-bi-cmps/src/components/gapCloser__c/index.tsx +0 -100
  142. package/template/neo-bi-cmps/src/components/gapCloser__c/model.ts +0 -46
  143. package/template/neo-bi-cmps/src/components/gapCloser__c/style.scss +0 -60
  144. package/template/neo-bi-cmps/src/components/kpiCards__c/README.md +0 -35
  145. package/template/neo-bi-cmps/src/components/kpiCards__c/index.tsx +0 -70
  146. package/template/neo-bi-cmps/src/components/kpiCards__c/model.ts +0 -50
  147. package/template/neo-bi-cmps/src/components/kpiCards__c/style.scss +0 -33
  148. package/template/neo-bi-cmps/src/components/oppList__c/README.md +0 -52
  149. package/template/neo-bi-cmps/src/components/oppList__c/index.tsx +0 -285
  150. package/template/neo-bi-cmps/src/components/oppList__c/model.ts +0 -86
  151. package/template/neo-bi-cmps/src/components/oppList__c/style.scss +0 -133
  152. package/template/neo-bi-cmps/src/components/pipelineFunnel__c/README.md +0 -39
  153. package/template/neo-bi-cmps/src/components/pipelineFunnel__c/index.tsx +0 -130
  154. package/template/neo-bi-cmps/src/components/pipelineFunnel__c/model.ts +0 -66
  155. package/template/neo-bi-cmps/src/components/pipelineFunnel__c/style.scss +0 -133
  156. package/template/neo-bi-cmps/src/components/stageSwitch__c/README.md +0 -36
  157. package/template/neo-bi-cmps/src/components/stageSwitch__c/index.tsx +0 -118
  158. package/template/neo-bi-cmps/src/components/stageSwitch__c/model.ts +0 -92
  159. package/template/neo-bi-cmps/src/components/stageTimeChart__c/README.md +0 -37
  160. package/template/neo-bi-cmps/src/components/stageTimeChart__c/index.tsx +0 -126
  161. package/template/neo-bi-cmps/src/components/stageTimeChart__c/model.ts +0 -57
  162. package/template/neo-bi-cmps/src/components/tabSwitch__c/README.md +0 -37
  163. package/template/neo-bi-cmps/src/components/tabSwitch__c/index.tsx +0 -80
  164. package/template/neo-bi-cmps/src/components/tabSwitch__c/model.ts +0 -45
  165. package/template/neo-bi-cmps/src/components/tabSwitch__c/style.scss +0 -37
  166. package/template/neo-bi-cmps/src/utils/axiosFetcher.ts +0 -37
  167. package/template/neo-bi-cmps/src/utils/queryObjectData.ts +0 -76
  168. package/template/neo-bi-cmps/src/utils/xobjects.ts +0 -162
  169. package/template/neo-custom-cmp-template/src/utils/axiosFetcher.ts +0 -37
  170. package/template/neo-custom-cmp-template/src/utils/queryObjectData.ts +0 -112
  171. package/template/neo-custom-cmp-template/src/utils/xobjects.ts +0 -162
  172. package/template/neo-h5-cmps/src/utils/axiosFetcher.ts +0 -37
  173. package/template/neo-h5-cmps/src/utils/xobjects.ts +0 -167
  174. package/template/neo-order-cmps/src/utils/axiosFetcher.ts +0 -37
  175. package/template/neo-order-cmps/src/utils/queryObjectData.ts +0 -112
  176. package/template/neo-order-cmps/src/utils/xobjects.ts +0 -162
  177. package/template/neo-web-entity-grid/src/utils/axiosFetcher.ts +0 -37
  178. package/template/neo-web-entity-grid/src/utils/queryObjectData.ts +0 -112
  179. package/template/neo-web-entity-grid/src/utils/xobjects.ts +0 -167
  180. package/template/neo-web-form/src/utils/axiosFetcher.ts +0 -37
  181. package/template/neo-web-form/src/utils/queryObjectData.ts +0 -112
  182. package/template/neo-web-form/src/utils/xobjects.ts +0 -167
@@ -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
+ }
@@ -11,234 +11,3 @@
11
11
  - [查询接口(通用查询接口)](https://doc.xiaoshouyi.com/#/proMan/workplaceDetailApi?url=%2F%2Fconcepts%2Fapi_queryInterfaceDescription.html&id=1405&dir=output_1757929564205&time=1758784491846): /rest/data/v2/query;
12
12
  - [获取业务类型描述](https://doc.xiaoshouyi.com/?sso-domain=login-cd.xiaoshouyi.com#/proMan/workplaceDetailApi?url=%2F%2Fconcepts%2Fapi_paas_customObject_getBusinessTypeDescription.html&id=1405&dir=output_1757929564205&time=1758798530511): /rest/data/v2.0/xobjects/{xObjectApiKey}/busiType;
13
13
  - 更多可用接口见[文档](https://doc.xiaoshouyi.com/#/proMan/workplaceDetailApi?url=%2F%2Fconcepts%2Fapi_apiIntroduction.html&id=1405&dir=output_1757929564205)。
14
-
15
- ## Open API 请求方法
16
-
17
- ### 基础请求工具
18
-
19
- #### axiosFetcher
20
- 基于 axios 封装的通用请求工具,支持 GET、POST、PATCH、DELETE 等请求方法。
21
-
22
- ```typescript
23
- import axiosFetcher from '@/utils/axiosFetcher';
24
-
25
- // 基本用法
26
- const result = await axiosFetcher({
27
- url: '/api/endpoint',
28
- method: 'GET',
29
- data: { key: 'value' },
30
- headers: { 'Custom-Header': 'value' },
31
- timeout: 30000
32
- });
33
- ```
34
-
35
- **参数说明:**
36
- - `url`: 请求地址
37
- - `method`: 请求方法,默认为 'GET'
38
- - `data`: 请求数据,GET 请求会转为 params
39
- - `headers`: 请求头,默认包含 'Content-Type': 'application/json'
40
- - `timeout`: 超时时间,默认 30000ms
41
-
42
- ### 业务对象相关接口
43
-
44
- #### 1. 查询业务对象数据列表 (queryXObjectData)
45
- 使用通用查询接口获取业务对象数据,支持分页和排序。
46
-
47
- ```typescript
48
- import { queryXObjectData } from '@/utils/queryObjectData';
49
-
50
- // 基本查询
51
- const result = await queryXObjectData({
52
- xObjectApiKey: 'Contact', // 业务对象 API Key
53
- fields: ['name', 'phone', 'email'], // 查询字段
54
- page: 1, // 页码
55
- pageSize: 10, // 每页数量
56
- orderBy: 'name asc' // 排序条件(可选)
57
- });
58
- ```
59
-
60
- **参数说明:**
61
- - `xObjectApiKey`: 业务对象的 API Key
62
- - `fields`: 需要查询的字段数组,会自动添加 'id' 字段
63
- - `page`: 页码,默认为 1
64
- - `pageSize`: 每页数量,默认为 10
65
- - `orderBy`: 排序条件,如 'name asc' 或 'createdTime desc'
66
-
67
- **返回结果:**
68
- ```typescript
69
- {
70
- code: 200,
71
- result: {
72
- // 查询结果数据
73
- records: any[],
74
- // 分页信息
75
- totalSize: number, // 总个数
76
- }
77
- }
78
- ```
79
-
80
- #### 2. 获取业务类型列表 (getEntityTypeList)
81
- 获取指定业务对象的业务类型列表。
82
-
83
- ```typescript
84
- import { getEntityTypeList } from '@/utils/xobjects';
85
-
86
- const result = await getEntityTypeList('Contact', {
87
- // 其他请求选项
88
- });
89
- ```
90
-
91
- **参数说明:**
92
- - `xObjectApiKey`: 业务对象的 API Key
93
- - `options`: 可选的请求配置
94
-
95
- #### 3. 获取对象列表 (getEntityList)
96
- 获取系统中的对象列表,支持标准对象和自定义对象。
97
-
98
- ```typescript
99
- import { getEntityList } from '@/utils/xobjects';
100
-
101
- // 获取标准对象列表
102
- const standardObjects = await getEntityList({
103
- custom: false, // 获取标准对象
104
- active: true // 仅获取有权限的对象
105
- });
106
-
107
- // 获取自定义对象列表
108
- const customObjects = await getEntityList({
109
- custom: true, // 获取自定义对象
110
- active: true // 仅获取有权限的对象
111
- });
112
- ```
113
-
114
- **参数说明:**
115
- - `custom`: 是否获取自定义对象,false 为标准对象,true 为自定义对象
116
- - `active`: 是否仅获取有权限的对象,默认为 true
117
-
118
- #### 4. 创建业务数据 (createXObject)
119
- 创建新的业务数据记录。
120
-
121
- ```typescript
122
- import { createXObject } from '@/utils/xobjects';
123
-
124
- const result = await createXObject('Contact', {
125
- method: 'POST',
126
- data: {
127
- name: '张三',
128
- phone: '13800138000',
129
- email: 'zhangsan@example.com'
130
- }
131
- });
132
- ```
133
-
134
- **参数说明:**
135
- - `xObjectApiKey`: 业务对象的 API Key
136
- - `options.data`: 要创建的数据对象
137
- - `options.method`: 请求方法,默认为 'GET'(建议使用 'POST')
138
-
139
- #### 5. 更新业务数据 (updateXObject)
140
- 更新指定的业务数据记录。
141
-
142
- ```typescript
143
- import { updateXObject } from '@/utils/xobjects';
144
-
145
- const result = await updateXObject('Contact', '12345', {
146
- method: 'PATCH',
147
- data: {
148
- name: '李四',
149
- phone: '13900139000'
150
- }
151
- });
152
- ```
153
-
154
- **参数说明:**
155
- - `xObjectApiKey`: 业务对象的 API Key
156
- - `objectId`: 要更新的记录 ID
157
- - `options.data`: 要更新的数据对象
158
- - `options.method`: 请求方法,默认为 'PATCH'
159
-
160
- #### 6. 获取业务数据信息 (getXObject)
161
- 获取指定业务数据记录的详细信息。
162
-
163
- ```typescript
164
- import { getXObject } from '@/utils/xobjects';
165
-
166
- const result = await getXObject('Contact', '12345', {
167
- // 其他请求选项
168
- });
169
- ```
170
-
171
- **参数说明:**
172
- - `xObjectApiKey`: 业务对象的 API Key
173
- - `objectId`: 要获取的记录 ID
174
- - `options`: 可选的请求配置
175
-
176
- #### 7. 删除业务数据 (deleteXObject)
177
- 删除指定的业务数据记录。
178
-
179
- ```typescript
180
- import { deleteXObject } from '@/utils/xobjects';
181
-
182
- const result = await deleteXObject('Contact', '12345');
183
- ```
184
-
185
- **参数说明:**
186
- - `xObjectApiKey`: 业务对象的 API Key
187
- - `objectId`: 要删除的记录 ID
188
-
189
- #### 8. 获取业务对象描述 (getXObjectDesc)
190
- 获取业务对象的描述信息。
191
-
192
- ```typescript
193
- import { getXObjectDesc } from '@/utils/xobjects';
194
-
195
- const result = await getXObjectDesc('Contact');
196
- ```
197
-
198
- **参数说明:**
199
- - `xObjectApiKey`: 业务对象的 API Key
200
-
201
- ### 使用示例
202
-
203
- ```typescript
204
- import {
205
- queryXObjectData,
206
- createXObject,
207
- updateXObject,
208
- getXObject,
209
- deleteXObject
210
- } from '@/utils/xobjects';
211
-
212
- // 查询联系人列表
213
- const contacts = await queryXObjectData({
214
- xObjectApiKey: 'Contact',
215
- fields: ['name', 'phone', 'email'],
216
- page: 1,
217
- pageSize: 20,
218
- orderBy: 'createdTime desc'
219
- });
220
-
221
- // 创建新联系人
222
- const newContact = await createXObject('Contact', {
223
- method: 'POST',
224
- data: {
225
- name: '王五',
226
- phone: '13700137000',
227
- email: 'wangwu@example.com'
228
- }
229
- });
230
-
231
- // 更新联系人
232
- const updatedContact = await updateXObject('Contact', newContact.id, {
233
- data: {
234
- name: '王五(更新)'
235
- }
236
- });
237
-
238
- // 获取联系人详情
239
- const contactDetail = await getXObject('Contact', newContact.id);
240
-
241
- // 删除联系人
242
- await deleteXObject('Contact', newContact.id);
243
- ```
244
-