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.
- package/dist/neo/neoService.js +1 -1
- package/dist/package.json.js +1 -1
- package/package.json +1 -1
- package/template/antd-custom-cmp-template/package.json +1 -1
- package/template/asset-manage-template/package.json +1 -1
- package/template/echarts-custom-cmp-template/package.json +1 -1
- package/template/empty-custom-cmp-template/package.json +1 -1
- package/template/map-custom-cmp-template/package.json +1 -1
- package/template/neo-bi-cmps/docs/gartner-pipeline-apis.md +279 -0
- package/template/neo-bi-cmps/docs/gartner-pipeline-prd.md +389 -0
- package/template/neo-bi-cmps/docs/neo-backend-dev/SKILL.md +188 -0
- package/template/neo-bi-cmps/docs/neo-backend-dev/references/01-Trigger/345/274/200/345/217/221.md +183 -0
- 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
- 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
- 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
- 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
- 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
- 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
- package/template/neo-bi-cmps/docs/neo-backend-dev/references/auth-config.md +77 -0
- package/template/neo-bi-cmps/docs/neo-backend-dev/scripts/deploy_server_script.py +118 -0
- package/template/neo-bi-cmps/docs/neo-backend-dev/scripts/download_server_script.py +74 -0
- package/template/neo-bi-cmps/docs/neo-backend-dev/scripts/gen_entity_desc.py +69 -0
- package/template/neo-bi-cmps/docs/neo-backend-dev/scripts/gen_entitylist.py +87 -0
- package/template/neo-bi-cmps/docs/neo-backend-dev/scripts/query_crm.py +65 -0
- package/template/neo-bi-cmps/docs/neo-backend-dev/scripts/uninstall_server_script.py +48 -0
- package/template/neo-bi-cmps/docs/neo-backend-dev/scripts/update_model_jar.py +49 -0
- package/template/neo-bi-cmps/docs/neo-frontend-dev/SKILL.md +138 -0
- package/template/neo-bi-cmps/docs/neo-frontend-dev/references/auth-config.md +77 -0
- package/template/neo-bi-cmps/docs/neo-frontend-dev/references/component-dev.md +205 -0
- package/template/neo-bi-cmps/docs/neo-frontend-dev/references/entityTable-example.md +167 -0
- package/template/neo-bi-cmps/docs/neo-frontend-dev/references/templates.md +38 -0
- package/template/neo-bi-cmps/docs/neo-frontend-dev/scripts/gen_entity_desc.py +69 -0
- package/template/neo-bi-cmps/docs/neo-frontend-dev/scripts/gen_entitylist.py +87 -0
- package/template/neo-bi-cmps/docs/neo-frontend-dev/scripts/query_crm.py +65 -0
- package/template/neo-bi-cmps/docs/prototype-pipeline-forecasting.html +2453 -0
- 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
- package/template/neo-bi-cmps/package.json +1 -1
- package/template/neo-bi-cmps/src/components/aiCommitDrawer__c/README.md +52 -0
- package/template/neo-bi-cmps/src/components/aiCommitDrawer__c/index.tsx +176 -0
- package/template/neo-bi-cmps/src/components/aiCommitDrawer__c/model.ts +49 -0
- package/template/neo-bi-cmps/src/components/aiCommitDrawer__c/style.scss +218 -0
- package/template/neo-bi-cmps/src/components/filterBar__c/README.md +35 -0
- package/template/neo-bi-cmps/src/components/filterBar__c/index.tsx +186 -0
- package/template/neo-bi-cmps/src/components/filterBar__c/model.ts +72 -0
- package/template/neo-bi-cmps/src/components/filterBar__c/style.scss +212 -0
- package/template/neo-bi-cmps/src/components/forecastChart__c/README.md +31 -0
- package/template/neo-bi-cmps/src/components/forecastChart__c/index.tsx +161 -0
- package/template/neo-bi-cmps/src/components/forecastChart__c/model.ts +39 -0
- package/template/neo-bi-cmps/src/components/forecastChart__c/style.scss +154 -0
- package/template/neo-bi-cmps/src/components/forecastGrid__c/README.md +36 -0
- package/template/neo-bi-cmps/src/components/forecastGrid__c/index.tsx +86 -0
- package/template/neo-bi-cmps/src/components/forecastGrid__c/model.ts +34 -0
- package/template/neo-bi-cmps/src/components/forecastGrid__c/style.scss +48 -0
- package/template/neo-bi-cmps/src/components/gapCloser__c/README.md +24 -0
- package/template/neo-bi-cmps/src/components/gapCloser__c/index.tsx +95 -0
- package/template/neo-bi-cmps/src/components/gapCloser__c/model.ts +43 -0
- package/template/neo-bi-cmps/src/components/gapCloser__c/style.scss +60 -0
- package/template/neo-bi-cmps/src/components/kpiCards__c/README.md +35 -0
- package/template/neo-bi-cmps/src/components/kpiCards__c/index.tsx +70 -0
- package/template/neo-bi-cmps/src/components/kpiCards__c/model.ts +35 -0
- package/template/neo-bi-cmps/src/components/kpiCards__c/style.scss +33 -0
- package/template/neo-bi-cmps/src/components/oppList__c/README.md +52 -0
- package/template/neo-bi-cmps/src/components/oppList__c/index.tsx +228 -0
- package/template/neo-bi-cmps/src/components/oppList__c/model.ts +40 -0
- package/template/neo-bi-cmps/src/components/oppList__c/style.scss +133 -0
- package/template/neo-bi-cmps/src/components/pipelineFunnel__c/README.md +39 -0
- package/template/neo-bi-cmps/src/components/pipelineFunnel__c/index.tsx +128 -0
- package/template/neo-bi-cmps/src/components/pipelineFunnel__c/model.ts +42 -0
- package/template/neo-bi-cmps/src/components/pipelineFunnel__c/style.scss +133 -0
- package/template/neo-bi-cmps/src/components/stageSwitch__c/README.md +36 -0
- package/template/neo-bi-cmps/src/components/stageSwitch__c/index.tsx +103 -0
- package/template/neo-bi-cmps/src/components/stageSwitch__c/model.ts +37 -0
- package/template/neo-bi-cmps/src/components/stageSwitch__c/style.scss +89 -0
- package/template/neo-bi-cmps/src/components/stageTimeChart__c/README.md +37 -0
- package/template/neo-bi-cmps/src/components/stageTimeChart__c/index.tsx +126 -0
- package/template/neo-bi-cmps/src/components/stageTimeChart__c/model.ts +35 -0
- package/template/neo-bi-cmps/src/components/stageTimeChart__c/style.scss +140 -0
- package/template/neo-bi-cmps/src/components/tabSwitch__c/README.md +37 -0
- package/template/neo-bi-cmps/src/components/tabSwitch__c/index.tsx +80 -0
- package/template/neo-bi-cmps/src/components/tabSwitch__c/model.ts +45 -0
- package/template/neo-bi-cmps/src/components/tabSwitch__c/style.scss +37 -0
- package/template/neo-custom-cmp-template/package.json +1 -1
- package/template/neo-custom-cmp-template/src/components/entityForm__c/index.tsx +48 -54
- package/template/neo-custom-cmp-template/src/components/entityForm__c/model.ts +1 -1
- package/template/neo-custom-cmp-template/src/components/entityForm__c/style.scss +80 -77
- package/template/neo-h5-cmps/package.json +1 -1
- package/template/neo-order-cmps/package.json +1 -1
- package/template/neo-web-entity-grid/package.json +1 -1
- package/template/neo-web-entity-grid/src/components/createForm__c/index.tsx +46 -54
- package/template/neo-web-entity-grid/src/components/createForm__c/resetAntd.scss +74 -0
- package/template/neo-web-entity-grid/src/components/createForm__c/style.scss +81 -152
- package/template/neo-web-entity-grid/src/components/searchForm__c/index.tsx +47 -52
- package/template/neo-web-entity-grid/src/components/searchForm__c/style.scss +60 -74
- package/template/neo-web-form/package.json +1 -1
- package/template/neo-web-form/src/components/batchAddTable__c/index.tsx +16 -7
- package/template/neo-web-form/src/components/batchAddTable__c/style.scss +14 -0
- package/template/neo-web-form/src/components/batchAddTable__c/tableModal.scss +60 -13
- package/template/react-custom-cmp-template/package.json +1 -1
- package/template/react-ts-custom-cmp-template/package.json +1 -1
- package/template/vue2-custom-cmp-template/package.json +1 -1
|
@@ -0,0 +1,2453 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
<title>Seller Workspace</title>
|
|
8
|
+
<style>
|
|
9
|
+
* {
|
|
10
|
+
margin: 0;
|
|
11
|
+
padding: 0;
|
|
12
|
+
box-sizing: border-box;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
body {
|
|
16
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
17
|
+
background: #f5f6fa;
|
|
18
|
+
color: #333;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.container {
|
|
22
|
+
max-width: 1400px;
|
|
23
|
+
margin: 0 auto;
|
|
24
|
+
padding: 20px;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
h1 {
|
|
28
|
+
font-size: 20px;
|
|
29
|
+
font-weight: 600;
|
|
30
|
+
margin-bottom: 4px;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.page-desc {
|
|
34
|
+
font-size: 13px;
|
|
35
|
+
color: #888;
|
|
36
|
+
margin-bottom: 16px;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* Tabs */
|
|
40
|
+
.tab-bar {
|
|
41
|
+
display: flex;
|
|
42
|
+
gap: 0;
|
|
43
|
+
margin-bottom: 20px;
|
|
44
|
+
border-bottom: 2px solid #e5e7eb;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.tab-btn {
|
|
48
|
+
padding: 10px 24px;
|
|
49
|
+
font-size: 14px;
|
|
50
|
+
font-weight: 600;
|
|
51
|
+
cursor: pointer;
|
|
52
|
+
border: none;
|
|
53
|
+
background: none;
|
|
54
|
+
color: #999;
|
|
55
|
+
border-bottom: 2px solid transparent;
|
|
56
|
+
margin-bottom: -2px;
|
|
57
|
+
transition: all 0.2s;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.tab-btn.active {
|
|
61
|
+
color: #6366f1;
|
|
62
|
+
border-bottom-color: #6366f1;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.tab-btn:hover {
|
|
66
|
+
color: #6366f1;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.tab-panel {
|
|
70
|
+
display: none;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.tab-panel.active {
|
|
74
|
+
display: block;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* 阶段切换卡片 */
|
|
78
|
+
.stage-tabs {
|
|
79
|
+
display: flex;
|
|
80
|
+
gap: 0;
|
|
81
|
+
margin-bottom: 20px;
|
|
82
|
+
background: #fff;
|
|
83
|
+
border-radius: 8px;
|
|
84
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
|
|
85
|
+
overflow: hidden;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.stage-tab {
|
|
89
|
+
flex: 1;
|
|
90
|
+
padding: 10px 16px;
|
|
91
|
+
cursor: pointer;
|
|
92
|
+
border-right: 1px solid #f0f0f0;
|
|
93
|
+
transition: all 0.2s;
|
|
94
|
+
position: relative;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.stage-tab:last-child {
|
|
98
|
+
border-right: none;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.stage-tab.active {
|
|
102
|
+
background: #fff;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.stage-tab.active::before {
|
|
106
|
+
content: '';
|
|
107
|
+
position: absolute;
|
|
108
|
+
top: 0;
|
|
109
|
+
left: 0;
|
|
110
|
+
right: 0;
|
|
111
|
+
height: 3px;
|
|
112
|
+
background: #6366f1;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.stage-tab:hover {
|
|
116
|
+
background: #fafafa;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.stage-tab .stage-name {
|
|
120
|
+
font-size: 11px;
|
|
121
|
+
color: #888;
|
|
122
|
+
margin-bottom: 2px;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.stage-tab.active .stage-name {
|
|
126
|
+
color: #6366f1;
|
|
127
|
+
font-weight: 600;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.stage-tab .stage-amount {
|
|
131
|
+
font-size: 15px;
|
|
132
|
+
font-weight: 700;
|
|
133
|
+
color: #333;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.stage-tab .stage-count {
|
|
137
|
+
font-size: 11px;
|
|
138
|
+
color: #999;
|
|
139
|
+
display: inline;
|
|
140
|
+
margin-left: 4px;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.stage-tab .stage-sub {
|
|
144
|
+
font-size: 10px;
|
|
145
|
+
color: #999;
|
|
146
|
+
margin-top: 2px;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/* 筛选栏 */
|
|
150
|
+
.filter-bar {
|
|
151
|
+
background: #fff;
|
|
152
|
+
border-radius: 8px;
|
|
153
|
+
padding: 16px 20px;
|
|
154
|
+
display: flex;
|
|
155
|
+
gap: 24px;
|
|
156
|
+
align-items: center;
|
|
157
|
+
margin-bottom: 20px;
|
|
158
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.filter-group {
|
|
162
|
+
display: flex;
|
|
163
|
+
align-items: center;
|
|
164
|
+
gap: 8px;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.filter-group label {
|
|
168
|
+
font-size: 13px;
|
|
169
|
+
color: #666;
|
|
170
|
+
white-space: nowrap;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.filter-group select,
|
|
174
|
+
.filter-group input {
|
|
175
|
+
padding: 6px 12px;
|
|
176
|
+
border: 1px solid #ddd;
|
|
177
|
+
border-radius: 6px;
|
|
178
|
+
font-size: 13px;
|
|
179
|
+
background: #fff;
|
|
180
|
+
cursor: pointer;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.filter-group select:focus {
|
|
184
|
+
border-color: #6366f1;
|
|
185
|
+
outline: none;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.filter-btn {
|
|
189
|
+
padding: 6px 16px;
|
|
190
|
+
border-radius: 6px;
|
|
191
|
+
font-size: 13px;
|
|
192
|
+
cursor: pointer;
|
|
193
|
+
border: 1px solid #ddd;
|
|
194
|
+
background: #fff;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.filter-btn.active {
|
|
198
|
+
background: #6366f1;
|
|
199
|
+
color: #fff;
|
|
200
|
+
border-color: #6366f1;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/* 核心指标卡片 */
|
|
204
|
+
.kpi-cards {
|
|
205
|
+
display: flex;
|
|
206
|
+
gap: 16px;
|
|
207
|
+
margin-bottom: 20px;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.kpi-card {
|
|
211
|
+
flex: 1;
|
|
212
|
+
background: #fff;
|
|
213
|
+
border-radius: 8px;
|
|
214
|
+
padding: 16px 20px;
|
|
215
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.kpi-card .kpi-label {
|
|
219
|
+
font-size: 12px;
|
|
220
|
+
color: #888;
|
|
221
|
+
margin-bottom: 4px;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.kpi-card .kpi-value {
|
|
225
|
+
font-size: 22px;
|
|
226
|
+
font-weight: 700;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.kpi-card .kpi-sub {
|
|
230
|
+
font-size: 12px;
|
|
231
|
+
color: #999;
|
|
232
|
+
margin-top: 4px;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.kpi-card .kpi-sub.negative {
|
|
236
|
+
color: #ef4444;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.kpi-card .kpi-sub.positive {
|
|
240
|
+
color: #22c55e;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/* BI看板 */
|
|
244
|
+
.bi-chart {
|
|
245
|
+
background: #fff;
|
|
246
|
+
border-radius: 8px;
|
|
247
|
+
padding: 20px;
|
|
248
|
+
margin-bottom: 20px;
|
|
249
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.bi-chart h3 {
|
|
253
|
+
font-size: 14px;
|
|
254
|
+
font-weight: 600;
|
|
255
|
+
margin-bottom: 16px;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.stacked-bar-container {
|
|
259
|
+
position: relative;
|
|
260
|
+
height: 80px;
|
|
261
|
+
margin: 20px 0;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.stacked-bar {
|
|
265
|
+
display: flex;
|
|
266
|
+
height: 48px;
|
|
267
|
+
border-radius: 6px;
|
|
268
|
+
overflow: hidden;
|
|
269
|
+
position: relative;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
.bar-segment {
|
|
273
|
+
display: flex;
|
|
274
|
+
align-items: center;
|
|
275
|
+
justify-content: center;
|
|
276
|
+
color: #fff;
|
|
277
|
+
font-size: 11px;
|
|
278
|
+
font-weight: 600;
|
|
279
|
+
cursor: default;
|
|
280
|
+
transition: opacity 0.2s;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.bar-segment:hover {
|
|
284
|
+
opacity: 0.85;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.bar-closed {
|
|
288
|
+
background: #ef4444;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
.bar-commit {
|
|
292
|
+
background: #f59e0b;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.bar-bestcase {
|
|
296
|
+
background: #22c55e;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.bar-pipeline {
|
|
300
|
+
background: #3b82f6;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
@keyframes blink {
|
|
304
|
+
|
|
305
|
+
0%,
|
|
306
|
+
100% {
|
|
307
|
+
opacity: 1;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
50% {
|
|
311
|
+
opacity: 0.3;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/* 日期范围选择弹窗 */
|
|
316
|
+
.date-picker-overlay {
|
|
317
|
+
display: none;
|
|
318
|
+
position: fixed;
|
|
319
|
+
top: 0;
|
|
320
|
+
left: 0;
|
|
321
|
+
right: 0;
|
|
322
|
+
bottom: 0;
|
|
323
|
+
background: rgba(0, 0, 0, 0.3);
|
|
324
|
+
z-index: 300;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
.date-picker-modal {
|
|
328
|
+
position: absolute;
|
|
329
|
+
top: 50%;
|
|
330
|
+
left: 50%;
|
|
331
|
+
transform: translate(-50%, -50%);
|
|
332
|
+
background: #fff;
|
|
333
|
+
border-radius: 12px;
|
|
334
|
+
padding: 24px;
|
|
335
|
+
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);
|
|
336
|
+
width: 680px;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
.date-picker-header {
|
|
340
|
+
display: flex;
|
|
341
|
+
gap: 16px;
|
|
342
|
+
margin-bottom: 16px;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
.date-input-box {
|
|
346
|
+
flex: 1;
|
|
347
|
+
display: flex;
|
|
348
|
+
align-items: center;
|
|
349
|
+
justify-content: space-between;
|
|
350
|
+
border: 1px solid #ddd;
|
|
351
|
+
border-radius: 6px;
|
|
352
|
+
padding: 8px 12px;
|
|
353
|
+
font-size: 13px;
|
|
354
|
+
color: #999;
|
|
355
|
+
cursor: pointer;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
.date-input-box.has-value {
|
|
359
|
+
color: #333;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
.date-picker-body {
|
|
363
|
+
display: flex;
|
|
364
|
+
gap: 24px;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
.calendar {
|
|
368
|
+
flex: 1;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
.calendar-nav {
|
|
372
|
+
display: flex;
|
|
373
|
+
justify-content: space-between;
|
|
374
|
+
align-items: center;
|
|
375
|
+
margin-bottom: 12px;
|
|
376
|
+
font-size: 13px;
|
|
377
|
+
font-weight: 600;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
.calendar-nav span {
|
|
381
|
+
cursor: pointer;
|
|
382
|
+
color: #999;
|
|
383
|
+
padding: 0 4px;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
.calendar-nav span:hover {
|
|
387
|
+
color: #6366f1;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
.calendar-grid {
|
|
391
|
+
display: grid;
|
|
392
|
+
grid-template-columns: repeat(7, 1fr);
|
|
393
|
+
gap: 2px;
|
|
394
|
+
text-align: center;
|
|
395
|
+
font-size: 12px;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
.calendar-grid .day-header {
|
|
399
|
+
color: #999;
|
|
400
|
+
font-weight: 600;
|
|
401
|
+
padding: 6px 0;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
.calendar-grid .day {
|
|
405
|
+
padding: 6px 0;
|
|
406
|
+
cursor: pointer;
|
|
407
|
+
border-radius: 4px;
|
|
408
|
+
transition: all 0.15s;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
.calendar-grid .day:hover {
|
|
412
|
+
background: #f0f0ff;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
.calendar-grid .day.today {
|
|
416
|
+
border: 1px solid #6366f1;
|
|
417
|
+
color: #6366f1;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
.calendar-grid .day.selected {
|
|
421
|
+
background: #6366f1;
|
|
422
|
+
color: #fff;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
.calendar-grid .day.in-range {
|
|
426
|
+
background: #e8e8ff;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
.calendar-grid .day.other-month {
|
|
430
|
+
color: #ccc;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
.date-picker-footer {
|
|
434
|
+
display: flex;
|
|
435
|
+
justify-content: flex-end;
|
|
436
|
+
margin-top: 16px;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
.date-picker-footer button {
|
|
440
|
+
padding: 8px 24px;
|
|
441
|
+
border: none;
|
|
442
|
+
border-radius: 6px;
|
|
443
|
+
background: #6366f1;
|
|
444
|
+
color: #fff;
|
|
445
|
+
cursor: pointer;
|
|
446
|
+
font-size: 13px;
|
|
447
|
+
font-weight: 600;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
.date-picker-footer button:hover {
|
|
451
|
+
background: #4f46e5;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/* Owner选人选部门组件 */
|
|
455
|
+
.owner-picker-wrap {
|
|
456
|
+
position: relative;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
.owner-picker-trigger {
|
|
460
|
+
display: flex;
|
|
461
|
+
align-items: center;
|
|
462
|
+
gap: 6px;
|
|
463
|
+
padding: 6px 12px;
|
|
464
|
+
border: 1px solid #ddd;
|
|
465
|
+
border-radius: 6px;
|
|
466
|
+
font-size: 13px;
|
|
467
|
+
cursor: pointer;
|
|
468
|
+
background: #fff;
|
|
469
|
+
min-width: 140px;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
.owner-picker-trigger:hover {
|
|
473
|
+
border-color: #6366f1;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
.owner-selected-text {
|
|
477
|
+
flex: 1;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
.owner-arrow {
|
|
481
|
+
font-size: 10px;
|
|
482
|
+
color: #999;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
.owner-picker-dropdown {
|
|
486
|
+
display: none;
|
|
487
|
+
position: absolute;
|
|
488
|
+
top: 100%;
|
|
489
|
+
left: 0;
|
|
490
|
+
margin-top: 4px;
|
|
491
|
+
width: 260px;
|
|
492
|
+
background: #fff;
|
|
493
|
+
border-radius: 8px;
|
|
494
|
+
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.15);
|
|
495
|
+
z-index: 250;
|
|
496
|
+
padding: 8px 0;
|
|
497
|
+
max-height: 340px;
|
|
498
|
+
overflow-y: auto;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
.owner-picker-dropdown.open {
|
|
502
|
+
display: block;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
.owner-search {
|
|
506
|
+
width: calc(100% - 16px);
|
|
507
|
+
margin: 0 8px 8px;
|
|
508
|
+
padding: 8px 10px;
|
|
509
|
+
border: 1px solid #eee;
|
|
510
|
+
border-radius: 6px;
|
|
511
|
+
font-size: 12px;
|
|
512
|
+
outline: none;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
.owner-search:focus {
|
|
516
|
+
border-color: #6366f1;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
.owner-section-label {
|
|
520
|
+
padding: 6px 14px;
|
|
521
|
+
font-size: 11px;
|
|
522
|
+
color: #999;
|
|
523
|
+
font-weight: 600;
|
|
524
|
+
text-transform: uppercase;
|
|
525
|
+
letter-spacing: 0.5px;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
.owner-item {
|
|
529
|
+
display: flex;
|
|
530
|
+
align-items: center;
|
|
531
|
+
gap: 8px;
|
|
532
|
+
padding: 8px 14px;
|
|
533
|
+
font-size: 13px;
|
|
534
|
+
cursor: pointer;
|
|
535
|
+
transition: background 0.15s;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
.owner-item:hover {
|
|
539
|
+
background: #f5f5ff;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
.owner-item.selected {
|
|
543
|
+
background: #f0f0ff;
|
|
544
|
+
color: #6366f1;
|
|
545
|
+
font-weight: 600;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
.owner-icon {
|
|
549
|
+
font-size: 14px;
|
|
550
|
+
width: 20px;
|
|
551
|
+
text-align: center;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
.owner-check {
|
|
555
|
+
width: 16px;
|
|
556
|
+
height: 16px;
|
|
557
|
+
border: 1.5px solid #ccc;
|
|
558
|
+
border-radius: 3px;
|
|
559
|
+
display: flex;
|
|
560
|
+
align-items: center;
|
|
561
|
+
justify-content: center;
|
|
562
|
+
font-size: 11px;
|
|
563
|
+
color: transparent;
|
|
564
|
+
flex-shrink: 0;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
.owner-item.selected .owner-check {
|
|
568
|
+
background: #6366f1;
|
|
569
|
+
border-color: #6366f1;
|
|
570
|
+
color: #fff;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/* Changes Since 问号提示 */
|
|
574
|
+
.help-tip {
|
|
575
|
+
position: relative;
|
|
576
|
+
display: inline-block;
|
|
577
|
+
cursor: help;
|
|
578
|
+
color: #999;
|
|
579
|
+
font-size: 12px;
|
|
580
|
+
margin-left: 2px;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
.help-tip .help-tip-text {
|
|
584
|
+
display: none;
|
|
585
|
+
position: absolute;
|
|
586
|
+
bottom: calc(100% + 6px);
|
|
587
|
+
left: 50%;
|
|
588
|
+
transform: translateX(-50%);
|
|
589
|
+
background: #333;
|
|
590
|
+
color: #fff;
|
|
591
|
+
padding: 6px 10px;
|
|
592
|
+
border-radius: 6px;
|
|
593
|
+
font-size: 11px;
|
|
594
|
+
white-space: nowrap;
|
|
595
|
+
z-index: 100;
|
|
596
|
+
font-weight: 400;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
.help-tip .help-tip-text::after {
|
|
600
|
+
content: '';
|
|
601
|
+
position: absolute;
|
|
602
|
+
top: 100%;
|
|
603
|
+
left: 50%;
|
|
604
|
+
transform: translateX(-50%);
|
|
605
|
+
border: 5px solid transparent;
|
|
606
|
+
border-top-color: #333;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
.help-tip:hover .help-tip-text {
|
|
610
|
+
display: block;
|
|
611
|
+
}
|
|
612
|
+
</style>
|
|
613
|
+
</head>
|
|
614
|
+
|
|
615
|
+
<body>
|
|
616
|
+
<div class="container">
|
|
617
|
+
<h1>Seller Workspace</h1>
|
|
618
|
+
<p class="page-desc" id="pageDesc">Pipeline: Review your opportunity funnel and deal health</p>
|
|
619
|
+
|
|
620
|
+
<!-- Tab Bar -->
|
|
621
|
+
<div class="tab-bar">
|
|
622
|
+
<button class="tab-btn active" onclick="switchTab('pipeline')">Pipeline</button>
|
|
623
|
+
<button class="tab-btn" onclick="switchTab('forecast')">Forecast</button>
|
|
624
|
+
</div>
|
|
625
|
+
|
|
626
|
+
<!-- ========== Pipeline Tab ========== -->
|
|
627
|
+
<div id="pipelineTab" class="tab-panel active">
|
|
628
|
+
|
|
629
|
+
<!-- 筛选栏 (Pipeline) -->
|
|
630
|
+
<div class="filter-bar">
|
|
631
|
+
<div class="filter-group">
|
|
632
|
+
<label>Close Date</label>
|
|
633
|
+
<select id="pipelineCloseDate">
|
|
634
|
+
<option>This Week</option>
|
|
635
|
+
<option>This Month</option>
|
|
636
|
+
<option selected>This Quarter</option>
|
|
637
|
+
<option value="Custom">Custom</option>
|
|
638
|
+
</select>
|
|
639
|
+
<span id="pipelineCustomRange" style="display:none;font-size:12px;color:#6366f1;cursor:pointer;"
|
|
640
|
+
onclick="openDatePicker('pipeline')"></span>
|
|
641
|
+
</div>
|
|
642
|
+
<div class="filter-group">
|
|
643
|
+
<label>Opportunity Owner</label>
|
|
644
|
+
<div class="owner-picker-wrap" id="pipelineOwnerWrap">
|
|
645
|
+
<div class="owner-picker-trigger" onclick="toggleOwnerPicker('pipeline')">
|
|
646
|
+
<span class="owner-selected-text" id="pipelineOwnerText">Current User</span>
|
|
647
|
+
<span class="owner-arrow">▾</span>
|
|
648
|
+
</div>
|
|
649
|
+
<div class="owner-picker-dropdown" id="pipelineOwnerDropdown">
|
|
650
|
+
<input type="text" class="owner-search" placeholder="Search people or departments..."
|
|
651
|
+
oninput="filterOwnerList('pipeline',this.value)">
|
|
652
|
+
<div class="owner-section-label">People</div>
|
|
653
|
+
<div class="owner-item selected" data-name="Current User" onclick="toggleOwnerItem('pipeline',this)"><span
|
|
654
|
+
class="owner-check">✓</span><span class="owner-icon">👤</span>Current User</div>
|
|
655
|
+
<div class="owner-item" data-name="Alice" onclick="toggleOwnerItem('pipeline',this)"><span
|
|
656
|
+
class="owner-check"></span><span class="owner-icon">👤</span>Alice</div>
|
|
657
|
+
<div class="owner-item" data-name="Steve" onclick="toggleOwnerItem('pipeline',this)"><span
|
|
658
|
+
class="owner-check"></span><span class="owner-icon">👤</span>Steve</div>
|
|
659
|
+
<div class="owner-item" data-name="Chloe" onclick="toggleOwnerItem('pipeline',this)"><span
|
|
660
|
+
class="owner-check"></span><span class="owner-icon">👤</span>Chloe</div>
|
|
661
|
+
<div class="owner-section-label">Departments</div>
|
|
662
|
+
<div class="owner-item" data-name="Sales Dept" onclick="toggleOwnerItem('pipeline',this)"><span
|
|
663
|
+
class="owner-check"></span><span class="owner-icon">🏢</span>Sales Dept</div>
|
|
664
|
+
<div class="owner-item" data-name="Enterprise Team" onclick="toggleOwnerItem('pipeline',this)"><span
|
|
665
|
+
class="owner-check"></span><span class="owner-icon">🏢</span>Enterprise Team</div>
|
|
666
|
+
</div>
|
|
667
|
+
</div>
|
|
668
|
+
</div>
|
|
669
|
+
<div class="filter-group">
|
|
670
|
+
<label>Business Type</label>
|
|
671
|
+
<select>
|
|
672
|
+
<option>New Business</option>
|
|
673
|
+
<option>Renewal</option>
|
|
674
|
+
<option>Expansion</option>
|
|
675
|
+
</select>
|
|
676
|
+
</div>
|
|
677
|
+
<div class="filter-group">
|
|
678
|
+
<label>Changes Since <span class="help-tip">?<span class="help-tip-text">Sets the baseline date for tracking
|
|
679
|
+
changes in close date, amount, stage, etc.</span></span></label>
|
|
680
|
+
<select id="pipelineChangesSince">
|
|
681
|
+
<option>Start of the Period</option>
|
|
682
|
+
<option value="Custom">Custom</option>
|
|
683
|
+
</select>
|
|
684
|
+
<span id="pipelineChangesSinceDate" style="display:none;font-size:12px;color:#6366f1;cursor:pointer;"
|
|
685
|
+
onclick="openSingleDatePicker('pipeline')"></span>
|
|
686
|
+
</div>
|
|
687
|
+
</div>
|
|
688
|
+
|
|
689
|
+
<!-- Pipeline 看板区:漏斗图 + 阶段停留时间 -->
|
|
690
|
+
<div style="display:flex;gap:20px;margin-bottom:20px;">
|
|
691
|
+
|
|
692
|
+
<!-- 商机漏斗 -->
|
|
693
|
+
<div style="flex:1;background:#fff;border-radius:8px;padding:20px;box-shadow:0 1px 3px rgba(0,0,0,0.08);">
|
|
694
|
+
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;">
|
|
695
|
+
<h3 style="font-size:14px;font-weight:600;">Pipeline Funnel</h3>
|
|
696
|
+
<span onclick="document.getElementById('funnelAiModal').style.display='flex'"
|
|
697
|
+
style="cursor:pointer;font-size:16px;" title="AI Analysis">✨</span>
|
|
698
|
+
</div>
|
|
699
|
+
<!-- 顶部销售金额 -->
|
|
700
|
+
<div style="text-align:center;margin-bottom:16px;">
|
|
701
|
+
<div style="font-size:12px;color:#888;">Sales Amount</div>
|
|
702
|
+
<div style="font-size:22px;font-weight:700;">$11.1M</div>
|
|
703
|
+
</div>
|
|
704
|
+
<!-- 漏斗主体:左转化率 + 中漏斗 + 右金额数量 -->
|
|
705
|
+
<div style="display:flex;align-items:stretch;gap:0;">
|
|
706
|
+
<!-- 左侧转化率 -->
|
|
707
|
+
<div
|
|
708
|
+
style="width:100px;display:flex;flex-direction:column;justify-content:center;gap:0;font-size:11px;color:#888;">
|
|
709
|
+
<div style="height:48px;display:flex;align-items:center;">—</div>
|
|
710
|
+
<div style="height:48px;display:flex;align-items:center;">56.3%</div>
|
|
711
|
+
<div style="height:48px;display:flex;align-items:center;">28.1%</div>
|
|
712
|
+
<div style="height:48px;display:flex;align-items:center;">10.0%</div>
|
|
713
|
+
</div>
|
|
714
|
+
<!-- 中间漏斗 -->
|
|
715
|
+
<div style="flex:1;display:flex;flex-direction:column;align-items:center;">
|
|
716
|
+
<div
|
|
717
|
+
style="width:100%;height:48px;background:#3b82f6;clip-path:polygon(5% 0%,95% 0%,85% 100%,15% 100%);display:flex;align-items:center;justify-content:center;color:#fff;font-size:12px;font-weight:600;cursor:pointer;"
|
|
718
|
+
onclick="selectStageByName('Prospecting')">Prospecting</div>
|
|
719
|
+
<div
|
|
720
|
+
style="width:100%;height:48px;background:#22c55e;clip-path:polygon(15% 0%,85% 0%,75% 100%,25% 100%);display:flex;align-items:center;justify-content:center;color:#fff;font-size:12px;font-weight:600;cursor:pointer;"
|
|
721
|
+
onclick="selectStageByName('Needs Analysis')">Needs Analysis</div>
|
|
722
|
+
<div
|
|
723
|
+
style="width:100%;height:48px;background:#f59e0b;clip-path:polygon(25% 0%,75% 0%,65% 100%,35% 100%);display:flex;align-items:center;justify-content:center;color:#fff;font-size:12px;font-weight:600;cursor:pointer;"
|
|
724
|
+
onclick="selectStageByName('Proposal/Price Quote')">Proposal</div>
|
|
725
|
+
<div
|
|
726
|
+
style="width:100%;height:48px;background:#8b5cf6;clip-path:polygon(35% 0%,65% 0%,58% 100%,42% 100%);display:flex;align-items:center;justify-content:center;color:#fff;font-size:12px;font-weight:600;cursor:pointer;"
|
|
727
|
+
onclick="selectStageByName('Closed Won')">Closed Won</div>
|
|
728
|
+
</div>
|
|
729
|
+
<!-- 右侧金额/数量 -->
|
|
730
|
+
<div
|
|
731
|
+
style="width:110px;display:flex;flex-direction:column;justify-content:center;gap:0;font-size:11px;color:#555;text-align:right;">
|
|
732
|
+
<div style="height:48px;display:flex;align-items:center;justify-content:flex-end;">$3.2M / 15</div>
|
|
733
|
+
<div style="height:48px;display:flex;align-items:center;justify-content:flex-end;">$1.8M / 7</div>
|
|
734
|
+
<div style="height:48px;display:flex;align-items:center;justify-content:flex-end;">$4.5M / 8</div>
|
|
735
|
+
<div style="height:48px;display:flex;align-items:center;justify-content:flex-end;">$1.6M / 3</div>
|
|
736
|
+
</div>
|
|
737
|
+
</div>
|
|
738
|
+
<!-- 底部图例 -->
|
|
739
|
+
<div
|
|
740
|
+
style="display:flex;gap:12px;justify-content:center;flex-wrap:wrap;margin-top:16px;font-size:11px;color:#666;">
|
|
741
|
+
<span><span
|
|
742
|
+
style="display:inline-block;width:10px;height:10px;background:#3b82f6;border-radius:50%;margin-right:4px;vertical-align:middle;"></span>Prospecting</span>
|
|
743
|
+
<span><span
|
|
744
|
+
style="display:inline-block;width:10px;height:10px;background:#22c55e;border-radius:50%;margin-right:4px;vertical-align:middle;"></span>Needs
|
|
745
|
+
Analysis</span>
|
|
746
|
+
<span><span
|
|
747
|
+
style="display:inline-block;width:10px;height:10px;background:#f59e0b;border-radius:50%;margin-right:4px;vertical-align:middle;"></span>Proposal</span>
|
|
748
|
+
<span><span
|
|
749
|
+
style="display:inline-block;width:10px;height:10px;background:#8b5cf6;border-radius:50%;margin-right:4px;vertical-align:middle;"></span>Closed
|
|
750
|
+
Won</span>
|
|
751
|
+
</div>
|
|
752
|
+
</div>
|
|
753
|
+
|
|
754
|
+
<!-- 阶段平均停留时间 -->
|
|
755
|
+
<div
|
|
756
|
+
style="flex:1;background:#fff;border-radius:8px;padding:20px;box-shadow:0 1px 3px rgba(0,0,0,0.08);display:flex;flex-direction:column;">
|
|
757
|
+
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:4px;">
|
|
758
|
+
<h3 style="font-size:14px;font-weight:600;">Avg. Time in Stage</h3>
|
|
759
|
+
<span onclick="document.getElementById('stageTimeAiModal').style.display='flex'"
|
|
760
|
+
style="cursor:pointer;font-size:16px;" title="AI Analysis">✨</span>
|
|
761
|
+
</div>
|
|
762
|
+
<div style="display:flex;gap:16px;font-size:11px;color:#888;margin-bottom:16px;">
|
|
763
|
+
<span><span
|
|
764
|
+
style="display:inline-block;width:12px;height:8px;background:#22c55e;border-radius:2px;margin-right:4px;vertical-align:middle;"></span>Actual</span>
|
|
765
|
+
<span><span
|
|
766
|
+
style="display:inline-block;width:0;height:0;border-left:4px solid transparent;border-right:4px solid transparent;border-top:6px solid #6366f1;margin-right:4px;vertical-align:middle;"></span>Target</span>
|
|
767
|
+
<span><span
|
|
768
|
+
style="display:inline-block;width:0;height:0;border-left:4px solid transparent;border-right:4px solid transparent;border-top:6px solid #ef4444;margin-right:4px;vertical-align:middle;"></span>Limit</span>
|
|
769
|
+
</div>
|
|
770
|
+
<div style="display:flex;flex-direction:column;gap:24px;flex:1;">
|
|
771
|
+
<!-- Prospecting -->
|
|
772
|
+
<div style="cursor:pointer;" onclick="selectStageByName('Prospecting')">
|
|
773
|
+
<div style="display:flex;justify-content:space-between;font-size:12px;margin-bottom:4px;">
|
|
774
|
+
<span>Prospecting</span><span style="font-weight:600;">8天6时</span></div>
|
|
775
|
+
<div style="position:relative;height:28px;background:#eee;border-radius:3px;">
|
|
776
|
+
<div style="height:100%;width:28%;background:#22c55e;border-radius:4px;"></div>
|
|
777
|
+
<div
|
|
778
|
+
style="position:absolute;left:33%;top:-3px;width:0;height:0;border-left:5px solid transparent;border-right:5px solid transparent;border-top:8px solid #6366f1;margin-left:-5px;">
|
|
779
|
+
</div>
|
|
780
|
+
<div
|
|
781
|
+
style="position:absolute;left:50%;top:-3px;width:0;height:0;border-left:5px solid transparent;border-right:5px solid transparent;border-top:8px solid #ef4444;margin-left:-5px;">
|
|
782
|
+
</div>
|
|
783
|
+
</div>
|
|
784
|
+
</div>
|
|
785
|
+
<!-- Needs Analysis -->
|
|
786
|
+
<div style="cursor:pointer;" onclick="selectStageByName('Needs Analysis')">
|
|
787
|
+
<div style="display:flex;justify-content:space-between;font-size:12px;margin-bottom:4px;"><span>Needs
|
|
788
|
+
Analysis</span><span style="font-weight:600;color:#eab308;">15天12时</span></div>
|
|
789
|
+
<div style="position:relative;height:28px;background:#eee;border-radius:3px;">
|
|
790
|
+
<div style="height:100%;width:52%;background:#eab308;border-radius:4px;"></div>
|
|
791
|
+
<div
|
|
792
|
+
style="position:absolute;left:40%;top:-3px;width:0;height:0;border-left:5px solid transparent;border-right:5px solid transparent;border-top:8px solid #6366f1;margin-left:-5px;">
|
|
793
|
+
</div>
|
|
794
|
+
<div
|
|
795
|
+
style="position:absolute;left:60%;top:-3px;width:0;height:0;border-left:5px solid transparent;border-right:5px solid transparent;border-top:8px solid #ef4444;margin-left:-5px;">
|
|
796
|
+
</div>
|
|
797
|
+
</div>
|
|
798
|
+
</div>
|
|
799
|
+
<!-- Proposal -->
|
|
800
|
+
<div style="cursor:pointer;" onclick="selectStageByName('Proposal/Price Quote')">
|
|
801
|
+
<div style="display:flex;justify-content:space-between;font-size:12px;margin-bottom:4px;">
|
|
802
|
+
<span>Proposal/Price Quote</span><span style="font-weight:600;color:#ef4444;">22天8时</span></div>
|
|
803
|
+
<div style="position:relative;height:28px;background:#eee;border-radius:3px;">
|
|
804
|
+
<div style="height:100%;width:74%;background:#ef4444;border-radius:4px;"></div>
|
|
805
|
+
<div
|
|
806
|
+
style="position:absolute;left:50%;top:-3px;width:0;height:0;border-left:5px solid transparent;border-right:5px solid transparent;border-top:8px solid #6366f1;margin-left:-5px;">
|
|
807
|
+
</div>
|
|
808
|
+
<div
|
|
809
|
+
style="position:absolute;left:67%;top:-3px;width:0;height:0;border-left:5px solid transparent;border-right:5px solid transparent;border-top:8px solid #ef4444;margin-left:-5px;">
|
|
810
|
+
</div>
|
|
811
|
+
</div>
|
|
812
|
+
</div>
|
|
813
|
+
</div>
|
|
814
|
+
</div>
|
|
815
|
+
|
|
816
|
+
</div><!-- 阶段切换区 -->
|
|
817
|
+
<div class="stage-tabs">
|
|
818
|
+
<div class="stage-tab active" onclick="switchStage(this,'Prospecting')">
|
|
819
|
+
<div class="stage-name">Prospecting</div>
|
|
820
|
+
<div><span class="stage-amount">$3.2M</span><span class="stage-count">(15)</span></div>
|
|
821
|
+
<div class="stage-sub"><span style="color:#16a34a;font-weight:700;">↑</span> $280K <span
|
|
822
|
+
style="color:#16a34a;font-weight:700;">↑</span> 3</div>
|
|
823
|
+
</div>
|
|
824
|
+
<div class="stage-tab" onclick="switchStage(this,'Needs Analysis')">
|
|
825
|
+
<div class="stage-name">Needs Analysis</div>
|
|
826
|
+
<div><span class="stage-amount">$1.8M</span><span class="stage-count">(7)</span></div>
|
|
827
|
+
<div class="stage-sub"><span style="color:#dc2626;font-weight:700;">↓</span> $320K <span
|
|
828
|
+
style="color:#dc2626;font-weight:700;">↓</span> 2</div>
|
|
829
|
+
</div>
|
|
830
|
+
<div class="stage-tab" onclick="switchStage(this,'Proposal/Price Quote')">
|
|
831
|
+
<div class="stage-name">Proposal/Price Quote</div>
|
|
832
|
+
<div><span class="stage-amount">$4.5M</span><span class="stage-count">(8)</span></div>
|
|
833
|
+
<div class="stage-sub"><span style="color:#16a34a;font-weight:700;">↑</span> $500K <span
|
|
834
|
+
style="color:#16a34a;font-weight:700;">↑</span> 3</div>
|
|
835
|
+
</div>
|
|
836
|
+
<div class="stage-tab" onclick="switchStage(this,'Closed Won')">
|
|
837
|
+
<div class="stage-name">Closed Won</div>
|
|
838
|
+
<div><span class="stage-amount">$1.6M</span><span class="stage-count">(3)</span></div>
|
|
839
|
+
<div class="stage-sub"><span style="color:#16a34a;font-weight:700;">↑</span> $400K <span
|
|
840
|
+
style="color:#16a34a;font-weight:700;">↑</span> 1</div>
|
|
841
|
+
</div>
|
|
842
|
+
<div class="stage-tab" onclick="switchStage(this,'Closed Lost')">
|
|
843
|
+
<div class="stage-name">Closed Lost</div>
|
|
844
|
+
<div><span class="stage-amount">$0.9M</span><span class="stage-count">(4)</span></div>
|
|
845
|
+
<div class="stage-sub"><span style="color:#dc2626;font-weight:700;">↓</span> $200K <span
|
|
846
|
+
style="color:#16a34a;font-weight:700;">↑</span> 2</div>
|
|
847
|
+
</div>
|
|
848
|
+
</div>
|
|
849
|
+
|
|
850
|
+
<!-- Pipeline 商机列表 -->
|
|
851
|
+
<div
|
|
852
|
+
style="background:#fff;border-radius:8px;padding:20px;box-shadow:0 1px 3px rgba(0,0,0,0.08);overflow-x:auto;">
|
|
853
|
+
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:4px;">
|
|
854
|
+
<h3 id="pipelineListTitle" style="font-size:14px;font-weight:600;">Opportunity List — Open</h3>
|
|
855
|
+
<div style="font-size:13px;color:#666;">Total: <span style="font-weight:700;">$18,500,000</span> · 12
|
|
856
|
+
opportunities</div>
|
|
857
|
+
</div>
|
|
858
|
+
<div id="pipelineSortFilterBar" style="font-size:12px;color:#888;margin-bottom:12px;">Sorted by Amount ·
|
|
859
|
+
Filtered by Close Date, Current User, Open Pipeline</div>
|
|
860
|
+
<table style="width:100%;border-collapse:collapse;font-size:13px;">
|
|
861
|
+
<thead>
|
|
862
|
+
<tr style="background:#f8f9fa;border-bottom:2px solid #e5e7eb;">
|
|
863
|
+
<th style="padding:10px 12px;text-align:left;user-select:none;">Opportunity Name <span
|
|
864
|
+
onclick="togglePipelineSort('name')" style="color:#ccc;font-size:10px;cursor:pointer;">⇅</span> <span
|
|
865
|
+
onclick="togglePipelineFilter('name')" style="color:#ccc;font-size:10px;cursor:pointer;">▽</span></th>
|
|
866
|
+
<th style="padding:10px 12px;text-align:right;user-select:none;">Amount <span
|
|
867
|
+
onclick="togglePipelineSort('amount')" style="color:#6366f1;font-size:10px;cursor:pointer;">↓</span>
|
|
868
|
+
<span onclick="togglePipelineFilter('amount')"
|
|
869
|
+
style="color:#ccc;font-size:10px;cursor:pointer;">▽</span></th>
|
|
870
|
+
<th style="padding:10px 12px;text-align:center;user-select:none;">Close Date <span
|
|
871
|
+
onclick="togglePipelineSort('closeDate')" style="color:#ccc;font-size:10px;cursor:pointer;">⇅</span>
|
|
872
|
+
<span onclick="togglePipelineFilter('closeDate')"
|
|
873
|
+
style="color:#ccc;font-size:10px;cursor:pointer;">▽</span></th>
|
|
874
|
+
<th style="padding:10px 12px;text-align:center;user-select:none;">Stage <span
|
|
875
|
+
onclick="togglePipelineSort('stage')" style="color:#ccc;font-size:10px;cursor:pointer;">⇅</span> <span
|
|
876
|
+
onclick="togglePipelineFilter('stage')" style="color:#ccc;font-size:10px;cursor:pointer;">▽</span>
|
|
877
|
+
</th>
|
|
878
|
+
<th style="padding:10px 12px;text-align:center;user-select:none;">✨ AI Score <span
|
|
879
|
+
onclick="togglePipelineSort('score')" style="color:#ccc;font-size:10px;cursor:pointer;">⇅</span> <span
|
|
880
|
+
onclick="togglePipelineFilter('score')" style="color:#ccc;font-size:10px;cursor:pointer;">▽</span>
|
|
881
|
+
</th>
|
|
882
|
+
<th style="padding:10px 12px;text-align:center;user-select:none;">✨ AI Win Rate <span
|
|
883
|
+
onclick="togglePipelineSort('winRate')" style="color:#ccc;font-size:10px;cursor:pointer;">⇅</span>
|
|
884
|
+
<span onclick="togglePipelineFilter('winRate')"
|
|
885
|
+
style="color:#ccc;font-size:10px;cursor:pointer;">▽</span></th>
|
|
886
|
+
<th style="padding:10px 12px;text-align:center;user-select:none;">Last Activity <span
|
|
887
|
+
onclick="togglePipelineSort('lastAct')" style="color:#ccc;font-size:10px;cursor:pointer;">⇅</span>
|
|
888
|
+
<span onclick="togglePipelineFilter('lastAct')"
|
|
889
|
+
style="color:#ccc;font-size:10px;cursor:pointer;">▽</span></th>
|
|
890
|
+
</tr>
|
|
891
|
+
</thead>
|
|
892
|
+
<tbody id="pipelineOppBody"></tbody>
|
|
893
|
+
</table>
|
|
894
|
+
</div>
|
|
895
|
+
|
|
896
|
+
</div><!-- end Pipeline Tab -->
|
|
897
|
+
|
|
898
|
+
<!-- ========== Forecast Tab ========== -->
|
|
899
|
+
<div id="forecastTab" class="tab-panel">
|
|
900
|
+
|
|
901
|
+
<!-- M1.1 全局筛选栏 -->
|
|
902
|
+
<div class="filter-bar">
|
|
903
|
+
<div class="filter-group">
|
|
904
|
+
<label>Close Date</label>
|
|
905
|
+
<select id="forecastCloseDate">
|
|
906
|
+
<option>This Week</option>
|
|
907
|
+
<option>This Month</option>
|
|
908
|
+
<option selected>This Quarter</option>
|
|
909
|
+
<option value="Custom">Custom</option>
|
|
910
|
+
</select>
|
|
911
|
+
<span id="forecastCustomRange" style="display:none;font-size:12px;color:#6366f1;cursor:pointer;"
|
|
912
|
+
onclick="openDatePicker('forecast')"></span>
|
|
913
|
+
</div>
|
|
914
|
+
<div class="filter-group">
|
|
915
|
+
<label>Opportunity Owner</label>
|
|
916
|
+
<div class="owner-picker-wrap" id="forecastOwnerWrap">
|
|
917
|
+
<div class="owner-picker-trigger" onclick="toggleOwnerPicker('forecast')">
|
|
918
|
+
<span class="owner-selected-text" id="forecastOwnerText">Current User</span>
|
|
919
|
+
<span class="owner-arrow">▾</span>
|
|
920
|
+
</div>
|
|
921
|
+
<div class="owner-picker-dropdown" id="forecastOwnerDropdown">
|
|
922
|
+
<input type="text" class="owner-search" placeholder="Search people or departments..."
|
|
923
|
+
oninput="filterOwnerList('forecast',this.value)">
|
|
924
|
+
<div class="owner-section-label">People</div>
|
|
925
|
+
<div class="owner-item selected" data-name="Current User" onclick="toggleOwnerItem('forecast',this)"><span
|
|
926
|
+
class="owner-check">✓</span><span class="owner-icon">👤</span>Current User</div>
|
|
927
|
+
<div class="owner-item" data-name="Alice" onclick="toggleOwnerItem('forecast',this)"><span
|
|
928
|
+
class="owner-check"></span><span class="owner-icon">👤</span>Alice</div>
|
|
929
|
+
<div class="owner-item" data-name="Steve" onclick="toggleOwnerItem('forecast',this)"><span
|
|
930
|
+
class="owner-check"></span><span class="owner-icon">👤</span>Steve</div>
|
|
931
|
+
<div class="owner-item" data-name="Chloe" onclick="toggleOwnerItem('forecast',this)"><span
|
|
932
|
+
class="owner-check"></span><span class="owner-icon">👤</span>Chloe</div>
|
|
933
|
+
<div class="owner-section-label">Departments</div>
|
|
934
|
+
<div class="owner-item" data-name="Sales Dept" onclick="toggleOwnerItem('forecast',this)"><span
|
|
935
|
+
class="owner-check"></span><span class="owner-icon">🏢</span>Sales Dept</div>
|
|
936
|
+
<div class="owner-item" data-name="Enterprise Team" onclick="toggleOwnerItem('forecast',this)"><span
|
|
937
|
+
class="owner-check"></span><span class="owner-icon">🏢</span>Enterprise Team</div>
|
|
938
|
+
</div>
|
|
939
|
+
</div>
|
|
940
|
+
</div>
|
|
941
|
+
<div class="filter-group">
|
|
942
|
+
<label>Business Type</label>
|
|
943
|
+
<select>
|
|
944
|
+
<option>New Business</option>
|
|
945
|
+
<option>Renewal</option>
|
|
946
|
+
<option>Expansion</option>
|
|
947
|
+
</select>
|
|
948
|
+
</div>
|
|
949
|
+
<div class="filter-group">
|
|
950
|
+
<label>Changes Since <span class="help-tip">?<span class="help-tip-text">Sets the baseline date for tracking
|
|
951
|
+
changes in close date, amount, stage, etc.</span></span></label>
|
|
952
|
+
<select id="forecastChangesSince">
|
|
953
|
+
<option>Start of the Period</option>
|
|
954
|
+
<option value="Custom">Custom</option>
|
|
955
|
+
</select>
|
|
956
|
+
<span id="forecastChangesSinceDate" style="display:none;font-size:12px;color:#6366f1;cursor:pointer;"
|
|
957
|
+
onclick="openSingleDatePicker('forecast')"></span>
|
|
958
|
+
</div>
|
|
959
|
+
</div>
|
|
960
|
+
|
|
961
|
+
<!-- KPI + BI + Gap Closer 一行布局 -->
|
|
962
|
+
<div style="display:flex;gap:12px;margin-bottom:16px;align-items:stretch;">
|
|
963
|
+
|
|
964
|
+
<!-- M1.2 核心指标卡片 - 左侧,2x2网格 -->
|
|
965
|
+
<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px;flex:1;min-width:0;">
|
|
966
|
+
<div class="kpi-card" style="padding:10px 12px;">
|
|
967
|
+
<div class="kpi-label">Quota</div>
|
|
968
|
+
<div class="kpi-value" style="font-size:16px;">$10,000,000</div>
|
|
969
|
+
</div>
|
|
970
|
+
<div class="kpi-card" style="padding:10px 12px;">
|
|
971
|
+
<div class="kpi-label">Closed</div>
|
|
972
|
+
<div class="kpi-value" style="font-size:16px;">$3,200,000</div>
|
|
973
|
+
<div class="kpi-sub negative">Gap: $6,800,000</div>
|
|
974
|
+
</div>
|
|
975
|
+
<div class="kpi-card" style="padding:10px 12px;">
|
|
976
|
+
<div class="kpi-label">Forecast</div>
|
|
977
|
+
<div class="kpi-value" style="font-size:16px;">$7,500,000</div>
|
|
978
|
+
<div class="kpi-sub negative">Gap: $2,500,000</div>
|
|
979
|
+
</div>
|
|
980
|
+
<div class="kpi-card" style="padding:10px 12px;">
|
|
981
|
+
<div class="kpi-label">✨ AI Forecast</div>
|
|
982
|
+
<div class="kpi-value" style="font-size:16px;">$6,800,000</div>
|
|
983
|
+
<div class="kpi-sub negative">Gap: $3,200,000</div>
|
|
984
|
+
</div>
|
|
985
|
+
</div>
|
|
986
|
+
|
|
987
|
+
<!-- M1.3 BI看板 - 中间 -->
|
|
988
|
+
<div class="bi-chart" style="flex:2;min-width:0;margin-bottom:0;padding:14px;">
|
|
989
|
+
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;">
|
|
990
|
+
<h3 style="font-size:14px;font-weight:600;margin:0;">Forecast Overview</h3>
|
|
991
|
+
<span onclick="document.getElementById('forecastAiModal').style.display='flex'"
|
|
992
|
+
style="cursor:pointer;font-size:16px;" title="AI Analysis">✨</span>
|
|
993
|
+
</div>
|
|
994
|
+
<div style="display:flex;gap:16px;flex:1;align-items:flex-end;">
|
|
995
|
+
<div style="flex:1;position:relative;height:220px;padding:16px 16px 40px 50px;">
|
|
996
|
+
<!-- 纵轴刻度 -->
|
|
997
|
+
<div
|
|
998
|
+
style="position:absolute;left:0;top:20px;bottom:50px;width:50px;display:flex;flex-direction:column;justify-content:space-between;align-items:flex-end;padding-right:8px;font-size:11px;color:#999;">
|
|
999
|
+
<span>$10M</span><span>$8M</span><span>$6M</span><span>$4M</span><span>$2M</span><span>$0</span>
|
|
1000
|
+
</div>
|
|
1001
|
+
<!-- 网格线 -->
|
|
1002
|
+
<div style="position:absolute;left:55px;right:20px;top:20px;bottom:50px;">
|
|
1003
|
+
<div style="position:absolute;top:0%;width:100%;border-top:1px solid #eee;"></div>
|
|
1004
|
+
<div style="position:absolute;top:20%;width:100%;border-top:1px solid #eee;"></div>
|
|
1005
|
+
<div style="position:absolute;top:40%;width:100%;border-top:1px solid #eee;"></div>
|
|
1006
|
+
<div style="position:absolute;top:60%;width:100%;border-top:1px solid #eee;"></div>
|
|
1007
|
+
<div style="position:absolute;top:80%;width:100%;border-top:1px solid #eee;"></div>
|
|
1008
|
+
<div style="position:absolute;top:100%;width:100%;border-top:1px solid #ccc;"></div>
|
|
1009
|
+
</div>
|
|
1010
|
+
<!-- Quota基准线 -->
|
|
1011
|
+
<div style="position:absolute;left:55px;right:20px;top:20px;border-top:2.5px solid #22c55e;z-index:5;">
|
|
1012
|
+
<span
|
|
1013
|
+
style="position:absolute;top:-18px;left:0;font-size:11px;background:#22c55e;color:#fff;padding:1px 8px;border-radius:3px;">Quota:
|
|
1014
|
+
$10M</span>
|
|
1015
|
+
</div>
|
|
1016
|
+
<!-- Forecast基准线 -->
|
|
1017
|
+
<div style="position:absolute;left:55px;right:20px;top:68px;border-top:2px solid #8b5cf6;z-index:5;">
|
|
1018
|
+
<span style="position:absolute;top:-16px;right:0;font-size:10px;color:#8b5cf6;">Forecast $7.5M</span>
|
|
1019
|
+
</div>
|
|
1020
|
+
<!-- AI基准线 -->
|
|
1021
|
+
<div style="position:absolute;left:55px;right:20px;top:81px;border-top:2px dashed #a78bfa;z-index:5;">
|
|
1022
|
+
<span style="position:absolute;top:-16px;right:80px;font-size:10px;color:#a78bfa;">✨ AI $6.8M</span>
|
|
1023
|
+
</div>
|
|
1024
|
+
<!-- 柱子区域 -->
|
|
1025
|
+
<div
|
|
1026
|
+
style="position:absolute;left:55px;right:20px;top:20px;bottom:50px;display:flex;align-items:flex-end;gap:0;">
|
|
1027
|
+
<div style="flex:0 0 60px;display:flex;flex-direction:column;align-items:center;">
|
|
1028
|
+
<span style="font-size:11px;font-weight:600;color:#3b82f6;margin-bottom:4px;">$3.2M</span>
|
|
1029
|
+
<div style="width:50px;height:61px;background:#3b82f6;border-radius:4px 4px 0 0;"></div>
|
|
1030
|
+
<span style="font-size:10px;color:#666;margin-top:6px;">Closed</span>
|
|
1031
|
+
</div>
|
|
1032
|
+
<div style="flex:0 0 60px;display:flex;flex-direction:column;align-items:center;">
|
|
1033
|
+
<span style="font-size:11px;font-weight:600;color:#22c55e;margin-bottom:4px;">$2.5M</span>
|
|
1034
|
+
<div style="width:50px;margin-bottom:61px;">
|
|
1035
|
+
<div style="width:50px;height:47px;background:#22c55e;border-radius:4px 4px 0 0;"></div>
|
|
1036
|
+
</div>
|
|
1037
|
+
<span style="font-size:10px;color:#666;margin-top:6px;">Commit</span>
|
|
1038
|
+
</div>
|
|
1039
|
+
<div style="flex:0 0 60px;display:flex;flex-direction:column;align-items:center;">
|
|
1040
|
+
<span style="font-size:11px;font-weight:600;color:#10b981;margin-bottom:4px;">$1.8M</span>
|
|
1041
|
+
<div style="width:50px;margin-bottom:108px;">
|
|
1042
|
+
<div style="width:50px;height:34px;background:#10b981;border-radius:4px 4px 0 0;"></div>
|
|
1043
|
+
</div>
|
|
1044
|
+
<span style="font-size:10px;color:#666;margin-top:6px;">Best Case</span>
|
|
1045
|
+
</div>
|
|
1046
|
+
<div style="flex:0 0 60px;display:flex;flex-direction:column;align-items:center;">
|
|
1047
|
+
<span style="font-size:11px;font-weight:600;color:#93c5fd;margin-bottom:4px;">$2.5M</span>
|
|
1048
|
+
<div style="width:50px;margin-bottom:143px;">
|
|
1049
|
+
<div style="width:50px;height:47px;background:#93c5fd;border-radius:4px 4px 0 0;"></div>
|
|
1050
|
+
</div>
|
|
1051
|
+
<span style="font-size:10px;color:#666;margin-top:6px;">Pipeline</span>
|
|
1052
|
+
</div>
|
|
1053
|
+
<div style="flex:0 0 20px;"></div>
|
|
1054
|
+
<div style="flex:0 0 60px;display:flex;flex-direction:column;align-items:center;">
|
|
1055
|
+
<span style="font-size:11px;font-weight:700;color:#6366f1;margin-bottom:4px;">$10M</span>
|
|
1056
|
+
<div
|
|
1057
|
+
style="width:50px;height:190px;background:linear-gradient(to top,#3b82f6 32%,#22c55e 32%,#22c55e 57%,#10b981 57%,#10b981 75%,#93c5fd 75%);border-radius:4px 4px 0 0;opacity:0.4;">
|
|
1058
|
+
</div>
|
|
1059
|
+
<span style="font-size:10px;color:#666;margin-top:6px;font-weight:600;">Total</span>
|
|
1060
|
+
</div>
|
|
1061
|
+
</div>
|
|
1062
|
+
</div>
|
|
1063
|
+
</div>
|
|
1064
|
+
</div>
|
|
1065
|
+
|
|
1066
|
+
<!-- M1.4 AI Gap Closer - 右侧,两张卡片上下排列 -->
|
|
1067
|
+
<div style="flex:1;min-width:0;display:flex;flex-direction:column;gap:10px;">
|
|
1068
|
+
<!-- Rescue Commit 卡片 -->
|
|
1069
|
+
<div style="background:linear-gradient(135deg,#fff7ed,#ffedd5);border-radius:8px;padding:12px 14px;box-shadow:0 1px 3px rgba(0,0,0,0.08);border-left:3px solid #f59e0b;flex:1;display:flex;flex-direction:column;justify-content:center;">
|
|
1070
|
+
<div style="font-size:13px;font-weight:600;color:#92400e;margin-bottom:6px;">⚠ Rescue Commit</div>
|
|
1071
|
+
<div style="margin-bottom:6px;"><span style="font-size:20px;font-weight:700;color:#333;">$1,200,000</span> <span style="font-size:11px;color:#888;">· 3 deals at risk</span></div>
|
|
1072
|
+
<div style="font-size:11px;color:#78716c;margin-bottom:8px;">Committed deals not fully AI-backed, still worth rescuing</div>
|
|
1073
|
+
<button onclick="openRescueDrawer()" style="padding:7px 14px;border:none;border-radius:6px;background:#f59e0b;color:#fff;cursor:pointer;font-size:12px;font-weight:600;width:100%;">View Deals</button>
|
|
1074
|
+
</div>
|
|
1075
|
+
<!-- Upgrade Candidates 卡片 -->
|
|
1076
|
+
<div style="background:linear-gradient(135deg,#f0f0ff,#e8e8ff);border-radius:8px;padding:12px 14px;box-shadow:0 1px 3px rgba(0,0,0,0.08);border-left:3px solid #6366f1;flex:1;display:flex;flex-direction:column;justify-content:center;">
|
|
1077
|
+
<div style="font-size:13px;font-weight:600;color:#4338ca;margin-bottom:6px;">✨ Upgrade Candidates</div>
|
|
1078
|
+
<div style="margin-bottom:6px;"><span style="font-size:20px;font-weight:700;color:#333;">$2,500,000</span> <span style="font-size:11px;color:#888;">· 4 deals to pull in</span></div>
|
|
1079
|
+
<div style="font-size:11px;color:#78716c;margin-bottom:8px;">High win-rate deals from Open Pipeline worth upgrading to Commit</div>
|
|
1080
|
+
<button onclick="openUpgradeDrawer()" style="padding:7px 14px;border:none;border-radius:6px;background:#6366f1;color:#fff;cursor:pointer;font-size:12px;font-weight:600;width:100%;">View Deals</button>
|
|
1081
|
+
</div>
|
|
1082
|
+
</div>
|
|
1083
|
+
|
|
1084
|
+
</div><!-- end KPI+BI+Gap row -->
|
|
1085
|
+
|
|
1086
|
+
<!-- Rescue Commit 抽屉 -->
|
|
1087
|
+
<div id="rescueDrawer" style="display:none;position:fixed;top:0;right:0;width:460px;height:100%;background:#fff;box-shadow:-4px 0 20px rgba(0,0,0,0.1);z-index:150;overflow-y:auto;padding:24px;">
|
|
1088
|
+
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;">
|
|
1089
|
+
<h3 style="font-size:16px;">⚠ Rescue Commit</h3>
|
|
1090
|
+
<button onclick="closeRescueDrawer()" style="border:none;background:none;font-size:20px;cursor:pointer;">✕</button>
|
|
1091
|
+
</div>
|
|
1092
|
+
<div style="font-size:12px;color:#888;margin-bottom:16px;">3 deals · <span style="color:#ef4444;font-weight:600;">$1,200,000 at risk</span></div>
|
|
1093
|
+
|
|
1094
|
+
<div style="border:1px solid #fde68a;border-left:3px solid #f59e0b;border-radius:10px;padding:14px;margin-bottom:10px;" id="rescueCard1">
|
|
1095
|
+
<div style="display:flex;justify-content:space-between;align-items:start;margin-bottom:6px;">
|
|
1096
|
+
<div>
|
|
1097
|
+
<div style="font-weight:600;font-size:14px;">Apollo Project</div>
|
|
1098
|
+
<div style="font-size:12px;color:#888;">Huawei · $1,000,000</div>
|
|
1099
|
+
<div style="font-size:11px;color:#888;margin-top:2px;"><span style="background:#f0f0f0;padding:1px 6px;border-radius:3px;font-size:10px;">Commit</span> · Close: 2026-04-15</div>
|
|
1100
|
+
</div>
|
|
1101
|
+
<span style="color:#f59e0b;font-weight:600;font-size:13px;">✨ 42%</span>
|
|
1102
|
+
</div>
|
|
1103
|
+
<div style="font-size:12px;color:#92400e;background:#fffbeb;padding:6px 8px;border-radius:4px;margin-bottom:10px;">⚠ Key sponsor left the company, engagement dropped significantly in last 14 days</div>
|
|
1104
|
+
<div style="display:flex;gap:8px;">
|
|
1105
|
+
<button onclick="prioritizeDeal('rescueCard1')" style="flex:1;padding:7px;border:none;border-radius:6px;background:#f59e0b;color:#fff;cursor:pointer;font-size:12px;font-weight:600;">Prioritize</button>
|
|
1106
|
+
<button onclick="dismissRescueDeal('rescueCard1')" style="flex:1;padding:7px;border:1px solid #ddd;border-radius:6px;background:#fff;cursor:pointer;font-size:12px;color:#666;">Dismiss</button>
|
|
1107
|
+
</div>
|
|
1108
|
+
</div>
|
|
1109
|
+
|
|
1110
|
+
<div style="border:1px solid #fde68a;border-left:3px solid #f59e0b;border-radius:10px;padding:14px;margin-bottom:10px;" id="rescueCard2">
|
|
1111
|
+
<div style="display:flex;justify-content:space-between;align-items:start;margin-bottom:6px;">
|
|
1112
|
+
<div>
|
|
1113
|
+
<div style="font-weight:600;font-size:14px;">Data Platform</div>
|
|
1114
|
+
<div style="font-size:12px;color:#888;">JD.com · $120,000</div>
|
|
1115
|
+
<div style="font-size:11px;color:#888;margin-top:2px;"><span style="background:#f0f0f0;padding:1px 6px;border-radius:3px;font-size:10px;">Commit</span> · Close: 2026-04-20</div>
|
|
1116
|
+
</div>
|
|
1117
|
+
<span style="color:#f59e0b;font-weight:600;font-size:13px;">✨ 38%</span>
|
|
1118
|
+
</div>
|
|
1119
|
+
<div style="font-size:12px;color:#92400e;background:#fffbeb;padding:6px 8px;border-radius:4px;margin-bottom:10px;">⚠ Close date pushed twice this quarter, activity level below similar deals</div>
|
|
1120
|
+
<div style="display:flex;gap:8px;">
|
|
1121
|
+
<button onclick="prioritizeDeal('rescueCard2')" style="flex:1;padding:7px;border:none;border-radius:6px;background:#f59e0b;color:#fff;cursor:pointer;font-size:12px;font-weight:600;">Prioritize</button>
|
|
1122
|
+
<button onclick="dismissRescueDeal('rescueCard2')" style="flex:1;padding:7px;border:1px solid #ddd;border-radius:6px;background:#fff;cursor:pointer;font-size:12px;color:#666;">Dismiss</button>
|
|
1123
|
+
</div>
|
|
1124
|
+
</div>
|
|
1125
|
+
|
|
1126
|
+
</div>
|
|
1127
|
+
|
|
1128
|
+
<!-- Upgrade Candidates 抽屉 -->
|
|
1129
|
+
<div id="upgradeDrawer" style="display:none;position:fixed;top:0;right:0;width:460px;height:100%;background:#fff;box-shadow:-4px 0 20px rgba(0,0,0,0.1);z-index:150;overflow-y:auto;padding:24px;">
|
|
1130
|
+
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;">
|
|
1131
|
+
<h3 style="font-size:16px;">✨ Upgrade Candidates</h3>
|
|
1132
|
+
<button onclick="closeUpgradeDrawer()" style="border:none;background:none;font-size:20px;cursor:pointer;">✕</button>
|
|
1133
|
+
</div>
|
|
1134
|
+
<div style="font-size:12px;color:#888;margin-bottom:16px;">4 deals · <span style="color:#6366f1;font-weight:600;">$2,500,000 potential</span></div>
|
|
1135
|
+
|
|
1136
|
+
<div style="border:1px solid #e5e7eb;border-left:3px solid #6366f1;border-radius:10px;padding:14px;margin-bottom:10px;" id="upgradeCard1">
|
|
1137
|
+
<div style="display:flex;justify-content:space-between;align-items:start;margin-bottom:6px;">
|
|
1138
|
+
<div>
|
|
1139
|
+
<div style="font-weight:600;font-size:14px;">Aurora Solution</div>
|
|
1140
|
+
<div style="font-size:12px;color:#888;">ByteDance · $800,000</div>
|
|
1141
|
+
<div style="font-size:11px;color:#888;margin-top:2px;"><span style="background:#f0f0f0;padding:1px 6px;border-radius:3px;font-size:10px;">Best Case</span> · Close: 2026-04-30 · <span style="color:#6366f1;font-size:10px;font-weight:600;">↑ Upgrade to Commit</span></div>
|
|
1142
|
+
</div>
|
|
1143
|
+
<span style="color:#6366f1;font-weight:600;font-size:13px;">✨ 91%</span>
|
|
1144
|
+
</div>
|
|
1145
|
+
<div style="font-size:12px;color:#555;background:#f0f0ff;padding:6px 8px;border-radius:4px;margin-bottom:10px;">✨ Very high engagement and deal progressing faster than average</div>
|
|
1146
|
+
<div style="display:flex;gap:8px;">
|
|
1147
|
+
<button onclick="prioritizeDeal('upgradeCard1')" style="flex:1;padding:7px;border:none;border-radius:6px;background:#6366f1;color:#fff;cursor:pointer;font-size:12px;font-weight:600;">Prioritize</button>
|
|
1148
|
+
<button onclick="dismissUpgradeDeal('upgradeCard1')" style="flex:1;padding:7px;border:1px solid #ddd;border-radius:6px;background:#fff;cursor:pointer;font-size:12px;color:#666;">Dismiss</button>
|
|
1149
|
+
</div>
|
|
1150
|
+
</div>
|
|
1151
|
+
|
|
1152
|
+
<div style="border:1px solid #e5e7eb;border-left:3px solid #6366f1;border-radius:10px;padding:14px;margin-bottom:10px;" id="upgradeCard2">
|
|
1153
|
+
<div style="display:flex;justify-content:space-between;align-items:start;margin-bottom:6px;">
|
|
1154
|
+
<div>
|
|
1155
|
+
<div style="font-weight:600;font-size:14px;">Cloud Migration</div>
|
|
1156
|
+
<div style="font-size:12px;color:#888;">Tencent · $1,700,000</div>
|
|
1157
|
+
<div style="font-size:11px;color:#888;margin-top:2px;"><span style="background:#f0f0f0;padding:1px 6px;border-radius:3px;font-size:10px;">Pipeline</span> · Close: 2026-05-20 · <span style="color:#6366f1;font-size:10px;font-weight:600;">↑ Upgrade to Commit</span></div>
|
|
1158
|
+
</div>
|
|
1159
|
+
<span style="color:#6366f1;font-weight:600;font-size:13px;">✨ 78%</span>
|
|
1160
|
+
</div>
|
|
1161
|
+
<div style="font-size:12px;color:#555;background:#f0f0ff;padding:6px 8px;border-radius:4px;margin-bottom:10px;">✨ Strong engagement signals and past wins with this account</div>
|
|
1162
|
+
<div style="display:flex;gap:8px;">
|
|
1163
|
+
<button onclick="prioritizeDeal('upgradeCard2')" style="flex:1;padding:7px;border:none;border-radius:6px;background:#6366f1;color:#fff;cursor:pointer;font-size:12px;font-weight:600;">Prioritize</button>
|
|
1164
|
+
<button onclick="dismissUpgradeDeal('upgradeCard2')" style="flex:1;padding:7px;border:1px solid #ddd;border-radius:6px;background:#fff;cursor:pointer;font-size:12px;color:#666;">Dismiss</button>
|
|
1165
|
+
</div>
|
|
1166
|
+
</div>
|
|
1167
|
+
|
|
1168
|
+
</div>
|
|
1169
|
+
|
|
1170
|
+
<!-- M1.5 预测矩阵表格 + M1.6 预测调整 -->
|
|
1171
|
+
<div
|
|
1172
|
+
style="background:#fff;border-radius:8px;padding:20px;margin-bottom:20px;box-shadow:0 1px 3px rgba(0,0,0,0.08);overflow-x:auto;">
|
|
1173
|
+
<h3 style="font-size:14px;font-weight:600;margin-bottom:12px;">Forecast Grid</h3>
|
|
1174
|
+
<table style="width:100%;border-collapse:collapse;font-size:13px;">
|
|
1175
|
+
<thead>
|
|
1176
|
+
<tr style="background:#f8f9fa;border-bottom:2px solid #e5e7eb;">
|
|
1177
|
+
<th style="padding:10px 12px;text-align:left;">Name</th>
|
|
1178
|
+
<th style="padding:10px 12px;text-align:right;">Quota</th>
|
|
1179
|
+
<th style="padding:10px 12px;text-align:right;">✨ AI Forecast</th>
|
|
1180
|
+
<th style="padding:10px 12px;text-align:right;">Closed</th>
|
|
1181
|
+
<th style="padding:10px 12px;text-align:right;">Commit</th>
|
|
1182
|
+
<th style="padding:10px 12px;text-align:right;">Best Case</th>
|
|
1183
|
+
<th style="padding:10px 12px;text-align:right;">Pipeline</th>
|
|
1184
|
+
<th style="padding:10px 12px;text-align:right;">Coverage</th>
|
|
1185
|
+
</tr>
|
|
1186
|
+
</thead>
|
|
1187
|
+
<tbody id="matrixBody">
|
|
1188
|
+
</tbody>
|
|
1189
|
+
</table>
|
|
1190
|
+
</div>
|
|
1191
|
+
|
|
1192
|
+
<!-- 商机明细列表(含Tab切换) -->
|
|
1193
|
+
<div
|
|
1194
|
+
style="background:#fff;border-radius:8px;padding:20px;margin-bottom:20px;box-shadow:0 1px 3px rgba(0,0,0,0.08);overflow-x:auto;">
|
|
1195
|
+
<!-- Tab切换 -->
|
|
1196
|
+
<div style="display:flex;gap:0;margin-bottom:16px;border-bottom:2px solid #e5e7eb;">
|
|
1197
|
+
<button class="opp-tab-btn active" onclick="switchOppTab('watch')"
|
|
1198
|
+
style="padding:8px 20px;font-size:13px;font-weight:600;cursor:pointer;border:none;background:none;color:#6366f1;border-bottom:2px solid #6366f1;margin-bottom:-2px;">✨
|
|
1199
|
+
Prioritized Deals</button>
|
|
1200
|
+
<button class="opp-tab-btn" onclick="switchOppTab('all')"
|
|
1201
|
+
style="padding:8px 20px;font-size:13px;font-weight:600;cursor:pointer;border:none;background:none;color:#999;border-bottom:2px solid transparent;margin-bottom:-2px;">All
|
|
1202
|
+
Opportunities</button>
|
|
1203
|
+
</div>
|
|
1204
|
+
|
|
1205
|
+
<!-- Prioritized Deals Tab (默认显示) -->
|
|
1206
|
+
|
|
1207
|
+
<!-- All Opportunities Tab -->
|
|
1208
|
+
<div id="oppTabAll" style="display:none;">
|
|
1209
|
+
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
|
|
1210
|
+
<h3 id="oppListTitle" style="font-size:14px;font-weight:600;">Opportunity List</h3>
|
|
1211
|
+
<div id="oppSummary" style="font-size:13px;color:#666;">Total: <span id="oppTotalAmount"
|
|
1212
|
+
style="font-weight:700;color:#333;">$4,300,000</span> · <span id="oppTotalCount"
|
|
1213
|
+
style="font-weight:600;">4</span> opportunities</div>
|
|
1214
|
+
</div>
|
|
1215
|
+
<table style="width:100%;border-collapse:collapse;font-size:13px;">
|
|
1216
|
+
<thead>
|
|
1217
|
+
<tr style="background:#f8f9fa;border-bottom:2px solid #e5e7eb;">
|
|
1218
|
+
<th style="padding:10px 12px;text-align:left;">Opportunity Name</th>
|
|
1219
|
+
<th style="padding:10px 12px;text-align:left;">Account</th>
|
|
1220
|
+
<th style="padding:10px 12px;text-align:left;">Forecast Type</th>
|
|
1221
|
+
<th style="padding:10px 12px;text-align:right;">Amount</th>
|
|
1222
|
+
<th style="padding:10px 12px;text-align:center;">Close Date</th>
|
|
1223
|
+
<th style="padding:10px 12px;text-align:center;">Stage</th>
|
|
1224
|
+
<th style="padding:10px 12px;text-align:center;">✨ AI Score</th>
|
|
1225
|
+
<th style="padding:10px 12px;text-align:center;">✨ AI Win Rate</th>
|
|
1226
|
+
<th style="padding:10px 12px;text-align:center;">Last Activity</th>
|
|
1227
|
+
</tr>
|
|
1228
|
+
</thead>
|
|
1229
|
+
<tbody id="oppBody"></tbody>
|
|
1230
|
+
</table>
|
|
1231
|
+
</div>
|
|
1232
|
+
|
|
1233
|
+
<!-- Prioritized Deals Tab -->
|
|
1234
|
+
<div id="oppTabWatch">
|
|
1235
|
+
<!-- 来源筛选Chips -->
|
|
1236
|
+
<div style="display:flex;gap:8px;margin-bottom:16px;">
|
|
1237
|
+
<button class="watch-sub-tab active" onclick="switchWatchSubTab('all')"
|
|
1238
|
+
style="padding:6px 14px;font-size:12px;font-weight:600;cursor:pointer;border:1px solid #6366f1;background:#f0f0ff;color:#6366f1;border-radius:20px;">All (3)</button>
|
|
1239
|
+
<button class="watch-sub-tab" onclick="switchWatchSubTab('rescue')"
|
|
1240
|
+
style="padding:6px 14px;font-size:12px;font-weight:600;cursor:pointer;border:1px solid #ddd;background:#fff;color:#666;border-radius:20px;">Rescue (1)</button>
|
|
1241
|
+
<button class="watch-sub-tab" onclick="switchWatchSubTab('upgrade')"
|
|
1242
|
+
style="padding:6px 14px;font-size:12px;font-weight:600;cursor:pointer;border:1px solid #ddd;background:#fff;color:#666;border-radius:20px;">Upgrade (2)</button>
|
|
1243
|
+
</div>
|
|
1244
|
+
|
|
1245
|
+
<!-- Prioritized Deals表格 -->
|
|
1246
|
+
<table style="width:100%;border-collapse:collapse;font-size:13px;">
|
|
1247
|
+
<thead>
|
|
1248
|
+
<tr style="background:#f8f9fa;border-bottom:2px solid #e5e7eb;">
|
|
1249
|
+
<th style="padding:10px 12px;text-align:left;">Opportunity Name</th>
|
|
1250
|
+
<th style="padding:10px 12px;text-align:left;">Account</th>
|
|
1251
|
+
<th style="padding:10px 12px;text-align:left;">Forecast Type</th>
|
|
1252
|
+
<th style="padding:10px 12px;text-align:right;">Amount</th>
|
|
1253
|
+
<th style="padding:10px 12px;text-align:center;">Close Date</th>
|
|
1254
|
+
<th style="padding:10px 12px;text-align:center;">Stage</th>
|
|
1255
|
+
<th style="padding:10px 12px;text-align:center;">✨ AI Score</th>
|
|
1256
|
+
<th style="padding:10px 12px;text-align:center;">✨ AI Win Rate</th>
|
|
1257
|
+
<th style="padding:10px 12px;text-align:center;">Last Activity</th>
|
|
1258
|
+
</tr>
|
|
1259
|
+
</thead>
|
|
1260
|
+
<tbody id="prioritizedBody">
|
|
1261
|
+
<tr style="border-bottom:1px solid #f0f0f0;" class="prioritized-row" data-source="rescue">
|
|
1262
|
+
<td style="padding:10px 12px;font-weight:500;color:#3b82f6;cursor:pointer;">Apollo Project</td>
|
|
1263
|
+
<td style="padding:10px 12px;">Huawei Tech</td>
|
|
1264
|
+
<td style="padding:10px 12px;">Commit</td>
|
|
1265
|
+
<td style="padding:10px 12px;text-align:right;">$1,000,000</td>
|
|
1266
|
+
<td style="padding:10px 12px;text-align:center;">2026-04-15</td>
|
|
1267
|
+
<td style="padding:10px 12px;text-align:center;">Negotiation</td>
|
|
1268
|
+
<td style="padding:10px 12px;text-align:center;"><span style="background:#f59e0b;color:#fff;padding:2px 8px;border-radius:10px;font-size:11px;">Medium</span></td>
|
|
1269
|
+
<td style="padding:10px 12px;text-align:center;color:#6366f1;font-weight:600;">42%</td>
|
|
1270
|
+
<td style="padding:10px 12px;text-align:center;color:#999;">2026-03-28</td>
|
|
1271
|
+
</tr>
|
|
1272
|
+
<tr style="border-bottom:1px solid #f0f0f0;" class="prioritized-row" data-source="upgrade">
|
|
1273
|
+
<td style="padding:10px 12px;font-weight:500;color:#3b82f6;cursor:pointer;">Aurora Solution</td>
|
|
1274
|
+
<td style="padding:10px 12px;">ByteDance</td>
|
|
1275
|
+
<td style="padding:10px 12px;">Best Case</td>
|
|
1276
|
+
<td style="padding:10px 12px;text-align:right;">$800,000</td>
|
|
1277
|
+
<td style="padding:10px 12px;text-align:center;">2026-04-30</td>
|
|
1278
|
+
<td style="padding:10px 12px;text-align:center;">Proposal</td>
|
|
1279
|
+
<td style="padding:10px 12px;text-align:center;"><span style="background:#22c55e;color:#fff;padding:2px 8px;border-radius:10px;font-size:11px;">High</span></td>
|
|
1280
|
+
<td style="padding:10px 12px;text-align:center;color:#6366f1;font-weight:600;">91%</td>
|
|
1281
|
+
<td style="padding:10px 12px;text-align:center;color:#999;">2026-03-29</td>
|
|
1282
|
+
</tr>
|
|
1283
|
+
<tr style="border-bottom:1px solid #f0f0f0;" class="prioritized-row" data-source="upgrade">
|
|
1284
|
+
<td style="padding:10px 12px;font-weight:500;color:#3b82f6;cursor:pointer;">Cloud Migration</td>
|
|
1285
|
+
<td style="padding:10px 12px;">Tencent</td>
|
|
1286
|
+
<td style="padding:10px 12px;">Pipeline</td>
|
|
1287
|
+
<td style="padding:10px 12px;text-align:right;">$1,700,000</td>
|
|
1288
|
+
<td style="padding:10px 12px;text-align:center;">2026-05-20</td>
|
|
1289
|
+
<td style="padding:10px 12px;text-align:center;">Discovery</td>
|
|
1290
|
+
<td style="padding:10px 12px;text-align:center;"><span style="background:#f59e0b;color:#fff;padding:2px 8px;border-radius:10px;font-size:11px;">Medium</span></td>
|
|
1291
|
+
<td style="padding:10px 12px;text-align:center;color:#6366f1;font-weight:600;">78%</td>
|
|
1292
|
+
<td style="padding:10px 12px;text-align:center;color:#999;">2026-03-27</td>
|
|
1293
|
+
</tr>
|
|
1294
|
+
</tbody>
|
|
1295
|
+
</table>
|
|
1296
|
+
</div>
|
|
1297
|
+
</div>
|
|
1298
|
+
|
|
1299
|
+
</div><!-- end Forecast Tab -->
|
|
1300
|
+
|
|
1301
|
+
</div><!-- end container -->
|
|
1302
|
+
|
|
1303
|
+
<!-- AI-Recommended Commit 抽屉 -->
|
|
1304
|
+
<div id="aiCommitDrawer"
|
|
1305
|
+
style="display:none;position:fixed;top:0;right:0;width:480px;height:100%;background:#fff;box-shadow:-4px 0 20px rgba(0,0,0,0.1);z-index:150;overflow-y:auto;padding:24px;">
|
|
1306
|
+
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:20px;">
|
|
1307
|
+
<h3 style="font-size:16px;">✨ AI Forecast</h3>
|
|
1308
|
+
<button onclick="closeAICommitDrawer()"
|
|
1309
|
+
style="border:none;background:none;font-size:20px;cursor:pointer;">✕</button>
|
|
1310
|
+
</div>
|
|
1311
|
+
<div style="font-size:12px;color:#888;margin-bottom:20px;"><span
|
|
1312
|
+
style="font-weight:700;color:#6366f1;font-size:16px;">$6,800,000</span> <span style="margin-left:4px;">Based on deal health and engagement signals</span></div>
|
|
1313
|
+
|
|
1314
|
+
<!-- Closed Won 汇总 -->
|
|
1315
|
+
<div style="background:#f8f9fa;border-radius:10px;padding:16px;margin-bottom:20px;">
|
|
1316
|
+
<div style="display:flex;justify-content:space-between;align-items:center;">
|
|
1317
|
+
<div>
|
|
1318
|
+
<div style="font-size:12px;color:#888;margin-bottom:2px;">Closed Won</div>
|
|
1319
|
+
<div style="font-size:20px;font-weight:700;">$3,200,000</div>
|
|
1320
|
+
</div>
|
|
1321
|
+
<div style="font-size:12px;color:#999;">5 deals</div>
|
|
1322
|
+
</div>
|
|
1323
|
+
</div>
|
|
1324
|
+
|
|
1325
|
+
<!-- Tab切换 -->
|
|
1326
|
+
<div style="display:flex;gap:0;margin-bottom:16px;border-bottom:2px solid #e5e7eb;">
|
|
1327
|
+
<button class="ai-commit-tab active" onclick="switchAICommitTab('pipeline')" style="padding:8px 16px;font-size:13px;font-weight:600;cursor:pointer;border:none;background:none;color:#6366f1;border-bottom:2px solid #6366f1;margin-bottom:-2px;">From Open Pipeline · $2,500,000 · 2 deals</button>
|
|
1328
|
+
<button class="ai-commit-tab" onclick="switchAICommitTab('commit')" style="padding:8px 16px;font-size:13px;font-weight:600;cursor:pointer;border:none;background:none;color:#999;border-bottom:2px solid transparent;margin-bottom:-2px;">From Commit · $1,800,000 · 2 deals</button>
|
|
1329
|
+
</div>
|
|
1330
|
+
|
|
1331
|
+
<!-- From Open Pipeline Tab -->
|
|
1332
|
+
<div id="aiCommitPipelineTab">
|
|
1333
|
+
<div style="border:1px solid #e5e7eb;border-left:3px solid #6366f1;border-radius:10px;padding:14px;margin-bottom:10px;">
|
|
1334
|
+
<div style="display:flex;justify-content:space-between;align-items:start;margin-bottom:6px;">
|
|
1335
|
+
<div>
|
|
1336
|
+
<div style="font-weight:600;font-size:14px;">Starlight Initiative</div>
|
|
1337
|
+
<div style="font-size:12px;color:#888;">Tencent · $500,000</div>
|
|
1338
|
+
</div>
|
|
1339
|
+
<span style="color:#6366f1;font-weight:600;font-size:13px;">✨ 52%</span>
|
|
1340
|
+
</div>
|
|
1341
|
+
<div style="font-size:12px;color:#999;">
|
|
1342
|
+
<span style="background:#f0f0f0;padding:1px 6px;border-radius:3px;font-size:10px;">Pipeline</span>
|
|
1343
|
+
<span style="margin-left:4px;">· Close: 2026-05-20</span>
|
|
1344
|
+
</div>
|
|
1345
|
+
<div style="font-size:12px;color:#666;background:#f8f9fa;padding:6px 8px;border-radius:4px;margin-top:6px;">Strong engagement signals and past wins with this account</div>
|
|
1346
|
+
</div>
|
|
1347
|
+
<div style="border:1px solid #e5e7eb;border-left:3px solid #6366f1;border-radius:10px;padding:14px;">
|
|
1348
|
+
<div style="display:flex;justify-content:space-between;align-items:start;margin-bottom:6px;">
|
|
1349
|
+
<div>
|
|
1350
|
+
<div style="font-weight:600;font-size:14px;">Galaxy Partnership</div>
|
|
1351
|
+
<div style="font-size:12px;color:#888;">Alibaba · $2,000,000</div>
|
|
1352
|
+
</div>
|
|
1353
|
+
<span style="color:#6366f1;font-weight:600;font-size:13px;">✨ 45%</span>
|
|
1354
|
+
</div>
|
|
1355
|
+
<div style="font-size:12px;color:#999;">
|
|
1356
|
+
<span style="background:#f0f0f0;padding:1px 6px;border-radius:3px;font-size:10px;">Best Case</span>
|
|
1357
|
+
<span style="margin-left:4px;">· Close: 2026-05-10</span>
|
|
1358
|
+
</div>
|
|
1359
|
+
<div style="font-size:12px;color:#666;background:#f8f9fa;padding:6px 8px;border-radius:4px;margin-top:6px;">High product usage and active engagement in last 7 days</div>
|
|
1360
|
+
</div>
|
|
1361
|
+
</div>
|
|
1362
|
+
|
|
1363
|
+
<!-- From Commit Tab -->
|
|
1364
|
+
<div id="aiCommitCommitTab" style="display:none;">
|
|
1365
|
+
<div style="border:1px solid #e5e7eb;border-radius:10px;padding:14px;margin-bottom:10px;">
|
|
1366
|
+
<div style="display:flex;justify-content:space-between;align-items:start;margin-bottom:6px;">
|
|
1367
|
+
<div>
|
|
1368
|
+
<div style="font-weight:600;font-size:14px;">Aurora Solution</div>
|
|
1369
|
+
<div style="font-size:12px;color:#888;">ByteDance · $800,000</div>
|
|
1370
|
+
</div>
|
|
1371
|
+
<span style="color:#6366f1;font-weight:600;font-size:13px;">✨ 91%</span>
|
|
1372
|
+
</div>
|
|
1373
|
+
<div style="font-size:12px;color:#999;">
|
|
1374
|
+
<span style="background:#f0f0f0;padding:1px 6px;border-radius:3px;font-size:10px;">Commit</span>
|
|
1375
|
+
<span style="margin-left:4px;">· Close: 2026-04-30</span>
|
|
1376
|
+
</div>
|
|
1377
|
+
</div>
|
|
1378
|
+
<div style="border:1px solid #e5e7eb;border-radius:10px;padding:14px;">
|
|
1379
|
+
<div style="display:flex;justify-content:space-between;align-items:start;margin-bottom:6px;">
|
|
1380
|
+
<div>
|
|
1381
|
+
<div style="font-weight:600;font-size:14px;">Apollo Project</div>
|
|
1382
|
+
<div style="font-size:12px;color:#888;">Huawei Tech · $1,000,000</div>
|
|
1383
|
+
</div>
|
|
1384
|
+
<span style="color:#6366f1;font-weight:600;font-size:13px;">✨ 86%</span>
|
|
1385
|
+
</div>
|
|
1386
|
+
<div style="font-size:12px;color:#999;">
|
|
1387
|
+
<span style="background:#f0f0f0;padding:1px 6px;border-radius:3px;font-size:10px;">Commit</span>
|
|
1388
|
+
<span style="margin-left:4px;">· Close: 2026-04-15</span>
|
|
1389
|
+
</div>
|
|
1390
|
+
</div>
|
|
1391
|
+
</div>
|
|
1392
|
+
</div>
|
|
1393
|
+
|
|
1394
|
+
<!-- ===== 全局Tooltip元素(Tab外部,始终可访问) ===== -->
|
|
1395
|
+
|
|
1396
|
+
<!-- Score Hover卡片 with radar chart -->
|
|
1397
|
+
<div id="scoreTooltip"
|
|
1398
|
+
style="display:none;position:fixed;background:#fff;border-radius:10px;padding:20px;width:360px;box-shadow:0 8px 30px rgba(0,0,0,0.15);z-index:200;font-size:13px;">
|
|
1399
|
+
<div style="font-weight:700;font-size:14px;margin-bottom:8px;">✨ Opportunity Score: <span id="stScore">High</span>
|
|
1400
|
+
</div>
|
|
1401
|
+
<div style="color:#666;font-size:12px;margin-bottom:12px;" id="stSummary">Customer has clear needs and budget
|
|
1402
|
+
allocated.</div>
|
|
1403
|
+
<canvas id="radarCanvas" width="200" height="200" style="display:block;margin:0 auto 12px;"></canvas>
|
|
1404
|
+
<div style="display:grid;grid-template-columns:1fr 1fr;gap:6px;font-size:12px;">
|
|
1405
|
+
<div style="display:flex;justify-content:space-between;padding:4px 8px;background:#f8f9fa;border-radius:4px;">
|
|
1406
|
+
<span>需求</span><span id="stDemand" style="font-weight:600;">优秀</span></div>
|
|
1407
|
+
<div style="display:flex;justify-content:space-between;padding:4px 8px;background:#f8f9fa;border-radius:4px;">
|
|
1408
|
+
<span>决策链</span><span id="stDecision" style="font-weight:600;">良好</span></div>
|
|
1409
|
+
<div style="display:flex;justify-content:space-between;padding:4px 8px;background:#f8f9fa;border-radius:4px;">
|
|
1410
|
+
<span>预算和采购流程</span><span id="stBudget" style="font-weight:600;">合格</span></div>
|
|
1411
|
+
<div style="display:flex;justify-content:space-between;padding:4px 8px;background:#f8f9fa;border-radius:4px;">
|
|
1412
|
+
<span>竞争</span><span id="stCompete" style="font-weight:600;">良好</span></div>
|
|
1413
|
+
</div>
|
|
1414
|
+
</div>
|
|
1415
|
+
|
|
1416
|
+
<!-- AI Win Rate Hover卡片 -->
|
|
1417
|
+
<div id="winRateTooltip"
|
|
1418
|
+
style="display:none;position:fixed;background:#fff;border-radius:10px;padding:16px;width:320px;box-shadow:0 8px 30px rgba(0,0,0,0.15);z-index:200;font-size:13px;">
|
|
1419
|
+
<div style="font-weight:600;font-size:12px;color:#888;margin-bottom:10px;">Baseline Probability <span
|
|
1420
|
+
id="ttBaseline">86%</span></div>
|
|
1421
|
+
<hr style="border:none;border-top:1px solid #eee;margin:8px 0;">
|
|
1422
|
+
<div style="font-weight:600;margin-bottom:4px;">Positive Factors</div>
|
|
1423
|
+
<div style="color:#22c55e;margin-bottom:8px;" id="ttPositives"></div>
|
|
1424
|
+
<hr style="border:none;border-top:1px solid #eee;margin:8px 0;">
|
|
1425
|
+
<div style="font-weight:600;margin-bottom:4px;">Negative Factors</div>
|
|
1426
|
+
<div style="color:#ef4444;margin-bottom:8px;" id="ttNegatives"></div>
|
|
1427
|
+
<hr style="border:none;border-top:1px solid #eee;margin:8px 0;">
|
|
1428
|
+
<div style="display:flex;justify-content:space-between;font-weight:700;font-size:16px;"><span>AI Win
|
|
1429
|
+
Rate</span><span id="ttWinRate" style="color:#6366f1;">86%</span></div>
|
|
1430
|
+
</div>
|
|
1431
|
+
|
|
1432
|
+
<!-- 变动详情Hover卡片 -->
|
|
1433
|
+
<div id="changeTooltip"
|
|
1434
|
+
style="display:none;position:fixed;background:#fff;border-radius:10px;padding:16px;width:280px;box-shadow:0 8px 30px rgba(0,0,0,0.15);z-index:200;font-size:13px;">
|
|
1435
|
+
<div style="font-weight:700;font-size:14px;" id="ctTitle">Stage progressed</div>
|
|
1436
|
+
<div style="margin:6px 0;color:#333;" id="ctDetail">Discovery → <span
|
|
1437
|
+
style="color:#ec4899;font-weight:600;">Solution Fit</span></div>
|
|
1438
|
+
<div style="color:#999;font-size:12px;" id="ctMeta">Changed on Jan 24, 2024</div>
|
|
1439
|
+
</div>
|
|
1440
|
+
|
|
1441
|
+
<!-- Commit Hover卡片 -->
|
|
1442
|
+
<div id="commitTooltip"
|
|
1443
|
+
style="display:none;position:fixed;background:#fff;border-radius:10px;padding:0;width:320px;box-shadow:0 8px 30px rgba(0,0,0,0.15);z-index:200;font-size:13px;overflow:hidden;">
|
|
1444
|
+
<div style="border-top:3px solid #22c55e;padding:16px;">
|
|
1445
|
+
<div style="display:flex;justify-content:space-between;align-items:start;">
|
|
1446
|
+
<div>
|
|
1447
|
+
<div style="display:flex;align-items:center;gap:6px;margin-bottom:6px;">
|
|
1448
|
+
<span id="cmArrowIcon"
|
|
1449
|
+
style="background:#22c55e;color:#fff;border-radius:50%;width:20px;height:20px;display:flex;align-items:center;justify-content:center;font-size:12px;">↑</span>
|
|
1450
|
+
<span style="font-weight:700;font-size:14px;" id="cmTitle">Value increased by $200K</span>
|
|
1451
|
+
</div>
|
|
1452
|
+
<div style="color:#333;margin-bottom:8px;" id="cmRange"></div>
|
|
1453
|
+
<div style="color:#999;font-size:12px;" id="cmDate">Updated on Mar 25, 2026</div>
|
|
1454
|
+
</div>
|
|
1455
|
+
<span style="color:#999;cursor:pointer;font-size:16px;"
|
|
1456
|
+
onclick="document.getElementById('commitTooltip').style.display='none'">✕</span>
|
|
1457
|
+
</div>
|
|
1458
|
+
</div>
|
|
1459
|
+
</div>
|
|
1460
|
+
|
|
1461
|
+
<!-- AI Suggest Category Tooltip -->
|
|
1462
|
+
<div id="fcSuggestTooltip"
|
|
1463
|
+
style="display:none;position:fixed;background:#fff;border-radius:10px;padding:14px;width:300px;box-shadow:0 8px 30px rgba(0,0,0,0.15);z-index:200;font-size:13px;border-left:3px solid #6366f1;">
|
|
1464
|
+
<div style="font-weight:600;color:#6366f1;" id="fcSuggestText">✨ AI recommends: Commit</div>
|
|
1465
|
+
<div style="color:#666;font-size:12px;margin-top:4px;" id="fcSuggestReason">Based on high product usage</div>
|
|
1466
|
+
</div>
|
|
1467
|
+
|
|
1468
|
+
<!-- Risk Warning Tooltip -->
|
|
1469
|
+
<div id="fcRiskTooltip"
|
|
1470
|
+
style="display:none;position:fixed;background:#fff;border-radius:10px;padding:14px;width:300px;box-shadow:0 8px 30px rgba(0,0,0,0.15);z-index:200;font-size:13px;border-left:3px solid #ef4444;">
|
|
1471
|
+
<div style="font-weight:600;color:#ef4444;">✨ AI suggests De-commit</div>
|
|
1472
|
+
<div style="color:#666;font-size:12px;margin-top:4px;" id="fcRiskReason"></div>
|
|
1473
|
+
</div>
|
|
1474
|
+
|
|
1475
|
+
<!-- AI Forecast Hover卡片 -->
|
|
1476
|
+
<div id="aiForecastTooltip"
|
|
1477
|
+
style="display:none;position:fixed;background:#fff;border-radius:10px;padding:16px;width:280px;box-shadow:0 8px 30px rgba(0,0,0,0.15);z-index:200;font-size:13px;">
|
|
1478
|
+
<div style="display:flex;justify-content:space-between;margin-bottom:6px;"><span>Closed Won</span><span
|
|
1479
|
+
id="afClosed" style="font-weight:600;"></span></div>
|
|
1480
|
+
<div style="display:flex;justify-content:space-between;margin-bottom:6px;"><span>AI-Recommended Commit</span><span
|
|
1481
|
+
id="afPredicted" style="font-weight:600;"></span></div>
|
|
1482
|
+
<hr style="border:none;border-top:1px solid #eee;margin:8px 0;">
|
|
1483
|
+
<div style="display:flex;justify-content:space-between;font-weight:700;"><span>Total AI Forecast</span><span
|
|
1484
|
+
id="afTotal" style="color:#6366f1;"></span></div>
|
|
1485
|
+
</div>
|
|
1486
|
+
|
|
1487
|
+
<script>
|
|
1488
|
+
// 模拟数据
|
|
1489
|
+
const people = [
|
|
1490
|
+
{ name: 'Alice', quota: 3000000, ai: 2400000, coverage: '2.8x', closed: 1200000, commit: 1800000, pulledIn: 800000, pulledInPct: 27, pulledInArrow: 'up', bestcase: 2500000, pipeline: 8400000, closedPct: 40, commitPct: 60, closedArrow: 'up', closedFrom: 1080000, commitArrow: 'up', commitFrom: 1600000, bestArrow: 'down', bestFrom: 2700000, pipeArrow: 'up', pipeFrom: 7900000 },
|
|
1491
|
+
{ name: 'Steve', quota: 3500000, ai: 2800000, coverage: '3.1x', closed: 800000, commit: 2200000, pulledIn: 500000, pulledInPct: 14, pulledInArrow: '', bestcase: 3000000, pipeline: 10850000, closedPct: 23, commitPct: 63, closedArrow: 'up', closedFrom: 600000, commitArrow: 'down', commitFrom: 2500000, bestArrow: 'up', bestFrom: 2600000, pipeArrow: 'up', pipeFrom: 10200000 },
|
|
1492
|
+
{ name: 'Chloe', quota: 3500000, ai: 1600000, coverage: '2.5x', closed: 1200000, commit: 1500000, pulledIn: 300000, pulledInPct: 9, pulledInArrow: 'up', bestcase: 2300000, pipeline: 8750000, closedPct: 34, commitPct: 43, closedArrow: '', closedFrom: 1200000, commitArrow: 'up', commitFrom: 1300000, bestArrow: '', bestFrom: 2300000, pipeArrow: 'down', pipeFrom: 9100000 }
|
|
1493
|
+
];
|
|
1494
|
+
|
|
1495
|
+
const opps = [
|
|
1496
|
+
{ name: 'Apollo Project', account: 'Huawei Tech', amount: 1000000, amtArrow: 'up', amtChange: { title: 'Amount increased', from: '$800,000', to: '$1,000,000', date: 'Mar 28, 2026' }, close: '2026-04-15', closeArrow: 'down', closeChange: { title: 'Close Date decreased', from: 'Mar 30', to: 'Apr 15', date: 'Mar 25, 2026' }, stage: 'Negotiation', stageArrow: 'up', stageChange: { title: 'Stage increased', from: 'Proposal', to: 'Negotiation', date: 'Mar 20, 2026' }, score: 'High', scoreColor: '#22c55e', scoreDims: { demand: '优秀', decision: '良好', budget: '合格', compete: '良好' }, scoreSummary: 'Customer has clear needs and budget allocated. Decision chain identified but not fully engaged.', winRate: '86%', lastAct: '2026-03-28 14:32:05', baseline: '80%', pos: 'Opportunity amount keeps going up. +6%<br>Past wins with this account. +4%', neg: 'Moving slowly vs historical velocity. -4%' },
|
|
1497
|
+
{ name: 'Starlight Initiative', account: 'Tencent', amount: 500000, amtArrow: '', amtChange: null, close: '2026-04-20', closeArrow: '', closeChange: null, stage: 'Needs Analysis', stageArrow: '', stageChange: null, score: 'Medium', scoreColor: '#f59e0b', scoreDims: { demand: '良好', decision: '风险', budget: '风险', compete: '合格' }, scoreSummary: 'Demand is validated but no executive sponsor. Budget and procurement process unclear.', winRate: '52%', lastAct: '2026-03-25 09:15:22', baseline: '59%', pos: 'Strong engagement signals. +8%', neg: 'No executive sponsor identified. -10%<br>Deal size above average for this stage. -5%' },
|
|
1498
|
+
{ name: 'Galaxy Partnership', account: 'Alibaba', amount: 2000000, amtArrow: 'down', amtChange: { title: 'Amount decreased', from: '$2,500,000', to: '$2,000,000', date: 'Mar 27, 2026' }, close: '2026-05-10', closeArrow: '', closeChange: null, stage: 'Proposal', stageArrow: 'up', stageChange: { title: 'Stage increased', from: 'Prospecting', to: 'Proposal', date: 'Mar 26, 2026' }, score: 'Medium', scoreColor: '#f59e0b', scoreDims: { demand: '合格', decision: '良好', budget: '合格', compete: '风险' }, scoreSummary: 'Strong competitor presence. Budget confirmed but procurement timeline is long.', winRate: '45%', lastAct: '2026-03-27 16:48:37', baseline: '54%', pos: 'Past wins with this account. +5%', neg: 'Opportunity is moving slowly. -8%<br>Low activity count. -6%' },
|
|
1499
|
+
{ name: 'Aurora Solution', account: 'ByteDance', amount: 800000, amtArrow: '', amtChange: null, close: '2026-04-30', closeArrow: 'up', closeChange: { title: 'Close Date increased', from: 'May 15', to: 'Apr 30', date: 'Mar 28, 2026' }, stage: 'Commit', stageArrow: '', stageChange: null, score: 'High', scoreColor: '#22c55e', scoreDims: { demand: '优秀', decision: '优秀', budget: '良好', compete: '优秀' }, scoreSummary: 'All dimensions strong. Executive sponsor fully engaged. No significant competition.', winRate: '91%', lastAct: '2026-03-29 11:05:18', baseline: '78%', pos: 'Very high engagement. +10%<br>Deal progressing faster than average. +5%', neg: 'Slightly above average deal size. -2%' }
|
|
1500
|
+
];
|
|
1501
|
+
|
|
1502
|
+
function fmt(n) { return '$' + n.toLocaleString(); }
|
|
1503
|
+
// Add forecastType to opps
|
|
1504
|
+
opps[0].forecastType = 'Commit';
|
|
1505
|
+
opps[0].riskWarning = 'NPS dropped to 4, key sponsor left';
|
|
1506
|
+
opps[0].isPrioritized = true;
|
|
1507
|
+
opps[1].forecastType = 'Pipeline';
|
|
1508
|
+
opps[2].forecastType = 'Best Case';
|
|
1509
|
+
opps[2].aiSuggest = { cat: 'Commit', reason: 'Based on high product usage' };
|
|
1510
|
+
opps[2].isPrioritized = false;
|
|
1511
|
+
opps[3].forecastType = 'Commit';
|
|
1512
|
+
function arrow(dir) { return dir === 'up' ? '<span style="color:#22c55e;font-weight:700;">↑</span>' : dir === 'down' ? '<span style="color:#ef4444;font-weight:700;">↓</span>' : ''; }
|
|
1513
|
+
function pctBar(pct, quota) {
|
|
1514
|
+
const color = pct >= 80 ? '#22c55e' : pct >= 50 ? '#f59e0b' : '#ef4444';
|
|
1515
|
+
return '<div style="display:flex;align-items:center;gap:6px;margin-top:4px;"><span style="font-size:11px;color:#999;min-width:30px;">' + pct + '%</span><div style="flex:1;height:6px;background:#eee;border-radius:3px;"><div style="height:6px;border-radius:3px;background:' + color + ';width:' + Math.min(pct, 100) + '%;"></div></div></div>';
|
|
1516
|
+
}
|
|
1517
|
+
</script>
|
|
1518
|
+
|
|
1519
|
+
<script>
|
|
1520
|
+
// 渲染矩阵表格
|
|
1521
|
+
function renderMatrix() {
|
|
1522
|
+
const body = document.getElementById('matrixBody');
|
|
1523
|
+
body.innerHTML = '';
|
|
1524
|
+
people.forEach(p => {
|
|
1525
|
+
const tr = document.createElement('tr');
|
|
1526
|
+
tr.style.borderBottom = '1px solid #f0f0f0';
|
|
1527
|
+
tr.innerHTML = `
|
|
1528
|
+
<td style="padding:12px;font-weight:600;">${p.name}</td>
|
|
1529
|
+
<td style="padding:12px;text-align:right;">${fmt(p.quota)}</td>
|
|
1530
|
+
<td style="padding:12px;text-align:right;cursor:pointer;color:#3b82f6;" class="ai-forecast-cell" data-name="${p.name}" data-closed="${p.closed}" data-predicted="${p.ai - p.closed}" data-total="${p.ai}" onclick="openAICommitDrawer()">${fmt(p.ai)}</td>
|
|
1531
|
+
<td style="padding:12px;text-align:right;cursor:pointer;" onclick="filterOpps('${p.name}','Closed')">
|
|
1532
|
+
${fmt(p.closed)} ${arrow(p.closedArrow)}
|
|
1533
|
+
${pctBar(p.closedPct)}
|
|
1534
|
+
</td>
|
|
1535
|
+
<td style="padding:12px;text-align:right;cursor:pointer;" onclick="filterOpps('${p.name}','Commit')" class="commit-cell" data-name="${p.name}" data-commit="${p.commit}" data-orig="${p.commit - 200000}" data-arrow="${p.commitArrow}">
|
|
1536
|
+
${fmt(p.commit)} ${arrow(p.commitArrow)}
|
|
1537
|
+
${pctBar(p.commitPct)}
|
|
1538
|
+
</td>
|
|
1539
|
+
<td style="padding:12px;text-align:right;cursor:pointer;" onclick="filterOpps('${p.name}','Best Case')">
|
|
1540
|
+
${fmt(p.bestcase)} ${arrow(p.bestArrow)}
|
|
1541
|
+
</td>
|
|
1542
|
+
<td style="padding:12px;text-align:right;cursor:pointer;" onclick="filterOpps('${p.name}','Pipeline')">${fmt(p.pipeline)} ${arrow(p.pipeArrow)}</td>
|
|
1543
|
+
<td style="padding:12px;text-align:right;">${p.coverage}</td>
|
|
1544
|
+
`;
|
|
1545
|
+
// Hover卡片 (Closed/BestCase/Pipeline) - 仅有箭头时展示
|
|
1546
|
+
tr.querySelectorAll('td').forEach((td, i) => {
|
|
1547
|
+
if (i === 3 || i === 5 || i === 6) {
|
|
1548
|
+
const arrowDir = (i === 3 && p.closedArrow) || (i === 5 && p.bestArrow) || (i === 6 && p.pipeArrow);
|
|
1549
|
+
const fieldName = i === 3 ? 'Closed' : i === 5 ? 'Best Case' : 'Pipeline';
|
|
1550
|
+
if (arrowDir) {
|
|
1551
|
+
td.addEventListener('mouseenter', function (e) {
|
|
1552
|
+
const dir = i === 3 ? p.closedArrow : i === 5 ? p.bestArrow : p.pipeArrow;
|
|
1553
|
+
const fromVal = i === 3 ? p.closedFrom : i === 5 ? p.bestFrom : p.pipeFrom;
|
|
1554
|
+
const toVal = i === 3 ? p.closed : i === 5 ? p.bestcase : p.pipeline;
|
|
1555
|
+
showHoverCardDynamic(e, fieldName, dir, fromVal, toVal);
|
|
1556
|
+
});
|
|
1557
|
+
td.addEventListener('mouseleave', hideHoverCard);
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
});
|
|
1561
|
+
// Commit cell hover - 仅有箭头时展示,使用统一卡片样式
|
|
1562
|
+
const commitCell = tr.querySelector('.commit-cell');
|
|
1563
|
+
if (commitCell && p.commitArrow) {
|
|
1564
|
+
commitCell.addEventListener('mouseenter', function (e) {
|
|
1565
|
+
const commit = parseInt(this.dataset.commit);
|
|
1566
|
+
const orig = parseInt(this.dataset.orig);
|
|
1567
|
+
const diff = commit - orig;
|
|
1568
|
+
const isUp = diff >= 0;
|
|
1569
|
+
if (!hoverCard) {
|
|
1570
|
+
hoverCard = document.createElement('div');
|
|
1571
|
+
hoverCard.style.cssText = 'position:fixed;background:#fff;border-radius:10px;padding:14px;width:280px;box-shadow:0 8px 30px rgba(0,0,0,0.15);z-index:200;font-size:13px;pointer-events:none;';
|
|
1572
|
+
document.body.appendChild(hoverCard);
|
|
1573
|
+
}
|
|
1574
|
+
hoverCard.innerHTML = '<div style="font-weight:700;margin-bottom:6px;">Commit ' + (isUp ? 'increased' : 'decreased') + '</div><div>' + fmt(orig) + ' → <span style="color:#ec4899;font-weight:600;">' + fmt(commit) + '</span></div><div style="color:#999;font-size:11px;margin-top:6px;">Changed on Mar 25, 2026</div>';
|
|
1575
|
+
hoverCard.style.display = 'block';
|
|
1576
|
+
hoverCard.style.left = (e.clientX + 12) + 'px';
|
|
1577
|
+
hoverCard.style.top = (e.clientY + 12) + 'px';
|
|
1578
|
+
});
|
|
1579
|
+
commitCell.addEventListener('mouseleave', hideHoverCard);
|
|
1580
|
+
}
|
|
1581
|
+
// AI Forecast cell hover
|
|
1582
|
+
const aiCell = tr.querySelector('.ai-forecast-cell');
|
|
1583
|
+
if (aiCell) {
|
|
1584
|
+
aiCell.addEventListener('mouseenter', function (e) {
|
|
1585
|
+
const tt = document.getElementById('aiForecastTooltip');
|
|
1586
|
+
tt.querySelector('#afClosed').textContent = fmt(parseInt(this.dataset.closed));
|
|
1587
|
+
tt.querySelector('#afPredicted').textContent = fmt(parseInt(this.dataset.predicted));
|
|
1588
|
+
tt.querySelector('#afTotal').textContent = fmt(parseInt(this.dataset.total));
|
|
1589
|
+
tt.style.display = 'block';
|
|
1590
|
+
tt.style.left = (e.clientX + 12) + 'px';
|
|
1591
|
+
tt.style.top = (e.clientY + 12) + 'px';
|
|
1592
|
+
});
|
|
1593
|
+
aiCell.addEventListener('mouseleave', function () {
|
|
1594
|
+
document.getElementById('aiForecastTooltip').style.display = 'none';
|
|
1595
|
+
});
|
|
1596
|
+
}
|
|
1597
|
+
body.appendChild(tr);
|
|
1598
|
+
});
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
// 渲染商机列表
|
|
1602
|
+
function renderOpps(filter) {
|
|
1603
|
+
const body = document.getElementById('oppBody');
|
|
1604
|
+
const title = document.getElementById('oppListTitle');
|
|
1605
|
+
title.textContent = filter ? `Opportunity List — ${filter}` : 'Opportunity List';
|
|
1606
|
+
body.innerHTML = '';
|
|
1607
|
+
let totalAmt = 0;
|
|
1608
|
+
opps.forEach(o => { totalAmt += o.amount; });
|
|
1609
|
+
opps.forEach(o => {
|
|
1610
|
+
const tr = document.createElement('tr');
|
|
1611
|
+
tr.style.borderBottom = '1px solid #f0f0f0';
|
|
1612
|
+
tr.innerHTML = `
|
|
1613
|
+
<td style="padding:10px 12px;font-weight:500;color:#3b82f6;cursor:pointer;">${o.name}</td>
|
|
1614
|
+
<td style="padding:10px 12px;">${o.account}</td>
|
|
1615
|
+
<td style="padding:10px 12px;text-align:left;">${o.forecastType || 'Pipeline'}${o.aiSuggest ? '<span class="fc-suggest" data-cat="' + o.aiSuggest.cat + '" data-reason="' + o.aiSuggest.reason + '" style="cursor:pointer;margin-left:4px;">✨</span>' : ''}${o.riskWarning ? '<span class="fc-risk" data-msg="' + o.riskWarning + '" style="cursor:pointer;margin-left:4px;">⚠️</span>' : ''}${(o.aiSuggest || o.riskWarning) && o.isPrioritized ? '<span style="background:#e5e7eb;color:#666;padding:1px 6px;border-radius:3px;font-size:10px;margin-left:4px;">Prioritized</span>' : ''}</td>
|
|
1616
|
+
<td style="padding:10px 12px;text-align:right;">
|
|
1617
|
+
${fmt(o.amount)} ${o.amtArrow ? '<span class="change-arrow" data-type="amt" data-idx="' + opps.indexOf(o) + '">' + arrow(o.amtArrow) + '</span>' : ''}
|
|
1618
|
+
</td>
|
|
1619
|
+
<td style="padding:10px 12px;text-align:center;">
|
|
1620
|
+
${o.close} ${o.closeArrow ? '<span class="change-arrow" data-type="close" data-idx="' + opps.indexOf(o) + '">' + arrow(o.closeArrow) + '</span>' : ''}
|
|
1621
|
+
</td>
|
|
1622
|
+
<td style="padding:10px 12px;text-align:center;">
|
|
1623
|
+
${o.stage} ${o.stageArrow ? '<span class="change-arrow" data-type="stage" data-idx="' + opps.indexOf(o) + '">' + arrow(o.stageArrow) + '</span>' : ''}
|
|
1624
|
+
</td>
|
|
1625
|
+
<td style="padding:10px 12px;text-align:center;cursor:pointer;" class="score-cell" data-idx="${opps.indexOf(o)}"><span style="background:${o.scoreColor};color:#fff;padding:2px 8px;border-radius:10px;font-size:11px;">${o.score}</span></td>
|
|
1626
|
+
<td style="padding:10px 12px;text-align:center;cursor:pointer;color:#6366f1;font-weight:600;" class="win-rate" data-idx="${opps.indexOf(o)}">${o.winRate}</td>
|
|
1627
|
+
<td style="padding:10px 12px;text-align:center;color:#333;">${o.lastAct}</td>
|
|
1628
|
+
`;
|
|
1629
|
+
body.appendChild(tr);
|
|
1630
|
+
});
|
|
1631
|
+
bindTooltips();
|
|
1632
|
+
}
|
|
1633
|
+
</script>
|
|
1634
|
+
|
|
1635
|
+
<script>
|
|
1636
|
+
// 交互函数
|
|
1637
|
+
function setFilter(btn) {
|
|
1638
|
+
btn.parentElement.querySelectorAll('.filter-btn').forEach(b => b.classList.remove('active'));
|
|
1639
|
+
btn.classList.add('active');
|
|
1640
|
+
}
|
|
1641
|
+
function refreshData() { renderMatrix(); renderOpps(); }
|
|
1642
|
+
function filterOpps(name, category) {
|
|
1643
|
+
// 自动切回All Opportunities Tab
|
|
1644
|
+
switchOppTab('all');
|
|
1645
|
+
document.getElementById('oppListTitle').textContent = `Opportunity List — ${name} / ${category}`;
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
// 商机列表Tab切换
|
|
1649
|
+
function switchOppTab(tab) {
|
|
1650
|
+
const allTab = document.getElementById('oppTabAll');
|
|
1651
|
+
const watchTab = document.getElementById('oppTabWatch');
|
|
1652
|
+
const btns = document.querySelectorAll('.opp-tab-btn');
|
|
1653
|
+
if (tab === 'watch') {
|
|
1654
|
+
watchTab.style.display = 'block';
|
|
1655
|
+
allTab.style.display = 'none';
|
|
1656
|
+
btns[0].style.color = '#6366f1';
|
|
1657
|
+
btns[0].style.borderBottomColor = '#6366f1';
|
|
1658
|
+
btns[1].style.color = '#999';
|
|
1659
|
+
btns[1].style.borderBottomColor = 'transparent';
|
|
1660
|
+
} else {
|
|
1661
|
+
watchTab.style.display = 'none';
|
|
1662
|
+
allTab.style.display = 'block';
|
|
1663
|
+
btns[0].style.color = '#999';
|
|
1664
|
+
btns[0].style.borderBottomColor = 'transparent';
|
|
1665
|
+
btns[1].style.color = '#6366f1';
|
|
1666
|
+
btns[1].style.borderBottomColor = '#6366f1';
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
// Prioritized Deals 来源筛选
|
|
1671
|
+
function switchWatchSubTab(sub) {
|
|
1672
|
+
document.querySelectorAll('.watch-sub-tab').forEach(b => {
|
|
1673
|
+
b.style.border = '1px solid #ddd'; b.style.background = '#fff'; b.style.color = '#666';
|
|
1674
|
+
});
|
|
1675
|
+
event.target.style.border = '1px solid #6366f1'; event.target.style.background = '#f0f0ff'; event.target.style.color = '#6366f1';
|
|
1676
|
+
document.querySelectorAll('.prioritized-row').forEach(row => {
|
|
1677
|
+
if (sub === 'all') { row.style.display = ''; }
|
|
1678
|
+
else { row.style.display = row.dataset.source === sub ? '' : 'none'; }
|
|
1679
|
+
});
|
|
1680
|
+
}
|
|
1681
|
+
function openGapDrawer() { document.getElementById('gapDrawer').style.display = 'block'; }
|
|
1682
|
+
function closeGapDrawer() { document.getElementById('gapDrawer').style.display = 'none'; }
|
|
1683
|
+
function openRescueDrawer() { document.getElementById('rescueDrawer').style.display = 'block'; }
|
|
1684
|
+
function closeRescueDrawer() { document.getElementById('rescueDrawer').style.display = 'none'; }
|
|
1685
|
+
function openUpgradeDrawer() { document.getElementById('upgradeDrawer').style.display = 'block'; }
|
|
1686
|
+
function closeUpgradeDrawer() { document.getElementById('upgradeDrawer').style.display = 'none'; }
|
|
1687
|
+
function prioritizeDeal(cardId) {
|
|
1688
|
+
const card = document.getElementById(cardId);
|
|
1689
|
+
card.style.background = '#f0fdf4';
|
|
1690
|
+
card.style.borderColor = '#22c55e';
|
|
1691
|
+
card.querySelectorAll('button')[0].textContent = '✓ Prioritized';
|
|
1692
|
+
card.querySelectorAll('button')[0].style.background = '#22c55e';
|
|
1693
|
+
card.querySelectorAll('button')[1].style.display = 'none';
|
|
1694
|
+
}
|
|
1695
|
+
function dismissRescueDeal(cardId) {
|
|
1696
|
+
const card = document.getElementById(cardId);
|
|
1697
|
+
card.style.opacity = '0.4';
|
|
1698
|
+
card.style.pointerEvents = 'none';
|
|
1699
|
+
}
|
|
1700
|
+
function dismissUpgradeDeal(cardId) {
|
|
1701
|
+
const card = document.getElementById(cardId);
|
|
1702
|
+
card.style.opacity = '0.4';
|
|
1703
|
+
card.style.pointerEvents = 'none';
|
|
1704
|
+
}
|
|
1705
|
+
function openAICommitDrawer() { document.getElementById('aiCommitDrawer').style.display = 'block'; }
|
|
1706
|
+
function closeAICommitDrawer() { document.getElementById('aiCommitDrawer').style.display = 'none'; }
|
|
1707
|
+
function switchAICommitTab(tab) {
|
|
1708
|
+
document.querySelectorAll('.ai-commit-tab').forEach(b => {
|
|
1709
|
+
b.style.color = '#999'; b.style.borderBottomColor = 'transparent';
|
|
1710
|
+
});
|
|
1711
|
+
if (tab === 'pipeline') {
|
|
1712
|
+
document.querySelectorAll('.ai-commit-tab')[0].style.color = '#6366f1';
|
|
1713
|
+
document.querySelectorAll('.ai-commit-tab')[0].style.borderBottomColor = '#6366f1';
|
|
1714
|
+
document.getElementById('aiCommitPipelineTab').style.display = 'block';
|
|
1715
|
+
document.getElementById('aiCommitCommitTab').style.display = 'none';
|
|
1716
|
+
} else {
|
|
1717
|
+
document.querySelectorAll('.ai-commit-tab')[1].style.color = '#6366f1';
|
|
1718
|
+
document.querySelectorAll('.ai-commit-tab')[1].style.borderBottomColor = '#6366f1';
|
|
1719
|
+
document.getElementById('aiCommitPipelineTab').style.display = 'none';
|
|
1720
|
+
document.getElementById('aiCommitCommitTab').style.display = 'block';
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
function acceptDeal(id) {
|
|
1724
|
+
const card = document.getElementById('gapCard' + id);
|
|
1725
|
+
card.style.background = '#f0fdf4';
|
|
1726
|
+
card.style.borderColor = '#22c55e';
|
|
1727
|
+
card.querySelector('button:first-child').textContent = '✓ Following';
|
|
1728
|
+
card.querySelector('button:first-child').style.background = '#22c55e';
|
|
1729
|
+
card.querySelector('button:last-child').style.display = 'none';
|
|
1730
|
+
}
|
|
1731
|
+
function dismissDeal(id) {
|
|
1732
|
+
const card = document.getElementById('gapCard' + id);
|
|
1733
|
+
card.style.opacity = '0.4';
|
|
1734
|
+
card.style.pointerEvents = 'none';
|
|
1735
|
+
}
|
|
1736
|
+
|
|
1737
|
+
// Hover卡片 - 矩阵
|
|
1738
|
+
// Hover卡片 - 矩阵
|
|
1739
|
+
let hoverCard = null;
|
|
1740
|
+
function showHoverCardDynamic(e, fieldName, dir, fromVal, toVal) {
|
|
1741
|
+
if (!hoverCard) {
|
|
1742
|
+
hoverCard = document.createElement('div');
|
|
1743
|
+
hoverCard.style.cssText = 'position:fixed;background:#fff;border-radius:10px;padding:14px;width:280px;box-shadow:0 8px 30px rgba(0,0,0,0.15);z-index:200;font-size:13px;pointer-events:none;';
|
|
1744
|
+
document.body.appendChild(hoverCard);
|
|
1745
|
+
}
|
|
1746
|
+
var word = dir === 'up' ? 'increased' : 'decreased';
|
|
1747
|
+
var valHtml = (fromVal !== undefined && toVal !== undefined) ? '<div>' + fmt(fromVal) + ' → <span style="color:#ec4899;font-weight:600;">' + fmt(toVal) + '</span></div>' : '';
|
|
1748
|
+
hoverCard.innerHTML = '<div style="font-weight:700;margin-bottom:6px;">' + fieldName + ' ' + word + '</div>' + valHtml + '<div style="color:#999;font-size:11px;margin-top:6px;">Changed on Mar 28, 2026</div>';
|
|
1749
|
+
hoverCard.style.display = 'block';
|
|
1750
|
+
hoverCard.style.left = (e.clientX + 12) + 'px';
|
|
1751
|
+
hoverCard.style.top = (e.clientY + 12) + 'px';
|
|
1752
|
+
}
|
|
1753
|
+
function hideHoverCard() { if (hoverCard) hoverCard.style.display = 'none'; }
|
|
1754
|
+
|
|
1755
|
+
// Tooltips - 商机列表
|
|
1756
|
+
function bindTooltips() {
|
|
1757
|
+
// Score hover
|
|
1758
|
+
const scoreMap = { '优秀': 4, '良好': 3, '合格': 2, '风险': 1 };
|
|
1759
|
+
const scoreColorMap = { '优秀': '#22c55e', '良好': '#3b82f6', '合格': '#f59e0b', '风险': '#ef4444' };
|
|
1760
|
+
document.querySelectorAll('.score-cell').forEach(el => {
|
|
1761
|
+
el.addEventListener('mouseenter', function (e) {
|
|
1762
|
+
const idx = this.dataset.idx;
|
|
1763
|
+
const o = opps[idx];
|
|
1764
|
+
const st = document.getElementById('scoreTooltip');
|
|
1765
|
+
st.querySelector('#stScore').textContent = o.score;
|
|
1766
|
+
st.querySelector('#stScore').style.color = o.scoreColor;
|
|
1767
|
+
st.querySelector('#stSummary').textContent = o.scoreSummary;
|
|
1768
|
+
st.querySelector('#stDemand').textContent = o.scoreDims.demand;
|
|
1769
|
+
st.querySelector('#stDemand').style.color = scoreColorMap[o.scoreDims.demand];
|
|
1770
|
+
st.querySelector('#stDecision').textContent = o.scoreDims.decision;
|
|
1771
|
+
st.querySelector('#stDecision').style.color = scoreColorMap[o.scoreDims.decision];
|
|
1772
|
+
st.querySelector('#stBudget').textContent = o.scoreDims.budget;
|
|
1773
|
+
st.querySelector('#stBudget').style.color = scoreColorMap[o.scoreDims.budget];
|
|
1774
|
+
st.querySelector('#stCompete').textContent = o.scoreDims.compete;
|
|
1775
|
+
st.querySelector('#stCompete').style.color = scoreColorMap[o.scoreDims.compete];
|
|
1776
|
+
// Draw radar
|
|
1777
|
+
const canvas = document.getElementById('radarCanvas');
|
|
1778
|
+
const ctx = canvas.getContext('2d');
|
|
1779
|
+
ctx.clearRect(0, 0, 200, 200);
|
|
1780
|
+
const cx = 100, cy = 100, r = 70;
|
|
1781
|
+
const dims = [o.scoreDims.demand, o.scoreDims.decision, o.scoreDims.budget, o.scoreDims.compete];
|
|
1782
|
+
const vals = dims.map(d => scoreMap[d] / 4);
|
|
1783
|
+
const labels = ['需求', '决策链', '预算/采购', '竞争'];
|
|
1784
|
+
const angles = dims.map((_, i) => (Math.PI * 2 * i / 4) - Math.PI / 2);
|
|
1785
|
+
// Grid
|
|
1786
|
+
[0.25, 0.5, 0.75, 1].forEach(s => {
|
|
1787
|
+
ctx.beginPath();
|
|
1788
|
+
angles.forEach((a, i) => { const x = cx + Math.cos(a) * r * s; const y = cy + Math.sin(a) * r * s; i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y); });
|
|
1789
|
+
ctx.closePath(); ctx.strokeStyle = '#e5e7eb'; ctx.stroke();
|
|
1790
|
+
});
|
|
1791
|
+
// Axes
|
|
1792
|
+
angles.forEach(a => { ctx.beginPath(); ctx.moveTo(cx, cy); ctx.lineTo(cx + Math.cos(a) * r, cy + Math.sin(a) * r); ctx.strokeStyle = '#e5e7eb'; ctx.stroke(); });
|
|
1793
|
+
// Data
|
|
1794
|
+
ctx.beginPath();
|
|
1795
|
+
angles.forEach((a, i) => { const x = cx + Math.cos(a) * r * vals[i]; const y = cy + Math.sin(a) * r * vals[i]; i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y); });
|
|
1796
|
+
ctx.closePath(); ctx.fillStyle = 'rgba(99,102,241,0.2)'; ctx.fill(); ctx.strokeStyle = '#6366f1'; ctx.lineWidth = 2; ctx.stroke();
|
|
1797
|
+
// Labels
|
|
1798
|
+
ctx.fillStyle = '#666'; ctx.font = '11px sans-serif'; ctx.textAlign = 'center';
|
|
1799
|
+
labels.forEach((l, i) => { const x = cx + Math.cos(angles[i]) * (r + 16); const y = cy + Math.sin(angles[i]) * (r + 16) + 4; ctx.fillText(l, x, y); });
|
|
1800
|
+
st.style.display = 'block';
|
|
1801
|
+
st.style.left = (e.clientX - 370) + 'px';
|
|
1802
|
+
st.style.top = (e.clientY - 100) + 'px';
|
|
1803
|
+
});
|
|
1804
|
+
el.addEventListener('mouseleave', function () {
|
|
1805
|
+
document.getElementById('scoreTooltip').style.display = 'none';
|
|
1806
|
+
});
|
|
1807
|
+
});
|
|
1808
|
+
// AI Win Rate hover
|
|
1809
|
+
document.querySelectorAll('.win-rate').forEach(el => {
|
|
1810
|
+
el.addEventListener('mouseenter', function (e) {
|
|
1811
|
+
const idx = this.dataset.idx;
|
|
1812
|
+
const o = opps[idx];
|
|
1813
|
+
const tt = document.getElementById('winRateTooltip');
|
|
1814
|
+
tt.querySelector('#ttBaseline').textContent = o.baseline;
|
|
1815
|
+
tt.querySelector('#ttPositives').innerHTML = o.pos;
|
|
1816
|
+
tt.querySelector('#ttNegatives').innerHTML = o.neg;
|
|
1817
|
+
tt.querySelector('#ttWinRate').textContent = o.winRate;
|
|
1818
|
+
tt.style.display = 'block';
|
|
1819
|
+
tt.style.left = (e.clientX - 330) + 'px';
|
|
1820
|
+
tt.style.top = (e.clientY - 20) + 'px';
|
|
1821
|
+
});
|
|
1822
|
+
el.addEventListener('mouseleave', function () {
|
|
1823
|
+
document.getElementById('winRateTooltip').style.display = 'none';
|
|
1824
|
+
});
|
|
1825
|
+
});
|
|
1826
|
+
// 变动箭头hover
|
|
1827
|
+
document.querySelectorAll('.change-arrow').forEach(el => {
|
|
1828
|
+
el.style.cursor = 'pointer';
|
|
1829
|
+
el.addEventListener('mouseenter', function (e) {
|
|
1830
|
+
const idx = this.dataset.idx;
|
|
1831
|
+
const type = this.dataset.type;
|
|
1832
|
+
const o = opps[idx];
|
|
1833
|
+
const change = type === 'amt' ? o.amtChange : type === 'close' ? o.closeChange : o.stageChange;
|
|
1834
|
+
if (!change) return;
|
|
1835
|
+
const ct = document.getElementById('changeTooltip');
|
|
1836
|
+
ct.querySelector('#ctTitle').textContent = change.title;
|
|
1837
|
+
ct.querySelector('#ctDetail').innerHTML = change.from + ' → <span style="color:#ec4899;font-weight:600;">' + change.to + '</span>';
|
|
1838
|
+
ct.querySelector('#ctMeta').textContent = 'Changed on ' + change.date;
|
|
1839
|
+
ct.style.display = 'block';
|
|
1840
|
+
ct.style.left = (e.clientX + 12) + 'px';
|
|
1841
|
+
ct.style.top = (e.clientY + 12) + 'px';
|
|
1842
|
+
});
|
|
1843
|
+
el.addEventListener('mouseleave', function () {
|
|
1844
|
+
document.getElementById('changeTooltip').style.display = 'none';
|
|
1845
|
+
});
|
|
1846
|
+
});
|
|
1847
|
+
// AI Suggest hover
|
|
1848
|
+
document.querySelectorAll('.fc-suggest').forEach(el => {
|
|
1849
|
+
el.addEventListener('mouseenter', function (e) {
|
|
1850
|
+
const tt = document.getElementById('fcSuggestTooltip');
|
|
1851
|
+
tt.querySelector('#fcSuggestText').textContent = '✨ AI recommends: ' + this.dataset.cat;
|
|
1852
|
+
tt.querySelector('#fcSuggestReason').textContent = this.dataset.reason;
|
|
1853
|
+
tt.style.display = 'block';
|
|
1854
|
+
tt.style.left = (e.clientX + 12) + 'px';
|
|
1855
|
+
tt.style.top = (e.clientY + 12) + 'px';
|
|
1856
|
+
});
|
|
1857
|
+
el.addEventListener('mouseleave', function () {
|
|
1858
|
+
document.getElementById('fcSuggestTooltip').style.display = 'none';
|
|
1859
|
+
});
|
|
1860
|
+
});
|
|
1861
|
+
// Risk Warning click
|
|
1862
|
+
document.querySelectorAll('.fc-risk').forEach(el => {
|
|
1863
|
+
el.addEventListener('click', function (e) {
|
|
1864
|
+
e.stopPropagation();
|
|
1865
|
+
const tt = document.getElementById('fcRiskTooltip');
|
|
1866
|
+
tt.querySelector('#fcRiskReason').textContent = this.dataset.msg;
|
|
1867
|
+
tt.style.display = tt.style.display === 'block' ? 'none' : 'block';
|
|
1868
|
+
tt.style.left = (e.clientX + 12) + 'px';
|
|
1869
|
+
tt.style.top = (e.clientY + 12) + 'px';
|
|
1870
|
+
});
|
|
1871
|
+
});
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
// KPI卡片点击联动
|
|
1875
|
+
function filterPipelineByKpi(type) {
|
|
1876
|
+
const labels = { open: 'Open Pipeline', new: 'New', won: 'Won', lost: 'Lost', overdue: 'Overdue', amtChanged: 'Amount Changed', dateChanged: 'Close Date Changed' };
|
|
1877
|
+
document.getElementById('pipelineListTitle').textContent = 'Opportunity List — ' + (labels[type] || type);
|
|
1878
|
+
renderPipelineOpps(type);
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1881
|
+
// Tab切换
|
|
1882
|
+
function switchTab(tab) {
|
|
1883
|
+
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
|
|
1884
|
+
document.querySelectorAll('.tab-panel').forEach(p => p.classList.remove('active'));
|
|
1885
|
+
if (tab === 'pipeline') {
|
|
1886
|
+
document.querySelectorAll('.tab-btn')[0].classList.add('active');
|
|
1887
|
+
document.getElementById('pipelineTab').classList.add('active');
|
|
1888
|
+
document.getElementById('pageDesc').textContent = 'Pipeline: Review your opportunity funnel and deal health';
|
|
1889
|
+
renderPipelineOpps('Open');
|
|
1890
|
+
} else {
|
|
1891
|
+
document.querySelectorAll('.tab-btn')[1].classList.add('active');
|
|
1892
|
+
document.getElementById('forecastTab').classList.add('active');
|
|
1893
|
+
document.getElementById('pageDesc').textContent = 'Forecast: Monitor quota attainment, review team forecasts, and uncover risks';
|
|
1894
|
+
}
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1897
|
+
// 阶段切换
|
|
1898
|
+
function switchStage(el, stage) {
|
|
1899
|
+
document.querySelectorAll('.stage-tab').forEach(t => t.classList.remove('active'));
|
|
1900
|
+
el.classList.add('active');
|
|
1901
|
+
document.getElementById('pipelineListTitle').textContent = 'Opportunity List — ' + stage;
|
|
1902
|
+
renderPipelineOpps(stage);
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1905
|
+
// 看板区点击联动阶段切换区
|
|
1906
|
+
function selectStageByName(stageName) {
|
|
1907
|
+
const tabs = document.querySelectorAll('.stage-tab');
|
|
1908
|
+
tabs.forEach(tab => {
|
|
1909
|
+
const name = tab.querySelector('.stage-name').textContent.trim();
|
|
1910
|
+
if (name === stageName) {
|
|
1911
|
+
switchStage(tab, stageName);
|
|
1912
|
+
}
|
|
1913
|
+
});
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
// Pipeline商机数据
|
|
1917
|
+
const pipelineOpps = [
|
|
1918
|
+
{ name: 'Apollo Project', type: 'Commit', account: 'Huawei Tech', amount: 1000000, amtArrow: 'up', amtChange: { title: 'Amount increased', from: '$800,000', to: '$1,000,000', date: 'Mar 28, 2026' }, close: '2026-04-15', closeArrow: 'down', closeChange: { title: 'Close Date decreased', from: 'Mar 30', to: 'Apr 15', date: 'Mar 25, 2026' }, stage: 'Negotiation', stageArrow: 'up', stageChange: { title: 'Stage increased', from: 'Proposal', to: 'Negotiation', date: 'Mar 20, 2026' }, score: 'High', scoreColor: '#22c55e', scoreDims: { demand: '优秀', decision: '良好', budget: '合格', compete: '良好' }, scoreSummary: 'Customer has clear needs and budget allocated.', winRate: '86%', baseline: '80%', pos: 'Opportunity amount keeps going up. +6%<br>Past wins with this account. +4%', neg: 'Moving slowly vs historical velocity. -4%', lastAct: '2026-03-28 14:32:05' },
|
|
1919
|
+
{ name: 'Starlight Initiative', type: 'Pipeline', account: 'Tencent', amount: 500000, amtArrow: '', amtChange: null, close: '2026-04-20', closeArrow: '', closeChange: null, stage: 'Needs Analysis', stageArrow: '', stageChange: null, score: 'Medium', scoreColor: '#f59e0b', scoreDims: { demand: '良好', decision: '风险', budget: '风险', compete: '合格' }, scoreSummary: 'No executive sponsor. Budget unclear.', winRate: '52%', baseline: '59%', pos: 'Strong engagement signals. +8%', neg: 'No executive sponsor identified. -10%<br>Deal size above average. -5%', lastAct: '2026-03-25 09:15:22' },
|
|
1920
|
+
{ name: 'Galaxy Partnership', type: 'Best Case', account: 'Alibaba', amount: 2000000, amtArrow: 'down', amtChange: { title: 'Amount decreased', from: '$2,500,000', to: '$2,000,000', date: 'Mar 27, 2026' }, close: '2026-05-10', closeArrow: '', closeChange: null, stage: 'Proposal', stageArrow: 'up', stageChange: { title: 'Stage increased', from: 'Prospecting', to: 'Proposal', date: 'Mar 26, 2026' }, score: 'Medium', scoreColor: '#f59e0b', scoreDims: { demand: '合格', decision: '良好', budget: '合格', compete: '风险' }, scoreSummary: 'Strong competitor. Procurement timeline long.', winRate: '45%', baseline: '54%', pos: 'Past wins with this account. +5%', neg: 'Moving slowly. -8%<br>Low activity count. -6%', lastAct: '2026-03-27 16:48:37' },
|
|
1921
|
+
{ name: 'Aurora Solution', type: 'Commit', account: 'ByteDance', amount: 800000, amtArrow: '', amtChange: null, close: '2026-04-30', closeArrow: 'up', closeChange: { title: 'Close Date increased', from: 'May 15', to: 'Apr 30', date: 'Mar 28, 2026' }, stage: 'Commit', stageArrow: '', stageChange: null, score: 'High', scoreColor: '#22c55e', scoreDims: { demand: '优秀', decision: '优秀', budget: '良好', compete: '优秀' }, scoreSummary: 'All dimensions strong. No significant competition.', winRate: '91%', baseline: '78%', pos: 'Very high engagement. +10%<br>Progressing faster than average. +5%', neg: 'Slightly above average deal size. -2%', lastAct: '2026-03-29 11:05:18' },
|
|
1922
|
+
{ name: 'Cloud Migration', type: 'Pipeline', account: 'Meituan', amount: 1700000, amtArrow: '', amtChange: null, close: '2026-05-20', closeArrow: '', closeChange: null, stage: 'Prospecting', stageArrow: '', stageChange: null, score: 'Low', scoreColor: '#ef4444', scoreDims: { demand: '合格', decision: '风险', budget: '风险', compete: '风险' }, scoreSummary: 'Early stage. No decision maker identified.', winRate: '28%', baseline: '45%', pos: 'Large deal size potential. +3%', neg: 'No activity in 14 days. -12%<br>No sponsor. -8%', lastAct: '2026-03-20 08:22:41' },
|
|
1923
|
+
{ name: 'Data Platform', type: 'Best Case', account: 'JD.com', amount: 3500000, amtArrow: 'up', amtChange: { title: 'Amount increased', from: '$3,000,000', to: '$3,500,000', date: 'Mar 28, 2026' }, close: '2026-04-25', closeArrow: 'down', closeChange: { title: 'Close Date decreased', from: 'Apr 15', to: 'Apr 25', date: 'Mar 26, 2026' }, stage: 'Proposal', stageArrow: 'up', stageChange: { title: 'Stage increased', from: 'Needs Analysis', to: 'Proposal', date: 'Mar 25, 2026' }, score: 'High', scoreColor: '#22c55e', scoreDims: { demand: '优秀', decision: '良好', budget: '良好', compete: '合格' }, scoreSummary: 'Strong demand. Budget approved. Moderate competition.', winRate: '78%', baseline: '72%', pos: 'Amount increasing. +5%<br>High engagement. +8%', neg: 'Close date pushed. -7%', lastAct: '2026-03-28 14:32:05' }
|
|
1924
|
+
];
|
|
1925
|
+
|
|
1926
|
+
function renderPipelineOpps(stage) {
|
|
1927
|
+
const body = document.getElementById('pipelineOppBody');
|
|
1928
|
+
body.innerHTML = '';
|
|
1929
|
+
const scoreMap = { '优秀': 4, '良好': 3, '合格': 2, '风险': 1 };
|
|
1930
|
+
pipelineOpps.forEach((o, idx) => {
|
|
1931
|
+
const tr = document.createElement('tr');
|
|
1932
|
+
tr.style.borderBottom = '1px solid #f0f0f0';
|
|
1933
|
+
const arrowHtml = (dir) => dir === 'up' ? '<span style="color:#22c55e;font-weight:700;margin-left:4px;">↑</span>' : dir === 'down' ? '<span style="color:#ef4444;font-weight:700;margin-left:4px;">↓</span>' : '';
|
|
1934
|
+
tr.innerHTML = `
|
|
1935
|
+
<td style="padding:10px 12px;font-weight:500;color:#3b82f6;cursor:pointer;">${o.name} <span style="color:#ccc;font-size:11px;cursor:pointer;" title="Edit">✏️</span></td>
|
|
1936
|
+
<td style="padding:10px 12px;text-align:right;">$${o.amount.toLocaleString()} <span style="color:#ccc;font-size:11px;cursor:pointer;" title="Edit">✏️</span>${o.amtArrow ? '<span class="pl-change" data-type="amt" data-idx="' + idx + '" style="cursor:pointer;">' + arrowHtml(o.amtArrow) + '</span>' : ''}</td>
|
|
1937
|
+
<td style="padding:10px 12px;text-align:center;">${o.close} <span style="color:#ccc;font-size:11px;cursor:pointer;" title="Edit">✏️</span>${o.closeArrow ? '<span class="pl-change" data-type="close" data-idx="' + idx + '" style="cursor:pointer;">' + arrowHtml(o.closeArrow) + '</span>' : ''}</td>
|
|
1938
|
+
<td style="padding:10px 12px;text-align:center;">${o.stage}${o.stageArrow ? '<span class="pl-change" data-type="stage" data-idx="' + idx + '" style="cursor:pointer;">' + arrowHtml(o.stageArrow) + '</span>' : ''}</td>
|
|
1939
|
+
<td style="padding:10px 12px;text-align:center;cursor:pointer;" class="pl-score" data-idx="${idx}"><span style="background:${o.scoreColor};color:#fff;padding:2px 8px;border-radius:10px;font-size:11px;">${o.score}</span></td>
|
|
1940
|
+
<td style="padding:10px 12px;text-align:center;cursor:pointer;color:#333;" class="pl-winrate" data-idx="${idx}" style="color:#333;">${o.winRate}</td>
|
|
1941
|
+
<td style="padding:10px 12px;text-align:center;color:#333;">${o.lastAct}</td>
|
|
1942
|
+
`;
|
|
1943
|
+
body.appendChild(tr);
|
|
1944
|
+
});
|
|
1945
|
+
bindPipelineTooltips();
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
function bindPipelineTooltips() {
|
|
1949
|
+
const scoreMap = { '优秀': 4, '良好': 3, '合格': 2, '风险': 1 };
|
|
1950
|
+
const scoreColorMap = { '优秀': '#22c55e', '良好': '#3b82f6', '合格': '#f59e0b', '风险': '#ef4444' };
|
|
1951
|
+
// Amount/CloseDate/Stage change arrows
|
|
1952
|
+
document.querySelectorAll('.pl-change').forEach(el => {
|
|
1953
|
+
el.addEventListener('mouseenter', function (e) {
|
|
1954
|
+
const idx = this.dataset.idx;
|
|
1955
|
+
const type = this.dataset.type;
|
|
1956
|
+
const o = pipelineOpps[idx];
|
|
1957
|
+
const change = type === 'amt' ? o.amtChange : type === 'close' ? o.closeChange : o.stageChange;
|
|
1958
|
+
if (!change) return;
|
|
1959
|
+
const ct = document.getElementById('changeTooltip');
|
|
1960
|
+
ct.querySelector('#ctTitle').textContent = change.title;
|
|
1961
|
+
ct.querySelector('#ctDetail').innerHTML = change.from + ' → <span style="color:#ec4899;font-weight:600;">' + change.to + '</span>';
|
|
1962
|
+
ct.querySelector('#ctMeta').textContent = 'Changed on ' + change.date;
|
|
1963
|
+
ct.style.display = 'block';
|
|
1964
|
+
ct.style.left = (e.clientX + 12) + 'px';
|
|
1965
|
+
ct.style.top = (e.clientY + 12) + 'px';
|
|
1966
|
+
});
|
|
1967
|
+
el.addEventListener('mouseleave', function () {
|
|
1968
|
+
document.getElementById('changeTooltip').style.display = 'none';
|
|
1969
|
+
});
|
|
1970
|
+
});
|
|
1971
|
+
// Score hover
|
|
1972
|
+
document.querySelectorAll('.pl-score').forEach(el => {
|
|
1973
|
+
el.addEventListener('mouseenter', function (e) {
|
|
1974
|
+
const o = pipelineOpps[this.dataset.idx];
|
|
1975
|
+
const st = document.getElementById('scoreTooltip');
|
|
1976
|
+
st.querySelector('#stScore').textContent = o.score;
|
|
1977
|
+
st.querySelector('#stScore').style.color = o.scoreColor;
|
|
1978
|
+
st.querySelector('#stSummary').textContent = o.scoreSummary;
|
|
1979
|
+
st.querySelector('#stDemand').textContent = o.scoreDims.demand;
|
|
1980
|
+
st.querySelector('#stDemand').style.color = scoreColorMap[o.scoreDims.demand];
|
|
1981
|
+
st.querySelector('#stDecision').textContent = o.scoreDims.decision;
|
|
1982
|
+
st.querySelector('#stDecision').style.color = scoreColorMap[o.scoreDims.decision];
|
|
1983
|
+
st.querySelector('#stBudget').textContent = o.scoreDims.budget;
|
|
1984
|
+
st.querySelector('#stBudget').style.color = scoreColorMap[o.scoreDims.budget];
|
|
1985
|
+
st.querySelector('#stCompete').textContent = o.scoreDims.compete;
|
|
1986
|
+
st.querySelector('#stCompete').style.color = scoreColorMap[o.scoreDims.compete];
|
|
1987
|
+
const canvas = document.getElementById('radarCanvas');
|
|
1988
|
+
const ctx = canvas.getContext('2d');
|
|
1989
|
+
ctx.clearRect(0, 0, 200, 200);
|
|
1990
|
+
const cx = 100, cy = 100, r = 70;
|
|
1991
|
+
const dims = [o.scoreDims.demand, o.scoreDims.decision, o.scoreDims.budget, o.scoreDims.compete];
|
|
1992
|
+
const vals = dims.map(d => scoreMap[d] / 4);
|
|
1993
|
+
const labels = ['需求', '决策链', '预算/采购', '竞争'];
|
|
1994
|
+
const angles = dims.map((_, i) => (Math.PI * 2 * i / 4) - Math.PI / 2);
|
|
1995
|
+
[0.25, 0.5, 0.75, 1].forEach(s => { ctx.beginPath(); angles.forEach((a, i) => { const x = cx + Math.cos(a) * r * s; const y = cy + Math.sin(a) * r * s; i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y); }); ctx.closePath(); ctx.strokeStyle = '#e5e7eb'; ctx.stroke(); });
|
|
1996
|
+
angles.forEach(a => { ctx.beginPath(); ctx.moveTo(cx, cy); ctx.lineTo(cx + Math.cos(a) * r, cy + Math.sin(a) * r); ctx.strokeStyle = '#e5e7eb'; ctx.stroke(); });
|
|
1997
|
+
ctx.beginPath(); angles.forEach((a, i) => { const x = cx + Math.cos(a) * r * vals[i]; const y = cy + Math.sin(a) * r * vals[i]; i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y); }); ctx.closePath(); ctx.fillStyle = 'rgba(99,102,241,0.2)'; ctx.fill(); ctx.strokeStyle = '#6366f1'; ctx.lineWidth = 2; ctx.stroke();
|
|
1998
|
+
ctx.fillStyle = '#666'; ctx.font = '11px sans-serif'; ctx.textAlign = 'center';
|
|
1999
|
+
labels.forEach((l, i) => { ctx.fillText(l, cx + Math.cos(angles[i]) * (r + 16), cy + Math.sin(angles[i]) * (r + 16) + 4); });
|
|
2000
|
+
st.style.display = 'block';
|
|
2001
|
+
st.style.left = (e.clientX - 370) + 'px';
|
|
2002
|
+
st.style.top = (e.clientY - 100) + 'px';
|
|
2003
|
+
});
|
|
2004
|
+
el.addEventListener('mouseleave', function () { document.getElementById('scoreTooltip').style.display = 'none'; });
|
|
2005
|
+
});
|
|
2006
|
+
// AI Win Rate hover
|
|
2007
|
+
document.querySelectorAll('.pl-winrate').forEach(el => {
|
|
2008
|
+
el.addEventListener('mouseenter', function (e) {
|
|
2009
|
+
const o = pipelineOpps[this.dataset.idx];
|
|
2010
|
+
const tt = document.getElementById('winRateTooltip');
|
|
2011
|
+
tt.querySelector('#ttBaseline').textContent = o.baseline;
|
|
2012
|
+
tt.querySelector('#ttPositives').innerHTML = o.pos;
|
|
2013
|
+
tt.querySelector('#ttNegatives').innerHTML = o.neg;
|
|
2014
|
+
tt.querySelector('#ttWinRate').textContent = o.winRate;
|
|
2015
|
+
tt.style.display = 'block';
|
|
2016
|
+
tt.style.left = (e.clientX - 330) + 'px';
|
|
2017
|
+
tt.style.top = (e.clientY - 20) + 'px';
|
|
2018
|
+
});
|
|
2019
|
+
el.addEventListener('mouseleave', function () { document.getElementById('winRateTooltip').style.display = 'none'; });
|
|
2020
|
+
});
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2023
|
+
// 初始化
|
|
2024
|
+
renderPipelineOpps('Open');
|
|
2025
|
+
renderMatrix();
|
|
2026
|
+
renderOpps();
|
|
2027
|
+
</script>
|
|
2028
|
+
|
|
2029
|
+
<!-- AI分析弹窗 - Pipeline Funnel -->
|
|
2030
|
+
<div id="funnelAiModal"
|
|
2031
|
+
style="display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.4);z-index:300;justify-content:center;align-items:center;"
|
|
2032
|
+
onclick="if(event.target===this)this.style.display='none'">
|
|
2033
|
+
<div style="background:#fff;border-radius:12px;padding:24px;width:480px;box-shadow:0 20px 60px rgba(0,0,0,0.2);">
|
|
2034
|
+
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;">
|
|
2035
|
+
<h3 style="font-size:16px;">✨ AI Insight</h3>
|
|
2036
|
+
<span onclick="document.getElementById('funnelAiModal').style.display='none'"
|
|
2037
|
+
style="cursor:pointer;font-size:20px;color:#999;">✕</span>
|
|
2038
|
+
</div>
|
|
2039
|
+
<div style="font-size:13px;color:#555;line-height:1.8;">
|
|
2040
|
+
Proposal stage holds the largest amount ($4.5M), accounting for 40.5% of total pipeline. Consider reviewing deal
|
|
2041
|
+
progression speed in this stage to prevent stagnation.<br><br>
|
|
2042
|
+
Closed Won conversion rate is 10.0% from Prospecting — below the historical average of 15%. The drop-off between
|
|
2043
|
+
Needs Analysis and Proposal suggests qualification criteria may need tightening.
|
|
2044
|
+
</div>
|
|
2045
|
+
</div>
|
|
2046
|
+
</div>
|
|
2047
|
+
|
|
2048
|
+
<!-- AI分析弹窗 - Avg. Time in Stage -->
|
|
2049
|
+
<div id="stageTimeAiModal"
|
|
2050
|
+
style="display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.4);z-index:300;justify-content:center;align-items:center;"
|
|
2051
|
+
onclick="if(event.target===this)this.style.display='none'">
|
|
2052
|
+
<div style="background:#fff;border-radius:12px;padding:24px;width:480px;box-shadow:0 20px 60px rgba(0,0,0,0.2);">
|
|
2053
|
+
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;">
|
|
2054
|
+
<h3 style="font-size:16px;">✨ AI Insight</h3>
|
|
2055
|
+
<span onclick="document.getElementById('stageTimeAiModal').style.display='none'"
|
|
2056
|
+
style="cursor:pointer;font-size:20px;color:#999;">✕</span>
|
|
2057
|
+
</div>
|
|
2058
|
+
<div style="font-size:13px;color:#555;line-height:1.8;">
|
|
2059
|
+
Proposal stage avg. time (22d 8h) exceeds the limit of 20 days. 3 deals have been stuck for over 25 days —
|
|
2060
|
+
recommend checking decision chain engagement.<br><br>
|
|
2061
|
+
Prospecting stage is performing well at 8d 6h, within the target of 10 days. Needs Analysis shows early signs of
|
|
2062
|
+
delay at 15d 12h, approaching the limit threshold.
|
|
2063
|
+
</div>
|
|
2064
|
+
</div>
|
|
2065
|
+
</div>
|
|
2066
|
+
|
|
2067
|
+
<!-- AI分析弹窗 - Forecast Overview -->
|
|
2068
|
+
<div id="forecastAiModal"
|
|
2069
|
+
style="display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.4);z-index:300;justify-content:center;align-items:center;"
|
|
2070
|
+
onclick="if(event.target===this)this.style.display='none'">
|
|
2071
|
+
<div style="background:#fff;border-radius:12px;padding:24px;width:480px;box-shadow:0 20px 60px rgba(0,0,0,0.2);">
|
|
2072
|
+
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;">
|
|
2073
|
+
<h3 style="font-size:16px;">✨ AI Insight</h3>
|
|
2074
|
+
<span onclick="document.getElementById('forecastAiModal').style.display='none'"
|
|
2075
|
+
style="cursor:pointer;font-size:20px;color:#999;">✕</span>
|
|
2076
|
+
</div>
|
|
2077
|
+
<div style="border-left:4px solid #6366f1; padding-left:16px;">
|
|
2078
|
+
<div style="font-size:13px;font-weight:600;color:#6366f1;margin-bottom:8px;">Current Status</div>
|
|
2079
|
+
<div style="font-size:13px;color:#555;line-height:1.8;">
|
|
2080
|
+
Current forecast ($7.5M) covers 75% of quota. AI predicts $6.8M — a $700K gap vs. team commit.<br><br>
|
|
2081
|
+
Steve's coverage (3.1x) is strong but commit rate is low at 23%, suggest reviewing deal quality and ensuring
|
|
2082
|
+
high-score deals are properly prioritized in the commit list.
|
|
2083
|
+
</div>
|
|
2084
|
+
</div>
|
|
2085
|
+
</div>
|
|
2086
|
+
</div>
|
|
2087
|
+
|
|
2088
|
+
<!-- 日期范围选择弹窗 -->
|
|
2089
|
+
<div id="datePickerOverlay" class="date-picker-overlay" onclick="if(event.target===this)closeDatePicker()">
|
|
2090
|
+
<div class="date-picker-modal">
|
|
2091
|
+
<div class="date-picker-header">
|
|
2092
|
+
<div id="dpStartBox" class="date-input-box" onclick="dpFocus='start'"><span>Select date</span><span>📅</span>
|
|
2093
|
+
</div>
|
|
2094
|
+
<div id="dpEndBox" class="date-input-box" onclick="dpFocus='end'"><span>Select date</span><span>📅</span></div>
|
|
2095
|
+
</div>
|
|
2096
|
+
<div class="date-picker-body">
|
|
2097
|
+
<div class="calendar">
|
|
2098
|
+
<div class="calendar-nav">
|
|
2099
|
+
<span onclick="dpNav(-12,'left')">«</span>
|
|
2100
|
+
<span onclick="dpNav(-1,'left')">‹</span>
|
|
2101
|
+
<span id="dpLeftTitle">Mar 2026</span>
|
|
2102
|
+
<span onclick="dpNav(1,'left')">›</span>
|
|
2103
|
+
<span onclick="dpNav(12,'left')">»</span>
|
|
2104
|
+
</div>
|
|
2105
|
+
<div class="calendar-grid" id="dpLeftGrid"></div>
|
|
2106
|
+
</div>
|
|
2107
|
+
<div class="calendar">
|
|
2108
|
+
<div class="calendar-nav">
|
|
2109
|
+
<span onclick="dpNav(-12,'right')">«</span>
|
|
2110
|
+
<span onclick="dpNav(-1,'right')">‹</span>
|
|
2111
|
+
<span id="dpRightTitle">Mar 2026</span>
|
|
2112
|
+
<span onclick="dpNav(1,'right')">›</span>
|
|
2113
|
+
<span onclick="dpNav(12,'right')">»</span>
|
|
2114
|
+
</div>
|
|
2115
|
+
<div class="calendar-grid" id="dpRightGrid"></div>
|
|
2116
|
+
</div>
|
|
2117
|
+
</div>
|
|
2118
|
+
<div class="date-picker-footer">
|
|
2119
|
+
<button onclick="confirmDatePicker()">OK</button>
|
|
2120
|
+
</div>
|
|
2121
|
+
</div>
|
|
2122
|
+
</div>
|
|
2123
|
+
|
|
2124
|
+
<!-- 单日期选择弹窗(Changes Since) -->
|
|
2125
|
+
<div id="singleDateOverlay" class="date-picker-overlay" onclick="if(event.target===this)closeSingleDatePicker()">
|
|
2126
|
+
<div class="date-picker-modal" style="width:360px;">
|
|
2127
|
+
<div class="date-picker-header">
|
|
2128
|
+
<div id="sdpDateBox" class="date-input-box"><span>Select date</span><span>📅</span></div>
|
|
2129
|
+
</div>
|
|
2130
|
+
<div class="date-picker-body">
|
|
2131
|
+
<div class="calendar" style="flex:1;">
|
|
2132
|
+
<div class="calendar-nav">
|
|
2133
|
+
<span onclick="sdpNav(-12)">«</span>
|
|
2134
|
+
<span onclick="sdpNav(-1)">‹</span>
|
|
2135
|
+
<span id="sdpTitle">Mar 2026</span>
|
|
2136
|
+
<span onclick="sdpNav(1)">›</span>
|
|
2137
|
+
<span onclick="sdpNav(12)">»</span>
|
|
2138
|
+
</div>
|
|
2139
|
+
<div class="calendar-grid" id="sdpGrid"></div>
|
|
2140
|
+
</div>
|
|
2141
|
+
</div>
|
|
2142
|
+
<div class="date-picker-footer">
|
|
2143
|
+
<button onclick="confirmSingleDatePicker()">OK</button>
|
|
2144
|
+
</div>
|
|
2145
|
+
</div>
|
|
2146
|
+
</div>
|
|
2147
|
+
|
|
2148
|
+
<script>
|
|
2149
|
+
// 日期范围选择器逻辑
|
|
2150
|
+
let dpSource = 'pipeline'; // 当前触发来源
|
|
2151
|
+
let dpFocus = 'start'; // 当前聚焦:start / end
|
|
2152
|
+
let dpStartDate = null;
|
|
2153
|
+
let dpEndDate = null;
|
|
2154
|
+
let dpLeftMonth = new Date(2026, 2, 1); // Mar 2026
|
|
2155
|
+
let dpRightMonth = new Date(2026, 2, 1);
|
|
2156
|
+
|
|
2157
|
+
const dpMonthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
2158
|
+
|
|
2159
|
+
function onCloseDateChange(sel) {
|
|
2160
|
+
if (sel.value === 'Custom') {
|
|
2161
|
+
dpSource = sel.id === 'forecastCloseDate' ? 'forecast' : 'pipeline';
|
|
2162
|
+
openDatePicker(dpSource);
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2166
|
+
function openDatePicker(source) {
|
|
2167
|
+
dpSource = source;
|
|
2168
|
+
dpStartDate = null;
|
|
2169
|
+
dpEndDate = null;
|
|
2170
|
+
dpFocus = 'start';
|
|
2171
|
+
dpLeftMonth = new Date(2026, 2, 1);
|
|
2172
|
+
dpRightMonth = new Date(2026, 2, 1);
|
|
2173
|
+
document.getElementById('dpStartBox').querySelector('span').textContent = 'Select date';
|
|
2174
|
+
document.getElementById('dpStartBox').classList.remove('has-value');
|
|
2175
|
+
document.getElementById('dpEndBox').querySelector('span').textContent = 'Select date';
|
|
2176
|
+
document.getElementById('dpEndBox').classList.remove('has-value');
|
|
2177
|
+
renderCalendars();
|
|
2178
|
+
document.getElementById('datePickerOverlay').style.display = 'block';
|
|
2179
|
+
}
|
|
2180
|
+
|
|
2181
|
+
function closeDatePicker() {
|
|
2182
|
+
document.getElementById('datePickerOverlay').style.display = 'none';
|
|
2183
|
+
// 如果没选日期,恢复下拉为This Quarter
|
|
2184
|
+
if (!dpStartDate || !dpEndDate) {
|
|
2185
|
+
const sel = document.getElementById(dpSource === 'forecast' ? 'forecastCloseDate' : 'pipelineCloseDate');
|
|
2186
|
+
sel.value = 'This Quarter';
|
|
2187
|
+
const rangeSpan = document.getElementById(dpSource === 'forecast' ? 'forecastCustomRange' : 'pipelineCustomRange');
|
|
2188
|
+
rangeSpan.style.display = 'none';
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
|
|
2192
|
+
function dpNav(offset, side) {
|
|
2193
|
+
if (side === 'left') {
|
|
2194
|
+
dpLeftMonth = new Date(dpLeftMonth.getFullYear(), dpLeftMonth.getMonth() + offset, 1);
|
|
2195
|
+
} else {
|
|
2196
|
+
dpRightMonth = new Date(dpRightMonth.getFullYear(), dpRightMonth.getMonth() + offset, 1);
|
|
2197
|
+
}
|
|
2198
|
+
renderCalendars();
|
|
2199
|
+
}
|
|
2200
|
+
|
|
2201
|
+
function renderCalendars() {
|
|
2202
|
+
renderOneCalendar('dpLeftGrid', 'dpLeftTitle', dpLeftMonth);
|
|
2203
|
+
renderOneCalendar('dpRightGrid', 'dpRightTitle', dpRightMonth);
|
|
2204
|
+
}
|
|
2205
|
+
|
|
2206
|
+
function renderOneCalendar(gridId, titleId, monthDate) {
|
|
2207
|
+
const grid = document.getElementById(gridId);
|
|
2208
|
+
const title = document.getElementById(titleId);
|
|
2209
|
+
const year = monthDate.getFullYear();
|
|
2210
|
+
const month = monthDate.getMonth();
|
|
2211
|
+
title.textContent = dpMonthNames[month] + ' ' + year;
|
|
2212
|
+
|
|
2213
|
+
let html = '';
|
|
2214
|
+
['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'].forEach(d => { html += '<div class="day-header">' + d + '</div>'; });
|
|
2215
|
+
|
|
2216
|
+
const firstDay = new Date(year, month, 1).getDay();
|
|
2217
|
+
const daysInMonth = new Date(year, month + 1, 0).getDate();
|
|
2218
|
+
const prevDays = new Date(year, month, 0).getDate();
|
|
2219
|
+
const today = new Date();
|
|
2220
|
+
|
|
2221
|
+
// 上月尾部
|
|
2222
|
+
for (let i = firstDay - 1; i >= 0; i--) {
|
|
2223
|
+
html += '<div class="day other-month">' + (prevDays - i) + '</div>';
|
|
2224
|
+
}
|
|
2225
|
+
// 本月
|
|
2226
|
+
for (let d = 1; d <= daysInMonth; d++) {
|
|
2227
|
+
const date = new Date(year, month, d);
|
|
2228
|
+
let cls = 'day';
|
|
2229
|
+
if (date.toDateString() === today.toDateString()) cls += ' today';
|
|
2230
|
+
if (dpStartDate && date.toDateString() === dpStartDate.toDateString()) cls += ' selected';
|
|
2231
|
+
if (dpEndDate && date.toDateString() === dpEndDate.toDateString()) cls += ' selected';
|
|
2232
|
+
if (dpStartDate && dpEndDate && date > dpStartDate && date < dpEndDate) cls += ' in-range';
|
|
2233
|
+
html += '<div class="' + cls + '" onclick="dpSelectDay(' + year + ',' + month + ',' + d + ')">' + d + '</div>';
|
|
2234
|
+
}
|
|
2235
|
+
// 下月头部
|
|
2236
|
+
const totalCells = firstDay + daysInMonth;
|
|
2237
|
+
const remaining = (7 - totalCells % 7) % 7;
|
|
2238
|
+
for (let i = 1; i <= remaining; i++) {
|
|
2239
|
+
html += '<div class="day other-month">' + i + '</div>';
|
|
2240
|
+
}
|
|
2241
|
+
grid.innerHTML = html;
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
function dpSelectDay(y, m, d) {
|
|
2245
|
+
const date = new Date(y, m, d);
|
|
2246
|
+
if (dpFocus === 'start') {
|
|
2247
|
+
dpStartDate = date;
|
|
2248
|
+
if (dpEndDate && dpEndDate < dpStartDate) dpEndDate = null;
|
|
2249
|
+
dpFocus = 'end';
|
|
2250
|
+
} else {
|
|
2251
|
+
if (date < dpStartDate) {
|
|
2252
|
+
dpStartDate = date;
|
|
2253
|
+
} else {
|
|
2254
|
+
dpEndDate = date;
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2257
|
+
// 更新输入框
|
|
2258
|
+
if (dpStartDate) {
|
|
2259
|
+
document.getElementById('dpStartBox').querySelector('span').textContent = fmtDate(dpStartDate);
|
|
2260
|
+
document.getElementById('dpStartBox').classList.add('has-value');
|
|
2261
|
+
}
|
|
2262
|
+
if (dpEndDate) {
|
|
2263
|
+
document.getElementById('dpEndBox').querySelector('span').textContent = fmtDate(dpEndDate);
|
|
2264
|
+
document.getElementById('dpEndBox').classList.add('has-value');
|
|
2265
|
+
}
|
|
2266
|
+
renderCalendars();
|
|
2267
|
+
}
|
|
2268
|
+
|
|
2269
|
+
function fmtDate(d) {
|
|
2270
|
+
return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0');
|
|
2271
|
+
}
|
|
2272
|
+
|
|
2273
|
+
function confirmDatePicker() {
|
|
2274
|
+
if (!dpStartDate || !dpEndDate) return;
|
|
2275
|
+
const rangeText = fmtDate(dpStartDate) + ' ~ ' + fmtDate(dpEndDate);
|
|
2276
|
+
const rangeSpan = document.getElementById(dpSource === 'forecast' ? 'forecastCustomRange' : 'pipelineCustomRange');
|
|
2277
|
+
rangeSpan.textContent = rangeText;
|
|
2278
|
+
rangeSpan.style.display = 'inline';
|
|
2279
|
+
document.getElementById('datePickerOverlay').style.display = 'none';
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
// 绑定Close Date下拉事件
|
|
2283
|
+
document.getElementById('pipelineCloseDate').addEventListener('change', function () {
|
|
2284
|
+
if (this.value === 'Custom') { openDatePicker('pipeline'); }
|
|
2285
|
+
});
|
|
2286
|
+
document.getElementById('forecastCloseDate').addEventListener('change', function () {
|
|
2287
|
+
if (this.value === 'Custom') { openDatePicker('forecast'); }
|
|
2288
|
+
});
|
|
2289
|
+
|
|
2290
|
+
// Owner选人选部门组件(多选)
|
|
2291
|
+
function toggleOwnerPicker(source) {
|
|
2292
|
+
const dd = document.getElementById(source + 'OwnerDropdown');
|
|
2293
|
+
const isOpen = dd.classList.contains('open');
|
|
2294
|
+
document.querySelectorAll('.owner-picker-dropdown').forEach(d => d.classList.remove('open'));
|
|
2295
|
+
if (!isOpen) {
|
|
2296
|
+
dd.classList.add('open');
|
|
2297
|
+
dd.querySelector('.owner-search').value = '';
|
|
2298
|
+
dd.querySelectorAll('.owner-item').forEach(item => item.style.display = '');
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
|
|
2302
|
+
function toggleOwnerItem(source, el) {
|
|
2303
|
+
el.classList.toggle('selected');
|
|
2304
|
+
const check = el.querySelector('.owner-check');
|
|
2305
|
+
check.textContent = el.classList.contains('selected') ? '✓' : '';
|
|
2306
|
+
updateOwnerText(source);
|
|
2307
|
+
}
|
|
2308
|
+
|
|
2309
|
+
function updateOwnerText(source) {
|
|
2310
|
+
const wrap = document.getElementById(source + 'OwnerWrap');
|
|
2311
|
+
const selected = wrap.querySelectorAll('.owner-item.selected');
|
|
2312
|
+
const names = Array.from(selected).map(item => item.dataset.name);
|
|
2313
|
+
const textEl = document.getElementById(source + 'OwnerText');
|
|
2314
|
+
if (names.length === 0) {
|
|
2315
|
+
textEl.textContent = 'Select...';
|
|
2316
|
+
} else if (names.length === 1) {
|
|
2317
|
+
textEl.textContent = names[0];
|
|
2318
|
+
} else {
|
|
2319
|
+
textEl.textContent = names[0] + ' +' + (names.length - 1);
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
|
|
2323
|
+
function filterOwnerList(source, keyword) {
|
|
2324
|
+
const dd = document.getElementById(source + 'OwnerDropdown');
|
|
2325
|
+
const kw = keyword.toLowerCase();
|
|
2326
|
+
dd.querySelectorAll('.owner-item').forEach(item => {
|
|
2327
|
+
const text = item.textContent.toLowerCase();
|
|
2328
|
+
item.style.display = text.includes(kw) ? '' : 'none';
|
|
2329
|
+
});
|
|
2330
|
+
}
|
|
2331
|
+
|
|
2332
|
+
// 点击外部关闭Owner下拉
|
|
2333
|
+
document.addEventListener('click', function (e) {
|
|
2334
|
+
if (!e.target.closest('.owner-picker-wrap')) {
|
|
2335
|
+
document.querySelectorAll('.owner-picker-dropdown').forEach(d => d.classList.remove('open'));
|
|
2336
|
+
}
|
|
2337
|
+
});
|
|
2338
|
+
|
|
2339
|
+
// 单日期选择器(Changes Since)
|
|
2340
|
+
let sdpSource = 'pipeline';
|
|
2341
|
+
let sdpSelectedDate = null;
|
|
2342
|
+
let sdpMonth = new Date(2026, 2, 1);
|
|
2343
|
+
|
|
2344
|
+
function openSingleDatePicker(source) {
|
|
2345
|
+
sdpSource = source;
|
|
2346
|
+
sdpSelectedDate = null;
|
|
2347
|
+
sdpMonth = new Date(2026, 2, 1);
|
|
2348
|
+
document.getElementById('sdpDateBox').querySelector('span').textContent = 'Select date';
|
|
2349
|
+
document.getElementById('sdpDateBox').classList.remove('has-value');
|
|
2350
|
+
renderSingleCalendar();
|
|
2351
|
+
document.getElementById('singleDateOverlay').style.display = 'block';
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2354
|
+
function closeSingleDatePicker() {
|
|
2355
|
+
document.getElementById('singleDateOverlay').style.display = 'none';
|
|
2356
|
+
if (!sdpSelectedDate) {
|
|
2357
|
+
const sel = document.getElementById(sdpSource + 'ChangesSince');
|
|
2358
|
+
sel.value = 'Start of the Period';
|
|
2359
|
+
document.getElementById(sdpSource + 'ChangesSinceDate').style.display = 'none';
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
2362
|
+
|
|
2363
|
+
function sdpNav(offset) {
|
|
2364
|
+
sdpMonth = new Date(sdpMonth.getFullYear(), sdpMonth.getMonth() + offset, 1);
|
|
2365
|
+
renderSingleCalendar();
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2368
|
+
function renderSingleCalendar() {
|
|
2369
|
+
const grid = document.getElementById('sdpGrid');
|
|
2370
|
+
const title = document.getElementById('sdpTitle');
|
|
2371
|
+
const year = sdpMonth.getFullYear();
|
|
2372
|
+
const month = sdpMonth.getMonth();
|
|
2373
|
+
title.textContent = dpMonthNames[month] + ' ' + year;
|
|
2374
|
+
let html = '';
|
|
2375
|
+
['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'].forEach(d => { html += '<div class="day-header">' + d + '</div>'; });
|
|
2376
|
+
const firstDay = new Date(year, month, 1).getDay();
|
|
2377
|
+
const daysInMonth = new Date(year, month + 1, 0).getDate();
|
|
2378
|
+
const prevDays = new Date(year, month, 0).getDate();
|
|
2379
|
+
const today = new Date();
|
|
2380
|
+
for (let i = firstDay - 1; i >= 0; i--) {
|
|
2381
|
+
html += '<div class="day other-month">' + (prevDays - i) + '</div>';
|
|
2382
|
+
}
|
|
2383
|
+
for (let d = 1; d <= daysInMonth; d++) {
|
|
2384
|
+
const date = new Date(year, month, d);
|
|
2385
|
+
let cls = 'day';
|
|
2386
|
+
if (date.toDateString() === today.toDateString()) cls += ' today';
|
|
2387
|
+
if (sdpSelectedDate && date.toDateString() === sdpSelectedDate.toDateString()) cls += ' selected';
|
|
2388
|
+
html += '<div class="' + cls + '" onclick="sdpSelectDay(' + year + ',' + month + ',' + d + ')">' + d + '</div>';
|
|
2389
|
+
}
|
|
2390
|
+
const totalCells = firstDay + daysInMonth;
|
|
2391
|
+
const remaining = (7 - totalCells % 7) % 7;
|
|
2392
|
+
for (let i = 1; i <= remaining; i++) {
|
|
2393
|
+
html += '<div class="day other-month">' + i + '</div>';
|
|
2394
|
+
}
|
|
2395
|
+
grid.innerHTML = html;
|
|
2396
|
+
}
|
|
2397
|
+
|
|
2398
|
+
function sdpSelectDay(y, m, d) {
|
|
2399
|
+
sdpSelectedDate = new Date(y, m, d);
|
|
2400
|
+
document.getElementById('sdpDateBox').querySelector('span').textContent = fmtDate(sdpSelectedDate);
|
|
2401
|
+
document.getElementById('sdpDateBox').classList.add('has-value');
|
|
2402
|
+
renderSingleCalendar();
|
|
2403
|
+
}
|
|
2404
|
+
|
|
2405
|
+
function confirmSingleDatePicker() {
|
|
2406
|
+
if (!sdpSelectedDate) return;
|
|
2407
|
+
const dateText = fmtDate(sdpSelectedDate);
|
|
2408
|
+
const span = document.getElementById(sdpSource + 'ChangesSinceDate');
|
|
2409
|
+
span.textContent = dateText;
|
|
2410
|
+
span.style.display = 'inline';
|
|
2411
|
+
document.getElementById('singleDateOverlay').style.display = 'none';
|
|
2412
|
+
}
|
|
2413
|
+
|
|
2414
|
+
// 绑定Changes Since下拉事件
|
|
2415
|
+
document.getElementById('pipelineChangesSince').addEventListener('change', function () {
|
|
2416
|
+
if (this.value === 'Custom') { openSingleDatePicker('pipeline'); }
|
|
2417
|
+
else { document.getElementById('pipelineChangesSinceDate').style.display = 'none'; }
|
|
2418
|
+
});
|
|
2419
|
+
document.getElementById('forecastChangesSince').addEventListener('change', function () {
|
|
2420
|
+
if (this.value === 'Custom') { openSingleDatePicker('forecast'); }
|
|
2421
|
+
else { document.getElementById('forecastChangesSinceDate').style.display = 'none'; }
|
|
2422
|
+
});
|
|
2423
|
+
|
|
2424
|
+
// Pipeline列表排序与筛选(原型演示)
|
|
2425
|
+
let pipelineSortField = 'amount';
|
|
2426
|
+
let pipelineSortDir = 'desc';
|
|
2427
|
+
|
|
2428
|
+
function togglePipelineSort(field) {
|
|
2429
|
+
if (pipelineSortField === field) {
|
|
2430
|
+
pipelineSortDir = pipelineSortDir === 'desc' ? 'asc' : pipelineSortDir === 'asc' ? 'none' : 'desc';
|
|
2431
|
+
} else {
|
|
2432
|
+
pipelineSortField = field;
|
|
2433
|
+
pipelineSortDir = 'desc';
|
|
2434
|
+
}
|
|
2435
|
+
updatePipelineSortFilterBar();
|
|
2436
|
+
}
|
|
2437
|
+
|
|
2438
|
+
function togglePipelineFilter(field) {
|
|
2439
|
+
// 原型演示:仅展示交互效果
|
|
2440
|
+
alert('Filter panel for: ' + field);
|
|
2441
|
+
}
|
|
2442
|
+
|
|
2443
|
+
function updatePipelineSortFilterBar() {
|
|
2444
|
+
const bar = document.getElementById('pipelineSortFilterBar');
|
|
2445
|
+
const fieldNames = { name: 'Opportunity Name', amount: 'Amount', closeDate: 'Close Date', score: 'Score', winRate: 'AI Win Rate', lastAct: 'Last Activity' };
|
|
2446
|
+
const sortText = pipelineSortDir !== 'none' ? 'Sorted by ' + (fieldNames[pipelineSortField] || pipelineSortField) : '';
|
|
2447
|
+
const filterText = 'Filtered by Close Date, Current User, Open Pipeline';
|
|
2448
|
+
bar.textContent = [sortText, filterText].filter(Boolean).join(' · ');
|
|
2449
|
+
}
|
|
2450
|
+
</script>
|
|
2451
|
+
</body>
|
|
2452
|
+
|
|
2453
|
+
</html>
|