yidaconnector 2026.6.11
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/LICENSE +21 -0
- package/README.md +383 -0
- package/bin/yida.js +670 -0
- package/lib/app/form-navigation.js +58 -0
- package/lib/app/get-schema.js +538 -0
- package/lib/auth/auth.js +294 -0
- package/lib/auth/cdp-browser-login.js +390 -0
- package/lib/auth/codex-login.js +71 -0
- package/lib/auth/login.js +475 -0
- package/lib/auth/org.js +363 -0
- package/lib/auth/qr-login.js +1563 -0
- package/lib/core/chalk.js +384 -0
- package/lib/core/check-update.js +82 -0
- package/lib/core/cli-error.js +39 -0
- package/lib/core/command-manifest.js +106 -0
- package/lib/core/env-cmd.js +545 -0
- package/lib/core/env-manager.js +601 -0
- package/lib/core/env.js +287 -0
- package/lib/core/i18n.js +177 -0
- package/lib/core/locales/ar.js +805 -0
- package/lib/core/locales/de.js +805 -0
- package/lib/core/locales/en.js +1623 -0
- package/lib/core/locales/es.js +805 -0
- package/lib/core/locales/fr.js +805 -0
- package/lib/core/locales/hi.js +805 -0
- package/lib/core/locales/ja.js +1197 -0
- package/lib/core/locales/ko.js +807 -0
- package/lib/core/locales/pt.js +805 -0
- package/lib/core/locales/vi.js +805 -0
- package/lib/core/locales/zh-HK.js +1233 -0
- package/lib/core/locales/zh.js +1584 -0
- package/lib/core/query-data.js +781 -0
- package/lib/core/redact.js +100 -0
- package/lib/core/utils.js +799 -0
- package/lib/core/yida-client.js +117 -0
- package/package.json +94 -0
- package/project/config.json +4 -0
- package/project/pages/src/demo-birthday-game.oyd.jsx +832 -0
- package/project/pages/src/demo-chip-insight.oyd.jsx +983 -0
- package/project/pages/src/demo-compat-smoke.oyd.jsx +58 -0
- package/project/pages/src/demo-crm-batch-entry.oyd.jsx +805 -0
- package/project/pages/src/demo-crm-dashboard.oyd.jsx +677 -0
- package/project/pages/src/demo-future-vision-2026.oyd.jsx +1102 -0
- package/project/pages/src/demo-ppt.oyd.jsx +1192 -0
- package/project/pages/src/demo-salary-calculator.oyd.jsx +904 -0
- package/project/pages/src/yidaconnector-knowledge-doc.oyd.jsx +1714 -0
- package/project/prd/demo-birthday-game.md +39 -0
- package/project/prd/demo-crm.md +463 -0
- package/project/prd/demo-dingtalk-ai-solution-center.md +425 -0
- package/project/prd/demo-future-vision-2026.md +78 -0
- package/project/prd/demo-salary-calculator.md +101 -0
- package/scripts/build-skills-package.js +406 -0
- package/scripts/check-syntax.js +59 -0
- package/scripts/demo-dws.sh +106 -0
- package/scripts/e2e-real/cleanup.js +67 -0
- package/scripts/e2e-real/fixtures/form-fields.json +18 -0
- package/scripts/e2e-real/full-runner.js +1566 -0
- package/scripts/e2e-real/runner.js +293 -0
- package/scripts/e2e-real/skill-coverage.js +115 -0
- package/scripts/generate-command-docs.js +109 -0
- package/scripts/nightly-smoke.js +134 -0
- package/scripts/postinstall.js +545 -0
- package/scripts/solution-center-runner.js +368 -0
- package/scripts/validate-ci.sh +50 -0
- package/scripts/validate-command-manifest.js +119 -0
- package/scripts/validate-package-size.js +78 -0
- package/scripts/validate-skills.js +247 -0
- package/scripts/validate-structure.js +66 -0
- package/yida-skills/SKILL.md +163 -0
- package/yida-skills/references/yida-api.md +1309 -0
- package/yida-skills/skills/large-file-write/SKILL.md +91 -0
- package/yida-skills/skills/large-file-write/references/write-patterns.md +149 -0
- package/yida-skills/skills/large-file-write/scripts/write.js +157 -0
- package/yida-skills/skills/yida-data-management/SKILL.md +252 -0
- package/yida-skills/skills/yida-data-management/references/api-matrix.md +49 -0
- package/yida-skills/skills/yida-data-management/references/data-format-guide.md +159 -0
- package/yida-skills/skills/yida-data-management/references/verified-endpoints.md +62 -0
- package/yida-skills/skills/yida-login/SKILL.md +159 -0
- package/yida-skills/skills/yida-logout/SKILL.md +67 -0
|
@@ -0,0 +1,677 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// CRM 销售仪表盘 - 自定义页面
|
|
3
|
+
// ============================================================
|
|
4
|
+
|
|
5
|
+
// 表单 ID 配置
|
|
6
|
+
var FORMS = {
|
|
7
|
+
customer: 'FORM-48FB56AFA31E43B09BDE30B7165D3E92D7WT', // 客户信息表
|
|
8
|
+
opportunity: 'FORM-ECCF67D4CC994968B2675C9CBD0F04A536P0', // 商机管理表
|
|
9
|
+
contract: 'FORM-66D473275377454DB51AC8577788E536UB4R' // 合同审批表
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// 字段 ID 别名
|
|
13
|
+
var FIELDS = {
|
|
14
|
+
// 客户信息表
|
|
15
|
+
customerType: 'radioField_l1h43yhd1', // 客户类型
|
|
16
|
+
customerLevel: 'selectField_l1h54iojj', // 客户级别
|
|
17
|
+
customerName: 'textField_l1h42qsqz', // 客户名称
|
|
18
|
+
// 商机管理表
|
|
19
|
+
opportunityStage: 'selectField_ozj64pdqc', // 商机阶段
|
|
20
|
+
expectedAmount: 'numberField_ozj651thb', // 预计金额
|
|
21
|
+
opportunityName: 'textField_ozj624rw0', // 商机名称
|
|
22
|
+
followUpRecords: 'tableField_ozj7cibrf', // 跟进记录(子表)
|
|
23
|
+
// 合同审批表
|
|
24
|
+
contractAmount: 'numberField_rvie67pbf', // 合同金额
|
|
25
|
+
contractName: 'textField_rvid2vb2q' // 合同名称
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// 商机阶段列表
|
|
29
|
+
var OPPORTUNITY_STAGES = ['初步接触', '需求确认', '方案报价', '谈判', '赢单', '输单'];
|
|
30
|
+
|
|
31
|
+
// 密度配置
|
|
32
|
+
var DENSITY_CONFIG = {
|
|
33
|
+
compact: {
|
|
34
|
+
cardPadding: '8px 12px',
|
|
35
|
+
fontSize: '12px',
|
|
36
|
+
lineHeight: '1.4',
|
|
37
|
+
sectionGap: '8px',
|
|
38
|
+
titleSize: '14px',
|
|
39
|
+
metricSize: '20px',
|
|
40
|
+
tableRowHeight: '32px'
|
|
41
|
+
},
|
|
42
|
+
comfortable: {
|
|
43
|
+
cardPadding: '16px 20px',
|
|
44
|
+
fontSize: '14px',
|
|
45
|
+
lineHeight: '1.6',
|
|
46
|
+
sectionGap: '16px',
|
|
47
|
+
titleSize: '16px',
|
|
48
|
+
metricSize: '28px',
|
|
49
|
+
tableRowHeight: '44px'
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// ============================================================
|
|
54
|
+
// 状态管理
|
|
55
|
+
// ============================================================
|
|
56
|
+
|
|
57
|
+
var _customState = {
|
|
58
|
+
density: 'compact', // 默认紧凑模式
|
|
59
|
+
loading: true,
|
|
60
|
+
customerCount: 0,
|
|
61
|
+
opportunityCount: 0,
|
|
62
|
+
contractTotal: 0,
|
|
63
|
+
monthlyNewCustomers: 0,
|
|
64
|
+
stageStats: {}, // 各阶段商机数量
|
|
65
|
+
recentOpportunities: [], // 最近商机列表
|
|
66
|
+
refreshTimer: null
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export function getCustomState(key) {
|
|
70
|
+
if (key) return _customState[key];
|
|
71
|
+
return _.clone(_customState);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function setCustomState(newState) {
|
|
75
|
+
Object.keys(newState).forEach(function(key) {
|
|
76
|
+
_customState[key] = newState[key];
|
|
77
|
+
});
|
|
78
|
+
this.forceUpdate();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function forceUpdate() {
|
|
82
|
+
this.setState({ timestamp: new Date().getTime() });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ============================================================
|
|
86
|
+
// 生命周期
|
|
87
|
+
// ============================================================
|
|
88
|
+
|
|
89
|
+
export function didMount() {
|
|
90
|
+
var that = this;
|
|
91
|
+
// 初始加载数据
|
|
92
|
+
this.loadAllData();
|
|
93
|
+
// 设置 30 秒自动刷新
|
|
94
|
+
_customState.refreshTimer = setInterval(function() {
|
|
95
|
+
that.loadAllData();
|
|
96
|
+
}, 30000);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function didUnmount() {
|
|
100
|
+
// 清理定时器
|
|
101
|
+
if (_customState.refreshTimer) {
|
|
102
|
+
clearInterval(_customState.refreshTimer);
|
|
103
|
+
_customState.refreshTimer = null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ============================================================
|
|
108
|
+
// 数据加载方法
|
|
109
|
+
// ============================================================
|
|
110
|
+
|
|
111
|
+
export function loadAllData() {
|
|
112
|
+
var that = this;
|
|
113
|
+
_customState.loading = true;
|
|
114
|
+
this.forceUpdate();
|
|
115
|
+
|
|
116
|
+
// 并行加载所有数据(商机查询已合并为单个请求,避免并发限流)
|
|
117
|
+
Promise.all([
|
|
118
|
+
that.loadCustomerCount(),
|
|
119
|
+
that.loadOpportunityData(),
|
|
120
|
+
that.loadContractTotal(),
|
|
121
|
+
that.loadMonthlyNewCustomers()
|
|
122
|
+
]).then(function() {
|
|
123
|
+
_customState.loading = false;
|
|
124
|
+
that.forceUpdate();
|
|
125
|
+
}).catch(function() {
|
|
126
|
+
_customState.loading = false;
|
|
127
|
+
that.forceUpdate();
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function loadCustomerCount() {
|
|
132
|
+
var that = this;
|
|
133
|
+
return this.utils.yida.searchFormDatas({
|
|
134
|
+
formUuid: FORMS.customer,
|
|
135
|
+
pageSize: 1,
|
|
136
|
+
currentPage: 1
|
|
137
|
+
}).then(function(res) {
|
|
138
|
+
_customState.customerCount = res.totalCount || 0;
|
|
139
|
+
}).catch(function(err) {
|
|
140
|
+
that.utils.toast({ title: '加载客户数据失败', type: 'error' });
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// 合并商机查询:一次请求获取商机总数、阶段统计、最近商机
|
|
145
|
+
export function loadOpportunityData() {
|
|
146
|
+
var that = this;
|
|
147
|
+
var stats = {};
|
|
148
|
+
OPPORTUNITY_STAGES.forEach(function(stage) {
|
|
149
|
+
stats[stage] = 0;
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
return this.utils.yida.searchFormDatas({
|
|
153
|
+
formUuid: FORMS.opportunity,
|
|
154
|
+
pageSize: 100,
|
|
155
|
+
currentPage: 1
|
|
156
|
+
}).then(function(res) {
|
|
157
|
+
// 1. 设置商机总数
|
|
158
|
+
_customState.opportunityCount = res.totalCount || 0;
|
|
159
|
+
|
|
160
|
+
// 2. 设置最近商机(取前10条)
|
|
161
|
+
var data = res.data || [];
|
|
162
|
+
_customState.recentOpportunities = data.slice(0, 10);
|
|
163
|
+
|
|
164
|
+
// 3. 统计阶段分布
|
|
165
|
+
data.forEach(function(item) {
|
|
166
|
+
var stage = item.formData[FIELDS.opportunityStage];
|
|
167
|
+
if (stage && stats.hasOwnProperty(stage)) {
|
|
168
|
+
stats[stage]++;
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// 如果数据超过100条,继续加载分页完善阶段统计
|
|
173
|
+
if (res.totalCount > 100) {
|
|
174
|
+
return that.loadAllStageStats(res.totalCount, stats);
|
|
175
|
+
}
|
|
176
|
+
_customState.stageStats = stats;
|
|
177
|
+
}).catch(function(err) {
|
|
178
|
+
that.utils.toast({ title: '加载商机数据失败', type: 'error' });
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function loadContractTotal() {
|
|
183
|
+
var that = this;
|
|
184
|
+
return this.utils.yida.searchFormDatas({
|
|
185
|
+
formUuid: FORMS.contract,
|
|
186
|
+
pageSize: 100,
|
|
187
|
+
currentPage: 1
|
|
188
|
+
}).then(function(res) {
|
|
189
|
+
var total = 0;
|
|
190
|
+
var data = res.data || [];
|
|
191
|
+
data.forEach(function(item) {
|
|
192
|
+
var amount = item.formData[FIELDS.contractAmount] || 0;
|
|
193
|
+
total += Number(amount) || 0;
|
|
194
|
+
});
|
|
195
|
+
// 如果数据超过100条,继续查询
|
|
196
|
+
if (res.totalCount > 100) {
|
|
197
|
+
return that.loadAllContractAmounts(res.totalCount, total);
|
|
198
|
+
}
|
|
199
|
+
_customState.contractTotal = total;
|
|
200
|
+
}).catch(function(err) {
|
|
201
|
+
that.utils.toast({ title: '加载合同数据失败', type: 'error' });
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function loadAllContractAmounts(totalCount, currentTotal) {
|
|
206
|
+
var that = this;
|
|
207
|
+
var pages = Math.ceil(totalCount / 100);
|
|
208
|
+
var promises = [];
|
|
209
|
+
|
|
210
|
+
for (var i = 2; i <= pages; i++) {
|
|
211
|
+
promises.push(
|
|
212
|
+
that.utils.yida.searchFormDatas({
|
|
213
|
+
formUuid: FORMS.contract,
|
|
214
|
+
pageSize: 100,
|
|
215
|
+
currentPage: i
|
|
216
|
+
})
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return Promise.all(promises).then(function(results) {
|
|
221
|
+
var total = currentTotal;
|
|
222
|
+
results.forEach(function(res) {
|
|
223
|
+
var data = res.data || [];
|
|
224
|
+
data.forEach(function(item) {
|
|
225
|
+
var amount = item.formData[FIELDS.contractAmount] || 0;
|
|
226
|
+
total += Number(amount) || 0;
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
_customState.contractTotal = total;
|
|
230
|
+
}).catch(function(err) {
|
|
231
|
+
that.utils.toast({ title: '加载合同数据失败', type: 'error' });
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export function loadMonthlyNewCustomers() {
|
|
236
|
+
var that = this;
|
|
237
|
+
var now = new Date();
|
|
238
|
+
var year = now.getFullYear();
|
|
239
|
+
var month = now.getMonth() + 1;
|
|
240
|
+
var monthStr = month < 10 ? '0' + month : '' + month;
|
|
241
|
+
var createFrom = year + '-' + monthStr + '-01';
|
|
242
|
+
var createTo = year + '-' + monthStr + '-31';
|
|
243
|
+
|
|
244
|
+
return this.utils.yida.searchFormDatas({
|
|
245
|
+
formUuid: FORMS.customer,
|
|
246
|
+
pageSize: 1,
|
|
247
|
+
currentPage: 1,
|
|
248
|
+
createFrom: createFrom,
|
|
249
|
+
createTo: createTo
|
|
250
|
+
}).then(function(res) {
|
|
251
|
+
_customState.monthlyNewCustomers = res.totalCount || 0;
|
|
252
|
+
}).catch(function(err) {
|
|
253
|
+
that.utils.toast({ title: '加载本月客户数据失败', type: 'error' });
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// loadAllStageStats: 加载超过100条时的分页数据,用于完善阶段统计
|
|
258
|
+
export function loadAllStageStats(totalCount, currentStats) {
|
|
259
|
+
var that = this;
|
|
260
|
+
var pages = Math.ceil(totalCount / 100);
|
|
261
|
+
var promises = [];
|
|
262
|
+
|
|
263
|
+
for (var i = 2; i <= pages; i++) {
|
|
264
|
+
promises.push(
|
|
265
|
+
that.utils.yida.searchFormDatas({
|
|
266
|
+
formUuid: FORMS.opportunity,
|
|
267
|
+
pageSize: 100,
|
|
268
|
+
currentPage: i
|
|
269
|
+
})
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
return Promise.all(promises).then(function(results) {
|
|
274
|
+
var stats = currentStats;
|
|
275
|
+
results.forEach(function(res) {
|
|
276
|
+
var data = res.data || [];
|
|
277
|
+
data.forEach(function(item) {
|
|
278
|
+
var stage = item.formData[FIELDS.opportunityStage];
|
|
279
|
+
if (stage && stats.hasOwnProperty(stage)) {
|
|
280
|
+
stats[stage]++;
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
_customState.stageStats = stats;
|
|
285
|
+
}).catch(function(err) {
|
|
286
|
+
that.utils.toast({ title: '加载商机阶段数据失败', type: 'error' });
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// ============================================================
|
|
291
|
+
// 交互方法
|
|
292
|
+
// ============================================================
|
|
293
|
+
|
|
294
|
+
export function switchDensity(densityKey) {
|
|
295
|
+
_customState.density = densityKey;
|
|
296
|
+
this.forceUpdate();
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export function refreshData() {
|
|
300
|
+
this.loadAllData();
|
|
301
|
+
this.utils.toast({ title: '刷新成功', type: 'success' });
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// ============================================================
|
|
305
|
+
// 辅助方法
|
|
306
|
+
// ============================================================
|
|
307
|
+
|
|
308
|
+
export function formatMoney(num) {
|
|
309
|
+
if (num === null || num === undefined) return '0';
|
|
310
|
+
var n = Number(num);
|
|
311
|
+
if (isNaN(n)) return '0';
|
|
312
|
+
if (n >= 10000) {
|
|
313
|
+
return (n / 10000).toFixed(1) + '万';
|
|
314
|
+
}
|
|
315
|
+
return n.toLocaleString();
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export function getStageColor(stage) {
|
|
319
|
+
var colorMap = {
|
|
320
|
+
'初步接触': '#1677FF',
|
|
321
|
+
'需求确认': '#722ED1',
|
|
322
|
+
'方案报价': '#FA8C16',
|
|
323
|
+
'谈判': '#13C2C2',
|
|
324
|
+
'赢单': '#52C41A',
|
|
325
|
+
'输单': '#FF4D4F'
|
|
326
|
+
};
|
|
327
|
+
return colorMap[stage] || '#86909C';
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// ============================================================
|
|
331
|
+
// 渲染方法
|
|
332
|
+
// ============================================================
|
|
333
|
+
|
|
334
|
+
export function renderDensityToggle(d) {
|
|
335
|
+
var that = this;
|
|
336
|
+
var options = [
|
|
337
|
+
{ key: 'compact', label: '紧凑' },
|
|
338
|
+
{ key: 'comfortable', label: '舒适' }
|
|
339
|
+
];
|
|
340
|
+
|
|
341
|
+
return (
|
|
342
|
+
<div style={{ display: 'flex', gap: '4px', alignItems: 'center' }}>
|
|
343
|
+
{options.map(function(option) {
|
|
344
|
+
var isActive = _customState.density === option.key;
|
|
345
|
+
return (
|
|
346
|
+
<button
|
|
347
|
+
key={option.key}
|
|
348
|
+
onClick={function() { that.switchDensity(option.key); }}
|
|
349
|
+
style={{
|
|
350
|
+
height: '24px',
|
|
351
|
+
padding: '0 10px',
|
|
352
|
+
fontSize: '12px',
|
|
353
|
+
border: '1px solid ' + (isActive ? 'var(--color-brand1-6)' : '#E5E6EB'),
|
|
354
|
+
borderRadius: '4px',
|
|
355
|
+
background: isActive ? 'var(--color-brand1-1)' : '#fff',
|
|
356
|
+
color: isActive ? 'var(--color-brand1-6)' : '#4E5969',
|
|
357
|
+
cursor: 'pointer',
|
|
358
|
+
outline: 'none'
|
|
359
|
+
}}
|
|
360
|
+
>
|
|
361
|
+
{option.label}
|
|
362
|
+
</button>
|
|
363
|
+
);
|
|
364
|
+
})}
|
|
365
|
+
</div>
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
export function renderMetricCard(title, value, unit, color, d) {
|
|
370
|
+
return (
|
|
371
|
+
<div style={{
|
|
372
|
+
flex: 1,
|
|
373
|
+
minWidth: '0',
|
|
374
|
+
background: '#FFFFFF',
|
|
375
|
+
borderRadius: '8px',
|
|
376
|
+
padding: d.cardPadding,
|
|
377
|
+
border: '1px solid #E5E6EB',
|
|
378
|
+
boxShadow: '0 1px 3px rgba(0,0,0,0.04)'
|
|
379
|
+
}}>
|
|
380
|
+
<div style={{
|
|
381
|
+
fontSize: d.fontSize,
|
|
382
|
+
color: '#86909C',
|
|
383
|
+
marginBottom: '4px'
|
|
384
|
+
}}>
|
|
385
|
+
{title}
|
|
386
|
+
</div>
|
|
387
|
+
<div style={{
|
|
388
|
+
fontSize: d.metricSize,
|
|
389
|
+
fontWeight: 600,
|
|
390
|
+
color: color || '#1D2129',
|
|
391
|
+
lineHeight: 1.2
|
|
392
|
+
}}>
|
|
393
|
+
{value}
|
|
394
|
+
{unit && <span style={{ fontSize: d.fontSize, fontWeight: 400, marginLeft: '2px' }}>{unit}</span>}
|
|
395
|
+
</div>
|
|
396
|
+
</div>
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
export function renderStageStats(d) {
|
|
401
|
+
var that = this;
|
|
402
|
+
var stats = _customState.stageStats;
|
|
403
|
+
var total = 0;
|
|
404
|
+
OPPORTUNITY_STAGES.forEach(function(stage) {
|
|
405
|
+
total += stats[stage] || 0;
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
return (
|
|
409
|
+
<div style={{
|
|
410
|
+
background: '#FFFFFF',
|
|
411
|
+
borderRadius: '8px',
|
|
412
|
+
padding: d.cardPadding,
|
|
413
|
+
border: '1px solid #E5E6EB',
|
|
414
|
+
boxShadow: '0 1px 3px rgba(0,0,0,0.04)'
|
|
415
|
+
}}>
|
|
416
|
+
<div style={{
|
|
417
|
+
fontSize: d.titleSize,
|
|
418
|
+
fontWeight: 600,
|
|
419
|
+
color: '#1D2129',
|
|
420
|
+
marginBottom: d.sectionGap,
|
|
421
|
+
paddingBottom: '8px',
|
|
422
|
+
borderBottom: '1px solid #F2F3F5'
|
|
423
|
+
}}>
|
|
424
|
+
商机阶段分布
|
|
425
|
+
</div>
|
|
426
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
|
427
|
+
{OPPORTUNITY_STAGES.map(function(stage) {
|
|
428
|
+
var count = stats[stage] || 0;
|
|
429
|
+
var percent = total > 0 ? (count / total * 100) : 0;
|
|
430
|
+
var color = that.getStageColor(stage);
|
|
431
|
+
|
|
432
|
+
return (
|
|
433
|
+
<div key={stage} style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
434
|
+
<div style={{
|
|
435
|
+
width: '60px',
|
|
436
|
+
fontSize: d.fontSize,
|
|
437
|
+
color: '#4E5969',
|
|
438
|
+
flexShrink: 0
|
|
439
|
+
}}>
|
|
440
|
+
{stage}
|
|
441
|
+
</div>
|
|
442
|
+
<div style={{
|
|
443
|
+
flex: 1,
|
|
444
|
+
height: '16px',
|
|
445
|
+
background: '#F2F3F5',
|
|
446
|
+
borderRadius: '4px',
|
|
447
|
+
overflow: 'hidden'
|
|
448
|
+
}}>
|
|
449
|
+
<div style={{
|
|
450
|
+
width: percent + '%',
|
|
451
|
+
height: '100%',
|
|
452
|
+
background: color,
|
|
453
|
+
borderRadius: '4px',
|
|
454
|
+
transition: 'width 0.3s ease'
|
|
455
|
+
}} />
|
|
456
|
+
</div>
|
|
457
|
+
<div style={{
|
|
458
|
+
width: '50px',
|
|
459
|
+
fontSize: d.fontSize,
|
|
460
|
+
color: '#1D2129',
|
|
461
|
+
textAlign: 'right',
|
|
462
|
+
flexShrink: 0
|
|
463
|
+
}}>
|
|
464
|
+
{count}
|
|
465
|
+
</div>
|
|
466
|
+
</div>
|
|
467
|
+
);
|
|
468
|
+
})}
|
|
469
|
+
</div>
|
|
470
|
+
</div>
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
export function renderRecentOpportunities(d) {
|
|
475
|
+
var that = this;
|
|
476
|
+
var opportunities = _customState.recentOpportunities;
|
|
477
|
+
|
|
478
|
+
return (
|
|
479
|
+
<div style={{
|
|
480
|
+
background: '#FFFFFF',
|
|
481
|
+
borderRadius: '8px',
|
|
482
|
+
padding: d.cardPadding,
|
|
483
|
+
border: '1px solid #E5E6EB',
|
|
484
|
+
boxShadow: '0 1px 3px rgba(0,0,0,0.04)'
|
|
485
|
+
}}>
|
|
486
|
+
<div style={{
|
|
487
|
+
fontSize: d.titleSize,
|
|
488
|
+
fontWeight: 600,
|
|
489
|
+
color: '#1D2129',
|
|
490
|
+
marginBottom: d.sectionGap,
|
|
491
|
+
paddingBottom: '8px',
|
|
492
|
+
borderBottom: '1px solid #F2F3F5'
|
|
493
|
+
}}>
|
|
494
|
+
最近商机
|
|
495
|
+
</div>
|
|
496
|
+
{opportunities.length === 0 ? (
|
|
497
|
+
<div style={{
|
|
498
|
+
textAlign: 'center',
|
|
499
|
+
padding: '24px',
|
|
500
|
+
color: '#C9CDD4',
|
|
501
|
+
fontSize: d.fontSize
|
|
502
|
+
}}>
|
|
503
|
+
暂无商机数据
|
|
504
|
+
</div>
|
|
505
|
+
) : (
|
|
506
|
+
<div style={{ overflow: 'auto' }}>
|
|
507
|
+
<table style={{
|
|
508
|
+
width: '100%',
|
|
509
|
+
borderCollapse: 'collapse',
|
|
510
|
+
fontSize: d.fontSize
|
|
511
|
+
}}>
|
|
512
|
+
<thead>
|
|
513
|
+
<tr style={{ borderBottom: '1px solid #F2F3F5' }}>
|
|
514
|
+
<th style={{ padding: '8px', textAlign: 'left', color: '#86909C', fontWeight: 500 }}>商机名称</th>
|
|
515
|
+
<th style={{ padding: '8px', textAlign: 'left', color: '#86909C', fontWeight: 500 }}>阶段</th>
|
|
516
|
+
<th style={{ padding: '8px', textAlign: 'right', color: '#86909C', fontWeight: 500 }}>预计金额</th>
|
|
517
|
+
<th style={{ padding: '8px', textAlign: 'left', color: '#86909C', fontWeight: 500 }}>负责人</th>
|
|
518
|
+
</tr>
|
|
519
|
+
</thead>
|
|
520
|
+
<tbody>
|
|
521
|
+
{opportunities.map(function(item, index) {
|
|
522
|
+
var formData = _.get(item, 'formData', {});
|
|
523
|
+
var name = formData[FIELDS.opportunityName] || '-';
|
|
524
|
+
var stage = formData[FIELDS.opportunityStage] || '-';
|
|
525
|
+
var amount = formData[FIELDS.expectedAmount];
|
|
526
|
+
var originator = item.originator || {};
|
|
527
|
+
var originatorName = originator.name;
|
|
528
|
+
var displayName = '-';
|
|
529
|
+
if (originatorName) {
|
|
530
|
+
displayName = originatorName.zh_CN || originatorName.en_US || originatorName;
|
|
531
|
+
if (typeof displayName === 'object') {
|
|
532
|
+
displayName = '-';
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
return (
|
|
537
|
+
<tr key={item.formInstId || index} style={{
|
|
538
|
+
borderBottom: '1px solid #F2F3F5',
|
|
539
|
+
height: d.tableRowHeight
|
|
540
|
+
}}>
|
|
541
|
+
<td style={{ padding: '8px', color: '#1D2129', maxWidth: '200px', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
|
542
|
+
{name}
|
|
543
|
+
</td>
|
|
544
|
+
<td style={{ padding: '8px' }}>
|
|
545
|
+
<span style={{
|
|
546
|
+
display: 'inline-block',
|
|
547
|
+
padding: '2px 8px',
|
|
548
|
+
borderRadius: '4px',
|
|
549
|
+
fontSize: '12px',
|
|
550
|
+
color: that.getStageColor(stage),
|
|
551
|
+
background: that.getStageColor(stage) + '15'
|
|
552
|
+
}}>
|
|
553
|
+
{stage}
|
|
554
|
+
</span>
|
|
555
|
+
</td>
|
|
556
|
+
<td style={{ padding: '8px', textAlign: 'right', color: '#1D2129' }}>
|
|
557
|
+
{amount != null ? '¥' + that.formatMoney(amount) : '-'}
|
|
558
|
+
</td>
|
|
559
|
+
<td style={{ padding: '8px', color: '#4E5969' }}>
|
|
560
|
+
{displayName}
|
|
561
|
+
</td>
|
|
562
|
+
</tr>
|
|
563
|
+
);
|
|
564
|
+
})}
|
|
565
|
+
</tbody>
|
|
566
|
+
</table>
|
|
567
|
+
</div>
|
|
568
|
+
)}
|
|
569
|
+
</div>
|
|
570
|
+
);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// ============================================================
|
|
574
|
+
// 主渲染函数
|
|
575
|
+
// ============================================================
|
|
576
|
+
|
|
577
|
+
export function renderJsx() {
|
|
578
|
+
var that = this;
|
|
579
|
+
var timestamp = this.state.timestamp;
|
|
580
|
+
var d = DENSITY_CONFIG[_customState.density] || DENSITY_CONFIG.compact;
|
|
581
|
+
var isMobile = this.utils.isMobile();
|
|
582
|
+
|
|
583
|
+
var pageStyle = {
|
|
584
|
+
minHeight: '100vh',
|
|
585
|
+
background: '#F7F8FA',
|
|
586
|
+
padding: isMobile ? '12px' : d.cardPadding,
|
|
587
|
+
borderRadius: '0 !important',
|
|
588
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", sans-serif',
|
|
589
|
+
boxSizing: 'border-box'
|
|
590
|
+
};
|
|
591
|
+
|
|
592
|
+
return (
|
|
593
|
+
<div style={pageStyle}>
|
|
594
|
+
{/* 隐藏的时间戳,用于触发重渲染 */}
|
|
595
|
+
<div style={{ display: 'none' }}>{timestamp}</div>
|
|
596
|
+
|
|
597
|
+
{/* 顶部标题栏 */}
|
|
598
|
+
<div style={{
|
|
599
|
+
display: 'flex',
|
|
600
|
+
justifyContent: 'space-between',
|
|
601
|
+
alignItems: 'center',
|
|
602
|
+
marginBottom: d.sectionGap,
|
|
603
|
+
padding: d.cardPadding,
|
|
604
|
+
background: '#FFFFFF',
|
|
605
|
+
borderRadius: '8px',
|
|
606
|
+
border: '1px solid #E5E6EB',
|
|
607
|
+
boxShadow: '0 1px 3px rgba(0,0,0,0.04)'
|
|
608
|
+
}}>
|
|
609
|
+
<div style={{
|
|
610
|
+
fontSize: d.titleSize,
|
|
611
|
+
fontWeight: 600,
|
|
612
|
+
color: '#1D2129'
|
|
613
|
+
}}>
|
|
614
|
+
CRM 销售仪表盘
|
|
615
|
+
</div>
|
|
616
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '12px' }}>
|
|
617
|
+
<button
|
|
618
|
+
onClick={function() { that.refreshData(); }}
|
|
619
|
+
style={{
|
|
620
|
+
height: '24px',
|
|
621
|
+
padding: '0 10px',
|
|
622
|
+
fontSize: '12px',
|
|
623
|
+
border: '1px solid #E5E6EB',
|
|
624
|
+
borderRadius: '4px',
|
|
625
|
+
background: '#fff',
|
|
626
|
+
color: '#4E5969',
|
|
627
|
+
cursor: 'pointer',
|
|
628
|
+
outline: 'none'
|
|
629
|
+
}}
|
|
630
|
+
>
|
|
631
|
+
刷新
|
|
632
|
+
</button>
|
|
633
|
+
{that.renderDensityToggle(d)}
|
|
634
|
+
</div>
|
|
635
|
+
</div>
|
|
636
|
+
|
|
637
|
+
{/* 加载状态 */}
|
|
638
|
+
{_customState.loading && (
|
|
639
|
+
<div style={{
|
|
640
|
+
textAlign: 'center',
|
|
641
|
+
padding: '20px',
|
|
642
|
+
color: '#86909C',
|
|
643
|
+
fontSize: d.fontSize
|
|
644
|
+
}}>
|
|
645
|
+
数据加载中...
|
|
646
|
+
</div>
|
|
647
|
+
)}
|
|
648
|
+
|
|
649
|
+
{/* 指标卡片区域 */}
|
|
650
|
+
<div style={{
|
|
651
|
+
display: 'flex',
|
|
652
|
+
gap: d.sectionGap,
|
|
653
|
+
marginBottom: d.sectionGap,
|
|
654
|
+
flexWrap: isMobile ? 'wrap' : 'nowrap'
|
|
655
|
+
}}>
|
|
656
|
+
{that.renderMetricCard('客户总数', _customState.customerCount, '家', '#1677FF', d)}
|
|
657
|
+
{that.renderMetricCard('商机总数', _customState.opportunityCount, '个', '#722ED1', d)}
|
|
658
|
+
{that.renderMetricCard('合同总额', '¥' + that.formatMoney(_customState.contractTotal), '', '#52C41A', d)}
|
|
659
|
+
{that.renderMetricCard('本月新增客户', _customState.monthlyNewCustomers, '家', '#FA8C16', d)}
|
|
660
|
+
</div>
|
|
661
|
+
|
|
662
|
+
{/* 下方区域:阶段统计 + 最近商机 */}
|
|
663
|
+
<div style={{
|
|
664
|
+
display: 'flex',
|
|
665
|
+
gap: d.sectionGap,
|
|
666
|
+
flexDirection: isMobile ? 'column' : 'row'
|
|
667
|
+
}}>
|
|
668
|
+
<div style={{ flex: isMobile ? 'none' : '0 0 360px', width: isMobile ? '100%' : 'auto' }}>
|
|
669
|
+
{that.renderStageStats(d)}
|
|
670
|
+
</div>
|
|
671
|
+
<div style={{ flex: 1 }}>
|
|
672
|
+
{that.renderRecentOpportunities(d)}
|
|
673
|
+
</div>
|
|
674
|
+
</div>
|
|
675
|
+
</div>
|
|
676
|
+
);
|
|
677
|
+
}
|