@openyida/yidacli 0.1.0

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.
@@ -0,0 +1,904 @@
1
+ // ============================================================
2
+ // 状态管理
3
+ // ============================================================
4
+
5
+ const _customState = {
6
+ // 输入参数
7
+ monthlySalary: 25000,
8
+ bonusMonths: 1,
9
+ city: 'beijing',
10
+ providentFundRate: 0.12,
11
+ socialSecurityBase: 'actual', // actual / min / max
12
+ // 专项扣除(年度)
13
+ childEducation: 0,
14
+ housingLoan: 0,
15
+ elderCare: 0,
16
+ supplementProvidentFund: 0,
17
+ // 高级设置展开状态
18
+ advancedExpanded: false,
19
+ // 计算结果
20
+ result: null,
21
+ // 复制状态
22
+ copySuccess: false,
23
+ };
24
+
25
+ // 各城市社保公积金数据(2026年)
26
+ const CITY_DATA = {
27
+ beijing: { name: '北京', pension: 0.08, medical: 0.02, unemployment: 0.005, minBase: 6821, maxBase: 35283 },
28
+ shanghai: { name: '上海', pension: 0.08, medical: 0.02, unemployment: 0.005, minBase: 7310, maxBase: 36549 },
29
+ guangzhou: { name: '广州', pension: 0.08, medical: 0.02, unemployment: 0.005, minBase: 5765, maxBase: 28825 },
30
+ shenzhen: { name: '深圳', pension: 0.08, medical: 0.005,unemployment: 0.003, minBase: 6623, maxBase: 33115 },
31
+ hangzhou: { name: '杭州', pension: 0.08, medical: 0.02, unemployment: 0.005, minBase: 5782, maxBase: 28910 },
32
+ chengdu: { name: '成都', pension: 0.08, medical: 0.02, unemployment: 0.005, minBase: 4420, maxBase: 22100 },
33
+ wuhan: { name: '武汉', pension: 0.08, medical: 0.02, unemployment: 0.005, minBase: 4200, maxBase: 21000 },
34
+ nanjing: { name: '南京', pension: 0.08, medical: 0.02, unemployment: 0.005, minBase: 5500, maxBase: 27500 },
35
+ xian: { name: '西安', pension: 0.08, medical: 0.02, unemployment: 0.005, minBase: 4200, maxBase: 21000 },
36
+ custom: { name: '自定义', pension: 0.08, medical: 0.02, unemployment: 0.005, minBase: 5000, maxBase: 25000 },
37
+ };
38
+
39
+ // 个税累进税率表(年度应纳税所得额)
40
+ const TAX_BRACKETS = [
41
+ { limit: 36000, rate: 0.03, deduction: 0 },
42
+ { limit: 144000, rate: 0.10, deduction: 2520 },
43
+ { limit: 300000, rate: 0.20, deduction: 16920 },
44
+ { limit: 420000, rate: 0.25, deduction: 31920 },
45
+ { limit: 660000, rate: 0.30, deduction: 52920 },
46
+ { limit: 960000, rate: 0.35, deduction: 85920 },
47
+ { limit: Infinity, rate: 0.45, deduction: 181920 },
48
+ ];
49
+
50
+ /**
51
+ * 计算年度个税
52
+ */
53
+ function calcAnnualTax(taxableIncome) {
54
+ if (taxableIncome <= 0) return 0;
55
+ for (var i = 0; i < TAX_BRACKETS.length; i++) {
56
+ if (taxableIncome <= TAX_BRACKETS[i].limit) {
57
+ return taxableIncome * TAX_BRACKETS[i].rate - TAX_BRACKETS[i].deduction;
58
+ }
59
+ }
60
+ return 0;
61
+ }
62
+
63
+ /**
64
+ * 核心计算引擎
65
+ */
66
+ function calculate(state) {
67
+ var salary = parseFloat(state.monthlySalary) || 0;
68
+ var bonusMonths = parseFloat(state.bonusMonths) || 0;
69
+ var cityData = CITY_DATA[state.city] || CITY_DATA.beijing;
70
+ var pfRate = parseFloat(state.providentFundRate) || 0.12;
71
+
72
+ // 确定社保基数
73
+ var socialBase;
74
+ if (state.socialSecurityBase === 'min') {
75
+ socialBase = cityData.minBase;
76
+ } else if (state.socialSecurityBase === 'max') {
77
+ socialBase = cityData.maxBase;
78
+ } else {
79
+ socialBase = Math.min(Math.max(salary, cityData.minBase), cityData.maxBase);
80
+ }
81
+
82
+ // 公积金基数(同社保基数逻辑)
83
+ var pfBase = Math.min(Math.max(salary, cityData.minBase), cityData.maxBase);
84
+
85
+ // 月度五险一金个人部分
86
+ var monthlyPension = socialBase * cityData.pension;
87
+ var monthlyMedical = socialBase * cityData.medical;
88
+ var monthlyUnemployment = socialBase * cityData.unemployment;
89
+ var monthlyProvidentFund = pfBase * pfRate;
90
+ var monthlySupplementPF = parseFloat(state.supplementProvidentFund) || 0;
91
+ var monthlySocialInsurance = monthlyPension + monthlyMedical + monthlyUnemployment + monthlyProvidentFund + monthlySupplementPF;
92
+
93
+ // 年度五险一金
94
+ var annualSocialInsurance = monthlySocialInsurance * 12;
95
+
96
+ // 税前年薪
97
+ var annualBonus = salary * bonusMonths;
98
+ var annualGrossSalary = salary * 12 + annualBonus;
99
+
100
+ // 专项扣除年度合计
101
+ var annualSpecialDeduction = (parseFloat(state.childEducation) || 0) * 12
102
+ + (parseFloat(state.housingLoan) || 0) * 12
103
+ + (parseFloat(state.elderCare) || 0) * 12;
104
+
105
+ // 应纳税所得额(年度)
106
+ var annualTaxableIncome = annualGrossSalary - 60000 - annualSocialInsurance - annualSpecialDeduction;
107
+
108
+ // 年度个税
109
+ var annualTax = calcAnnualTax(annualTaxableIncome);
110
+
111
+ // 月度个税(平均)
112
+ var monthlyTax = annualTax / 12;
113
+
114
+ // 税后月收入
115
+ var monthlyNetSalary = salary - monthlySocialInsurance - monthlyTax;
116
+
117
+ // 税后年薪
118
+ var annualNetSalary = monthlyNetSalary * 12 + annualBonus * (1 - (annualTax > 0 ? annualTax / annualGrossSalary : 0));
119
+
120
+ // 平均时薪(按176小时/月)
121
+ var hourlyRate = annualGrossSalary / (176 * 12);
122
+
123
+ // 实际税率
124
+ var effectiveTaxRate = annualGrossSalary > 0 ? (annualTax / annualGrossSalary) : 0;
125
+
126
+ return {
127
+ annualGrossSalary: round2(annualGrossSalary),
128
+ annualNetSalary: round2(annualNetSalary),
129
+ monthlyNetSalary: round2(monthlyNetSalary),
130
+ hourlyRate: round2(hourlyRate),
131
+ monthlySocialInsurance: round2(monthlySocialInsurance),
132
+ monthlyTax: round2(monthlyTax),
133
+ annualTax: round2(annualTax),
134
+ annualSocialInsurance: round2(annualSocialInsurance),
135
+ effectiveTaxRate: round2(effectiveTaxRate * 100),
136
+ breakdown: {
137
+ monthlyPension: round2(monthlyPension),
138
+ monthlyMedical: round2(monthlyMedical),
139
+ monthlyUnemployment: round2(monthlyUnemployment),
140
+ monthlyProvidentFund: round2(monthlyProvidentFund),
141
+ monthlySupplementPF: round2(monthlySupplementPF),
142
+ },
143
+ };
144
+ }
145
+
146
+ function round2(value) {
147
+ return Math.round(value * 100) / 100;
148
+ }
149
+
150
+ function formatMoney(value) {
151
+ if (value === undefined || value === null) return '0.00';
152
+ return value.toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
153
+ }
154
+
155
+ export function getCustomState(key) {
156
+ if (key) return _customState[key];
157
+ return { ..._customState };
158
+ }
159
+
160
+ export function setCustomState(newState) {
161
+ Object.keys(newState).forEach(function(key) {
162
+ _customState[key] = newState[key];
163
+ });
164
+ this.forceUpdate();
165
+ }
166
+
167
+ export function forceUpdate() {
168
+ this.setState({ timestamp: new Date().getTime() });
169
+ }
170
+
171
+ // ============================================================
172
+ // 生命周期
173
+ // ============================================================
174
+
175
+ export function didMount() {
176
+ // 从 localStorage 恢复上次输入
177
+ try {
178
+ var saved = localStorage.getItem('salary_calculator_state');
179
+ if (saved) {
180
+ var parsed = JSON.parse(saved);
181
+ Object.keys(parsed).forEach(function(key) {
182
+ if (key in _customState) {
183
+ _customState[key] = parsed[key];
184
+ }
185
+ });
186
+ }
187
+ } catch (e) {
188
+ // 忽略存储读取错误
189
+ }
190
+
191
+ // 初始计算
192
+ _customState.result = calculate(_customState);
193
+ this.forceUpdate();
194
+ }
195
+
196
+ export function didUnmount() {}
197
+
198
+ export function saveToStorage() {
199
+ try {
200
+ var toSave = {
201
+ monthlySalary: _customState.monthlySalary,
202
+ bonusMonths: _customState.bonusMonths,
203
+ city: _customState.city,
204
+ providentFundRate: _customState.providentFundRate,
205
+ socialSecurityBase: _customState.socialSecurityBase,
206
+ childEducation: _customState.childEducation,
207
+ housingLoan: _customState.housingLoan,
208
+ elderCare: _customState.elderCare,
209
+ supplementProvidentFund: _customState.supplementProvidentFund,
210
+ };
211
+ localStorage.setItem('salary_calculator_state', JSON.stringify(toSave));
212
+ } catch (e) {}
213
+ }
214
+
215
+ export function triggerCalculate() {
216
+ _customState.result = calculate(_customState);
217
+ this.saveToStorage();
218
+ this.forceUpdate();
219
+ }
220
+
221
+ export function handleCalculate() {
222
+ _customState.result = calculate(_customState);
223
+ this.saveToStorage();
224
+ this.forceUpdate();
225
+ }
226
+
227
+ export function handleCopy() {
228
+ var result = _customState.result || {};
229
+ if (!result.annualGrossSalary) return;
230
+ var state = _customState;
231
+ var cityName = (CITY_DATA[state.city] || CITY_DATA.beijing).name;
232
+ var text = [
233
+ '📊 薪资计算结果',
234
+ '━━━━━━━━━━━━━━━━━━',
235
+ '城市:' + cityName,
236
+ '月薪:¥' + formatMoney(state.monthlySalary),
237
+ '年终奖:' + state.bonusMonths + ' 个月',
238
+ '━━━━━━━━━━━━━━━━━━',
239
+ '税前年薪:¥' + formatMoney(result.annualGrossSalary),
240
+ '税后月收入:¥' + formatMoney(result.monthlyNetSalary),
241
+ '税后年薪:¥' + formatMoney(result.annualNetSalary),
242
+ '平均时薪:¥' + formatMoney(result.hourlyRate) + '/小时',
243
+ '月缴五险一金:¥' + formatMoney(result.monthlySocialInsurance),
244
+ '月缴个税:¥' + formatMoney(result.monthlyTax),
245
+ '综合税率:' + result.effectiveTaxRate + '%',
246
+ '━━━━━━━━━━━━━━━━━━',
247
+ '计算结果仅供参考,以实际劳动合同和当地政策为准',
248
+ ].join('\n');
249
+
250
+ try {
251
+ navigator.clipboard.writeText(text).then(() => {
252
+ _customState.copySuccess = true;
253
+ this.forceUpdate();
254
+ setTimeout(() => {
255
+ _customState.copySuccess = false;
256
+ this.forceUpdate();
257
+ }, 2000);
258
+ });
259
+ } catch (e) {
260
+ this.utils.toast({ title: '复制失败,请手动复制', type: 'error' });
261
+ }
262
+ }
263
+
264
+ export function toggleAdvanced() {
265
+ _customState.advancedExpanded = !_customState.advancedExpanded;
266
+ this.forceUpdate();
267
+ }
268
+
269
+ // ============================================================
270
+ // 渲染
271
+ // ============================================================
272
+
273
+ export function renderJsx() {
274
+ var { timestamp } = this.state;
275
+ var state = _customState;
276
+ var result = state.result || {};
277
+
278
+ // ---- 样式定义 ----
279
+ var styles = {
280
+ page: {
281
+ minHeight: '100vh',
282
+ background: '#F3F4F6',
283
+ fontFamily: "'PingFang SC', 'Microsoft YaHei', Inter, sans-serif",
284
+ color: '#1F2937',
285
+ },
286
+ header: {
287
+ background: 'linear-gradient(135deg, #059669 0%, #10B981 100%)',
288
+ padding: '24px 32px',
289
+ color: '#fff',
290
+ },
291
+ headerTitle: {
292
+ fontSize: '24px',
293
+ fontWeight: '700',
294
+ margin: '0 0 4px 0',
295
+ letterSpacing: '0.5px',
296
+ },
297
+ headerSubtitle: {
298
+ fontSize: '13px',
299
+ opacity: '0.85',
300
+ margin: 0,
301
+ },
302
+ container: {
303
+ maxWidth: '1200px',
304
+ margin: '0 auto',
305
+ padding: '24px 16px',
306
+ display: 'flex',
307
+ gap: '24px',
308
+ alignItems: 'flex-start',
309
+ flexWrap: 'wrap',
310
+ },
311
+ inputPanel: {
312
+ flex: '0 0 380px',
313
+ minWidth: '320px',
314
+ background: '#fff',
315
+ borderRadius: '12px',
316
+ boxShadow: '0 1px 8px rgba(0,0,0,0.08)',
317
+ padding: '24px',
318
+ },
319
+ resultPanel: {
320
+ flex: '1 1 500px',
321
+ minWidth: '300px',
322
+ display: 'flex',
323
+ flexDirection: 'column',
324
+ gap: '16px',
325
+ },
326
+ sectionTitle: {
327
+ fontSize: '14px',
328
+ fontWeight: '600',
329
+ color: '#374151',
330
+ marginBottom: '16px',
331
+ paddingBottom: '8px',
332
+ borderBottom: '2px solid #D1FAE5',
333
+ },
334
+ fieldGroup: {
335
+ marginBottom: '16px',
336
+ },
337
+ label: {
338
+ display: 'block',
339
+ fontSize: '13px',
340
+ color: '#6B7280',
341
+ marginBottom: '6px',
342
+ fontWeight: '500',
343
+ },
344
+ input: {
345
+ width: '100%',
346
+ height: '40px',
347
+ border: '1.5px solid #E5E7EB',
348
+ borderRadius: '8px',
349
+ padding: '0 12px',
350
+ fontSize: '14px',
351
+ color: '#1F2937',
352
+ outline: 'none',
353
+ boxSizing: 'border-box',
354
+ transition: 'border-color 0.2s',
355
+ fontFamily: "'Roboto Mono', 'SF Mono', monospace",
356
+ },
357
+ select: {
358
+ width: '100%',
359
+ height: '40px',
360
+ border: '1.5px solid #E5E7EB',
361
+ borderRadius: '8px',
362
+ padding: '0 12px',
363
+ fontSize: '14px',
364
+ color: '#1F2937',
365
+ outline: 'none',
366
+ boxSizing: 'border-box',
367
+ background: '#fff',
368
+ cursor: 'pointer',
369
+ },
370
+ sliderRow: {
371
+ display: 'flex',
372
+ alignItems: 'center',
373
+ gap: '12px',
374
+ },
375
+ slider: {
376
+ flex: 1,
377
+ accentColor: '#10B981',
378
+ },
379
+ sliderValue: {
380
+ minWidth: '60px',
381
+ textAlign: 'right',
382
+ fontSize: '14px',
383
+ fontWeight: '600',
384
+ color: '#10B981',
385
+ fontFamily: "'Roboto Mono', monospace",
386
+ },
387
+ quickBtns: {
388
+ display: 'flex',
389
+ gap: '8px',
390
+ marginTop: '8px',
391
+ flexWrap: 'wrap',
392
+ },
393
+ quickBtn: {
394
+ padding: '4px 12px',
395
+ borderRadius: '20px',
396
+ border: '1.5px solid #10B981',
397
+ background: '#fff',
398
+ color: '#10B981',
399
+ fontSize: '12px',
400
+ cursor: 'pointer',
401
+ fontWeight: '500',
402
+ },
403
+ quickBtnActive: {
404
+ padding: '4px 12px',
405
+ borderRadius: '20px',
406
+ border: '1.5px solid #10B981',
407
+ background: '#10B981',
408
+ color: '#fff',
409
+ fontSize: '12px',
410
+ cursor: 'pointer',
411
+ fontWeight: '500',
412
+ },
413
+ advancedToggle: {
414
+ display: 'flex',
415
+ alignItems: 'center',
416
+ justifyContent: 'space-between',
417
+ cursor: 'pointer',
418
+ padding: '10px 0',
419
+ borderTop: '1px solid #F3F4F6',
420
+ marginTop: '8px',
421
+ color: '#6B7280',
422
+ fontSize: '13px',
423
+ fontWeight: '500',
424
+ },
425
+ calcBtn: {
426
+ width: '100%',
427
+ height: '44px',
428
+ background: 'linear-gradient(135deg, #059669 0%, #10B981 100%)',
429
+ color: '#fff',
430
+ border: 'none',
431
+ borderRadius: '10px',
432
+ fontSize: '15px',
433
+ fontWeight: '600',
434
+ cursor: 'pointer',
435
+ marginTop: '16px',
436
+ letterSpacing: '1px',
437
+ },
438
+ // 结果卡片
439
+ gridRow: {
440
+ display: 'grid',
441
+ gridTemplateColumns: '1fr 1fr',
442
+ gap: '12px',
443
+ },
444
+ card: {
445
+ background: '#fff',
446
+ borderRadius: '12px',
447
+ padding: '20px',
448
+ boxShadow: '0 1px 8px rgba(0,0,0,0.07)',
449
+ },
450
+ cardHighlight: {
451
+ background: 'linear-gradient(135deg, #059669 0%, #10B981 100%)',
452
+ borderRadius: '12px',
453
+ padding: '20px',
454
+ boxShadow: '0 4px 16px rgba(16,185,129,0.25)',
455
+ color: '#fff',
456
+ },
457
+ cardLabel: {
458
+ fontSize: '12px',
459
+ color: '#9CA3AF',
460
+ marginBottom: '8px',
461
+ fontWeight: '500',
462
+ },
463
+ cardLabelLight: {
464
+ fontSize: '12px',
465
+ color: 'rgba(255,255,255,0.8)',
466
+ marginBottom: '8px',
467
+ fontWeight: '500',
468
+ },
469
+ cardValue: {
470
+ fontSize: '26px',
471
+ fontWeight: '700',
472
+ color: '#10B981',
473
+ fontFamily: "'Roboto Mono', 'SF Mono', monospace",
474
+ lineHeight: 1.2,
475
+ },
476
+ cardValueLight: {
477
+ fontSize: '26px',
478
+ fontWeight: '700',
479
+ color: '#fff',
480
+ fontFamily: "'Roboto Mono', 'SF Mono', monospace",
481
+ lineHeight: 1.2,
482
+ },
483
+ cardNote: {
484
+ fontSize: '11px',
485
+ color: '#9CA3AF',
486
+ marginTop: '4px',
487
+ },
488
+ cardNoteLight: {
489
+ fontSize: '11px',
490
+ color: 'rgba(255,255,255,0.7)',
491
+ marginTop: '4px',
492
+ },
493
+ // 扣除明细
494
+ detailCard: {
495
+ background: '#fff',
496
+ borderRadius: '12px',
497
+ padding: '20px',
498
+ boxShadow: '0 1px 8px rgba(0,0,0,0.07)',
499
+ },
500
+ detailRow: {
501
+ display: 'flex',
502
+ justifyContent: 'space-between',
503
+ alignItems: 'center',
504
+ padding: '8px 0',
505
+ borderBottom: '1px solid #F9FAFB',
506
+ fontSize: '13px',
507
+ },
508
+ detailLabel: {
509
+ color: '#6B7280',
510
+ },
511
+ detailValue: {
512
+ fontWeight: '600',
513
+ color: '#1F2937',
514
+ fontFamily: "'Roboto Mono', monospace",
515
+ },
516
+ detailValueRed: {
517
+ fontWeight: '600',
518
+ color: '#EF4444',
519
+ fontFamily: "'Roboto Mono', monospace",
520
+ },
521
+ detailValueGreen: {
522
+ fontWeight: '600',
523
+ color: '#10B981',
524
+ fontFamily: "'Roboto Mono', monospace",
525
+ },
526
+ // 进度条
527
+ progressBar: {
528
+ height: '8px',
529
+ borderRadius: '4px',
530
+ background: '#F3F4F6',
531
+ overflow: 'hidden',
532
+ marginTop: '4px',
533
+ },
534
+ // 复制按钮
535
+ actionRow: {
536
+ display: 'flex',
537
+ gap: '10px',
538
+ },
539
+ copyBtn: {
540
+ flex: 1,
541
+ height: '40px',
542
+ background: '#fff',
543
+ border: '1.5px solid #10B981',
544
+ borderRadius: '8px',
545
+ color: '#10B981',
546
+ fontSize: '13px',
547
+ fontWeight: '600',
548
+ cursor: 'pointer',
549
+ },
550
+ disclaimer: {
551
+ textAlign: 'center',
552
+ fontSize: '11px',
553
+ color: '#9CA3AF',
554
+ padding: '16px',
555
+ lineHeight: 1.6,
556
+ },
557
+ };
558
+
559
+ // ---- 事件处理 ----
560
+
561
+ function handleSalaryChange(e) {
562
+ _customState.monthlySalary = parseFloat(e.target.value) || 0;
563
+ }
564
+
565
+ function handleCityChange(e) {
566
+ _customState.city = e.target.value;
567
+ triggerCalculate();
568
+ }
569
+
570
+ function handleBonusSlider(e) {
571
+ _customState.bonusMonths = parseFloat(e.target.value);
572
+ triggerCalculate();
573
+ }
574
+
575
+ function handlePFRateChange(e) {
576
+ _customState.providentFundRate = parseFloat(e.target.value) || 0.12;
577
+ triggerCalculate();
578
+ }
579
+
580
+ function handleSSBaseChange(e) {
581
+ _customState.socialSecurityBase = e.target.value;
582
+ triggerCalculate();
583
+ }
584
+
585
+ function handleSpecialDeductionChange(field, e) {
586
+ _customState[field] = parseFloat(e.target.value) || 0;
587
+ }
588
+
589
+ function handleSupplementPFChange(e) {
590
+ _customState.supplementProvidentFund = parseFloat(e.target.value) || 0;
591
+ }
592
+
593
+
594
+
595
+ // ---- 计算进度条宽度 ----
596
+
597
+ var cityOptions = Object.keys(CITY_DATA).map(function(key) {
598
+ return <option key={key} value={key}>{CITY_DATA[key].name}</option>;
599
+ });
600
+
601
+ var bonusQuickOptions = [
602
+ { label: '13薪', value: 1 },
603
+ { label: '14薪', value: 2 },
604
+ { label: '15薪', value: 3 },
605
+ { label: '16薪', value: 4 },
606
+ ];
607
+
608
+ return (
609
+ <div style={styles.page}>
610
+ <div style={{ display: 'none' }}>{timestamp}</div>
611
+
612
+ {/* 顶部标题栏 */}
613
+ <div style={styles.header}>
614
+ <h1 style={styles.headerTitle}>💰 个人薪资计算器</h1>
615
+ <p style={styles.headerSubtitle}>精准估算收入结构 · 让你看清每一分钱的去向 · 数据基于 2026 年政策</p>
616
+ </div>
617
+
618
+ <div style={styles.container}>
619
+ {/* 左侧输入区 */}
620
+ <div style={styles.inputPanel}>
621
+ <div style={styles.sectionTitle}>📝 基础信息</div>
622
+
623
+ {/* 月薪 */}
624
+ <div style={styles.fieldGroup}>
625
+ <label style={styles.label}>月薪(税前,元)</label>
626
+ <input
627
+ id="input-salary"
628
+ type="number"
629
+ style={styles.input}
630
+ defaultValue={state.monthlySalary}
631
+ placeholder="例如:25000"
632
+ onChange={function(e) { _customState.monthlySalary = parseFloat(e.target.value) || 0; }}
633
+ onBlur={(e) => { this.handleCalculate(); }}
634
+ />
635
+ </div>
636
+
637
+ {/* 工作城市 */}
638
+ <div style={styles.fieldGroup}>
639
+ <label style={styles.label}>工作城市</label>
640
+ <select style={styles.select} defaultValue={state.city} onChange={(e) => { _customState.city = e.target.value; this.triggerCalculate(); }}>
641
+ {cityOptions}
642
+ </select>
643
+ </div>
644
+
645
+ {/* 年终奖月数 */}
646
+ <div style={styles.fieldGroup}>
647
+ <label style={styles.label}>年终奖月数:<span style={{ color: '#10B981', fontWeight: 700 }}>{state.bonusMonths} 个月</span></label>
648
+ <div style={styles.sliderRow}>
649
+ <input
650
+ type="range"
651
+ min="0"
652
+ max="6"
653
+ step="0.5"
654
+ style={styles.slider}
655
+ defaultValue={state.bonusMonths}
656
+ onChange={(e) => { _customState.bonusMonths = parseFloat(e.target.value); this.triggerCalculate(); }}
657
+ />
658
+ <span style={styles.sliderValue}>{state.bonusMonths}月</span>
659
+ </div>
660
+ <div style={styles.quickBtns}>
661
+ {bonusQuickOptions.map(function(opt) {
662
+ return (
663
+ <button
664
+ key={opt.value}
665
+ style={state.bonusMonths === opt.value ? styles.quickBtnActive : styles.quickBtn}
666
+ onClick={(e) => { _customState.bonusMonths = opt.value; this.triggerCalculate(); }}
667
+ >
668
+ {opt.label}
669
+ </button>
670
+ );
671
+ })}
672
+ </div>
673
+ </div>
674
+
675
+ {/* 高级设置折叠 */}
676
+ <div style={styles.advancedToggle} onClick={(e) => { this.toggleAdvanced(); }}>
677
+ <span>⚙️ 高级设置(公积金比例、专项扣除等)</span>
678
+ <span>{state.advancedExpanded ? '▲' : '▼'}</span>
679
+ </div>
680
+
681
+ {state.advancedExpanded && (
682
+ <div>
683
+ {/* 公积金比例 */}
684
+ <div style={styles.fieldGroup}>
685
+ <label style={styles.label}>公积金比例:<span style={{ color: '#10B981', fontWeight: 700 }}>{Math.round((state.providentFundRate || 0.12) * 100)}%</span></label>
686
+ <div style={styles.sliderRow}>
687
+ defaultValue={state.providentFundRate}
688
+ onChange={(e) => { _customState.providentFundRate = parseFloat(e.target.value) || 0.12; this.triggerCalculate(); }}
689
+ <span style={styles.sliderValue}>{Math.round((state.providentFundRate || 0.12) * 100)}%</span>
690
+ </div>
691
+ </div>
692
+
693
+ {/* 社保方案 */}
694
+ <div style={styles.fieldGroup}>
695
+ <label style={styles.label}>社保缴纳基数</label>
696
+ <select style={styles.select} defaultValue={state.socialSecurityBase} onChange={(e) => { _customState.socialSecurityBase = e.target.value; this.triggerCalculate(); }}>
697
+ <option value="actual">按实际工资(推荐)</option>
698
+ <option value="min">按最低基数</option>
699
+ <option value="max">按上限基数</option>
700
+ </select>
701
+ </div>
702
+
703
+ {/* 专项扣除 */}
704
+ <div style={{ ...styles.sectionTitle, marginTop: '12px' }}>🏠 专项附加扣除(月度,元)</div>
705
+
706
+ <div style={styles.fieldGroup}>
707
+ <label style={styles.label}>子女教育</label>
708
+ <input
709
+ type="number"
710
+ style={styles.input}
711
+ defaultValue={state.childEducation}
712
+ placeholder="0(每孩每月最高2000)"
713
+ onChange={function(e) { _customState.childEducation = parseFloat(e.target.value) || 0; }}
714
+ onBlur={(e) => { this.handleCalculate(); }}
715
+ />
716
+ </div>
717
+
718
+ <div style={styles.fieldGroup}>
719
+ <label style={styles.label}>住房贷款利息</label>
720
+ <input
721
+ type="number"
722
+ style={styles.input}
723
+ defaultValue={state.housingLoan}
724
+ placeholder="0(每月最高1000)"
725
+ onChange={function(e) { _customState.housingLoan = parseFloat(e.target.value) || 0; }}
726
+ onBlur={(e) => { this.handleCalculate(); }}
727
+ />
728
+ </div>
729
+
730
+ <div style={styles.fieldGroup}>
731
+ <label style={styles.label}>赡养老人</label>
732
+ <input
733
+ type="number"
734
+ style={styles.input}
735
+ defaultValue={state.elderCare}
736
+ placeholder="0(每月最高3000)"
737
+ onChange={function(e) { _customState.elderCare = parseFloat(e.target.value) || 0; }}
738
+ onBlur={(e) => { this.handleCalculate(); }}
739
+ />
740
+ </div>
741
+
742
+ <div style={styles.fieldGroup}>
743
+ <label style={styles.label}>补充公积金(月度)</label>
744
+ <input
745
+ type="number"
746
+ style={styles.input}
747
+ defaultValue={state.supplementProvidentFund}
748
+ placeholder="0"
749
+ onChange={function(e) { _customState.supplementProvidentFund = parseFloat(e.target.value) || 0; }}
750
+ onBlur={(e) => { this.handleCalculate(); }}
751
+ />
752
+ </div>
753
+ </div>
754
+ )}
755
+
756
+ </div>
757
+
758
+ {/* 右侧结果区 */}
759
+ <div style={styles.resultPanel}>
760
+ {/* 4宫格核心指标 */}
761
+ <div style={styles.gridRow}>
762
+ {/* 税后月收入 - 最突出 */}
763
+ <div style={{ ...styles.cardHighlight, gridColumn: 'span 2' }}>
764
+ <div style={styles.cardLabelLight}>💵 税后月收入</div>
765
+ <div style={styles.cardValueLight}>¥ {formatMoney(result.monthlyNetSalary || 0)}</div>
766
+ <div style={styles.cardNoteLight}>实际每月到手金额</div>
767
+ </div>
768
+
769
+ <div style={styles.card}>
770
+ <div style={styles.cardLabel}>📅 税前年薪</div>
771
+ <div style={styles.cardValue}>¥ {formatMoney(result.annualGrossSalary || 0)}</div>
772
+ <div style={styles.cardNote}>含年终奖</div>
773
+ </div>
774
+
775
+ <div style={styles.card}>
776
+ <div style={styles.cardLabel}>🏦 税后年薪</div>
777
+ <div style={styles.cardValue}>¥ {formatMoney(result.annualNetSalary || 0)}</div>
778
+ <div style={styles.cardNote}>实际到手</div>
779
+ </div>
780
+
781
+ <div style={styles.card}>
782
+ <div style={styles.cardLabel}>⏰ 平均时薪</div>
783
+ <div style={styles.cardValue}>¥ {formatMoney(result.hourlyRate || 0)}</div>
784
+ <div style={styles.cardNote}>按 176 小时/月</div>
785
+ </div>
786
+
787
+ <div style={styles.card}>
788
+ <div style={styles.cardLabel}>📊 综合税率</div>
789
+ <div style={styles.cardValue}>{result.effectiveTaxRate || 0}%</div>
790
+ <div style={styles.cardNote}>个税 / 税前年薪</div>
791
+ </div>
792
+ </div>
793
+
794
+ {/* 月度资金流向明细 */}
795
+ <div style={styles.detailCard}>
796
+ <div style={styles.sectionTitle}>📋 月度资金流向明细</div>
797
+
798
+ <div style={styles.detailRow}>
799
+ <span style={styles.detailLabel}>税前月薪</span>
800
+ <span style={styles.detailValue}>¥ {formatMoney(state.monthlySalary || 0)}</span>
801
+ </div>
802
+
803
+ {/* 进度条可视化 */}
804
+ <div style={{ margin: '12px 0 16px' }}>
805
+ <div style={{ fontSize: '12px', color: '#6B7280', marginBottom: '8px' }}>月薪构成分布</div>
806
+ {(function() {
807
+ var gross = result.annualGrossSalary / 12 || 0;
808
+ var net = result.monthlyNetSalary || 0;
809
+ var si = result.monthlySocialInsurance || 0;
810
+ var tax = result.monthlyTax || 0;
811
+ var netPct = gross > 0 ? (net / gross) * 100 : 0;
812
+ var siPct = gross > 0 ? (si / gross) * 100 : 0;
813
+ var taxPct = gross > 0 ? (tax / gross) * 100 : 0;
814
+ return (
815
+ <div>
816
+ <div style={{ display: 'flex', height: '12px', borderRadius: '6px', overflow: 'hidden', gap: '2px' }}>
817
+ <div style={{ width: netPct + '%', background: '#10B981', borderRadius: '6px 0 0 6px', transition: 'width 0.4s' }} title={'到手 ' + netPct.toFixed(1) + '%'} />
818
+ <div style={{ width: siPct + '%', background: '#3B82F6', transition: 'width 0.4s' }} title={'五险一金 ' + siPct.toFixed(1) + '%'} />
819
+ <div style={{ width: taxPct + '%', background: '#9CA3AF', borderRadius: '0 6px 6px 0', transition: 'width 0.4s' }} title={'个税 ' + taxPct.toFixed(1) + '%'} />
820
+ </div>
821
+ <div style={{ display: 'flex', gap: '16px', marginTop: '6px', fontSize: '11px', color: '#6B7280' }}>
822
+ <span><span style={{ display: 'inline-block', width: '8px', height: '8px', borderRadius: '2px', background: '#10B981', marginRight: '4px' }} />到手 {netPct.toFixed(1)}%</span>
823
+ <span><span style={{ display: 'inline-block', width: '8px', height: '8px', borderRadius: '2px', background: '#3B82F6', marginRight: '4px' }} />五险一金 {siPct.toFixed(1)}%</span>
824
+ <span><span style={{ display: 'inline-block', width: '8px', height: '8px', borderRadius: '2px', background: '#9CA3AF', marginRight: '4px' }} />个税 {taxPct.toFixed(1)}%</span>
825
+ </div>
826
+ </div>
827
+ );
828
+ })()}
829
+ </div>
830
+
831
+ <div style={styles.detailRow}>
832
+ <span style={styles.detailLabel}>— 养老保险({Math.round((CITY_DATA[state.city] || CITY_DATA.beijing).pension * 100)}%)</span>
833
+ <span style={styles.detailValueRed}>- ¥ {formatMoney((result.breakdown || {}).monthlyPension || 0)}</span>
834
+ </div>
835
+ <div style={styles.detailRow}>
836
+ <span style={styles.detailLabel}>— 医疗保险({((CITY_DATA[state.city] || CITY_DATA.beijing).medical * 100).toFixed(1)}%)</span>
837
+ <span style={styles.detailValueRed}>- ¥ {formatMoney((result.breakdown || {}).monthlyMedical || 0)}</span>
838
+ </div>
839
+ <div style={styles.detailRow}>
840
+ <span style={styles.detailLabel}>— 失业保险({((CITY_DATA[state.city] || CITY_DATA.beijing).unemployment * 100).toFixed(1)}%)</span>
841
+ <span style={styles.detailValueRed}>- ¥ {formatMoney((result.breakdown || {}).monthlyUnemployment || 0)}</span>
842
+ </div>
843
+ <div style={styles.detailRow}>
844
+ <span style={styles.detailLabel}>— 住房公积金({Math.round((state.providentFundRate || 0.12) * 100)}%)</span>
845
+ <span style={styles.detailValueRed}>- ¥ {formatMoney((result.breakdown || {}).monthlyProvidentFund || 0)}</span>
846
+ </div>
847
+ {(state.supplementProvidentFund > 0) && (
848
+ <div style={styles.detailRow}>
849
+ <span style={styles.detailLabel}>— 补充公积金</span>
850
+ <span style={styles.detailValueRed}>- ¥ {formatMoney((result.breakdown || {}).monthlySupplementPF || 0)}</span>
851
+ </div>
852
+ )}
853
+ <div style={{ ...styles.detailRow, borderBottom: 'none', fontWeight: '600' }}>
854
+ <span style={styles.detailLabel}>— 五险一金合计</span>
855
+ <span style={styles.detailValueRed}>- ¥ {formatMoney(result.monthlySocialInsurance || 0)}</span>
856
+ </div>
857
+ <div style={{ ...styles.detailRow, borderBottom: 'none', fontWeight: '600' }}>
858
+ <span style={styles.detailLabel}>— 月度个税</span>
859
+ <span style={styles.detailValueRed}>- ¥ {formatMoney(result.monthlyTax || 0)}</span>
860
+ </div>
861
+ <div style={{ ...styles.detailRow, borderBottom: 'none', background: '#F0FDF4', borderRadius: '8px', padding: '10px 12px', marginTop: '8px' }}>
862
+ <span style={{ color: '#059669', fontWeight: '700', fontSize: '14px' }}>= 税后月收入</span>
863
+ <span style={{ ...styles.detailValueGreen, fontSize: '18px' }}>¥ {formatMoney(result.monthlyNetSalary || 0)}</span>
864
+ </div>
865
+ </div>
866
+
867
+ {/* 年度汇总 */}
868
+ <div style={styles.detailCard}>
869
+ <div style={styles.sectionTitle}>📆 年度汇总</div>
870
+ <div style={styles.detailRow}>
871
+ <span style={styles.detailLabel}>税前年薪(含年终奖)</span>
872
+ <span style={styles.detailValue}>¥ {formatMoney(result.annualGrossSalary || 0)}</span>
873
+ </div>
874
+ <div style={styles.detailRow}>
875
+ <span style={styles.detailLabel}>年缴五险一金</span>
876
+ <span style={styles.detailValueRed}>- ¥ {formatMoney(result.annualSocialInsurance || 0)}</span>
877
+ </div>
878
+ <div style={styles.detailRow}>
879
+ <span style={styles.detailLabel}>年缴个税</span>
880
+ <span style={styles.detailValueRed}>- ¥ {formatMoney(result.annualTax || 0)}</span>
881
+ </div>
882
+ <div style={{ ...styles.detailRow, borderBottom: 'none', background: '#F0FDF4', borderRadius: '8px', padding: '10px 12px', marginTop: '8px' }}>
883
+ <span style={{ color: '#059669', fontWeight: '700', fontSize: '14px' }}>税后年薪(实际到手)</span>
884
+ <span style={{ ...styles.detailValueGreen, fontSize: '18px' }}>¥ {formatMoney(result.annualNetSalary || 0)}</span>
885
+ </div>
886
+ </div>
887
+
888
+ {/* 操作按钮 */}
889
+ <div style={styles.actionRow}>
890
+ <button style={styles.copyBtn} onClick={(e) => { this.handleCopy(); }}>
891
+ {state.copySuccess ? '✅ 已复制!' : '📋 一键复制结果'}
892
+ </button>
893
+ </div>
894
+
895
+ {/* 免责声明 */}
896
+ <div style={styles.disclaimer}>
897
+ ⚠️ 计算结果仅供参考,以实际劳动合同和当地政策为准。<br />
898
+ 社保比例数据基于 2026 年各城市政策,个税税率依据现行累进税率表。
899
+ </div>
900
+ </div>
901
+ </div>
902
+ </div>
903
+ );
904
+ }