Skip to content

Commit 88aa976

Browse files
committed
feat: 实现 FloatingBall 技术交流群悬浮球组件
- 创建 FloatingBall 组件,显示在页面右下角 - 使用微信群图标样式,包含'交流群'文字 - 鼠标悬停显示微信二维码弹窗 - 提示用户扫码发送'leetcode'加入算法交流群 - 下载并集成指定的二维码图片 - 保持图片原始比例不变形
1 parent b316f6c commit 88aa976

File tree

6 files changed

+146
-2
lines changed

6 files changed

+146
-2
lines changed

.kiro/specs/min-stack-visualizer/tasks.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,8 @@
159159
- **Property 7: 播放控制状态一致性**
160160
- **Validates: Requirements 7.3, 7.5, 7.9, 7.15**
161161

162-
- [ ] 13. UI 组件开发 - FloatingBall
163-
- [ ] 13.1 实现 FloatingBall 组件
162+
- [x] 13. UI 组件开发 - FloatingBall
163+
- [x] 13.1 实现 FloatingBall 组件
164164
- 创建 components/FloatingBall/FloatingBall.tsx 和样式文件
165165
- 右下角悬浮球(微信群图标样式)
166166
- 悬停显示二维码

public/wechat-qrcode.png

328 KB
Loading

src/App.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { InputPanel } from './components/InputPanel';
55
import { CodePanel } from './components/CodePanel/CodePanel';
66
import { Canvas } from './components/Canvas';
77
import { ControlPanel } from './components/ControlPanel';
8+
import { FloatingBall } from './components/FloatingBall';
89
import type { Operation, Language, AlgorithmStep } from './types';
910
import { generateSteps } from './services/stepGenerator';
1011
import {
@@ -189,6 +190,9 @@ function App() {
189190
/>
190191
)}
191192
</main>
193+
194+
{/* 技术交流群悬浮球 */}
195+
<FloatingBall />
192196
</div>
193197
);
194198
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
.floating-ball-container {
2+
position: fixed;
3+
bottom: 24px;
4+
right: 24px;
5+
z-index: 1000;
6+
}
7+
8+
.floating-ball {
9+
display: flex;
10+
flex-direction: column;
11+
align-items: center;
12+
justify-content: center;
13+
width: 60px;
14+
height: 60px;
15+
background: linear-gradient(135deg, #07c160 0%, #06ad56 100%);
16+
border-radius: 50%;
17+
cursor: pointer;
18+
box-shadow: 0 4px 12px rgba(7, 193, 96, 0.4);
19+
transition: all 0.3s ease;
20+
}
21+
22+
.floating-ball:hover {
23+
transform: scale(1.1);
24+
box-shadow: 0 6px 16px rgba(7, 193, 96, 0.5);
25+
}
26+
27+
.wechat-group-icon {
28+
width: 24px;
29+
height: 24px;
30+
color: white;
31+
}
32+
33+
.ball-text {
34+
font-size: 10px;
35+
color: white;
36+
margin-top: 2px;
37+
font-weight: 500;
38+
}
39+
40+
.qrcode-popup {
41+
position: absolute;
42+
bottom: 70px;
43+
right: 0;
44+
background: white;
45+
border-radius: 12px;
46+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
47+
padding: 16px;
48+
animation: fadeIn 0.2s ease;
49+
}
50+
51+
.qrcode-popup::after {
52+
content: '';
53+
position: absolute;
54+
bottom: -8px;
55+
right: 20px;
56+
width: 0;
57+
height: 0;
58+
border-left: 8px solid transparent;
59+
border-right: 8px solid transparent;
60+
border-top: 8px solid white;
61+
}
62+
63+
.qrcode-content {
64+
display: flex;
65+
flex-direction: column;
66+
align-items: center;
67+
}
68+
69+
.qrcode-image {
70+
width: 200px;
71+
height: auto;
72+
border-radius: 8px;
73+
}
74+
75+
.qrcode-tip {
76+
margin-top: 12px;
77+
font-size: 13px;
78+
color: #333;
79+
text-align: center;
80+
line-height: 1.5;
81+
}
82+
83+
.qrcode-tip strong {
84+
color: #07c160;
85+
}
86+
87+
@keyframes fadeIn {
88+
from {
89+
opacity: 0;
90+
transform: translateY(10px);
91+
}
92+
to {
93+
opacity: 1;
94+
transform: translateY(0);
95+
}
96+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { useState } from 'react';
2+
import './FloatingBall.css';
3+
import qrcodeImage from '/wechat-qrcode.png';
4+
5+
export function FloatingBall() {
6+
const [isHovered, setIsHovered] = useState(false);
7+
8+
return (
9+
<div className="floating-ball-container">
10+
<div
11+
className="floating-ball"
12+
onMouseEnter={() => setIsHovered(true)}
13+
onMouseLeave={() => setIsHovered(false)}
14+
>
15+
<svg
16+
className="wechat-group-icon"
17+
viewBox="0 0 24 24"
18+
fill="currentColor"
19+
>
20+
<path d="M8.5 11.5a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm4 0a1 1 0 1 0 0-2 1 1 0 0 0 0 2z" />
21+
<path d="M9 2C4.58 2 1 5.58 1 10c0 1.5.4 2.9 1.1 4.1L1 18l3.9-1.1c1.2.7 2.6 1.1 4.1 1.1 4.42 0 8-3.58 8-8s-3.58-8-8-8zm0 14c-1.3 0-2.5-.3-3.6-.9l-.4-.2-2.5.7.7-2.5-.2-.4C2.3 11.5 2 10.3 2 9c0-3.87 3.13-7 7-7s7 3.13 7 7-3.13 7-7 7z" />
22+
<path d="M19 10c0-1.3-.3-2.5-.9-3.6l.9-3.4-3.4.9C14.5 3.3 13.3 3 12 3c-.5 0-1 .05-1.5.13C12.3 2.4 14.6 2 17 2c4.42 0 8 3.58 8 8 0 1.5-.4 2.9-1.1 4.1L25 18l-3.9-1.1c-1.2.7-2.6 1.1-4.1 1.1-.5 0-1-.05-1.5-.13.8.08 1.6.13 2.5.13 1.3 0 2.5-.3 3.6-.9l.4-.2 2.5.7-.7-2.5.2-.4c.6-1.1.9-2.3.9-3.6z" />
23+
</svg>
24+
<span className="ball-text">交流群</span>
25+
</div>
26+
27+
{isHovered && (
28+
<div className="qrcode-popup">
29+
<div className="qrcode-content">
30+
<img
31+
src={qrcodeImage}
32+
alt="微信二维码"
33+
className="qrcode-image"
34+
/>
35+
<p className="qrcode-tip">
36+
微信扫码发送 <strong>"leetcode"</strong> 加入算法交流群
37+
</p>
38+
</div>
39+
</div>
40+
)}
41+
</div>
42+
);
43+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { FloatingBall } from './FloatingBall';

0 commit comments

Comments
 (0)