neo-cmp-cli 1.13.15 → 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.
Files changed (180) 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/neoLogin.js +1 -1
  5. package/dist/package.json.js +1 -1
  6. package/docs//351/200/232/347/224/250/344/273/243/347/220/206/346/216/245/345/217/243/forward.zip +0 -0
  7. package/docs//351/200/232/347/224/250/344/273/243/347/220/206/346/216/245/345/217/243//350/207/252/345/256/232/344/271/211API:/351/200/232/347/224/250/344/273/243/347/220/206/346/216/245/345/217/243/344/275/277/347/224/250/350/257/264/346/230/216.md +13 -0
  8. package/package.json +1 -1
  9. package/template/antd-custom-cmp-template/package.json +1 -1
  10. package/template/asset-manage-template/package.json +2 -2
  11. package/template/echarts-custom-cmp-template/package.json +1 -1
  12. package/template/empty-custom-cmp-template/package.json +2 -2
  13. package/template/map-custom-cmp-template/package.json +1 -1
  14. package/template/neo-bi-cmps/neo.config.js +7 -1
  15. package/template/neo-bi-cmps/package.json +8 -7
  16. package/template/neo-bi-cmps/public/403.html +77 -0
  17. package/template/neo-bi-cmps/src/assets/icon/barChart.svg +1 -0
  18. package/template/neo-bi-cmps/src/assets/icon/card.svg +1 -0
  19. package/template/neo-bi-cmps/src/assets/icon/filter.svg +1 -0
  20. package/template/neo-bi-cmps/src/assets/icon/funnel.svg +1 -0
  21. package/template/neo-bi-cmps/src/assets/icon/tab.svg +1 -0
  22. package/template/neo-bi-cmps/src/components/filterBar__c/README.md +3 -14
  23. package/template/neo-bi-cmps/src/components/filterBar__c/common.scss +29 -0
  24. package/template/neo-bi-cmps/src/components/filterBar__c/index.tsx +668 -146
  25. package/template/neo-bi-cmps/src/components/filterBar__c/model.ts +26 -48
  26. package/template/neo-bi-cmps/src/components/filterBar__c/style.scss +46 -139
  27. package/template/neo-bi-cmps/src/components/targetNumber__c/customStyleConfig/index.tsx +11 -10
  28. package/template/neo-bi-cmps/src/components/targetNumber__c/index.tsx +9 -16
  29. package/template/neo-bi-cmps/src/utils/common.ts +231 -0
  30. package/template/neo-bi-cmps/src/utils/filter2chartFilter.ts +268 -0
  31. package/template/neo-bi-cmps/src/utils/filterBar.ts +140 -0
  32. package/template/neo-bi-cmps/src/utils/pipelineFunnel.ts +341 -0
  33. package/template/neo-bi-cmps/src/utils/queryByCustomSQL.ts +117 -0
  34. package/template/neo-bi-cmps/src/utils/requestDebounce.ts +22 -0
  35. package/template/neo-bi-cmps/src/utils/simpleTable.tsx +344 -0
  36. package/template/neo-bi-cmps/src/utils/stageSwitch.ts +15 -0
  37. package/template/neo-bi-cmps/src/utils/stageTimeChart.ts +90 -0
  38. package/template/neo-bi-cmps/src/utils/targetNumber.ts +12 -0
  39. package/template/neo-custom-cmp-template/package.json +2 -2
  40. package/template/neo-h5-cmps/package.json +2 -2
  41. package/template/neo-order-cmps/package.json +2 -2
  42. package/template/neo-pipeline-cmps/.prettierrc.js +12 -0
  43. package/template/neo-pipeline-cmps/@types/neo-ui-common.d.ts +36 -0
  44. package/template/neo-pipeline-cmps/README.md +99 -0
  45. package/template/neo-pipeline-cmps/commitlint.config.js +59 -0
  46. package/template/neo-pipeline-cmps/neo.config.js +124 -0
  47. package/template/neo-pipeline-cmps/package.json +66 -0
  48. package/template/neo-pipeline-cmps/public/403.html +77 -0
  49. package/template/neo-pipeline-cmps/public/css/base.css +283 -0
  50. package/template/neo-pipeline-cmps/public/demo.html +2453 -0
  51. package/template/neo-pipeline-cmps/public/scripts/app/bluebird.js +6679 -0
  52. package/template/neo-pipeline-cmps/public/template.html +13 -0
  53. package/template/neo-pipeline-cmps/src/assets/css/common.scss +127 -0
  54. package/template/neo-pipeline-cmps/src/assets/css/mixin.scss +47 -0
  55. package/template/neo-pipeline-cmps/src/assets/icon/barChart.svg +1 -0
  56. package/template/neo-pipeline-cmps/src/assets/icon/card.svg +1 -0
  57. package/template/neo-pipeline-cmps/src/assets/icon/filter.svg +1 -0
  58. package/template/neo-pipeline-cmps/src/assets/icon/funnel.svg +1 -0
  59. package/template/neo-pipeline-cmps/src/assets/icon/tab.svg +1 -0
  60. package/template/neo-pipeline-cmps/src/assets/img/AIBtn.gif +0 -0
  61. package/template/neo-pipeline-cmps/src/assets/img/NeoCRM.jpg +0 -0
  62. package/template/neo-pipeline-cmps/src/assets/img/aiLogo.png +0 -0
  63. package/template/neo-pipeline-cmps/src/assets/img/card-list.svg +1 -0
  64. package/template/neo-pipeline-cmps/src/assets/img/contact-form.svg +1 -0
  65. package/template/neo-pipeline-cmps/src/assets/img/custom-form.svg +1 -0
  66. package/template/neo-pipeline-cmps/src/assets/img/custom-widget.svg +1 -0
  67. package/template/neo-pipeline-cmps/src/assets/img/data-list.svg +1 -0
  68. package/template/neo-pipeline-cmps/src/assets/img/detail.svg +1 -0
  69. package/template/neo-pipeline-cmps/src/assets/img/favicon.png +0 -0
  70. package/template/neo-pipeline-cmps/src/assets/img/map.svg +1 -0
  71. package/template/neo-pipeline-cmps/src/assets/img/search.svg +1 -0
  72. package/template/neo-pipeline-cmps/src/assets/img/table.svg +1 -0
  73. package/template/neo-pipeline-cmps/src/components/filterBar__c/README.md +24 -0
  74. package/template/neo-pipeline-cmps/src/components/filterBar__c/common.scss +29 -0
  75. package/template/neo-pipeline-cmps/src/components/filterBar__c/index.tsx +730 -0
  76. package/template/neo-pipeline-cmps/src/components/filterBar__c/model.ts +50 -0
  77. package/template/neo-pipeline-cmps/src/components/filterBar__c/style.scss +119 -0
  78. package/template/neo-pipeline-cmps/src/components/pipelineFunnel__c/index.tsx +415 -0
  79. package/template/neo-pipeline-cmps/src/components/pipelineFunnel__c/model.ts +79 -0
  80. package/template/neo-pipeline-cmps/src/components/pipelineFunnel__c/style.scss +83 -0
  81. package/template/neo-pipeline-cmps/src/components/showHealthResult__c/index.tsx +463 -0
  82. package/template/neo-pipeline-cmps/src/components/showHealthResult__c/model.ts +45 -0
  83. package/template/neo-pipeline-cmps/src/components/showHealthResult__c/style.scss +137 -0
  84. package/template/neo-pipeline-cmps/src/components/simpleTable__c/README.md +90 -0
  85. package/template/neo-pipeline-cmps/src/components/simpleTable__c/common.scss +195 -0
  86. package/template/neo-pipeline-cmps/src/components/simpleTable__c/index.tsx +665 -0
  87. package/template/neo-pipeline-cmps/src/components/simpleTable__c/model.ts +124 -0
  88. package/template/neo-pipeline-cmps/src/components/simpleTable__c/style.scss +193 -0
  89. package/template/neo-pipeline-cmps/src/components/stageSwitch__c/index.tsx +511 -0
  90. package/template/neo-pipeline-cmps/src/components/stageSwitch__c/model.ts +70 -0
  91. package/template/{neo-bi-cmps → neo-pipeline-cmps}/src/components/stageSwitch__c/style.scss +4 -2
  92. package/template/neo-pipeline-cmps/src/components/stageTimeChart__c/index.tsx +455 -0
  93. package/template/neo-pipeline-cmps/src/components/stageTimeChart__c/model.ts +103 -0
  94. package/template/{neo-bi-cmps → neo-pipeline-cmps}/src/components/stageTimeChart__c/style.scss +3 -2
  95. package/template/neo-pipeline-cmps/src/utils/common.ts +229 -0
  96. package/template/neo-pipeline-cmps/src/utils/filter2chartFilter.ts +268 -0
  97. package/template/neo-pipeline-cmps/src/utils/filterBar.ts +140 -0
  98. package/template/neo-pipeline-cmps/src/utils/pipelineFunnel.ts +343 -0
  99. package/template/neo-pipeline-cmps/src/utils/queryByCustomSQL.ts +117 -0
  100. package/template/neo-pipeline-cmps/src/utils/requestDebounce.ts +22 -0
  101. package/template/neo-pipeline-cmps/src/utils/simpleTable.tsx +344 -0
  102. package/template/neo-pipeline-cmps/src/utils/stageSwitch.ts +15 -0
  103. package/template/neo-pipeline-cmps/src/utils/stageTimeChart.ts +90 -0
  104. package/template/neo-pipeline-cmps/src/utils/targetNumber.ts +12 -0
  105. package/template/neo-pipeline-cmps/tsconfig.json +40 -0
  106. package/template/neo-web-entity-grid/package.json +2 -2
  107. package/template/neo-web-form/package.json +2 -2
  108. package/template/react-custom-cmp-template/package.json +1 -1
  109. package/template/react-ts-custom-cmp-template/package.json +1 -1
  110. package/template/vue2-custom-cmp-template/package.json +1 -1
  111. package/template/neo-bi-cmps/.npmrc copy +0 -1
  112. package/template/neo-bi-cmps/docs/gartner-pipeline-apis.md +0 -251
  113. package/template/neo-bi-cmps/docs/gartner-pipeline-prd.md +0 -389
  114. package/template/neo-bi-cmps/docs/neo-backend-dev/SKILL.md +0 -188
  115. package/template/neo-bi-cmps/docs/neo-backend-dev/references/01-Trigger/345/274/200/345/217/221.md +0 -183
  116. package/template/neo-bi-cmps/docs/neo-backend-dev/references/02-/350/207/252/345/256/232/344/271/211API/345/274/200/345/217/221.md +0 -196
  117. package/template/neo-bi-cmps/docs/neo-backend-dev/references/03-SDK/345/267/245/345/205/267/347/261/273/346/216/245/345/217/243.md +0 -346
  118. package/template/neo-bi-cmps/docs/neo-backend-dev/references/04-/350/256/241/345/210/222/344/275/234/344/270/232/345/274/200/345/217/221.md +0 -188
  119. package/template/neo-bi-cmps/docs/neo-backend-dev/references/05-/351/241/265/351/235/242/345/274/200/345/217/221.md +0 -293
  120. package/template/neo-bi-cmps/docs/neo-backend-dev/references/06-/346/265/201/347/250/213/346/211/251/345/261/225/345/274/200/345/217/221.md +0 -175
  121. package/template/neo-bi-cmps/docs/neo-backend-dev/references/PaaS/345/271/263/345/217/260/345/274/200/345/217/221/346/211/213/345/206/214/350/247/243/350/257/273.md +0 -313
  122. package/template/neo-bi-cmps/docs/neo-backend-dev/references/auth-config.md +0 -77
  123. package/template/neo-bi-cmps/docs/neo-backend-dev/scripts/deploy_server_script.py +0 -118
  124. package/template/neo-bi-cmps/docs/neo-backend-dev/scripts/download_server_script.py +0 -74
  125. package/template/neo-bi-cmps/docs/neo-backend-dev/scripts/gen_entity_desc.py +0 -69
  126. package/template/neo-bi-cmps/docs/neo-backend-dev/scripts/gen_entitylist.py +0 -87
  127. package/template/neo-bi-cmps/docs/neo-backend-dev/scripts/query_crm.py +0 -65
  128. package/template/neo-bi-cmps/docs/neo-backend-dev/scripts/uninstall_server_script.py +0 -48
  129. package/template/neo-bi-cmps/docs/neo-backend-dev/scripts/update_model_jar.py +0 -49
  130. package/template/neo-bi-cmps/docs/neo-frontend-dev/SKILL.md +0 -138
  131. package/template/neo-bi-cmps/docs/neo-frontend-dev/references/auth-config.md +0 -77
  132. package/template/neo-bi-cmps/docs/neo-frontend-dev/references/component-dev.md +0 -205
  133. package/template/neo-bi-cmps/docs/neo-frontend-dev/references/entityTable-example.md +0 -167
  134. package/template/neo-bi-cmps/docs/neo-frontend-dev/references/templates.md +0 -38
  135. package/template/neo-bi-cmps/docs/neo-frontend-dev/scripts/gen_entity_desc.py +0 -69
  136. package/template/neo-bi-cmps/docs/neo-frontend-dev/scripts/gen_entitylist.py +0 -87
  137. package/template/neo-bi-cmps/docs/neo-frontend-dev/scripts/query_crm.py +0 -65
  138. package/template/neo-bi-cmps/docs//350/264/246/345/217/267/347/233/270/345/205/263/344/277/241/346/201/257.md +0 -10
  139. package/template/neo-bi-cmps/src/components/aiCommitDrawer__c/README.md +0 -52
  140. package/template/neo-bi-cmps/src/components/aiCommitDrawer__c/index.tsx +0 -183
  141. package/template/neo-bi-cmps/src/components/aiCommitDrawer__c/model.ts +0 -90
  142. package/template/neo-bi-cmps/src/components/aiCommitDrawer__c/style.scss +0 -218
  143. package/template/neo-bi-cmps/src/components/forecastChart__c/README.md +0 -31
  144. package/template/neo-bi-cmps/src/components/forecastChart__c/index.tsx +0 -158
  145. package/template/neo-bi-cmps/src/components/forecastChart__c/model.ts +0 -40
  146. package/template/neo-bi-cmps/src/components/forecastChart__c/style.scss +0 -154
  147. package/template/neo-bi-cmps/src/components/forecastGrid__c/README.md +0 -36
  148. package/template/neo-bi-cmps/src/components/forecastGrid__c/index.tsx +0 -86
  149. package/template/neo-bi-cmps/src/components/forecastGrid__c/model.ts +0 -62
  150. package/template/neo-bi-cmps/src/components/forecastGrid__c/style.scss +0 -48
  151. package/template/neo-bi-cmps/src/components/gapCloser__c/README.md +0 -24
  152. package/template/neo-bi-cmps/src/components/gapCloser__c/index.tsx +0 -100
  153. package/template/neo-bi-cmps/src/components/gapCloser__c/model.ts +0 -46
  154. package/template/neo-bi-cmps/src/components/gapCloser__c/style.scss +0 -60
  155. package/template/neo-bi-cmps/src/components/kpiCards__c/README.md +0 -35
  156. package/template/neo-bi-cmps/src/components/kpiCards__c/index.tsx +0 -70
  157. package/template/neo-bi-cmps/src/components/kpiCards__c/model.ts +0 -50
  158. package/template/neo-bi-cmps/src/components/kpiCards__c/style.scss +0 -33
  159. package/template/neo-bi-cmps/src/components/oppList__c/README.md +0 -52
  160. package/template/neo-bi-cmps/src/components/oppList__c/index.tsx +0 -285
  161. package/template/neo-bi-cmps/src/components/oppList__c/model.ts +0 -86
  162. package/template/neo-bi-cmps/src/components/oppList__c/style.scss +0 -133
  163. package/template/neo-bi-cmps/src/components/pipelineFunnel__c/index.tsx +0 -130
  164. package/template/neo-bi-cmps/src/components/pipelineFunnel__c/model.ts +0 -66
  165. package/template/neo-bi-cmps/src/components/pipelineFunnel__c/style.scss +0 -133
  166. package/template/neo-bi-cmps/src/components/stageSwitch__c/index.tsx +0 -118
  167. package/template/neo-bi-cmps/src/components/stageSwitch__c/model.ts +0 -92
  168. package/template/neo-bi-cmps/src/components/stageTimeChart__c/index.tsx +0 -126
  169. package/template/neo-bi-cmps/src/components/stageTimeChart__c/model.ts +0 -57
  170. package/template/neo-bi-cmps/src/components/tabSwitch__c/README.md +0 -37
  171. package/template/neo-bi-cmps/src/components/tabSwitch__c/index.tsx +0 -80
  172. package/template/neo-bi-cmps/src/components/tabSwitch__c/model.ts +0 -45
  173. package/template/neo-bi-cmps/src/components/tabSwitch__c/style.scss +0 -37
  174. package/template/neo-bi-cmps/src/utils/axiosFetcher.ts +0 -37
  175. package/template/neo-bi-cmps/src/utils/queryObjectData.ts +0 -76
  176. package/template/neo-bi-cmps/src/utils/xobjects.ts +0 -162
  177. /package/template/neo-bi-cmps/{docs/prototype-pipeline-forecasting.html → public/demo.html} +0 -0
  178. /package/template/{neo-bi-cmps → neo-pipeline-cmps}/src/components/pipelineFunnel__c/README.md +0 -0
  179. /package/template/{neo-bi-cmps → neo-pipeline-cmps}/src/components/stageSwitch__c/README.md +0 -0
  180. /package/template/{neo-bi-cmps → neo-pipeline-cmps}/src/components/stageTimeChart__c/README.md +0 -0
@@ -0,0 +1,455 @@
1
+ /**
2
+ * @file 阶段停留时间组件
3
+ * @description 展示各销售阶段的平均停留时间;图表数据来自 NeoBI queryDataTask,目标/上限来自 XObject 的 customItem3/4(天)
4
+ */
5
+ import * as React from 'react';
6
+ import { Spin } from 'antd';
7
+ // @ts-ignore
8
+ import { request, xObject } from 'neo-open-api';
9
+ // @ts-ignore
10
+ import { BaseCmp, NeoEvent } from 'neo-ui-common';
11
+ // @ts-ignore
12
+ import isEqual from 'lodash/isEqual';
13
+ import { filter2chartFilter } from '../../utils/filter2chartFilter';
14
+ import {
15
+ buildQueryDataTaskFormBody,
16
+ extractStageKeyFromStageName,
17
+ parseNumberOrNaN,
18
+ getDefaultFilterWhereByProps,
19
+ } from '../../utils/common';
20
+ import {
21
+ buildStageTimeItems,
22
+ isClosedOutcomeStage,
23
+ parseDurationToDays,
24
+ type StageTimeItem,
25
+ type StageTimeRow,
26
+ } from '../../utils/stageTimeChart';
27
+
28
+ import './style.scss';
29
+
30
+ const QUERY_DATA_TASK_URL = '/rest/neobi/v2.0/bestpractices/queryDataTask';
31
+
32
+ const FORM_URLENCODED_UTF8 = 'application/x-www-form-urlencoded;charset=UTF-8';
33
+
34
+ interface ThresholdsXObjectApi {
35
+ xObjectApiKey?: string;
36
+ fields?: string[];
37
+ page?: number;
38
+ pageSize?: number;
39
+ }
40
+
41
+ interface StageTimeChartProps {
42
+ title?: string;
43
+ viewId?: string;
44
+ viewType?: string;
45
+ data?: {
46
+ __NeoCurrentUser?: { id?: string | number };
47
+ };
48
+ thresholdsXObjectApi?: ThresholdsXObjectApi;
49
+ showAiButton?: boolean;
50
+ onStageClick?: (stageName: string) => void;
51
+ className?: string;
52
+ style?: React.CSSProperties;
53
+ }
54
+
55
+ /** 阈值行:仅将 customItem3/4 规范为 number,其余字段保持原样 */
56
+ interface ThresholdRecordRow extends Record<string, unknown> {
57
+ customItem3__c: number;
58
+ customItem4__c: number;
59
+ }
60
+
61
+ interface StageTimeChartState {
62
+ loading: boolean;
63
+ error: string | null;
64
+ items: StageTimeItem[];
65
+ filter: any;
66
+ /** 与阈值行 customItem2__c 一致,默认 New Business(可由 setFilter 更新) */
67
+ oppType: string;
68
+ /** fetchThresholdRecords 拉取的完整列表(已转换 customItem3/4 为 number) */
69
+ thresholdRecords: ThresholdRecordRow[];
70
+ }
71
+
72
+ class StageTimeChart extends BaseCmp<StageTimeChartProps, StageTimeChartState> {
73
+ /** 初始化 state */
74
+ constructor(props: StageTimeChartProps) {
75
+ super(props);
76
+ const initialOppType =
77
+ String(
78
+ (props as StageTimeChartProps & { oppType?: string }).oppType ?? '',
79
+ ).trim() || 'New Business';
80
+ this.state = {
81
+ loading: false,
82
+ error: null,
83
+ items: [],
84
+ filter: getDefaultFilterWhereByProps(props),
85
+ oppType: initialOppType,
86
+ thresholdRecords: [],
87
+ };
88
+
89
+ this.getThresholdRecord = this.getThresholdRecord.bind(this);
90
+ this.fetchThresholdRecords = this.fetchThresholdRecords.bind(this);
91
+ this.fetchChartRows = this.fetchChartRows.bind(this);
92
+ this.fetchAllData = this.fetchAllData.bind(this);
93
+ this.refreshData = this.refreshData.bind(this);
94
+ this.setFilter = this.setFilter.bind(this);
95
+ }
96
+
97
+ /** 挂载后拉取阈值与图表 */
98
+ componentDidMount() {
99
+ this.fetchAllData();
100
+
101
+ /*
102
+ // 监听一个广播事件
103
+ NeoEvent.listen('updateFilterData', (filterData: any) => {
104
+ console.log('StageTimeChart 监听到了一个广播事件 updateFilterData: ', filterData);
105
+ this.setFilter(filterData);
106
+ });
107
+ */
108
+ }
109
+
110
+ /** 关键 props 变化时重新拉数 */
111
+ componentDidUpdate(
112
+ prevProps: StageTimeChartProps,
113
+ prevState: StageTimeChartState,
114
+ ) {
115
+ const uid = this.props.data?.__NeoCurrentUser?.id;
116
+ const puid = prevProps.data?.__NeoCurrentUser?.id;
117
+ const api = this.props.thresholdsXObjectApi;
118
+ const papi = prevProps.thresholdsXObjectApi;
119
+ const apiKeyChanged =
120
+ api?.xObjectApiKey !== papi?.xObjectApiKey ||
121
+ JSON.stringify(api?.fields ?? []) !== JSON.stringify(papi?.fields ?? []);
122
+ const filterChanged = !isEqual(this.state.filter, prevState.filter);
123
+ const oppTypeChanged = this.state.oppType !== prevState.oppType;
124
+
125
+ if (
126
+ this.props.viewId !== prevProps.viewId ||
127
+ this.props.viewType !== prevProps.viewType ||
128
+ uid !== puid ||
129
+ apiKeyChanged ||
130
+ filterChanged ||
131
+ oppTypeChanged
132
+ ) {
133
+ this.fetchAllData();
134
+ }
135
+ }
136
+
137
+ /** 按 customItem1__c / customItem2__c 在列表中查找单行;customItem2 默认取 state.oppType */
138
+ getThresholdRecord(
139
+ customItem1__c: string = String(this.state.oppType ?? 'New Business'),
140
+ customItem2__c: string,
141
+ source: ThresholdRecordRow[] = this.state.thresholdRecords,
142
+ ): ThresholdRecordRow | undefined {
143
+ const k1 = String(customItem1__c ?? '').trim();
144
+ const k2 = String(customItem2__c ?? '').trim();
145
+ return source.find((r) => {
146
+ return (
147
+ String(r.customItem1__c ?? '').trim() === k1 &&
148
+ String(r.customItem2__c ?? '').trim() === k2
149
+ );
150
+ });
151
+ }
152
+
153
+ /** 请求阈值列表,仅把 customItem3/4 转为 number,并写入 state.thresholdRecords */
154
+ async fetchThresholdRecords(): Promise<ThresholdRecordRow[]> {
155
+ const { thresholdsXObjectApi } = this.props;
156
+ const apiKey = thresholdsXObjectApi?.xObjectApiKey;
157
+ if (!apiKey || String(apiKey).trim() === '') {
158
+ this.setState({ thresholdRecords: [] });
159
+ return [];
160
+ }
161
+
162
+ const defaultFields = [
163
+ 'customItem1__c',
164
+ 'customItem2__c',
165
+ 'customItem3__c',
166
+ 'customItem4__c',
167
+ ];
168
+ const fields = thresholdsXObjectApi?.fields?.length
169
+ ? [...thresholdsXObjectApi.fields]
170
+ : defaultFields;
171
+ const uniq = Array.from(new Set(fields));
172
+
173
+ try {
174
+ const result = await xObject.query({
175
+ xObjectApiKey: apiKey,
176
+ fields: uniq,
177
+ page: thresholdsXObjectApi?.page ?? 1,
178
+ pageSize: thresholdsXObjectApi?.pageSize ?? 500,
179
+ });
180
+
181
+ if (!result?.status) {
182
+ console.warn(
183
+ 'StageTimeChart xObject.query(thresholds) 非成功:',
184
+ result?.msg ?? result,
185
+ );
186
+ this.setState({ thresholdRecords: [] });
187
+ return [];
188
+ }
189
+
190
+ const records = (result.data ?? []) as Record<string, unknown>[];
191
+ const thresholdRecords: ThresholdRecordRow[] = records
192
+ .filter((r) => r && typeof r === 'object')
193
+ .map((r) => ({
194
+ ...r,
195
+ customItem3__c: parseNumberOrNaN(r.customItem3__c),
196
+ customItem4__c: parseNumberOrNaN(r.customItem4__c),
197
+ }));
198
+
199
+ this.setState({ thresholdRecords });
200
+ return thresholdRecords;
201
+ } catch (e) {
202
+ console.error('StageTimeChart 加载阈值数据失败:', e);
203
+ this.setState({ thresholdRecords: [] });
204
+ return [];
205
+ }
206
+ }
207
+
208
+ /** 请求 queryDataTask,解析表格行并挂上每行对应的 target/limit(来自阈值行 customItem3/4) */
209
+ async fetchChartRows(
210
+ thresholdRecords: ThresholdRecordRow[],
211
+ ): Promise<{ rows: StageTimeRow[]; error: string | null }> {
212
+ const { viewId, viewType } = this.props;
213
+ const oppType = this.state.oppType ?? 'New Business';
214
+ const userId = this.props.data?.__NeoCurrentUser?.id;
215
+
216
+ if (viewId == null || viewId === '' || userId == null || userId === '') {
217
+ return {
218
+ rows: [],
219
+ error: '缺少 viewId 或当前用户 id(data.__NeoCurrentUser.id)',
220
+ };
221
+ }
222
+
223
+ const filterObj =
224
+ this.state.filter &&
225
+ typeof this.state.filter === 'object' &&
226
+ !Array.isArray(this.state.filter)
227
+ ? this.state.filter
228
+ : {};
229
+
230
+ try {
231
+ const res = await request({
232
+ url: QUERY_DATA_TASK_URL,
233
+ method: 'POST',
234
+ data: buildQueryDataTaskFormBody({
235
+ viewId: String(viewId),
236
+ userId,
237
+ type: viewType ?? 'sync',
238
+ filter: filterObj,
239
+ }),
240
+ headers: {
241
+ 'Content-Type': FORM_URLENCODED_UTF8,
242
+ },
243
+ });
244
+
245
+ const status = res?.status;
246
+ const table = res?.data;
247
+
248
+ if (status !== 0 || !Array.isArray(table)) {
249
+ return {
250
+ rows: [],
251
+ error: res?.message || res?.msg || '查询图表数据失败',
252
+ };
253
+ }
254
+
255
+ const rowsMatrix = table as unknown[][];
256
+ if (!Array.isArray(rowsMatrix) || rowsMatrix.length < 2) {
257
+ return { rows: [], error: null };
258
+ }
259
+
260
+ const dataRows = rowsMatrix.slice(1);
261
+ const rows: StageTimeRow[] = [];
262
+ for (let i = 0; i < dataRows.length; i++) {
263
+ const row = dataRows[i];
264
+ if (!Array.isArray(row) || row.length < 3) continue;
265
+ const raw = row[2];
266
+ const actualTimeDisplay = String(raw ?? '');
267
+ const actualTime =
268
+ typeof raw === 'number' && Number.isFinite(raw)
269
+ ? raw
270
+ : parseDurationToDays(raw);
271
+ const stageName = String(row[0] ?? '');
272
+ if (isClosedOutcomeStage(stageName)) continue;
273
+ const stageKey = extractStageKeyFromStageName(stageName);
274
+ const th = this.getThresholdRecord(oppType, stageKey, thresholdRecords);
275
+ const targetPercent = th?.customItem3__c ?? NaN;
276
+ const limitPercent = th?.customItem4__c ?? NaN;
277
+
278
+ rows.push({
279
+ stageName,
280
+ actualTime,
281
+ actualTimeDisplay,
282
+ targetPercent: Number.isFinite(targetPercent) ? targetPercent : NaN,
283
+ limitPercent: Number.isFinite(limitPercent) ? limitPercent : NaN,
284
+ });
285
+ }
286
+
287
+ return { rows, error: null };
288
+ } catch (e: any) {
289
+ console.error('StageTimeChart queryDataTask 失败:', e);
290
+ return {
291
+ rows: [],
292
+ error: e?.message || '网络请求失败',
293
+ };
294
+ }
295
+ }
296
+
297
+ /** 先拉阈值再拉图表,合并为 items */
298
+ async fetchAllData() {
299
+ this.setState({ loading: true, error: null });
300
+
301
+ const thresholdRecords = await this.fetchThresholdRecords();
302
+ const chartResult = await this.fetchChartRows(thresholdRecords);
303
+
304
+ if (chartResult.error) {
305
+ this.setState({
306
+ loading: false,
307
+ error: chartResult.error,
308
+ items: [],
309
+ });
310
+ return;
311
+ }
312
+
313
+ const items = buildStageTimeItems(chartResult.rows);
314
+
315
+ this.setState({
316
+ loading: false,
317
+ error: null,
318
+ items,
319
+ });
320
+ }
321
+
322
+ /** 刷新图表与阈值(设计器可绑定) */
323
+ @NeoEvent.function
324
+ async refreshData() {
325
+ await this.fetchAllData();
326
+ }
327
+
328
+ /** 设置 queryDataTask 的 filter 并重新拉数(设计器可绑定) */
329
+ @NeoEvent.function
330
+ setFilter(filter?: any) {
331
+ if (!filter) {
332
+ return;
333
+ }
334
+ const nextFilter = filter2chartFilter(filter);
335
+ console.log('[StageTimeChart__c] setFilter:', filter, nextFilter);
336
+ if (!isEqual(nextFilter, this.state.filter)) {
337
+ const nextOppType =
338
+ filter.businessTypeLabel != null &&
339
+ String(filter.businessTypeLabel).trim() !== ''
340
+ ? String(filter.businessTypeLabel).trim()
341
+ : this.state.oppType;
342
+ this.setState({ filter: nextFilter, oppType: nextOppType });
343
+ }
344
+ }
345
+
346
+ @NeoEvent.dispatch
347
+ onActiveStageChange(eventData?: any) {}
348
+
349
+ // 点击阶段切换时,更新当前激活的销售阶段
350
+ handleStageClick(stageName: string) {
351
+ this.onActiveStageChange({
352
+ activeStage: stageName,
353
+ });
354
+ }
355
+
356
+ /** 渲染标题、图例与阶段条形图 */
357
+ render() {
358
+ const {
359
+ title = 'Avg. Time in Stage',
360
+ // showAiButton = false,
361
+ onStageClick,
362
+ className,
363
+ style,
364
+ } = this.props;
365
+ const showAiButton = false;
366
+
367
+ const { loading, error, items } = this.state;
368
+
369
+ return (
370
+ <div
371
+ className={`stageTimeChart__c ${className || ''}`}
372
+ style={style}
373
+ data-time="2026.4.15 01"
374
+ >
375
+ <Spin spinning={loading}>
376
+ <div className="chart-header">
377
+ <h3 className="chart-title">{title}</h3>
378
+ {showAiButton && (
379
+ <span
380
+ className="ai-btn"
381
+ onClick={() => console.log('AI Analysis clicked')}
382
+ title="AI Analysis"
383
+ >
384
+
385
+ </span>
386
+ )}
387
+ </div>
388
+
389
+ {error ? (
390
+ <div
391
+ className="stage-time-chart-error"
392
+ style={{ color: '#cf1322', fontSize: 12, marginBottom: 8 }}
393
+ >
394
+ {error}
395
+ </div>
396
+ ) : null}
397
+
398
+ <div className="chart-legend">
399
+ <span className="legend-item">
400
+ <span className="legend-actual" />
401
+ Actual
402
+ </span>
403
+ <span className="legend-item">
404
+ <span className="legend-target" />
405
+ Target
406
+ </span>
407
+ <span className="legend-item">
408
+ <span className="legend-limit" />
409
+ Limit
410
+ </span>
411
+ </div>
412
+
413
+ <div className="chart-body">
414
+ {items.map((item, index) => (
415
+ <div
416
+ key={`${item.stageName}-${index}`}
417
+ className="stage-time-item"
418
+ onClick={() => this.handleStageClick(item.stageName)}
419
+ >
420
+ <div className="stage-time-header">
421
+ <span className="stage-name">{item.stageName}</span>
422
+ <span
423
+ className="stage-time-value"
424
+ style={{ color: item.actualColor }}
425
+ >
426
+ {item.actualTime}
427
+ </span>
428
+ </div>
429
+ <div className="stage-time-bar">
430
+ <div
431
+ className="stage-time-actual"
432
+ style={{
433
+ width: `${item.barActualWidthPct}%`,
434
+ backgroundColor: item.actualColor,
435
+ }}
436
+ />
437
+ <div
438
+ className="stage-time-target"
439
+ style={{ left: `${item.barTargetLeftPct}%` }}
440
+ />
441
+ <div
442
+ className="stage-time-limit"
443
+ style={{ left: `${item.barLimitLeftPct}%` }}
444
+ />
445
+ </div>
446
+ </div>
447
+ ))}
448
+ </div>
449
+ </Spin>
450
+ </div>
451
+ );
452
+ }
453
+ }
454
+
455
+ export default StageTimeChart;
@@ -0,0 +1,103 @@
1
+ export class StageTimeChartModel {
2
+ label: string = '阶段停留时间';
3
+ description: string =
4
+ '展示各销售阶段的平均停留时间;图表来自 NeoBI queryDataTask,目标/上限来自 XObject customItem3/4(天)';
5
+ iconUrl: string = 'https://custom-widgets.bj.bcebos.com/barChart.svg';
6
+ targetPage: string[] = ['all'];
7
+ targetDevice: string = 'all';
8
+
9
+ defaultComProps = {
10
+ title: 'Avg. Time in Stage',
11
+ viewId: '4264466340118875',
12
+ viewType: 'sync',
13
+ showAiButton: true,
14
+ /** 与阈值记录 customItem2__c 匹配的商机类型,默认 New Business */
15
+ oppType: 'New Business',
16
+ /** 阈值:customItem1__c 阶段键、customItem2__c 商机类型、customItem3__c/4 目标与上限(天) */
17
+ thresholdsXObjectApi: {
18
+ xObjectApiKey: 'customEntity105__c',
19
+ fields: [
20
+ 'customItem1__c',
21
+ 'customItem2__c',
22
+ 'customItem3__c',
23
+ 'customItem4__c',
24
+ ],
25
+ page: 1,
26
+ pageSize: 1000,
27
+ },
28
+ };
29
+
30
+ events = [
31
+ {
32
+ apiKey: 'onActiveStageChange',
33
+ label: '点击阶段条触发',
34
+ helpText:
35
+ '点击阶段条触发;事件参数含 activeStage(当前激活阶段)',
36
+ eventParams:
37
+ '[{"apiKey":"eventParam","children":[{"apiKey":"activeStage","label":"当前激活阶段","type":"String"}],"label":"事件入参","type":"Object"}]',
38
+ },
39
+ ];
40
+
41
+ functions = [
42
+ {
43
+ apiKey: 'refreshData',
44
+ label: '刷新数据',
45
+ helpTextKey: '重新请求 queryDataTask 与阈值配置,刷新停留时间',
46
+ },
47
+ {
48
+ apiKey: 'setFilter',
49
+ label: '设置过滤条件',
50
+ helpTextKey: '设置 queryDataTask 的 filter 并重新拉取图表数据',
51
+ funcInParams: [
52
+ {
53
+ apiKey: 'filter',
54
+ label: '过滤条件',
55
+ type: 'Object',
56
+ required: false,
57
+ },
58
+ ],
59
+ },
60
+ ];
61
+
62
+ propsSchema = [
63
+ {
64
+ type: 'panelInput',
65
+ name: 'title',
66
+ label: '标题',
67
+ },
68
+ {
69
+ type: 'panelInput',
70
+ name: 'viewId',
71
+ label: '视图 ID',
72
+ },
73
+ {
74
+ type: 'panelInput',
75
+ name: 'viewType',
76
+ label: '视图类型(请求 type)',
77
+ },
78
+ {
79
+ type: 'panelInput',
80
+ name: 'oppType',
81
+ label: '商机类型(匹配 customItem2__c)',
82
+ },
83
+ {
84
+ type: 'xObjectDataApi',
85
+ name: 'thresholdsXObjectApi',
86
+ label: '阶段目标/上限数据源',
87
+ value: {
88
+ xObjectApiKey: '',
89
+ fields: [
90
+ 'customItem1__c',
91
+ 'customItem2__c',
92
+ 'customItem3__c',
93
+ 'customItem4__c',
94
+ ],
95
+ },
96
+ placeholder:
97
+ '实体需含 customItem1__c(阶段)、customItem2__c(商机类型)、customItem3/4(目标/上限天)',
98
+ custom: true,
99
+ },
100
+ ];
101
+ }
102
+
103
+ export default StageTimeChartModel;
@@ -1,10 +1,11 @@
1
- .stage-time-chart__c {
1
+ .stageTimeChart__c {
2
2
  background: #fff;
3
3
  border-radius: 8px;
4
- padding: 20px;
4
+ padding: 12px 20px;
5
5
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
6
6
  display: flex;
7
7
  flex-direction: column;
8
+ min-height: 400px;
8
9
 
9
10
  .chart-header {
10
11
  display: flex;