neo-cmp-cli 1.13.12 → 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 (61) 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/package.json +1 -1
  10. package/template/neo-bi-cmps/src/components/aiCommitDrawer__c/README.md +52 -0
  11. package/template/neo-bi-cmps/src/components/aiCommitDrawer__c/index.tsx +176 -0
  12. package/template/neo-bi-cmps/src/components/aiCommitDrawer__c/model.ts +49 -0
  13. package/template/neo-bi-cmps/src/components/aiCommitDrawer__c/style.scss +218 -0
  14. package/template/neo-bi-cmps/src/components/filterBar__c/README.md +35 -0
  15. package/template/neo-bi-cmps/src/components/filterBar__c/index.tsx +186 -0
  16. package/template/neo-bi-cmps/src/components/filterBar__c/model.ts +72 -0
  17. package/template/neo-bi-cmps/src/components/filterBar__c/style.scss +212 -0
  18. package/template/neo-bi-cmps/src/components/forecastChart__c/README.md +31 -0
  19. package/template/neo-bi-cmps/src/components/forecastChart__c/index.tsx +161 -0
  20. package/template/neo-bi-cmps/src/components/forecastChart__c/model.ts +39 -0
  21. package/template/neo-bi-cmps/src/components/forecastChart__c/style.scss +154 -0
  22. package/template/neo-bi-cmps/src/components/forecastGrid__c/README.md +36 -0
  23. package/template/neo-bi-cmps/src/components/forecastGrid__c/index.tsx +86 -0
  24. package/template/neo-bi-cmps/src/components/forecastGrid__c/model.ts +34 -0
  25. package/template/neo-bi-cmps/src/components/forecastGrid__c/style.scss +48 -0
  26. package/template/neo-bi-cmps/src/components/gapCloser__c/README.md +24 -0
  27. package/template/neo-bi-cmps/src/components/gapCloser__c/index.tsx +95 -0
  28. package/template/neo-bi-cmps/src/components/gapCloser__c/model.ts +43 -0
  29. package/template/neo-bi-cmps/src/components/gapCloser__c/style.scss +60 -0
  30. package/template/neo-bi-cmps/src/components/kpiCards__c/README.md +35 -0
  31. package/template/neo-bi-cmps/src/components/kpiCards__c/index.tsx +70 -0
  32. package/template/neo-bi-cmps/src/components/kpiCards__c/model.ts +35 -0
  33. package/template/neo-bi-cmps/src/components/kpiCards__c/style.scss +33 -0
  34. package/template/neo-bi-cmps/src/components/oppList__c/README.md +52 -0
  35. package/template/neo-bi-cmps/src/components/oppList__c/index.tsx +228 -0
  36. package/template/neo-bi-cmps/src/components/oppList__c/model.ts +40 -0
  37. package/template/neo-bi-cmps/src/components/oppList__c/style.scss +133 -0
  38. package/template/neo-bi-cmps/src/components/pipelineFunnel__c/README.md +39 -0
  39. package/template/neo-bi-cmps/src/components/pipelineFunnel__c/index.tsx +128 -0
  40. package/template/neo-bi-cmps/src/components/pipelineFunnel__c/model.ts +42 -0
  41. package/template/neo-bi-cmps/src/components/pipelineFunnel__c/style.scss +133 -0
  42. package/template/neo-bi-cmps/src/components/stageSwitch__c/README.md +36 -0
  43. package/template/neo-bi-cmps/src/components/stageSwitch__c/index.tsx +103 -0
  44. package/template/neo-bi-cmps/src/components/stageSwitch__c/model.ts +37 -0
  45. package/template/neo-bi-cmps/src/components/stageSwitch__c/style.scss +89 -0
  46. package/template/neo-bi-cmps/src/components/stageTimeChart__c/README.md +37 -0
  47. package/template/neo-bi-cmps/src/components/stageTimeChart__c/index.tsx +126 -0
  48. package/template/neo-bi-cmps/src/components/stageTimeChart__c/model.ts +35 -0
  49. package/template/neo-bi-cmps/src/components/stageTimeChart__c/style.scss +140 -0
  50. package/template/neo-bi-cmps/src/components/tabSwitch__c/README.md +37 -0
  51. package/template/neo-bi-cmps/src/components/tabSwitch__c/index.tsx +80 -0
  52. package/template/neo-bi-cmps/src/components/tabSwitch__c/model.ts +45 -0
  53. package/template/neo-bi-cmps/src/components/tabSwitch__c/style.scss +37 -0
  54. package/template/neo-custom-cmp-template/package.json +1 -1
  55. package/template/neo-h5-cmps/package.json +1 -1
  56. package/template/neo-order-cmps/package.json +1 -1
  57. package/template/neo-web-entity-grid/package.json +1 -1
  58. package/template/neo-web-form/package.json +1 -1
  59. package/template/react-custom-cmp-template/package.json +1 -1
  60. package/template/react-ts-custom-cmp-template/package.json +1 -1
  61. 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;