neo-cmp-cli 1.13.16 → 1.13.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) 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/package.json +1 -1
  7. package/template/antd-custom-cmp-template/package.json +1 -1
  8. package/template/asset-manage-template/package.json +2 -2
  9. package/template/echarts-custom-cmp-template/package.json +1 -1
  10. package/template/empty-custom-cmp-template/package.json +2 -2
  11. package/template/map-custom-cmp-template/package.json +1 -1
  12. package/template/neo-bi-cmps/neo.config.js +7 -1
  13. package/template/neo-bi-cmps/package.json +8 -7
  14. package/template/neo-bi-cmps/public/403.html +77 -0
  15. package/template/neo-bi-cmps/public/demo.html +2453 -0
  16. package/template/neo-bi-cmps/src/assets/icon/barChart.svg +1 -0
  17. package/template/neo-bi-cmps/src/assets/icon/card.svg +1 -0
  18. package/template/neo-bi-cmps/src/assets/icon/filter.svg +1 -0
  19. package/template/neo-bi-cmps/src/assets/icon/funnel.svg +1 -0
  20. package/template/neo-bi-cmps/src/assets/icon/tab.svg +1 -0
  21. package/template/neo-bi-cmps/src/components/filterBar__c/README.md +3 -14
  22. package/template/neo-bi-cmps/src/components/filterBar__c/common.scss +29 -0
  23. package/template/neo-bi-cmps/src/components/filterBar__c/index.tsx +668 -146
  24. package/template/neo-bi-cmps/src/components/filterBar__c/model.ts +26 -48
  25. package/template/neo-bi-cmps/src/components/filterBar__c/style.scss +46 -139
  26. package/template/neo-bi-cmps/src/components/targetNumber__c/customStyleConfig/index.tsx +11 -10
  27. package/template/neo-bi-cmps/src/components/targetNumber__c/index.tsx +9 -16
  28. package/template/neo-bi-cmps/src/utils/common.ts +231 -0
  29. package/template/neo-bi-cmps/src/utils/filter2chartFilter.ts +268 -0
  30. package/template/neo-bi-cmps/src/utils/filterBar.ts +140 -0
  31. package/template/neo-bi-cmps/src/utils/pipelineFunnel.ts +341 -0
  32. package/template/neo-bi-cmps/src/utils/queryByCustomSQL.ts +117 -0
  33. package/template/neo-bi-cmps/src/utils/requestDebounce.ts +22 -0
  34. package/template/neo-bi-cmps/src/utils/simpleTable.tsx +344 -0
  35. package/template/neo-bi-cmps/src/utils/stageSwitch.ts +15 -0
  36. package/template/neo-bi-cmps/src/utils/stageTimeChart.ts +90 -0
  37. package/template/neo-bi-cmps/src/utils/targetNumber.ts +12 -0
  38. package/template/neo-custom-cmp-template/package.json +2 -2
  39. package/template/neo-h5-cmps/package.json +2 -2
  40. package/template/neo-order-cmps/package.json +2 -2
  41. package/template/neo-pipeline-cmps/.prettierrc.js +12 -0
  42. package/template/neo-pipeline-cmps/@types/neo-ui-common.d.ts +36 -0
  43. package/template/neo-pipeline-cmps/README.md +99 -0
  44. package/template/neo-pipeline-cmps/commitlint.config.js +59 -0
  45. package/template/neo-pipeline-cmps/neo.config.js +124 -0
  46. package/template/neo-pipeline-cmps/package.json +66 -0
  47. package/template/neo-pipeline-cmps/public/403.html +77 -0
  48. package/template/neo-pipeline-cmps/public/css/base.css +283 -0
  49. package/template/neo-pipeline-cmps/public/demo.html +2453 -0
  50. package/template/neo-pipeline-cmps/public/scripts/app/bluebird.js +6679 -0
  51. package/template/neo-pipeline-cmps/public/template.html +13 -0
  52. package/template/neo-pipeline-cmps/src/assets/css/common.scss +127 -0
  53. package/template/neo-pipeline-cmps/src/assets/css/mixin.scss +47 -0
  54. package/template/neo-pipeline-cmps/src/assets/icon/barChart.svg +1 -0
  55. package/template/neo-pipeline-cmps/src/assets/icon/card.svg +1 -0
  56. package/template/neo-pipeline-cmps/src/assets/icon/filter.svg +1 -0
  57. package/template/neo-pipeline-cmps/src/assets/icon/funnel.svg +1 -0
  58. package/template/neo-pipeline-cmps/src/assets/icon/tab.svg +1 -0
  59. package/template/neo-pipeline-cmps/src/assets/img/AIBtn.gif +0 -0
  60. package/template/neo-pipeline-cmps/src/assets/img/NeoCRM.jpg +0 -0
  61. package/template/neo-pipeline-cmps/src/assets/img/aiLogo.png +0 -0
  62. package/template/neo-pipeline-cmps/src/assets/img/card-list.svg +1 -0
  63. package/template/neo-pipeline-cmps/src/assets/img/contact-form.svg +1 -0
  64. package/template/neo-pipeline-cmps/src/assets/img/custom-form.svg +1 -0
  65. package/template/neo-pipeline-cmps/src/assets/img/custom-widget.svg +1 -0
  66. package/template/neo-pipeline-cmps/src/assets/img/data-list.svg +1 -0
  67. package/template/neo-pipeline-cmps/src/assets/img/detail.svg +1 -0
  68. package/template/neo-pipeline-cmps/src/assets/img/favicon.png +0 -0
  69. package/template/neo-pipeline-cmps/src/assets/img/map.svg +1 -0
  70. package/template/neo-pipeline-cmps/src/assets/img/search.svg +1 -0
  71. package/template/neo-pipeline-cmps/src/assets/img/table.svg +1 -0
  72. package/template/neo-pipeline-cmps/src/components/filterBar__c/README.md +24 -0
  73. package/template/neo-pipeline-cmps/src/components/filterBar__c/common.scss +29 -0
  74. package/template/neo-pipeline-cmps/src/components/filterBar__c/index.tsx +730 -0
  75. package/template/neo-pipeline-cmps/src/components/filterBar__c/model.ts +50 -0
  76. package/template/neo-pipeline-cmps/src/components/filterBar__c/style.scss +119 -0
  77. package/template/neo-pipeline-cmps/src/components/pipelineFunnel__c/index.tsx +415 -0
  78. package/template/neo-pipeline-cmps/src/components/pipelineFunnel__c/model.ts +79 -0
  79. package/template/neo-pipeline-cmps/src/components/pipelineFunnel__c/style.scss +83 -0
  80. package/template/neo-pipeline-cmps/src/components/showHealthResult__c/index.tsx +463 -0
  81. package/template/neo-pipeline-cmps/src/components/showHealthResult__c/model.ts +45 -0
  82. package/template/neo-pipeline-cmps/src/components/showHealthResult__c/style.scss +137 -0
  83. package/template/neo-pipeline-cmps/src/components/simpleTable__c/README.md +90 -0
  84. package/template/neo-pipeline-cmps/src/components/simpleTable__c/common.scss +195 -0
  85. package/template/neo-pipeline-cmps/src/components/simpleTable__c/index.tsx +665 -0
  86. package/template/neo-pipeline-cmps/src/components/simpleTable__c/model.ts +124 -0
  87. package/template/neo-pipeline-cmps/src/components/simpleTable__c/style.scss +193 -0
  88. package/template/neo-pipeline-cmps/src/components/stageSwitch__c/index.tsx +511 -0
  89. package/template/neo-pipeline-cmps/src/components/stageSwitch__c/model.ts +70 -0
  90. package/template/{neo-bi-cmps → neo-pipeline-cmps}/src/components/stageSwitch__c/style.scss +4 -2
  91. package/template/neo-pipeline-cmps/src/components/stageTimeChart__c/index.tsx +455 -0
  92. package/template/neo-pipeline-cmps/src/components/stageTimeChart__c/model.ts +103 -0
  93. package/template/{neo-bi-cmps → neo-pipeline-cmps}/src/components/stageTimeChart__c/style.scss +3 -2
  94. package/template/neo-pipeline-cmps/src/utils/common.ts +229 -0
  95. package/template/neo-pipeline-cmps/src/utils/filter2chartFilter.ts +268 -0
  96. package/template/neo-pipeline-cmps/src/utils/filterBar.ts +140 -0
  97. package/template/neo-pipeline-cmps/src/utils/pipelineFunnel.ts +343 -0
  98. package/template/neo-pipeline-cmps/src/utils/queryByCustomSQL.ts +117 -0
  99. package/template/neo-pipeline-cmps/src/utils/requestDebounce.ts +22 -0
  100. package/template/neo-pipeline-cmps/src/utils/simpleTable.tsx +344 -0
  101. package/template/neo-pipeline-cmps/src/utils/stageSwitch.ts +15 -0
  102. package/template/neo-pipeline-cmps/src/utils/stageTimeChart.ts +90 -0
  103. package/template/neo-pipeline-cmps/src/utils/targetNumber.ts +12 -0
  104. package/template/neo-pipeline-cmps/tsconfig.json +40 -0
  105. package/template/neo-web-entity-grid/package.json +2 -2
  106. package/template/neo-web-form/package.json +2 -2
  107. package/template/react-custom-cmp-template/package.json +1 -1
  108. package/template/react-ts-custom-cmp-template/package.json +1 -1
  109. package/template/vue2-custom-cmp-template/package.json +1 -1
  110. package/template/neo-bi-cmps/.npmrc copy +0 -1
  111. package/template/neo-bi-cmps/src/components/aiCommitDrawer__c/README.md +0 -52
  112. package/template/neo-bi-cmps/src/components/aiCommitDrawer__c/index.tsx +0 -183
  113. package/template/neo-bi-cmps/src/components/aiCommitDrawer__c/model.ts +0 -90
  114. package/template/neo-bi-cmps/src/components/aiCommitDrawer__c/style.scss +0 -218
  115. package/template/neo-bi-cmps/src/components/forecastChart__c/README.md +0 -31
  116. package/template/neo-bi-cmps/src/components/forecastChart__c/index.tsx +0 -158
  117. package/template/neo-bi-cmps/src/components/forecastChart__c/model.ts +0 -40
  118. package/template/neo-bi-cmps/src/components/forecastChart__c/style.scss +0 -154
  119. package/template/neo-bi-cmps/src/components/forecastGrid__c/README.md +0 -36
  120. package/template/neo-bi-cmps/src/components/forecastGrid__c/index.tsx +0 -86
  121. package/template/neo-bi-cmps/src/components/forecastGrid__c/model.ts +0 -62
  122. package/template/neo-bi-cmps/src/components/forecastGrid__c/style.scss +0 -48
  123. package/template/neo-bi-cmps/src/components/gapCloser__c/README.md +0 -24
  124. package/template/neo-bi-cmps/src/components/gapCloser__c/index.tsx +0 -100
  125. package/template/neo-bi-cmps/src/components/gapCloser__c/model.ts +0 -46
  126. package/template/neo-bi-cmps/src/components/gapCloser__c/style.scss +0 -60
  127. package/template/neo-bi-cmps/src/components/kpiCards__c/README.md +0 -35
  128. package/template/neo-bi-cmps/src/components/kpiCards__c/index.tsx +0 -70
  129. package/template/neo-bi-cmps/src/components/kpiCards__c/model.ts +0 -50
  130. package/template/neo-bi-cmps/src/components/kpiCards__c/style.scss +0 -33
  131. package/template/neo-bi-cmps/src/components/oppList__c/README.md +0 -52
  132. package/template/neo-bi-cmps/src/components/oppList__c/index.tsx +0 -285
  133. package/template/neo-bi-cmps/src/components/oppList__c/model.ts +0 -86
  134. package/template/neo-bi-cmps/src/components/oppList__c/style.scss +0 -133
  135. package/template/neo-bi-cmps/src/components/pipelineFunnel__c/index.tsx +0 -130
  136. package/template/neo-bi-cmps/src/components/pipelineFunnel__c/model.ts +0 -66
  137. package/template/neo-bi-cmps/src/components/pipelineFunnel__c/style.scss +0 -133
  138. package/template/neo-bi-cmps/src/components/stageSwitch__c/index.tsx +0 -118
  139. package/template/neo-bi-cmps/src/components/stageSwitch__c/model.ts +0 -92
  140. package/template/neo-bi-cmps/src/components/stageTimeChart__c/index.tsx +0 -126
  141. package/template/neo-bi-cmps/src/components/stageTimeChart__c/model.ts +0 -57
  142. package/template/neo-bi-cmps/src/components/tabSwitch__c/README.md +0 -37
  143. package/template/neo-bi-cmps/src/components/tabSwitch__c/index.tsx +0 -80
  144. package/template/neo-bi-cmps/src/components/tabSwitch__c/model.ts +0 -45
  145. package/template/neo-bi-cmps/src/components/tabSwitch__c/style.scss +0 -37
  146. package/template/neo-bi-cmps/src/utils/axiosFetcher.ts +0 -37
  147. package/template/neo-bi-cmps/src/utils/queryObjectData.ts +0 -76
  148. package/template/neo-bi-cmps/src/utils/xobjects.ts +0 -162
  149. /package/template/{neo-bi-cmps → neo-pipeline-cmps}/src/components/pipelineFunnel__c/README.md +0 -0
  150. /package/template/{neo-bi-cmps → neo-pipeline-cmps}/src/components/stageSwitch__c/README.md +0 -0
  151. /package/template/{neo-bi-cmps → neo-pipeline-cmps}/src/components/stageTimeChart__c/README.md +0 -0
@@ -1,200 +1,722 @@
1
1
  /**
2
2
  * @file 筛选栏组件
3
- * @description 支持日期范围、负责人、业务类型等多维度筛选
3
+ * @description 支持日期范围、负责人(user 实体)、业务类型等多维度筛选
4
4
  */
5
5
  import * as React from 'react';
6
+ import { DatePicker, Select, Spin } from 'antd';
7
+ import moment, { Moment } from 'moment';
6
8
  // @ts-ignore
7
- import { BaseCmp, StatusHoc, NeoEvent } from 'neo-ui-common';
9
+ import { request, xObject } from 'neo-open-api';
10
+ // @ts-ignore
11
+ import { BaseCmp, NeoEvent } from 'neo-ui-common';
12
+
13
+ import { getDefaultOpportunityOwnerIds } from '../../utils/common';
14
+
15
+ import {
16
+ defaultChangesSinceOptions,
17
+ momentRangeToTimestamps,
18
+ normalizeOptions,
19
+ parseBusinessTypes,
20
+ parseUserRecordsToOwnerOptions,
21
+ relativeCloseDateRangeFromCode,
22
+ resolveDefaultBusinessTypeValue,
23
+ type FilterOption,
24
+ type TimestampRange,
25
+ } from '../../utils/filterBar';
8
26
 
27
+ export type { FilterOption, TimestampRange } from '../../utils/filterBar';
28
+
29
+ import './common.scss';
9
30
  import './style.scss';
10
31
 
11
- interface FilterOption {
12
- value: string;
13
- label: string;
32
+ const { RangePicker } = DatePicker;
33
+
34
+ const BUSI_TYPE_URL = '/rest/data/v2.0/xobjects/opportunity/busiType';
35
+
36
+ /** Opportunity Owner 人员列表:平台 user 实体 */
37
+ const USER_ENTITY_API_KEY = 'user';
38
+ const USER_QUERY_FIELDS = [
39
+ 'id',
40
+ 'name',
41
+ 'nickName',
42
+ 'personalEmail',
43
+ 'telephone',
44
+ ] as const;
45
+
46
+ interface FilterBarEventData {
47
+ data: FilterBarChangePayload;
48
+ }
49
+
50
+ export interface FilterBarChangePayload {
51
+ closeDate: number | string;
52
+ closeDateCustomRange?: TimestampRange;
53
+ /** Opportunity Owner 多选:人员 id 列表,空数组表示未选 */
54
+ opportunityOwner: (number | string)[];
55
+ businessType: string | number;
56
+ /** 当前选中的业务类型展示名(与 businessType 对应) */
57
+ businessTypeLabel?: string;
58
+ businessTypeApiKey?: string;
59
+ changesSince: string | number;
60
+ /** Changes Since 为 Custom 时:所选日期的当日 0 点 Unix 毫秒时间戳 */
61
+ changesSinceCustomTime?: number;
14
62
  }
15
63
 
16
- interface FilterItem {
64
+ /** 设计器 / 页面注入的当前登录用户(与 __NeoCurrentUser 一致) */
65
+ interface NeoCurrentUser {
66
+ id: number;
17
67
  name: string;
18
- label: string;
19
- type: 'select' | 'date' | 'owner' | 'custom';
20
- options?: FilterOption[];
21
- placeholder?: string;
68
+ icon?: string;
69
+ departId?: number;
70
+ localCode?: string;
71
+ type?: number;
72
+ languageCode?: string;
73
+ currencyApiKey?: string;
22
74
  }
23
75
 
24
76
  interface FilterBarProps {
25
- filters: FilterItem[];
26
- values?: Record<string, string>;
27
- onChange?: (key: string, value: string) => void;
28
- onSearch?: () => void;
77
+ /** Close Date 下拉选项(由页面 / 设计器配置传入) */
78
+ closeDateOptions?: FilterOption[];
79
+ /**
80
+ * 默认业务类型:与接口返回项的 apiKey 相等时,将该项的 id(或 value)作为 Business Type 初始选中值
81
+ */
82
+ defaultBusiType?: string;
29
83
  className?: string;
30
84
  style?: React.CSSProperties;
85
+ /** 与 NeoEvent 并存:便于父组件直接监听 */
86
+ onValuesChange?: (payload: FilterBarChangePayload) => void;
87
+ /** 页面数据:含 __NeoCurrentUser,用于负责人列表首位插入当前用户 */
88
+ data?: { __NeoCurrentUser?: NeoCurrentUser };
89
+ }
90
+
91
+ interface FilterValues {
92
+ closeDate: number | string;
93
+ businessType: string | number;
94
+ changesSince: string | number;
31
95
  }
32
96
 
33
97
  interface FilterBarState {
34
- values: Record<string, string>;
35
- ownerPickerOpen: boolean;
36
- ownerSearchText: string;
98
+ closeDateOptions: FilterOption[];
99
+ changesSinceOptions: FilterOption[];
100
+ opportunityOwnerOptions: FilterOption[];
101
+ businessTypeOptions: FilterOption[];
102
+ values: FilterValues;
103
+ /** Opportunity Owner 选中的人员 id(多选) */
104
+ opportunityOwner: (number | string)[];
105
+ /** Close Date 为 custom 时的自定义区间时间戳 */
106
+ closeDateCustomRange: TimestampRange | null;
107
+ /** Changes Since 为 Custom 时的单个时间点(当日 0 点,Unix 毫秒) */
108
+ changesSinceCustomTime: number | null;
109
+ ownerLoading: boolean;
110
+ businessTypeLoading: boolean;
37
111
  }
38
112
 
39
113
  class FilterBar extends BaseCmp<FilterBarProps, FilterBarState> {
114
+ /** 初始化筛选 state 与 Close Date 选项 */
40
115
  constructor(props: FilterBarProps) {
41
116
  super(props);
117
+ const closeOpts = normalizeOptions(
118
+ props.closeDateOptions && props.closeDateOptions.length > 0
119
+ ? props.closeDateOptions
120
+ : FilterBar.defaultCloseDateOptions(),
121
+ );
122
+ const csOpts = defaultChangesSinceOptions();
123
+ const initialClose = closeOpts[2]?.value ?? 401; // 默认本季度
124
+ const initialRelativeRange = relativeCloseDateRangeFromCode(initialClose);
42
125
  this.state = {
43
- values: props.values || {},
44
- ownerPickerOpen: false,
45
- ownerSearchText: '',
126
+ closeDateOptions: closeOpts,
127
+ changesSinceOptions: csOpts,
128
+ opportunityOwnerOptions: [],
129
+ businessTypeOptions: [],
130
+ values: {
131
+ closeDate: initialClose,
132
+ businessType: '',
133
+ changesSince: csOpts[0]?.value ?? '',
134
+ },
135
+ opportunityOwner: getDefaultOpportunityOwnerIds(props),
136
+ closeDateCustomRange: initialRelativeRange,
137
+ changesSinceCustomTime: initialRelativeRange?.start ?? null,
138
+ ownerLoading: false,
139
+ businessTypeLoading: false,
46
140
  };
47
141
 
48
- this.handleFilterChange = this.handleFilterChange.bind(this);
49
- this.handleOwnerSelect = this.handleOwnerSelect.bind(this);
142
+ // 绑定方法到 this
143
+ this.syncDefaultBusinessTypeFromProps =
144
+ this.syncDefaultBusinessTypeFromProps.bind(this);
145
+ this.syncCloseDateOptionsFromProps =
146
+ this.syncCloseDateOptionsFromProps.bind(this);
147
+ this.buildPayload = this.buildPayload.bind(this);
148
+ this.emitChange = this.emitChange.bind(this);
149
+ this.loadOpportunityOwnerOptions =
150
+ this.loadOpportunityOwnerOptions.bind(this);
151
+ this.loadBusinessTypeOptions = this.loadBusinessTypeOptions.bind(this);
152
+ this.patchValues = this.patchValues.bind(this);
153
+ this.handleCloseDateChange = this.handleCloseDateChange.bind(this);
154
+ this.handleChangesSinceChange = this.handleChangesSinceChange.bind(this);
155
+ this.handleCloseDateRangeChange =
156
+ this.handleCloseDateRangeChange.bind(this);
157
+ this.handleChangesSinceTimeChange =
158
+ this.handleChangesSinceTimeChange.bind(this);
159
+ this.closeDateRangePickerValue =
160
+ this.closeDateRangePickerValue.bind(this);
161
+ this.changesSinceDatePickerValue =
162
+ this.changesSinceDatePickerValue.bind(this);
163
+ this.getFilters = this.getFilters.bind(this);
164
+ this.resetFilters = this.resetFilters.bind(this);
165
+ this.renderCloseDateBlock = this.renderCloseDateBlock.bind(this);
166
+ this.renderOwnerBlock = this.renderOwnerBlock.bind(this);
167
+ this.renderBusinessTypeBlock = this.renderBusinessTypeBlock.bind(this);
168
+ this.renderChangesSinceBlock = this.renderChangesSinceBlock.bind(this);
50
169
  }
51
170
 
171
+ /** Close Date 内置默认选项 */
172
+ static defaultCloseDateOptions(): FilterOption[] {
173
+ return [
174
+ { value: 201, label: 'This Week' },
175
+ { value: 301, label: 'This Month' },
176
+ { value: 401, label: 'This Quarter' },
177
+ { value: 'custom', label: 'Custom' },
178
+ ];
179
+ }
180
+
181
+ /** 同步 props 选项并拉取负责人、业务类型 */
52
182
  componentDidMount() {
53
- console.log('FilterBar 组件挂载');
183
+ this.syncCloseDateOptionsFromProps(this.props);
184
+ this.loadOpportunityOwnerOptions();
185
+ this.loadBusinessTypeOptions();
186
+ }
187
+
188
+ /** props.closeDateOptions 变化时同步本地选项 */
189
+ componentWillReceiveProps(nextProps: FilterBarProps) {
190
+ if (nextProps.closeDateOptions !== this.props.closeDateOptions) {
191
+ this.syncCloseDateOptionsFromProps(nextProps);
192
+ }
193
+ if (nextProps.defaultBusiType !== this.props.defaultBusiType) {
194
+ this.syncDefaultBusinessTypeFromProps(nextProps);
195
+ }
196
+ const nextOwnerIds = getDefaultOpportunityOwnerIds(nextProps);
197
+ const prevUserId = this.props.data?.__NeoCurrentUser?.id;
198
+ const nextUserId = nextProps.data?.__NeoCurrentUser?.id;
199
+ if (
200
+ nextOwnerIds.length > 0 &&
201
+ prevUserId !== nextUserId &&
202
+ this.state.opportunityOwner.length === 0
203
+ ) {
204
+ this.setState({ opportunityOwner: nextOwnerIds }, () =>
205
+ this.emitChange(),
206
+ );
207
+ }
208
+ }
209
+
210
+ /** defaultBusiType 配置变更时,在已加载的下拉数据中重新匹配默认业务类型 */
211
+ syncDefaultBusinessTypeFromProps(props: FilterBarProps) {
212
+ const { businessTypeOptions } = this.state;
213
+ if (!businessTypeOptions.length) return;
214
+ const nextVal = resolveDefaultBusinessTypeValue(
215
+ businessTypeOptions,
216
+ props.defaultBusiType,
217
+ );
218
+ this.setState(
219
+ (prev) => ({
220
+ values: { ...prev.values, businessType: nextVal },
221
+ }),
222
+ () => this.emitChange(),
223
+ );
224
+ }
225
+
226
+ /** 用 props 更新 Close Date 选项并校正当前选中值 */
227
+ syncCloseDateOptionsFromProps(props: FilterBarProps) {
228
+ const next = normalizeOptions(
229
+ props.closeDateOptions && props.closeDateOptions.length > 0
230
+ ? props.closeDateOptions
231
+ : FilterBar.defaultCloseDateOptions(),
232
+ );
233
+ this.setState((prev) => {
234
+ const stillValid = next.some((o) => o.value === prev.values.closeDate);
235
+ const closeDate = stillValid
236
+ ? prev.values.closeDate
237
+ : next[0]?.value ?? '';
238
+ const isCustom = String(closeDate).toLowerCase() === 'custom';
239
+ const relativeRange = !isCustom
240
+ ? relativeCloseDateRangeFromCode(closeDate)
241
+ : null;
242
+ return {
243
+ closeDateOptions: next,
244
+ values: { ...prev.values, closeDate },
245
+ closeDateCustomRange: isCustom
246
+ ? prev.closeDateCustomRange
247
+ : relativeRange,
248
+ changesSinceCustomTime: prev.values.changesSince === 'Custom'
249
+ ? prev.changesSinceCustomTime
250
+ : relativeRange?.start ?? null,
251
+ };
252
+ });
253
+ }
254
+
255
+ /** 由 state 组装对外事件 / 回调用的 payload */
256
+ buildPayload(): FilterBarChangePayload {
257
+ const {
258
+ values,
259
+ opportunityOwner,
260
+ closeDateCustomRange,
261
+ changesSinceCustomTime,
262
+ businessTypeOptions,
263
+ } = this.state;
264
+ const closeCustom = String(values.closeDate).toLowerCase() === 'custom';
265
+ const changesCustom = values.changesSince === 'Custom';
266
+
267
+ const rangeResolved = closeCustom
268
+ ? closeDateCustomRange
269
+ : closeDateCustomRange ??
270
+ relativeCloseDateRangeFromCode(values.closeDate);
271
+
272
+ const curBusinessType =
273
+ values.businessType === '' || values.businessType == null
274
+ ? undefined
275
+ : businessTypeOptions.find((o) => o.value === values.businessType);
276
+
277
+ return {
278
+ closeDate: values.closeDate,
279
+ closeDateCustomRange: rangeResolved ?? undefined,
280
+ opportunityOwner,
281
+ businessType: values.businessType,
282
+ businessTypeLabel: curBusinessType?.label,
283
+ businessTypeApiKey: curBusinessType?.apiKey,
284
+ changesSince: values.changesSince,
285
+ changesSinceCustomTime: changesCustom
286
+ ? changesSinceCustomTime ?? undefined
287
+ : rangeResolved?.start != null
288
+ ? rangeResolved.start
289
+ : undefined,
290
+ };
291
+ }
292
+
293
+ /** 打日志、触发 onValuesChange 与设计器事件 */
294
+ emitChange() {
295
+ const payload = this.buildPayload();
296
+ console.log('[FilterBar__c] filters change', payload);
297
+ const { onValuesChange } = this.props;
298
+ if (onValuesChange) {
299
+ onValuesChange(payload);
300
+ }
301
+ this.onFiltersChange({
302
+ data: payload
303
+ });
304
+
305
+ /*
306
+ console.log('触发了一个广播事件 updateFilterData:', payload);
307
+ NeoEvent.broadcast('updateFilterData', payload);
308
+ */
54
309
  }
55
310
 
56
- handleFilterChange(key: string, value: string) {
57
- this.setState((prevState) => ({
58
- values: { ...prevState.values, [key]: value },
59
- }));
311
+ /** 设计器事件:筛选条件变化(含自定义区间) */
312
+ @NeoEvent.dispatch
313
+ onFiltersChange(eventData?: FilterBarEventData) {}
60
314
 
61
- const { onChange } = this.props;
62
- if (onChange) {
63
- onChange(key, value);
315
+ /** 请求 user 实体,填充 Opportunity Owner 下拉 */
316
+ async loadOpportunityOwnerOptions() {
317
+ this.setState({ ownerLoading: true });
318
+ const currentUser = Object.assign({}, this.props.data?.__NeoCurrentUser);
319
+ // 给当前用户增加标记
320
+ if (currentUser && currentUser.name && currentUser.name.indexOf('(CurrentUser)') === -1) {
321
+ currentUser.name = currentUser.name + '(CurrentUser)';
322
+ }
323
+ const currentUserRecords =
324
+ currentUser?.id != null &&
325
+ currentUser.name != null &&
326
+ String(currentUser.name).trim() !== ''
327
+ ? [currentUser]
328
+ : [];
329
+
330
+ try {
331
+ const result = await xObject.query({
332
+ xObjectApiKey: USER_ENTITY_API_KEY,
333
+ fields: [...USER_QUERY_FIELDS],
334
+ page: 1,
335
+ pageSize: 500,
336
+ });
337
+
338
+ let opportunityOwnerOptions: FilterOption[] = [];
339
+ if (result?.status) {
340
+ const records = Array.isArray(result.data) ? result.data : [];
341
+ opportunityOwnerOptions = parseUserRecordsToOwnerOptions([
342
+ ...currentUserRecords,
343
+ ...records,
344
+ ]);
345
+ } else {
346
+ console.warn(
347
+ 'FilterBar xObject.query(user) 非成功:',
348
+ result?.msg ?? result,
349
+ );
350
+ opportunityOwnerOptions =
351
+ parseUserRecordsToOwnerOptions(currentUserRecords);
352
+ }
353
+
354
+ const defaultOwnerIds = getDefaultOpportunityOwnerIds(this.props);
355
+ let appliedOwnerDefault = false;
356
+ this.setState(
357
+ (prev) => {
358
+ const shouldApply =
359
+ defaultOwnerIds.length > 0 && prev.opportunityOwner.length === 0;
360
+ if (shouldApply) appliedOwnerDefault = true;
361
+ return {
362
+ ownerLoading: false,
363
+ opportunityOwnerOptions,
364
+ opportunityOwner: shouldApply
365
+ ? defaultOwnerIds
366
+ : prev.opportunityOwner,
367
+ };
368
+ },
369
+ () => {
370
+ if (appliedOwnerDefault) this.emitChange();
371
+ },
372
+ );
373
+ } catch (e) {
374
+ console.error('FilterBar 加载负责人(user)失败:', e);
375
+ const fallbackOptions =
376
+ parseUserRecordsToOwnerOptions(currentUserRecords);
377
+ const defaultOwnerIds = getDefaultOpportunityOwnerIds(this.props);
378
+ let appliedOwnerDefault = false;
379
+ this.setState(
380
+ (prev) => {
381
+ const shouldApply =
382
+ defaultOwnerIds.length > 0 && prev.opportunityOwner.length === 0;
383
+ if (shouldApply) appliedOwnerDefault = true;
384
+ return {
385
+ ownerLoading: false,
386
+ opportunityOwnerOptions: fallbackOptions,
387
+ opportunityOwner: shouldApply
388
+ ? defaultOwnerIds
389
+ : prev.opportunityOwner,
390
+ };
391
+ },
392
+ () => {
393
+ if (appliedOwnerDefault) this.emitChange();
394
+ },
395
+ );
396
+ }
397
+ }
398
+
399
+ /** 请求商机业务类型接口,填充 Business Type 下拉 */
400
+ async loadBusinessTypeOptions() {
401
+ this.setState({ businessTypeLoading: true });
402
+ try {
403
+ const res = await request({
404
+ url: BUSI_TYPE_URL,
405
+ method: 'GET',
406
+ data: {},
407
+ });
408
+ const businessTypeOptions = parseBusinessTypes(res);
409
+ const defaultBt = resolveDefaultBusinessTypeValue(
410
+ businessTypeOptions,
411
+ this.props.defaultBusiType,
412
+ );
413
+ this.setState(
414
+ (prev) => ({
415
+ businessTypeLoading: false,
416
+ businessTypeOptions,
417
+ values: {
418
+ ...prev.values,
419
+ businessType: defaultBt,
420
+ },
421
+ }),
422
+ () => {
423
+ if (defaultBt) this.emitChange();
424
+ },
425
+ );
426
+ } catch (e) {
427
+ console.error('FilterBar 加载业务类型失败:', e);
428
+ this.setState({ businessTypeLoading: false, businessTypeOptions: [] });
64
429
  }
65
430
  }
66
431
 
67
- handleOwnerSelect(name: string) {
68
- this.setState((prevState) => ({
69
- values: { ...prevState.values, owner: name },
70
- ownerPickerOpen: false,
71
- }));
432
+ /** 合并 values 子集并通知变更 */
433
+ patchValues(patch: Partial<FilterValues>) {
434
+ this.setState(
435
+ (prev) =>
436
+ ({
437
+ values: { ...prev.values, ...patch },
438
+ } as Pick<FilterBarState, 'values'>),
439
+ () => this.emitChange(),
440
+ );
441
+ }
72
442
 
73
- const { onChange } = this.props;
74
- if (onChange) {
75
- onChange('owner', name);
443
+ /** Close Date 下拉变更;非 custom 时按编码写入相对周期的起止时间戳及起点到 changesSinceCustomTime */
444
+ handleCloseDateChange(value: number | string) {
445
+ if (String(value).toLowerCase() !== 'custom') {
446
+ const relativeRange = relativeCloseDateRangeFromCode(value);
447
+ this.setState(
448
+ (prev) =>
449
+ ({
450
+ values: { ...prev.values, closeDate: value },
451
+ closeDateCustomRange: relativeRange,
452
+ changesSinceCustomTime:
453
+ prev.values.changesSince === 'Custom'
454
+ ? prev.changesSinceCustomTime
455
+ : relativeRange?.start ?? null,
456
+ } as Pick<
457
+ FilterBarState,
458
+ 'values' | 'closeDateCustomRange' | 'changesSinceCustomTime'
459
+ >),
460
+ () => this.emitChange(),
461
+ );
462
+ return;
76
463
  }
464
+ this.setState(
465
+ (prev) => ({
466
+ values: { ...prev.values, closeDate: value },
467
+ closeDateCustomRange: null,
468
+ changesSinceCustomTime:
469
+ prev.values.changesSince === 'Custom'
470
+ ? prev.changesSinceCustomTime
471
+ : null,
472
+ }),
473
+ () => this.emitChange(),
474
+ );
77
475
  }
78
476
 
79
- toggleOwnerPicker = () => {
80
- this.setState((prevState) => ({
81
- ownerPickerOpen: !prevState.ownerPickerOpen,
82
- }));
83
- };
84
-
85
- handleOwnerSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
86
- this.setState({ ownerSearchText: e.target.value });
87
- };
88
-
89
- renderFilterItem(filter: FilterItem) {
90
- const { values } = this.state;
91
-
92
- switch (filter.type) {
93
- case 'select':
94
- return (
95
- <div className="filter-group" key={filter.name}>
96
- <label>{filter.label}</label>
97
- <select
98
- value={values[filter.name] || ''}
99
- onChange={(e) =>
100
- this.handleFilterChange(filter.name, e.target.value)
101
- }
102
- >
103
- {filter.options?.map((opt) => (
104
- <option key={opt.value} value={opt.value}>
105
- {opt.label}
106
- </option>
107
- ))}
108
- </select>
477
+ /** Changes Since 下拉变更;非 Custom 且 Close Date 为相对周期时同步起点时间戳 */
478
+ handleChangesSinceChange(value: string | number) {
479
+ if (value !== 'Custom') {
480
+ const closeDate = this.state.values.closeDate;
481
+ const closeCustom = String(closeDate).toLowerCase() === 'custom';
482
+ const relativeRange = !closeCustom
483
+ ? relativeCloseDateRangeFromCode(closeDate)
484
+ : null;
485
+ this.setState(
486
+ (prev) =>
487
+ ({
488
+ values: { ...prev.values, changesSince: value },
489
+ changesSinceCustomTime: relativeRange?.start ?? null,
490
+ } as Pick<FilterBarState, 'values' | 'changesSinceCustomTime'>),
491
+ () => this.emitChange(),
492
+ );
493
+ return;
494
+ }
495
+ this.setState(
496
+ (prev) => ({
497
+ values: { ...prev.values, changesSince: value },
498
+ changesSinceCustomTime: null,
499
+ }),
500
+ () => this.emitChange(),
501
+ );
502
+ }
503
+
504
+ /** Close Date 自定义 RangePicker:写入时间戳区间 */
505
+ handleCloseDateRangeChange(dates: any) {
506
+ const closeDateCustomRange = momentRangeToTimestamps(dates);
507
+ this.setState({ closeDateCustomRange }, () => this.emitChange());
508
+ }
509
+
510
+ /** Changes Since 自定义 DatePicker:写入当日 0 点时间戳 */
511
+ handleChangesSinceTimeChange(date: Moment | null) {
512
+ const changesSinceCustomTime =
513
+ date != null ? moment(date).startOf('day').valueOf() : null;
514
+ this.setState({ changesSinceCustomTime }, () => this.emitChange());
515
+ }
516
+
517
+ /** state.closeDateCustomRange → RangePicker 受控值 */
518
+ closeDateRangePickerValue(): [Moment | null, Moment | null] | null {
519
+ const r = this.state.closeDateCustomRange;
520
+ if (!r || (r.start == null && r.end == null)) return null;
521
+ return [
522
+ r.start != null ? moment(r.start) : null,
523
+ r.end != null ? moment(r.end) : null,
524
+ ];
525
+ }
526
+
527
+ /** state.changesSinceCustomTime → DatePicker 受控值 */
528
+ changesSinceDatePickerValue(): Moment | null {
529
+ const t = this.state.changesSinceCustomTime;
530
+ if (t == null) return null;
531
+ return moment(t);
532
+ }
533
+
534
+ /** 动作流 / 外部调用:返回当前筛选快照 */
535
+ @NeoEvent.function
536
+ getFilters(): FilterBarChangePayload {
537
+ return this.buildPayload();
538
+ }
539
+
540
+ /** 动作流:恢复默认并清空自定义区间后通知 */
541
+ @NeoEvent.function
542
+ resetFilters() {
543
+ const { closeDateOptions, changesSinceOptions } = this.state;
544
+ const resetClose = closeDateOptions[0]?.value ?? '';
545
+ const resetCustom = String(resetClose).toLowerCase() === 'custom';
546
+ const resetRange = !resetCustom
547
+ ? relativeCloseDateRangeFromCode(resetClose)
548
+ : null;
549
+ this.setState(
550
+ {
551
+ values: {
552
+ closeDate: resetClose,
553
+ businessType: '',
554
+ changesSince: changesSinceOptions[0]?.value ?? '',
555
+ },
556
+ opportunityOwner: getDefaultOpportunityOwnerIds(this.props),
557
+ closeDateCustomRange: resetRange,
558
+ changesSinceCustomTime: resetRange?.start ?? null,
559
+ },
560
+ () => this.emitChange(),
561
+ );
562
+ }
563
+
564
+ /** Close Date + 自定义日期区间 */
565
+ renderCloseDateBlock() {
566
+ const { closeDateOptions, values } = this.state;
567
+ const showCustom = String(values.closeDate).toLowerCase() === 'custom';
568
+
569
+ return (
570
+ <div className="filter-block" key="closeDate">
571
+ <div className="filter-field">
572
+ <label>Close Date</label>
573
+ <Select
574
+ className="filter-select"
575
+ value={values.closeDate || undefined}
576
+ onChange={(v) => this.handleCloseDateChange(v)}
577
+ options={closeDateOptions.map((o) => ({
578
+ value: o.value,
579
+ label: o.label,
580
+ }))}
581
+ />
582
+ </div>
583
+ {showCustom && (
584
+ <div className="filter-field filter-field-range">
585
+ <RangePicker
586
+ value={this.closeDateRangePickerValue()}
587
+ onChange={this.handleCloseDateRangeChange}
588
+ format="YYYY-MM-DD"
589
+ />
109
590
  </div>
110
- );
591
+ )}
592
+ </div>
593
+ );
594
+ }
111
595
 
112
- case 'owner':
113
- return (
114
- <div className="filter-group owner-picker-wrap" key={filter.name}>
115
- <label>{filter.label}</label>
116
- <div
117
- className="owner-picker-trigger"
118
- onClick={this.toggleOwnerPicker}
119
- >
120
- <span className="owner-selected-text">
121
- {values[filter.name] || 'Current User'}
122
- </span>
123
- <span className="owner-arrow">▾</span>
124
- </div>
125
- {this.state.ownerPickerOpen && (
126
- <div className="owner-picker-dropdown open">
127
- <input
128
- type="text"
129
- className="owner-search"
130
- placeholder="Search people or departments..."
131
- onChange={this.handleOwnerSearch}
132
- />
133
- <div className="owner-section-label">People</div>
134
- {['Current User', 'Alice', 'Steve', 'Chloe']
135
- .filter((name) =>
136
- name
137
- .toLowerCase()
138
- .includes(this.state.ownerSearchText.toLowerCase()),
139
- )
140
- .map((name) => (
141
- <div
142
- key={name}
143
- className={`owner-item ${
144
- values[filter.name] === name ? 'selected' : ''
145
- }`}
146
- data-name={name}
147
- onClick={() => this.handleOwnerSelect(name)}
148
- >
149
- <span className="owner-check">
150
- {values[filter.name] === name ? '✓' : ''}
151
- </span>
152
- <span className="owner-icon">👤</span>
153
- {name}
154
- </div>
155
- ))}
156
- <div className="owner-section-label">Departments</div>
157
- {['Sales Dept', 'Enterprise Team']
158
- .filter((name) =>
159
- name
160
- .toLowerCase()
161
- .includes(this.state.ownerSearchText.toLowerCase()),
596
+ /** Opportunity Owner */
597
+ renderOwnerBlock() {
598
+ const { opportunityOwnerOptions, ownerLoading, opportunityOwner } =
599
+ this.state;
600
+
601
+ return (
602
+ <div className="filter-block" key="owner">
603
+ <div className="filter-field">
604
+ <label>Opportunity Owner</label>
605
+ <Spin spinning={ownerLoading}>
606
+ <div className="filter-select-spin-wrap">
607
+ <Select
608
+ className="filter-select filter-select-wide"
609
+ mode="multiple"
610
+ allowClear
611
+ showSearch
612
+ placeholder="Please select the owner"
613
+ value={opportunityOwner}
614
+ optionFilterProp="label"
615
+ maxTagCount={2}
616
+ maxTagPlaceholder={(omitted) => `+${omitted.length}`}
617
+ onChange={(v) =>
618
+ this.setState({ opportunityOwner: v ?? [] }, () =>
619
+ this.emitChange(),
162
620
  )
163
- .map((name) => (
164
- <div
165
- key={name}
166
- className={`owner-item ${
167
- values[filter.name] === name ? 'selected' : ''
168
- }`}
169
- data-name={name}
170
- onClick={() => this.handleOwnerSelect(name)}
171
- >
172
- <span className="owner-check">
173
- {values[filter.name] === name ? '✓' : ''}
174
- </span>
175
- <span className="owner-icon">🏢</span>
176
- {name}
177
- </div>
178
- ))}
179
- </div>
180
- )}
181
- </div>
182
- );
621
+ }
622
+ dropdownMatchSelectWidth={320}
623
+ options={opportunityOwnerOptions.map((o) => ({
624
+ value: o.value,
625
+ label: o.label,
626
+ }))}
627
+ />
628
+ </div>
629
+ </Spin>
630
+ </div>
631
+ </div>
632
+ );
633
+ }
183
634
 
184
- default:
185
- return null;
186
- }
635
+ /** Business Type */
636
+ renderBusinessTypeBlock() {
637
+ const { businessTypeOptions, businessTypeLoading, values } = this.state;
638
+
639
+ return (
640
+ <div className="filter-block" key="businessType">
641
+ <div className="filter-field">
642
+ <label>Business Type</label>
643
+ <Spin spinning={businessTypeLoading}>
644
+ <div className="filter-select-spin-wrap">
645
+ <Select
646
+ className="filter-select"
647
+ allowClear
648
+ showSearch
649
+ placeholder="Please select the business type"
650
+ value={values.businessType || undefined}
651
+ optionFilterProp="label"
652
+ onChange={(v) => this.patchValues({ businessType: v || '' })}
653
+ options={businessTypeOptions.map((o) => ({
654
+ value: o.value,
655
+ label: o.label,
656
+ }))}
657
+ />
658
+ </div>
659
+ </Spin>
660
+ </div>
661
+ </div>
662
+ );
663
+ }
664
+
665
+ /** Changes Since + Custom 时单个日期 */
666
+ renderChangesSinceBlock() {
667
+ const { changesSinceOptions, values } = this.state;
668
+ const showCustom = values.changesSince === 'Custom';
669
+
670
+ return (
671
+ <div className="filter-block" key="changesSince">
672
+ <div className="filter-field">
673
+ <label>
674
+ Changes Since
675
+ <span className="help-tip">
676
+ ?
677
+ <span className="help-tip-text">
678
+ 相对报表周期的变更起始时间;Custom 时可自选日期
679
+ </span>
680
+ </span>
681
+ </label>
682
+ <Select
683
+ className="filter-select"
684
+ value={values.changesSince || undefined}
685
+ onChange={(v) => this.handleChangesSinceChange(v)}
686
+ options={changesSinceOptions.map((o) => ({
687
+ value: o.value,
688
+ label: o.label,
689
+ }))}
690
+ />
691
+ </div>
692
+ {showCustom && (
693
+ <div className="filter-field filter-field-range">
694
+ <DatePicker
695
+ value={this.changesSinceDatePickerValue()}
696
+ onChange={this.handleChangesSinceTimeChange}
697
+ format="YYYY-MM-DD"
698
+ placeholder="Select date"
699
+ />
700
+ </div>
701
+ )}
702
+ </div>
703
+ );
187
704
  }
188
705
 
706
+ /** 筛选栏根布局 */
189
707
  render() {
190
- const { filters = [], className, style } = this.props;
708
+ const { className, style } = this.props;
709
+ console.log('[FilterBar__c] render', this.props);
191
710
 
192
711
  return (
193
- <div className={`filter-bar__c ${className || ''}`} style={style}>
194
- {filters.map((filter) => this.renderFilterItem(filter))}
712
+ <div className={`filterBar__c ${className || ''}`} style={style} data-time='2026.4.15 01'>
713
+ {this.renderCloseDateBlock()}
714
+ {this.renderOwnerBlock()}
715
+ {this.renderBusinessTypeBlock()}
716
+ {this.renderChangesSinceBlock()}
195
717
  </div>
196
718
  );
197
719
  }
198
720
  }
199
721
 
200
- export default StatusHoc(FilterBar);
722
+ export default FilterBar;