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,511 @@
1
+ /**
2
+ * @file 阶段切换卡片组件
3
+ * @description 切换显示不同销售阶段的数据;商机来自 xObject.query,阶段来自 stage 接口
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 isEqual from 'lodash/isEqual';
11
+ // @ts-ignore
12
+ import { BaseCmp, NeoEvent } from 'neo-ui-common';
13
+
14
+ import queryByCustomSQL from '../../utils/queryByCustomSQL';
15
+ import {
16
+ closeRangeFromFilter,
17
+ entityTypeIdForWhere,
18
+ extractStageKeyFromStageName,
19
+ formatAmountDisplay,
20
+ normalizeOwnerIdsForWhere,
21
+ parseNumberOrZero,
22
+ getDefaultFilterByProps,
23
+ } from '../../utils/common';
24
+ import { rowMoney, rowOpportunityStage } from '../../utils/stageSwitch';
25
+
26
+ import './style.scss';
27
+
28
+ const STAGE_LIST_URL =
29
+ '/rest/data/v2.0/xobjects/stage/actions/getStageListByEntityTypeApiKey';
30
+
31
+ interface StageTab {
32
+ key: string;
33
+ name: string;
34
+ amount: string;
35
+ count: number;
36
+ /** 暂无环比时可为空对象,不参与渲染 */
37
+ changes?: Record<string, unknown>;
38
+ }
39
+
40
+ interface StageSwitchProps {
41
+ stages?: StageTab[];
42
+ activeStage?: string;
43
+ data?: {
44
+ __NeoCurrentUser?: { id?: string | number };
45
+ };
46
+ onStageChange?: (stage: StageTab) => void;
47
+ className?: string;
48
+ style?: React.CSSProperties;
49
+ }
50
+
51
+ interface StageSwitchState {
52
+ activeStage: string;
53
+ opportunityList: Record<string, unknown>[];
54
+ /** 由接口 + getOpportunityList 生成的展示用阶段 */
55
+ stages: StageTab[];
56
+ loading: boolean;
57
+ error: string | null;
58
+ /** 与 FilterBar 事件 payload 一致(closeDateCustomRange / opportunityOwner / businessType 等);空对象时回退 defaultFilter */
59
+ filter: any;
60
+ defaultEntityTypeApiKey: string;
61
+ }
62
+
63
+ class StageSwitch extends BaseCmp<StageSwitchProps, StageSwitchState> {
64
+ constructor(props: StageSwitchProps) {
65
+ super(props);
66
+
67
+ const defaultFilter = getDefaultFilterByProps(props);
68
+
69
+ this.state = {
70
+ activeStage: props.activeStage || '',
71
+ opportunityList: [],
72
+ stages: props.stages?.length ? props.stages : [],
73
+ loading: false,
74
+ error: null,
75
+ filter: defaultFilter,
76
+ defaultEntityTypeApiKey:
77
+ defaultFilter.businessTypeApiKey ?? 'defaultBusiType',
78
+ };
79
+
80
+ this.getOpportunityQueryFilter = this.getOpportunityQueryFilter.bind(this);
81
+ this.fetchOpportunityList = this.fetchOpportunityList.bind(this);
82
+ this.getOpportunityStats = this.getOpportunityStats.bind(this);
83
+ this.fetchStageRows = this.fetchStageRows.bind(this);
84
+ this.buildStagesFromApi = this.buildStagesFromApi.bind(this);
85
+ this.fetchAllData = this.fetchAllData.bind(this);
86
+ this.handleStageClick = this.handleStageClick.bind(this);
87
+ this.refreshData = this.refreshData.bind(this);
88
+ this.setFilter = this.setFilter.bind(this);
89
+ this.updateActiveStage = this.updateActiveStage.bind(this);
90
+ }
91
+
92
+ componentDidMount() {
93
+ this.fetchAllData();
94
+
95
+ /*
96
+ // 监听一个广播事件
97
+ NeoEvent.listen('updateFilterData', (filterData: any) => {
98
+ console.log('StageSwitch 监听到了一个广播事件 updateFilterData: ', filterData);
99
+ this.setFilter(filterData);
100
+ });
101
+
102
+ // 广播事件:更新当前激活的销售阶段
103
+ NeoEvent.listen('updateActiveStage', (activeStage: string) => {
104
+ console.log('SimpleTable 监听到了一个广播事件 updateActiveStage: ', activeStage);
105
+ this.updateActiveStage(activeStage);
106
+ });
107
+ */
108
+ }
109
+
110
+ componentDidUpdate(prevProps: StageSwitchProps) {
111
+ if (
112
+ this.props.activeStage != null &&
113
+ this.props.activeStage !== '' &&
114
+ this.props.activeStage !== prevProps.activeStage
115
+ ) {
116
+ this.setState({ activeStage: this.props.activeStage });
117
+ }
118
+ }
119
+
120
+ /** 将 FilterBar 结构 filter 转为 xObject.query 的 where 字符串数组(SDK 按顺序 and 拼接) */
121
+ getOpportunityQueryFilter(): string[] {
122
+ const raw = this.state.filter;
123
+ const curFilter: Record<string, unknown> =
124
+ raw && typeof raw === 'object' && !Array.isArray(raw)
125
+ ? Object.keys(raw as object).length > 0
126
+ ? (raw as Record<string, unknown>)
127
+ : defaultFilter
128
+ : defaultFilter;
129
+
130
+ const where: string[] = [];
131
+
132
+ const range = closeRangeFromFilter(curFilter.closeDateCustomRange);
133
+ if (range) {
134
+ where.push(`closeDate >= ${range.start} and closeDate <= ${range.end}`);
135
+ }
136
+
137
+ const ownerIds = normalizeOwnerIdsForWhere(curFilter.opportunityOwner);
138
+ if (ownerIds.length === 1) {
139
+ where.push(`ownerId = ${ownerIds[0]}`);
140
+ } else if (ownerIds.length > 1) {
141
+ where.push(`ownerId in (${ownerIds.join(', ')})`);
142
+ }
143
+
144
+ const entityTypeId = entityTypeIdForWhere(curFilter.businessType);
145
+ if (entityTypeId != null) {
146
+ where.push(`entityType = ${entityTypeId}`);
147
+ }
148
+
149
+ return where;
150
+ }
151
+
152
+ /** 用于生成获取历史商机列表数据的查询条件 */
153
+ getHistoryOpportunityQueryFilter(): string[] {
154
+ const raw = this.state.filter;
155
+ const curFilter: Record<string, unknown> =
156
+ raw && typeof raw === 'object' && !Array.isArray(raw)
157
+ ? Object.keys(raw as object).length > 0
158
+ ? (raw as Record<string, unknown>)
159
+ : defaultFilter
160
+ : defaultFilter;
161
+
162
+ const where: string[] = [];
163
+
164
+ const range = closeRangeFromFilter(curFilter.closeDateCustomRange);
165
+ if (range) {
166
+ where.push(
167
+ `opportunity_1_closeDate >= ${range.start} and opportunity_1_closeDate <= ${range.end}`,
168
+ );
169
+ }
170
+
171
+ const ownerIds = normalizeOwnerIdsForWhere(curFilter.opportunityOwner);
172
+ if (ownerIds.length === 1) {
173
+ where.push(`opportunity_1_ownerId = ${ownerIds[0]}`);
174
+ } else if (ownerIds.length > 1) {
175
+ where.push(`opportunity_1_ownerId in (${ownerIds.join(', ')})`);
176
+ }
177
+
178
+ const entityTypeId = entityTypeIdForWhere(curFilter.businessType);
179
+ if (entityTypeId != null) {
180
+ where.push(`opportunity_1_entityType = ${entityTypeId}`);
181
+ }
182
+
183
+ if (curFilter.changesSinceCustomTime) {
184
+ where.push(`version = ${curFilter.changesSinceCustomTime}`);
185
+ }
186
+
187
+ return where;
188
+ }
189
+
190
+ /** 单次查询最多 1000 条 opportunity */
191
+ async fetchOpportunityList(): Promise<Record<string, unknown>[]> {
192
+ const result = await xObject.query({
193
+ xObjectApiKey: 'opportunity',
194
+ fields: ['id', 'money', 'customItem248__c'],
195
+ page: 1,
196
+ pageSize: 1000,
197
+ where: this.getOpportunityQueryFilter(),
198
+ });
199
+
200
+ return result?.status
201
+ ? ((result.data ?? []) as Record<string, unknown>[])
202
+ : [];
203
+ }
204
+
205
+ /** 查询历史商机列表数据 */
206
+ async fetchHistoryOpportunityList(): Promise<unknown[]> {
207
+ const result = await queryByCustomSQL({
208
+ xObjectApiKey: 'biCustomModel_397169_20260401104916618',
209
+ fields: [
210
+ 'opportunity_1_opportunityName',
211
+ 'opportunity_1_saleStageId',
212
+ 'opportunity_1_money',
213
+ ],
214
+ page: 1,
215
+ pageSize: 1000,
216
+ where: this.getHistoryOpportunityQueryFilter(),
217
+ });
218
+
219
+ return result?.status ? result.data ?? [] : [];
220
+ }
221
+
222
+ /**
223
+ * 按 stageName 过滤商机:取 stageName 第一个 `.` 之后的片段作为阶段键,与商机行上阶段文案同样规则比对。
224
+ * @returns count — 条数; amount — money(若无则 amount 字段)之和
225
+ */
226
+ getOpportunityStats(
227
+ stageName: string,
228
+ list: Record<string, unknown>[], // 商机列表
229
+ ): { amount: number; count: number } {
230
+ const key = extractStageKeyFromStageName(stageName);
231
+ let amount = 0; // 商机总金额
232
+ let count = 0; // 商机总条数
233
+
234
+ for (const row of list) {
235
+ if (!row || typeof row !== 'object') continue;
236
+ const rec = row as Record<string, unknown>;
237
+ const rowKey = extractStageKeyFromStageName(rowOpportunityStage(rec));
238
+ if (key !== rowKey) continue;
239
+ count += 1;
240
+ amount += rowMoney(rec);
241
+ }
242
+
243
+ return { amount, count };
244
+ }
245
+
246
+ /**
247
+ * 历史商机列表为自定义 SQL 返回的二维数组行:
248
+ * `[opportunityName, saleStageId, money, ...]` — 第 2 列为阶段 ID(与阶段 `key` 对应),第 3 列为销售额。
249
+ * @returns count — 条数; amount — 销售额之和
250
+ */
251
+ getHistoryOpportunityStats(
252
+ stageKey: string,
253
+ list: unknown[],
254
+ ): { amount: number; count: number } {
255
+ let amount = 0;
256
+ let count = 0;
257
+ const targetId = String(stageKey);
258
+
259
+ for (const row of list) {
260
+ if (!Array.isArray(row) || row.length < 3) continue;
261
+ if (String(row[1]) !== targetId) continue;
262
+ count += 1;
263
+ amount += parseNumberOrZero(row[2]);
264
+ }
265
+
266
+ return { amount, count };
267
+ }
268
+
269
+ async fetchStageRows(): Promise<Record<string, unknown>[]> {
270
+ const defaultEntityTypeApiKey =
271
+ this.state.defaultEntityTypeApiKey ?? 'defaultBusiType';
272
+
273
+ const res = await request({
274
+ url: STAGE_LIST_URL,
275
+ method: 'POST',
276
+ data: {
277
+ data: {
278
+ entityTypeApiKey: defaultEntityTypeApiKey,
279
+ },
280
+ },
281
+ });
282
+
283
+ return res.data || [];
284
+ }
285
+
286
+ buildStagesFromApi(
287
+ rows: any[], // 阶段列表
288
+ opportunityList: Record<string, unknown>[], // 商机列表
289
+ historyOpportunityList: unknown[], // 历史商机:SQL 二维数组行
290
+ ): StageTab[] {
291
+ console.log(
292
+ '[StageSwitch__c] buildStagesFromApi:',
293
+ rows,
294
+ opportunityList,
295
+ historyOpportunityList,
296
+ );
297
+ return rows.map((row, index) => {
298
+ const stageName = row.stageName;
299
+ const { amount, count } = this.getOpportunityStats(
300
+ stageName,
301
+ opportunityList,
302
+ );
303
+ const key = String(
304
+ row.id ?? row.apiKey ?? (stageName || `stage-${index}`),
305
+ );
306
+
307
+ // 根据商机ID,从历史商机列表中找到对应的商机
308
+ const { amount: historyAmount, count: historyCount } =
309
+ this.getHistoryOpportunityStats(key, historyOpportunityList);
310
+
311
+ const tab: StageTab = {
312
+ key,
313
+ name: stageName,
314
+ amount: formatAmountDisplay(amount),
315
+ count: count,
316
+ changes: {
317
+ amount: formatAmountDisplay(historyAmount),
318
+ count: historyCount,
319
+ amountDirection:
320
+ historyAmount < amount
321
+ ? 'up'
322
+ : historyAmount == amount
323
+ ? 'same'
324
+ : 'down',
325
+ countDirection:
326
+ historyCount < count
327
+ ? 'up'
328
+ : historyCount == count
329
+ ? 'same'
330
+ : 'down',
331
+ },
332
+ };
333
+ return tab;
334
+ });
335
+ }
336
+
337
+ async fetchAllData() {
338
+ this.setState({ loading: true, error: null });
339
+
340
+ try {
341
+ const [opportunityList, stageRows, historyOpportunityList] =
342
+ await Promise.all([
343
+ this.fetchOpportunityList(),
344
+ this.fetchStageRows(),
345
+ this.fetchHistoryOpportunityList(),
346
+ ]);
347
+
348
+ const stages = this.buildStagesFromApi(
349
+ stageRows,
350
+ opportunityList,
351
+ historyOpportunityList,
352
+ );
353
+
354
+ this.setState({
355
+ opportunityList,
356
+ stages,
357
+ loading: false,
358
+ });
359
+ } catch (e: unknown) {
360
+ console.error('StageSwitch 加载失败:', e);
361
+ const msg = e instanceof Error ? e.message : '加载失败';
362
+ this.setState({
363
+ loading: false,
364
+ error: msg,
365
+ opportunityList: [],
366
+ stages: [],
367
+ });
368
+ }
369
+ }
370
+
371
+ // 点击阶段切换时,更新当前激活的销售阶段
372
+ handleStageClick(stage: StageTab) {
373
+ const { onStageChange } = this.props;
374
+ if (onStageChange) {
375
+ onStageChange(stage);
376
+ }
377
+ // 广播事件:更新当前激活的销售阶段
378
+ const activeStage = extractStageKeyFromStageName(stage.name);
379
+ this.setState({ activeStage: activeStage });
380
+
381
+ this.onActiveStageChange({
382
+ activeStage,
383
+ });
384
+
385
+ // 触发一个广播事件
386
+ // NeoEvent.broadcast('updateActiveStage', activeStage);
387
+ }
388
+
389
+ @NeoEvent.function
390
+ async refreshData() {
391
+ await this.fetchAllData();
392
+ }
393
+
394
+ /**
395
+ * 与 pipelineFunnel 一致:传入 { relation, filter },将重新拉取商机并合并阶段统计。
396
+ */
397
+ @NeoEvent.function
398
+ setFilter(filter?: any) {
399
+ if (isEqual(filter, this.state.filter)) {
400
+ return;
401
+ }
402
+ let defaultEntityTypeApiKey = this.state.defaultEntityTypeApiKey;
403
+ if (filter.businessTypeApiKey) {
404
+ defaultEntityTypeApiKey = filter.businessTypeApiKey;
405
+ }
406
+ console.log('[StageSwitch__c] setFilter:', filter);
407
+ this.setState(
408
+ { filter: filter, defaultEntityTypeApiKey: defaultEntityTypeApiKey },
409
+ () => {
410
+ this.fetchAllData();
411
+ },
412
+ );
413
+ }
414
+
415
+ /**
416
+ * 更新当前激活的销售阶段
417
+ */
418
+ @NeoEvent.function
419
+ updateActiveStage(activeStage?: string) {
420
+ if (isEqual(activeStage, this.state.activeStage)) {
421
+ return;
422
+ }
423
+ console.log('[StageSwitch__c] updateActiveStage:', activeStage);
424
+ this.setState({ activeStage: activeStage || '' });
425
+ }
426
+
427
+ @NeoEvent.dispatch
428
+ onActiveStageChange(eventData?: any) {}
429
+
430
+ render() {
431
+ const { className, style } = this.props;
432
+ const { activeStage, stages, loading, error } = this.state;
433
+
434
+ return (
435
+ <div
436
+ className={`stageSwitch__c ${className || ''}`}
437
+ style={style}
438
+ data-time="2026.4.15 01"
439
+ >
440
+ <Spin spinning={loading}>
441
+ {error ? (
442
+ <div
443
+ className="stageSwitch__c-error"
444
+ style={{ color: '#cf1322', fontSize: 12, marginBottom: 8 }}
445
+ >
446
+ {error}
447
+ </div>
448
+ ) : null}
449
+ <div className="stage-tabs">
450
+ {stages.map((stage) => (
451
+ <div
452
+ key={stage.key}
453
+ className={`stage-tab ${
454
+ activeStage === extractStageKeyFromStageName(stage.name)
455
+ ? 'active'
456
+ : ''
457
+ }`}
458
+ onClick={() => this.handleStageClick(stage)}
459
+ >
460
+ <div className="stage-name">{stage.name}</div>
461
+ <div className="stage-amount-row">
462
+ <span className="stage-amount">{stage.amount}</span>
463
+ <span className="stage-count">({stage.count})</span>
464
+ </div>
465
+ {stage.changes && Object.keys(stage.changes).length > 0 && (
466
+ <div className="stage-sub">
467
+ <span
468
+ className={`change-amount ${
469
+ stage.changes.amountDirection === 'up'
470
+ ? 'positive'
471
+ : stage.changes.amountDirection === 'down'
472
+ ? 'negative'
473
+ : ''
474
+ }`}
475
+ >
476
+ {stage.changes.amountDirection === 'up'
477
+ ? '↑'
478
+ : stage.changes.amountDirection === 'same'
479
+ ? ''
480
+ : '↓'}{' '}
481
+ {stage.changes.amount}
482
+ </span>
483
+ <span> &nbsp; </span>
484
+ <span
485
+ className={`change-count ${
486
+ stage.changes.countDirection === 'up'
487
+ ? 'positive'
488
+ : stage.changes.amountDirection === 'down'
489
+ ? 'negative'
490
+ : ''
491
+ }`}
492
+ >
493
+ {stage.changes.countDirection === 'up'
494
+ ? '↑'
495
+ : stage.changes.countDirection === 'same'
496
+ ? ''
497
+ : '↓'}{' '}
498
+ {stage.changes.count}
499
+ </span>
500
+ </div>
501
+ )}
502
+ </div>
503
+ ))}
504
+ </div>
505
+ </Spin>
506
+ </div>
507
+ );
508
+ }
509
+ }
510
+
511
+ export default StageSwitch;
@@ -0,0 +1,70 @@
1
+ export class StageSwitchModel {
2
+ label: string = '阶段切换卡片';
3
+ description: string = '切换显示不同销售阶段的数据,包含金额、数量和变化趋势';
4
+ iconUrl: string = 'https://custom-widgets.bj.bcebos.com/card.svg';
5
+ targetPage: string[] = ['all'];
6
+ targetDevice: string = 'all';
7
+
8
+ defaultComProps = {
9
+ /** 请求阶段列表接口时传入的 defaultEntityTypeApiKey */
10
+ // defaultEntityTypeApiKey: 'defaultBusiType',
11
+ stages: [] as unknown[],
12
+ activeStage: 'Prospecting',
13
+ };
14
+
15
+ events = [
16
+ {
17
+ apiKey: 'onActiveStageChange',
18
+ label: '当前激活阶段变动后',
19
+ helpText:
20
+ '当前激活阶段变动后触发;事件参数含 activeStage(当前激活阶段)',
21
+ eventParams:
22
+ '[{"apiKey":"eventParam","children":[{"apiKey":"activeStage","label":"当前激活阶段","type":"String"}],"label":"事件入参","type":"Object"}]',
23
+ },
24
+ ];
25
+
26
+ functions = [
27
+ {
28
+ apiKey: 'updateActiveStage',
29
+ label: '更新当前激活阶段',
30
+ helpTextKey: '设置当前激活的销售阶段',
31
+ funcInParams: [
32
+ {
33
+ apiKey: 'activeStage',
34
+ label: '当前激活阶段',
35
+ type: 'String',
36
+ required: false,
37
+ },
38
+ ],
39
+ },
40
+ {
41
+ apiKey: 'setFilter',
42
+ label: '设置过滤条件',
43
+ helpTextKey:
44
+ '设置与图表一致的 filter(closeDate / ownerId / entityType),并重新拉取商机',
45
+ funcInParams: [
46
+ {
47
+ apiKey: 'filter',
48
+ label: '过滤条件(relation + filter 数组)',
49
+ type: 'Object',
50
+ required: false,
51
+ },
52
+ ],
53
+ },
54
+ {
55
+ apiKey: 'refreshData',
56
+ label: '刷新数据',
57
+ helpTextKey: '重新拉取商机列表与阶段列表',
58
+ },
59
+ ];
60
+
61
+ propsSchema = [
62
+ {
63
+ type: 'panelInput',
64
+ name: 'defaultEntityTypeApiKey',
65
+ label: '业务类型 ApiKey(阶段列表接口)',
66
+ },
67
+ ];
68
+ }
69
+
70
+ export default StageSwitchModel;
@@ -1,8 +1,10 @@
1
- .stage-switch__c {
1
+ .stageSwitch__c {
2
+ margin-top: 12px;
3
+
2
4
  .stage-tabs {
3
5
  display: flex;
4
6
  gap: 0;
5
- margin-bottom: 20px;
7
+ margin-bottom: 12px;
6
8
  background: #fff;
7
9
  border-radius: 8px;
8
10
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);