neo-cmp-cli 1.13.11 → 1.13.13

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 (100) hide show
  1. package/dist/neo/neoService.js +1 -1
  2. package/dist/package.json.js +1 -1
  3. package/package.json +1 -1
  4. package/template/antd-custom-cmp-template/package.json +1 -1
  5. package/template/asset-manage-template/package.json +1 -1
  6. package/template/echarts-custom-cmp-template/package.json +1 -1
  7. package/template/empty-custom-cmp-template/package.json +1 -1
  8. package/template/map-custom-cmp-template/package.json +1 -1
  9. package/template/neo-bi-cmps/docs/gartner-pipeline-apis.md +279 -0
  10. package/template/neo-bi-cmps/docs/gartner-pipeline-prd.md +389 -0
  11. package/template/neo-bi-cmps/docs/neo-backend-dev/SKILL.md +188 -0
  12. package/template/neo-bi-cmps/docs/neo-backend-dev/references/01-Trigger/345/274/200/345/217/221.md +183 -0
  13. 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 +196 -0
  14. 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 +346 -0
  15. 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 +188 -0
  16. package/template/neo-bi-cmps/docs/neo-backend-dev/references/05-/351/241/265/351/235/242/345/274/200/345/217/221.md +293 -0
  17. 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 +175 -0
  18. 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 +313 -0
  19. package/template/neo-bi-cmps/docs/neo-backend-dev/references/auth-config.md +77 -0
  20. package/template/neo-bi-cmps/docs/neo-backend-dev/scripts/deploy_server_script.py +118 -0
  21. package/template/neo-bi-cmps/docs/neo-backend-dev/scripts/download_server_script.py +74 -0
  22. package/template/neo-bi-cmps/docs/neo-backend-dev/scripts/gen_entity_desc.py +69 -0
  23. package/template/neo-bi-cmps/docs/neo-backend-dev/scripts/gen_entitylist.py +87 -0
  24. package/template/neo-bi-cmps/docs/neo-backend-dev/scripts/query_crm.py +65 -0
  25. package/template/neo-bi-cmps/docs/neo-backend-dev/scripts/uninstall_server_script.py +48 -0
  26. package/template/neo-bi-cmps/docs/neo-backend-dev/scripts/update_model_jar.py +49 -0
  27. package/template/neo-bi-cmps/docs/neo-frontend-dev/SKILL.md +138 -0
  28. package/template/neo-bi-cmps/docs/neo-frontend-dev/references/auth-config.md +77 -0
  29. package/template/neo-bi-cmps/docs/neo-frontend-dev/references/component-dev.md +205 -0
  30. package/template/neo-bi-cmps/docs/neo-frontend-dev/references/entityTable-example.md +167 -0
  31. package/template/neo-bi-cmps/docs/neo-frontend-dev/references/templates.md +38 -0
  32. package/template/neo-bi-cmps/docs/neo-frontend-dev/scripts/gen_entity_desc.py +69 -0
  33. package/template/neo-bi-cmps/docs/neo-frontend-dev/scripts/gen_entitylist.py +87 -0
  34. package/template/neo-bi-cmps/docs/neo-frontend-dev/scripts/query_crm.py +65 -0
  35. package/template/neo-bi-cmps/docs/prototype-pipeline-forecasting.html +2453 -0
  36. 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 +10 -0
  37. package/template/neo-bi-cmps/package.json +1 -1
  38. package/template/neo-bi-cmps/src/components/aiCommitDrawer__c/README.md +52 -0
  39. package/template/neo-bi-cmps/src/components/aiCommitDrawer__c/index.tsx +176 -0
  40. package/template/neo-bi-cmps/src/components/aiCommitDrawer__c/model.ts +49 -0
  41. package/template/neo-bi-cmps/src/components/aiCommitDrawer__c/style.scss +218 -0
  42. package/template/neo-bi-cmps/src/components/filterBar__c/README.md +35 -0
  43. package/template/neo-bi-cmps/src/components/filterBar__c/index.tsx +186 -0
  44. package/template/neo-bi-cmps/src/components/filterBar__c/model.ts +72 -0
  45. package/template/neo-bi-cmps/src/components/filterBar__c/style.scss +212 -0
  46. package/template/neo-bi-cmps/src/components/forecastChart__c/README.md +31 -0
  47. package/template/neo-bi-cmps/src/components/forecastChart__c/index.tsx +161 -0
  48. package/template/neo-bi-cmps/src/components/forecastChart__c/model.ts +39 -0
  49. package/template/neo-bi-cmps/src/components/forecastChart__c/style.scss +154 -0
  50. package/template/neo-bi-cmps/src/components/forecastGrid__c/README.md +36 -0
  51. package/template/neo-bi-cmps/src/components/forecastGrid__c/index.tsx +86 -0
  52. package/template/neo-bi-cmps/src/components/forecastGrid__c/model.ts +34 -0
  53. package/template/neo-bi-cmps/src/components/forecastGrid__c/style.scss +48 -0
  54. package/template/neo-bi-cmps/src/components/gapCloser__c/README.md +24 -0
  55. package/template/neo-bi-cmps/src/components/gapCloser__c/index.tsx +95 -0
  56. package/template/neo-bi-cmps/src/components/gapCloser__c/model.ts +43 -0
  57. package/template/neo-bi-cmps/src/components/gapCloser__c/style.scss +60 -0
  58. package/template/neo-bi-cmps/src/components/kpiCards__c/README.md +35 -0
  59. package/template/neo-bi-cmps/src/components/kpiCards__c/index.tsx +70 -0
  60. package/template/neo-bi-cmps/src/components/kpiCards__c/model.ts +35 -0
  61. package/template/neo-bi-cmps/src/components/kpiCards__c/style.scss +33 -0
  62. package/template/neo-bi-cmps/src/components/oppList__c/README.md +52 -0
  63. package/template/neo-bi-cmps/src/components/oppList__c/index.tsx +228 -0
  64. package/template/neo-bi-cmps/src/components/oppList__c/model.ts +40 -0
  65. package/template/neo-bi-cmps/src/components/oppList__c/style.scss +133 -0
  66. package/template/neo-bi-cmps/src/components/pipelineFunnel__c/README.md +39 -0
  67. package/template/neo-bi-cmps/src/components/pipelineFunnel__c/index.tsx +128 -0
  68. package/template/neo-bi-cmps/src/components/pipelineFunnel__c/model.ts +42 -0
  69. package/template/neo-bi-cmps/src/components/pipelineFunnel__c/style.scss +133 -0
  70. package/template/neo-bi-cmps/src/components/stageSwitch__c/README.md +36 -0
  71. package/template/neo-bi-cmps/src/components/stageSwitch__c/index.tsx +103 -0
  72. package/template/neo-bi-cmps/src/components/stageSwitch__c/model.ts +37 -0
  73. package/template/neo-bi-cmps/src/components/stageSwitch__c/style.scss +89 -0
  74. package/template/neo-bi-cmps/src/components/stageTimeChart__c/README.md +37 -0
  75. package/template/neo-bi-cmps/src/components/stageTimeChart__c/index.tsx +126 -0
  76. package/template/neo-bi-cmps/src/components/stageTimeChart__c/model.ts +35 -0
  77. package/template/neo-bi-cmps/src/components/stageTimeChart__c/style.scss +140 -0
  78. package/template/neo-bi-cmps/src/components/tabSwitch__c/README.md +37 -0
  79. package/template/neo-bi-cmps/src/components/tabSwitch__c/index.tsx +80 -0
  80. package/template/neo-bi-cmps/src/components/tabSwitch__c/model.ts +45 -0
  81. package/template/neo-bi-cmps/src/components/tabSwitch__c/style.scss +37 -0
  82. package/template/neo-custom-cmp-template/package.json +1 -1
  83. package/template/neo-custom-cmp-template/src/components/entityForm__c/index.tsx +48 -54
  84. package/template/neo-custom-cmp-template/src/components/entityForm__c/model.ts +1 -1
  85. package/template/neo-custom-cmp-template/src/components/entityForm__c/style.scss +80 -77
  86. package/template/neo-h5-cmps/package.json +1 -1
  87. package/template/neo-order-cmps/package.json +1 -1
  88. package/template/neo-web-entity-grid/package.json +1 -1
  89. package/template/neo-web-entity-grid/src/components/createForm__c/index.tsx +46 -54
  90. package/template/neo-web-entity-grid/src/components/createForm__c/resetAntd.scss +74 -0
  91. package/template/neo-web-entity-grid/src/components/createForm__c/style.scss +81 -152
  92. package/template/neo-web-entity-grid/src/components/searchForm__c/index.tsx +47 -52
  93. package/template/neo-web-entity-grid/src/components/searchForm__c/style.scss +60 -74
  94. package/template/neo-web-form/package.json +1 -1
  95. package/template/neo-web-form/src/components/batchAddTable__c/index.tsx +16 -7
  96. package/template/neo-web-form/src/components/batchAddTable__c/style.scss +14 -0
  97. package/template/neo-web-form/src/components/batchAddTable__c/tableModal.scss +60 -13
  98. package/template/react-custom-cmp-template/package.json +1 -1
  99. package/template/react-ts-custom-cmp-template/package.json +1 -1
  100. package/template/vue2-custom-cmp-template/package.json +1 -1
@@ -0,0 +1,186 @@
1
+ /**
2
+ * @file 筛选栏组件
3
+ * @description 支持日期范围、负责人、业务类型等多维度筛选
4
+ */
5
+ import * as React from 'react';
6
+ // @ts-ignore
7
+ import { BaseCmp, StatusHoc, NeoEvent } from 'neo-ui-common';
8
+
9
+ import './style.scss';
10
+
11
+ interface FilterOption {
12
+ value: string;
13
+ label: string;
14
+ }
15
+
16
+ interface FilterItem {
17
+ name: string;
18
+ label: string;
19
+ type: 'select' | 'date' | 'owner' | 'custom';
20
+ options?: FilterOption[];
21
+ placeholder?: string;
22
+ }
23
+
24
+ interface FilterBarProps {
25
+ filters: FilterItem[];
26
+ values?: Record<string, string>;
27
+ onChange?: (key: string, value: string) => void;
28
+ onSearch?: () => void;
29
+ className?: string;
30
+ style?: React.CSSProperties;
31
+ }
32
+
33
+ interface FilterBarState {
34
+ values: Record<string, string>;
35
+ ownerPickerOpen: boolean;
36
+ ownerSearchText: string;
37
+ }
38
+
39
+ class FilterBar extends BaseCmp<FilterBarProps, FilterBarState> {
40
+ constructor(props: FilterBarProps) {
41
+ super(props);
42
+ this.state = {
43
+ values: props.values || {},
44
+ ownerPickerOpen: false,
45
+ ownerSearchText: '',
46
+ };
47
+
48
+ this.handleFilterChange = this.handleFilterChange.bind(this);
49
+ this.handleOwnerSelect = this.handleOwnerSelect.bind(this);
50
+ }
51
+
52
+ componentDidMount() {
53
+ console.log('FilterBar 组件挂载');
54
+ }
55
+
56
+ handleFilterChange(key: string, value: string) {
57
+ this.setState((prevState) => ({
58
+ values: { ...prevState.values, [key]: value },
59
+ }));
60
+
61
+ const { onChange } = this.props;
62
+ if (onChange) {
63
+ onChange(key, value);
64
+ }
65
+ }
66
+
67
+ handleOwnerSelect(name: string) {
68
+ this.setState((prevState) => ({
69
+ values: { ...prevState.values, owner: name },
70
+ ownerPickerOpen: false,
71
+ }));
72
+
73
+ const { onChange } = this.props;
74
+ if (onChange) {
75
+ onChange('owner', name);
76
+ }
77
+ }
78
+
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) => this.handleFilterChange(filter.name, e.target.value)}
100
+ >
101
+ {filter.options?.map((opt) => (
102
+ <option key={opt.value} value={opt.value}>
103
+ {opt.label}
104
+ </option>
105
+ ))}
106
+ </select>
107
+ </div>
108
+ );
109
+
110
+ case 'owner':
111
+ return (
112
+ <div className="filter-group owner-picker-wrap" key={filter.name}>
113
+ <label>{filter.label}</label>
114
+ <div
115
+ className="owner-picker-trigger"
116
+ onClick={this.toggleOwnerPicker}
117
+ >
118
+ <span className="owner-selected-text">
119
+ {values[filter.name] || 'Current User'}
120
+ </span>
121
+ <span className="owner-arrow">▾</span>
122
+ </div>
123
+ {this.state.ownerPickerOpen && (
124
+ <div className="owner-picker-dropdown open">
125
+ <input
126
+ type="text"
127
+ className="owner-search"
128
+ placeholder="Search people or departments..."
129
+ onChange={this.handleOwnerSearch}
130
+ />
131
+ <div className="owner-section-label">People</div>
132
+ {['Current User', 'Alice', 'Steve', 'Chloe']
133
+ .filter((name) =>
134
+ name.toLowerCase().includes(this.state.ownerSearchText.toLowerCase())
135
+ )
136
+ .map((name) => (
137
+ <div
138
+ key={name}
139
+ className={`owner-item ${values[filter.name] === name ? 'selected' : ''}`}
140
+ data-name={name}
141
+ onClick={() => this.handleOwnerSelect(name)}
142
+ >
143
+ <span className="owner-check">{values[filter.name] === name ? '✓' : ''}</span>
144
+ <span className="owner-icon">👤</span>
145
+ {name}
146
+ </div>
147
+ ))}
148
+ <div className="owner-section-label">Departments</div>
149
+ {['Sales Dept', 'Enterprise Team']
150
+ .filter((name) =>
151
+ name.toLowerCase().includes(this.state.ownerSearchText.toLowerCase())
152
+ )
153
+ .map((name) => (
154
+ <div
155
+ key={name}
156
+ className={`owner-item ${values[filter.name] === name ? 'selected' : ''}`}
157
+ data-name={name}
158
+ onClick={() => this.handleOwnerSelect(name)}
159
+ >
160
+ <span className="owner-check">{values[filter.name] === name ? '✓' : ''}</span>
161
+ <span className="owner-icon">🏢</span>
162
+ {name}
163
+ </div>
164
+ ))}
165
+ </div>
166
+ )}
167
+ </div>
168
+ );
169
+
170
+ default:
171
+ return null;
172
+ }
173
+ }
174
+
175
+ render() {
176
+ const { filters = [], className, style } = this.props;
177
+
178
+ return (
179
+ <div className={`filter-bar__c ${className || ''}`} style={style}>
180
+ {filters.map((filter) => this.renderFilterItem(filter))}
181
+ </div>
182
+ );
183
+ }
184
+ }
185
+
186
+ export default StatusHoc(FilterBar);
@@ -0,0 +1,72 @@
1
+ export class FilterBarModel {
2
+ label: string = '筛选栏';
3
+ description: string = '支持日期范围、负责人、业务类型等多维度筛选';
4
+ iconUrl: string = 'https://custom-widgets.bj.bcebos.com/filterBar.svg';
5
+ targetPage: string[] = ['all'];
6
+ targetDevice: string = 'all';
7
+
8
+ defaultComProps = {
9
+ filters: [
10
+ {
11
+ name: 'closeDate',
12
+ label: 'Close Date',
13
+ type: 'select',
14
+ options: [
15
+ { value: 'This Week', label: 'This Week' },
16
+ { value: 'This Month', label: 'This Month' },
17
+ { value: 'This Quarter', label: 'This Quarter' },
18
+ { value: 'Custom', label: 'Custom' },
19
+ ],
20
+ },
21
+ {
22
+ name: 'owner',
23
+ label: 'Opportunity Owner',
24
+ type: 'owner',
25
+ },
26
+ {
27
+ name: 'businessType',
28
+ label: 'Business Type',
29
+ type: 'select',
30
+ options: [
31
+ { value: 'New Business', label: 'New Business' },
32
+ { value: 'Renewal', label: 'Renewal' },
33
+ { value: 'Expansion', label: 'Expansion' },
34
+ ],
35
+ },
36
+ {
37
+ name: 'changesSince',
38
+ label: 'Changes Since',
39
+ type: 'select',
40
+ options: [
41
+ { value: 'Start of the Period', label: 'Start of the Period' },
42
+ { value: 'Custom', label: 'Custom' },
43
+ ],
44
+ },
45
+ ],
46
+ values: {},
47
+ };
48
+
49
+ functions = [
50
+ {
51
+ apiKey: 'resetFilters',
52
+ label: '重置筛选条件',
53
+ helpTextKey: '重置所有筛选条件为默认值',
54
+ },
55
+ {
56
+ apiKey: 'getFilters',
57
+ label: '获取筛选条件',
58
+ helpTextKey: '获取当前筛选条件',
59
+ },
60
+ ];
61
+
62
+ propsSchema = [
63
+ {
64
+ type: 'object',
65
+ name: 'filters',
66
+ label: '筛选配置',
67
+ schema: [],
68
+ },
69
+ ];
70
+ }
71
+
72
+ export default FilterBarModel;
@@ -0,0 +1,212 @@
1
+ .filter-bar__c {
2
+ background: #fff;
3
+ border-radius: 8px;
4
+ padding: 16px 20px;
5
+ display: flex;
6
+ gap: 24px;
7
+ align-items: center;
8
+ margin-bottom: 20px;
9
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
10
+
11
+ .filter-group {
12
+ display: flex;
13
+ align-items: center;
14
+ gap: 8px;
15
+ position: relative;
16
+
17
+ label {
18
+ font-size: 13px;
19
+ color: #666;
20
+ white-space: nowrap;
21
+ }
22
+
23
+ select,
24
+ input {
25
+ padding: 6px 12px;
26
+ border: 1px solid #ddd;
27
+ border-radius: 6px;
28
+ font-size: 13px;
29
+ background: #fff;
30
+ cursor: pointer;
31
+ }
32
+
33
+ select:focus {
34
+ border-color: #6366f1;
35
+ outline: none;
36
+ }
37
+ }
38
+
39
+ .filter-btn {
40
+ padding: 6px 16px;
41
+ border-radius: 6px;
42
+ font-size: 13px;
43
+ cursor: pointer;
44
+ border: 1px solid #ddd;
45
+ background: #fff;
46
+
47
+ &.active {
48
+ background: #6366f1;
49
+ color: #fff;
50
+ border-color: #6366f1;
51
+ }
52
+ }
53
+
54
+ /* Owner选人选部门组件 */
55
+ .owner-picker-wrap {
56
+ position: relative;
57
+ }
58
+
59
+ .owner-picker-trigger {
60
+ display: flex;
61
+ align-items: center;
62
+ gap: 6px;
63
+ padding: 6px 12px;
64
+ border: 1px solid #ddd;
65
+ border-radius: 6px;
66
+ font-size: 13px;
67
+ cursor: pointer;
68
+ background: #fff;
69
+ min-width: 140px;
70
+
71
+ &:hover {
72
+ border-color: #6366f1;
73
+ }
74
+ }
75
+
76
+ .owner-selected-text {
77
+ flex: 1;
78
+ }
79
+
80
+ .owner-arrow {
81
+ font-size: 10px;
82
+ color: #999;
83
+ }
84
+
85
+ .owner-picker-dropdown {
86
+ display: none;
87
+ position: absolute;
88
+ top: 100%;
89
+ left: 0;
90
+ margin-top: 4px;
91
+ width: 260px;
92
+ background: #fff;
93
+ border-radius: 8px;
94
+ box-shadow: 0 8px 30px rgba(0, 0, 0, 0.15);
95
+ z-index: 250;
96
+ padding: 8px 0;
97
+ max-height: 340px;
98
+ overflow-y: auto;
99
+
100
+ &.open {
101
+ display: block;
102
+ }
103
+ }
104
+
105
+ .owner-search {
106
+ width: calc(100% - 16px);
107
+ margin: 0 8px 8px;
108
+ padding: 8px 10px;
109
+ border: 1px solid #eee;
110
+ border-radius: 6px;
111
+ font-size: 12px;
112
+ outline: none;
113
+
114
+ &:focus {
115
+ border-color: #6366f1;
116
+ }
117
+ }
118
+
119
+ .owner-section-label {
120
+ padding: 6px 14px;
121
+ font-size: 11px;
122
+ color: #999;
123
+ font-weight: 600;
124
+ text-transform: uppercase;
125
+ letter-spacing: 0.5px;
126
+ }
127
+
128
+ .owner-item {
129
+ display: flex;
130
+ align-items: center;
131
+ gap: 8px;
132
+ padding: 8px 14px;
133
+ font-size: 13px;
134
+ cursor: pointer;
135
+ transition: background 0.15s;
136
+
137
+ &:hover {
138
+ background: #f5f5ff;
139
+ }
140
+
141
+ &.selected {
142
+ background: #f0f0ff;
143
+ color: #6366f1;
144
+ font-weight: 600;
145
+ }
146
+ }
147
+
148
+ .owner-icon {
149
+ font-size: 14px;
150
+ width: 20px;
151
+ text-align: center;
152
+ }
153
+
154
+ .owner-check {
155
+ width: 16px;
156
+ height: 16px;
157
+ border: 1.5px solid #ccc;
158
+ border-radius: 3px;
159
+ display: flex;
160
+ align-items: center;
161
+ justify-content: center;
162
+ font-size: 11px;
163
+ color: transparent;
164
+ flex-shrink: 0;
165
+ }
166
+
167
+ .owner-item.selected .owner-check {
168
+ background: #6366f1;
169
+ border-color: #6366f1;
170
+ color: #fff;
171
+ }
172
+
173
+ /* Changes Since 问号提示 */
174
+ .help-tip {
175
+ position: relative;
176
+ display: inline-block;
177
+ cursor: help;
178
+ color: #999;
179
+ font-size: 12px;
180
+ margin-left: 2px;
181
+
182
+ .help-tip-text {
183
+ display: none;
184
+ position: absolute;
185
+ bottom: calc(100% + 6px);
186
+ left: 50%;
187
+ transform: translateX(-50%);
188
+ background: #333;
189
+ color: #fff;
190
+ padding: 6px 10px;
191
+ border-radius: 6px;
192
+ font-size: 11px;
193
+ white-space: nowrap;
194
+ z-index: 100;
195
+ font-weight: 400;
196
+
197
+ &::after {
198
+ content: '';
199
+ position: absolute;
200
+ top: 100%;
201
+ left: 50%;
202
+ transform: translateX(-50%);
203
+ border: 5px solid transparent;
204
+ border-top-color: #333;
205
+ }
206
+ }
207
+
208
+ &:hover .help-tip-text {
209
+ display: block;
210
+ }
211
+ }
212
+ }
@@ -0,0 +1,31 @@
1
+ # ForecastChart 组件
2
+
3
+ 预测看板图表组件,展示预测数据的堆叠柱状图。
4
+
5
+ ## 使用方式
6
+
7
+ ```tsx
8
+ import ForecastChart from './components/forecastChart__c';
9
+
10
+ <ForecastChart
11
+ title="Forecast Overview"
12
+ quotaValue="$10M"
13
+ forecastValue="$7.5M"
14
+ aiValue="$6.8M"
15
+ columns={[
16
+ { label: 'Closed', value: '$3.2M', color: '#3b82f6' },
17
+ { label: 'Commit', value: '$2.5M', color: '#22c55e' },
18
+ ]}
19
+ />
20
+ ```
21
+
22
+ ## Props
23
+
24
+ | 属性 | 说明 | 类型 | 默认值 |
25
+ |------|------|------|--------|
26
+ | title | 标题 | string | 'Forecast Overview' |
27
+ | quotaValue | Quota值 | string | '$10M' |
28
+ | forecastValue | Forecast值 | string | '$7.5M' |
29
+ | aiValue | AI预测值 | string | '$6.8M' |
30
+ | columns | 柱状图数据 | ChartColumn[] | [] |
31
+ | showAiButton | 显示AI按钮 | boolean | true |
@@ -0,0 +1,161 @@
1
+ /**
2
+ * @file 预测看板图表组件
3
+ * @description 展示预测数据的堆叠柱状图,包含Closed、Commit、Best Case、Pipeline等
4
+ */
5
+ import * as React from 'react';
6
+ // @ts-ignore
7
+ import { BaseCmp, StatusHoc, NeoEvent } from 'neo-ui-common';
8
+
9
+ import './style.scss';
10
+
11
+ interface ChartColumn {
12
+ label: string;
13
+ value: string;
14
+ height: number;
15
+ color: string;
16
+ }
17
+
18
+ interface ForecastChartProps {
19
+ title?: string;
20
+ quotaValue?: string;
21
+ forecastValue?: string;
22
+ aiValue?: string;
23
+ columns?: ChartColumn[];
24
+ showAiButton?: boolean;
25
+ className?: string;
26
+ style?: React.CSSProperties;
27
+ }
28
+
29
+ interface ForecastChartState {
30
+ loading: boolean;
31
+ }
32
+
33
+ class ForecastChart extends BaseCmp<ForecastChartProps, ForecastChartState> {
34
+ constructor(props: ForecastChartProps) {
35
+ super(props);
36
+ this.state = {
37
+ loading: false,
38
+ };
39
+ }
40
+
41
+ componentDidMount() {
42
+ console.log('ForecastChart 组件挂载');
43
+ }
44
+
45
+ getScaleHeight = (value: string, max: string): number => {
46
+ const numValue = parseFloat(value.replace(/[$,M]/g, ''));
47
+ const numMax = parseFloat(max.replace(/[$,M]/g, ''));
48
+ if (isNaN(numValue) || isNaN(numMax) || numMax === 0) return 0;
49
+ return (numValue / numMax) * 100;
50
+ };
51
+
52
+ render() {
53
+ const {
54
+ title = 'Forecast Overview',
55
+ quotaValue = '$10M',
56
+ forecastValue = '$7.5M',
57
+ aiValue = '$6.8M',
58
+ columns = [],
59
+ showAiButton = true,
60
+ className,
61
+ style,
62
+ } = this.props;
63
+
64
+ const maxValue = 10; // 默认最大值 $10M
65
+ const chartHeight = 190; // 总高度
66
+ const gridLineHeight = chartHeight * 0.8; // 网格区域高度
67
+
68
+ // 计算Quota、Forecast、AI基准线位置
69
+ const quotaPos = 0; // 顶部
70
+ const forecastPos = this.getScaleHeight(forecastValue, quotaValue);
71
+ const aiPos = this.getScaleHeight(aiValue, quotaValue);
72
+
73
+ return (
74
+ <div className={`forecast-chart__c ${className || ''}`} style={style}>
75
+ <div className="chart-header">
76
+ <h3 className="chart-title">{title}</h3>
77
+ {showAiButton && (
78
+ <span
79
+ className="ai-btn"
80
+ onClick={() => console.log('AI Analysis clicked')}
81
+ title="AI Analysis"
82
+ >
83
+
84
+ </span>
85
+ )}
86
+ </div>
87
+
88
+ <div className="chart-content">
89
+ <div className="chart-y-axis">
90
+ <span>$10M</span>
91
+ <span>$8M</span>
92
+ <span>$6M</span>
93
+ <span>$4M</span>
94
+ <span>$2M</span>
95
+ <span>$0</span>
96
+ </div>
97
+
98
+ <div className="chart-main">
99
+ {/* 网格线 */}
100
+ <div className="chart-grid">
101
+ <div style={{ top: '0%' }} />
102
+ <div style={{ top: '20%' }} />
103
+ <div style={{ top: '40%' }} />
104
+ <div style={{ top: '60%' }} />
105
+ <div style={{ top: '80%' }} />
106
+ <div style={{ top: '100%' }} />
107
+ </div>
108
+
109
+ {/* Quota基准线 */}
110
+ <div className="chart-quota-line" style={{ top: `${quotaPos}%` }}>
111
+ <span className="quota-label">Quota: {quotaValue}</span>
112
+ </div>
113
+
114
+ {/* Forecast基准线 */}
115
+ <div
116
+ className="chart-forecast-line"
117
+ style={{ top: `${forecastPos}%` }}
118
+ >
119
+ <span className="forecast-label">Forecast {forecastValue}</span>
120
+ </div>
121
+
122
+ {/* AI基准线 */}
123
+ <div
124
+ className="chart-ai-line"
125
+ style={{ top: `${aiPos}%` }}
126
+ >
127
+ <span className="ai-label">✨ AI {aiValue}</span>
128
+ </div>
129
+
130
+ {/* 柱状图 */}
131
+ <div className="chart-bars">
132
+ {columns.map((col, index) => {
133
+ const heightPercent = this.getScaleHeight(col.value, quotaValue);
134
+ return (
135
+ <div key={index} className="chart-bar">
136
+ <span
137
+ className="bar-value"
138
+ style={{ color: col.color }}
139
+ >
140
+ {col.value}
141
+ </span>
142
+ <div
143
+ className="bar-column"
144
+ style={{
145
+ height: `${(heightPercent / 100) * gridLineHeight}px`,
146
+ backgroundColor: col.color,
147
+ }}
148
+ />
149
+ <span className="bar-label">{col.label}</span>
150
+ </div>
151
+ );
152
+ })}
153
+ </div>
154
+ </div>
155
+ </div>
156
+ </div>
157
+ );
158
+ }
159
+ }
160
+
161
+ export default StatusHoc(ForecastChart);
@@ -0,0 +1,39 @@
1
+ export class ForecastChartModel {
2
+ label: string = '预测看板图表';
3
+ description: string = '展示预测数据的堆叠柱状图,包含Closed、Commit、Best Case、Pipeline等';
4
+ iconUrl: string = 'https://custom-widgets.bj.bcebos.com/forecastChart.svg';
5
+ targetPage: string[] = ['all'];
6
+ targetDevice: string = 'all';
7
+
8
+ defaultComProps = {
9
+ title: 'Forecast Overview',
10
+ quotaValue: '$10M',
11
+ forecastValue: '$7.5M',
12
+ aiValue: '$6.8M',
13
+ columns: [
14
+ { label: 'Closed', value: '$3.2M', height: 61, color: '#3b82f6' },
15
+ { label: 'Commit', value: '$2.5M', height: 47, color: '#22c55e' },
16
+ { label: 'Best Case', value: '$1.8M', height: 34, color: '#10b981' },
17
+ { label: 'Pipeline', value: '$2.5M', height: 47, color: '#93c5fd' },
18
+ ],
19
+ showAiButton: true,
20
+ };
21
+
22
+ functions = [
23
+ {
24
+ apiKey: 'refreshData',
25
+ label: '刷新数据',
26
+ helpTextKey: '刷新预测图表数据',
27
+ },
28
+ ];
29
+
30
+ propsSchema = [
31
+ {
32
+ type: 'string',
33
+ name: 'title',
34
+ label: '标题',
35
+ },
36
+ ];
37
+ }
38
+
39
+ export default ForecastChartModel;