Skip to content

Commit a3a89c6

Browse files
committed
perf: perf
1 parent 33af046 commit a3a89c6

2 files changed

Lines changed: 168 additions & 75 deletions

File tree

src/components/stateless/DottedStepper/index.jsx

Lines changed: 164 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,30 @@ const defaultSteps = [
3030
{ label: 'Review & Submit' },
3131
]
3232

33-
const StepIndicator = ({ currentStep, steps }) => {
33+
const StepIndicator = ({
34+
currentStep,
35+
steps,
36+
activeColor: propActiveColor,
37+
inactiveBgColor: propInactiveBgColor,
38+
circleSize = 44,
39+
gradientColors = ['#ff7a18', '#af002d', '#319197'],
40+
showPercent = true,
41+
}) => {
3442
const { themeSettings } = useProThemeContext()
3543
const isMobile = useStore((s) => s.isMobile)
3644
const isDark = themeSettings.themeMode === 'dark'
3745

38-
// 增强蓝色对比度
39-
const activeColor = isDark ? '#40a9ff' : '#0958d9' // 更深的蓝色
46+
// 允许外部传入颜色;否则基于 theme 决定默认颜色
47+
const activeColor = propActiveColor || (isDark ? '#40a9ff' : '#0958d9')
4048
const activeTextColor = '#ffffff'
41-
const inactiveBgColor = isDark ? '#262626' : '#f5f5f5'
49+
const inactiveBgColor = propInactiveBgColor || (isDark ? '#262626' : '#f5f5f5')
4250
const inactiveTextColor = isDark ? '#8c8c8c' : '#bfbfbf'
4351

52+
const desktopSize = circleSize
53+
const mobileSize = Math.max(Math.round(circleSize * 0.82), 32)
54+
55+
const progressPercent = Math.round((currentStep / Math.max(1, steps.length - 1)) * 100)
56+
4457
return (
4558
<div className="relative w-full">
4659
{isMobile ? (
@@ -50,11 +63,18 @@ const StepIndicator = ({ currentStep, steps }) => {
5063
<div key={step.label} className="flex items-center space-x-3">
5164
<motion.div
5265
className={clsx(
53-
'flex size-8 items-center justify-center rounded-full border-2 transition-all duration-300',
66+
'flex items-center justify-center rounded-full border-2 transition-all duration-300',
5467
index <= currentStep ? 'border-transparent shadow-lg' : 'border-gray-300 dark:border-gray-600'
5568
)}
56-
animate={{ scale: index === currentStep ? 1.1 : 1 }}
69+
animate={{ scale: index === currentStep ? 1.06 : 1 }}
5770
style={{
71+
width: mobileSize,
72+
height: mobileSize,
73+
minWidth: mobileSize,
74+
minHeight: mobileSize,
75+
display: 'flex',
76+
alignItems: 'center',
77+
justifyContent: 'center',
5878
backgroundColor: index <= currentStep ? activeColor : inactiveBgColor,
5979
color: index <= currentStep ? activeTextColor : inactiveTextColor,
6080
borderColor: index <= currentStep ? activeColor : undefined,
@@ -83,78 +103,109 @@ const StepIndicator = ({ currentStep, steps }) => {
83103
>
84104
{step.label}
85105
</div>
86-
{index === currentStep && (
87-
<div className="mt-1 text-xs" style={{ color: activeColor }}>
88-
当前步骤
106+
{index === currentStep && showPercent && (
107+
<div className="mt-1 text-xs font-semibold" style={{ color: activeColor }}>
108+
{progressPercent}%
89109
</div>
90110
)}
91111
</div>
92112
</div>
93113
))}
94114
</div>
95115
) : (
96-
// 桌面端水平布局
97-
<div className="flex items-center justify-between">
98-
{steps.map((step, index) => (
99-
<React.Fragment key={step.label}>
100-
<div className="flex flex-col items-center">
101-
<motion.div
102-
className={clsx(
103-
'flex size-8 items-center justify-center rounded-full border-2 transition-all duration-300',
104-
index <= currentStep ? 'border-transparent shadow-lg' : 'border-gray-300 dark:border-gray-600'
105-
)}
106-
animate={{ scale: index === currentStep ? 1.1 : 1 }}
107-
style={{
108-
backgroundColor: index <= currentStep ? activeColor : inactiveBgColor,
109-
color: index <= currentStep ? activeTextColor : inactiveTextColor,
110-
borderColor: index <= currentStep ? activeColor : undefined,
111-
boxShadow: index <= currentStep ? `0 4px 12px ${activeColor}40` : undefined,
112-
}}
113-
>
114-
{index < currentStep ? (
115-
<AnimatedIcon variant="spin" mode="hover">
116-
<CheckCircle size={16} />
117-
</AnimatedIcon>
118-
) : (
119-
<AnimatedIcon variant="spin" mode="hover">
120-
<Circle size={16} fill="currentColor" />
121-
</AnimatedIcon>
122-
)}
123-
</motion.div>
124-
<div
125-
className={clsx(
126-
'mt-2 text-center text-xs font-medium transition-colors duration-300',
127-
index <= currentStep ? '' : 'text-gray-500 dark:text-gray-400'
128-
)}
129-
style={{
130-
color: index <= currentStep ? activeColor : undefined,
131-
}}
132-
>
133-
{step.label}
134-
</div>
135-
</div>
136-
{index < steps.length - 1 && (
137-
<div className="relative mx-4 flex-grow">
138-
<div
139-
className="absolute top-1/2 h-1 w-full -translate-y-1/2 rounded-full"
140-
style={{ backgroundColor: inactiveBgColor }}
141-
/>
116+
// 桌面端水平布局(居中、限制标签宽度、优化连线)
117+
<div className="flex w-full justify-center" style={{ marginBottom: 12 }}>
118+
<div className="flex w-full max-w-3xl items-center">
119+
{steps.map((step, index) => (
120+
<React.Fragment key={step.label}>
121+
<div className="flex flex-col items-center" style={{ flex: '0 0 140px', minWidth: 80 }}>
142122
<motion.div
143-
className="absolute top-1/2 h-1 w-full -translate-y-1/2 rounded-full"
123+
className={clsx(
124+
'flex items-center justify-center rounded-full border-2 transition-all duration-300',
125+
index <= currentStep ? 'border-transparent shadow-lg' : 'border-gray-300 dark:border-gray-600'
126+
)}
127+
animate={{ scale: index === currentStep ? 1.06 : 1 }}
144128
style={{
145-
backgroundColor: activeColor,
146-
boxShadow: `0 0 8px ${activeColor}60`,
129+
width: desktopSize,
130+
height: desktopSize,
131+
minWidth: desktopSize,
132+
minHeight: desktopSize,
133+
display: 'flex',
134+
alignItems: 'center',
135+
justifyContent: 'center',
136+
backgroundColor: index <= currentStep ? activeColor : inactiveBgColor,
137+
color: index <= currentStep ? activeTextColor : inactiveTextColor,
138+
borderColor: index <= currentStep ? activeColor : undefined,
139+
boxShadow: index <= currentStep ? `0 6px 18px ${activeColor}30` : undefined,
140+
zIndex: 10,
147141
}}
148-
initial={{ width: '0%' }}
149-
animate={{
150-
width: index < currentStep ? '100%' : '0%',
142+
>
143+
{index < currentStep ? (
144+
<AnimatedIcon variant="spin" mode="hover">
145+
<CheckCircle size={18} />
146+
</AnimatedIcon>
147+
) : (
148+
<AnimatedIcon variant="spin" mode="hover">
149+
<Circle size={18} fill="currentColor" />
150+
</AnimatedIcon>
151+
)}
152+
</motion.div>
153+
154+
<div
155+
className={clsx(
156+
'mt-2 text-center text-xs font-medium transition-colors duration-300',
157+
index <= currentStep ? '' : 'text-gray-500 dark:text-gray-400'
158+
)}
159+
style={{
160+
color: index <= currentStep ? activeColor : undefined,
161+
maxWidth: 120,
162+
textOverflow: 'ellipsis',
163+
overflow: 'hidden',
164+
whiteSpace: 'nowrap',
151165
}}
152-
transition={{ duration: 0.5, ease: 'easeInOut' }}
153-
/>
166+
title={step.label}
167+
>
168+
{step.label}
169+
</div>
170+
171+
{index === currentStep && showPercent && (
172+
<div className="mt-1 text-xs font-semibold" style={{ color: activeColor }}>
173+
{progressPercent}%
174+
</div>
175+
)}
154176
</div>
155-
)}
156-
</React.Fragment>
157-
))}
177+
178+
{index < steps.length - 1 && (
179+
<div className="relative flex-1 px-2" style={{ display: 'flex', alignItems: 'center' }}>
180+
<div className="h-1 w-full rounded-full" style={{ backgroundColor: inactiveBgColor, zIndex: 0 }} />
181+
<motion.div
182+
className="absolute top-1/2 left-0 h-1 -translate-y-1/2 rounded-full"
183+
style={{
184+
backgroundImage: `linear-gradient(90deg, ${gradientColors.join(',')})`,
185+
backgroundSize: '200% 100%',
186+
boxShadow: `0 0 8px ${activeColor}60`,
187+
zIndex: 1,
188+
}}
189+
initial={{
190+
width: index < currentStep ? '100%' : index === currentStep ? '40%' : '0%',
191+
backgroundPosition: '0% 0%',
192+
}}
193+
animate={{
194+
width: index < currentStep ? '100%' : index === currentStep ? '40%' : '0%',
195+
backgroundPosition: index < currentStep ? ['0% 0%', '100% 0%'] : '0% 0%',
196+
}}
197+
transition={{
198+
duration: 0.5,
199+
ease: 'easeInOut',
200+
backgroundPosition:
201+
index < currentStep ? { repeat: Infinity, duration: 3, ease: 'linear' } : undefined,
202+
}}
203+
/>
204+
</div>
205+
)}
206+
</React.Fragment>
207+
))}
208+
</div>
158209
</div>
159210
)}
160211
</div>
@@ -164,6 +215,11 @@ const StepIndicator = ({ currentStep, steps }) => {
164215
StepIndicator.propTypes = {
165216
currentStep: PropTypes.number.isRequired,
166217
steps: PropTypes.array.isRequired,
218+
activeColor: PropTypes.string,
219+
inactiveBgColor: PropTypes.string,
220+
circleSize: PropTypes.number,
221+
gradientColors: PropTypes.array,
222+
showPercent: PropTypes.bool,
167223
}
168224

169225
const StepContent = ({ step }) => {
@@ -216,25 +272,25 @@ StepContent.propTypes = {
216272
step: PropTypes.object.isRequired,
217273
}
218274

219-
const NavigationButtons = ({ currentStep, totalSteps, handlePrev, handleNext }) => {
275+
const NavigationButtons = ({ currentStep, totalSteps, handlePrev, handleNext, primaryColor: primaryColorProp }) => {
220276
const { themeSettings } = useProThemeContext()
221277
const { token } = useToken()
222278
const isMobile = useStore((s) => s.isMobile)
223279
const isDark = themeSettings.themeMode === 'dark'
224280

225-
const primaryColor = isDark ? '#40a9ff' : '#0958d9'
281+
const primaryColor = primaryColorProp || (isDark ? '#40a9ff' : '#0958d9')
226282
const secondaryBg = isDark ? token.colorBgContainer : '#f8f9fa'
227283
const secondaryBorder = isDark ? token.colorBorder : '#d9d9d9'
228284
const secondaryText = isDark ? token.colorTextSecondary : '#666666'
229285

230286
return (
231-
<div className={clsx('flex gap-3', isMobile ? 'flex-col' : 'justify-between')}>
287+
<div className={clsx('flex gap-3', isMobile ? 'flex-col' : 'justify-between')} style={{ marginTop: 16 }}>
232288
<Button
233289
disabled={currentStep === 0}
234290
onClick={handlePrev}
235291
className="transition-all duration-200 hover:shadow-md"
236292
style={{
237-
visibility: currentStep === 0 ? 'hidden' : 'visible',
293+
display: currentStep === 0 ? 'none' : 'inline-flex',
238294
backgroundColor: secondaryBg,
239295
borderColor: secondaryBorder,
240296
color: secondaryText,
@@ -249,7 +305,7 @@ const NavigationButtons = ({ currentStep, totalSteps, handlePrev, handleNext })
249305
onClick={handleNext}
250306
className="font-medium transition-all duration-200 hover:shadow-lg"
251307
style={{
252-
visibility: currentStep === totalSteps - 1 ? 'hidden' : 'visible',
308+
display: currentStep === totalSteps - 1 ? 'none' : 'inline-flex',
253309
backgroundColor: primaryColor,
254310
borderColor: primaryColor,
255311
boxShadow: `0 2px 8px ${primaryColor}30`,
@@ -266,9 +322,18 @@ NavigationButtons.propTypes = {
266322
totalSteps: PropTypes.number.isRequired,
267323
handlePrev: PropTypes.func.isRequired,
268324
handleNext: PropTypes.func.isRequired,
325+
primaryColor: PropTypes.string,
269326
}
270327

271-
const DottedStepper = ({ steps = defaultSteps, className }) => {
328+
const DottedStepper = ({
329+
steps = defaultSteps,
330+
className,
331+
activeColor,
332+
inactiveBgColor,
333+
circleSize = 44,
334+
gradientColors = ['#ff7a18', '#af002d', '#319197'],
335+
showPercent = true,
336+
}) => {
272337
const [currentStep, setCurrentStep] = useState(0)
273338
const { token } = useToken()
274339

@@ -281,14 +346,26 @@ const DottedStepper = ({ steps = defaultSteps, className }) => {
281346
}
282347

283348
return (
284-
<div className={clsx('mx-auto w-full max-w-3xl p-4 md:p-6', className)} style={{ color: token.colorText }}>
285-
<StepIndicator currentStep={currentStep} steps={steps} />
349+
<div
350+
className={clsx('mx-auto w-full max-w-2xl p-6 md:max-w-3xl md:p-8', className)}
351+
style={{ color: token.colorText }}
352+
>
353+
<StepIndicator
354+
currentStep={currentStep}
355+
steps={steps}
356+
activeColor={activeColor}
357+
inactiveBgColor={inactiveBgColor}
358+
circleSize={circleSize}
359+
gradientColors={gradientColors}
360+
showPercent={showPercent}
361+
/>
286362
<StepContent step={steps[currentStep]} />
287363
<NavigationButtons
288364
currentStep={currentStep}
289365
handleNext={handleNext}
290366
handlePrev={handlePrev}
291367
totalSteps={steps.length}
368+
primaryColor={activeColor}
292369
/>
293370
</div>
294371
)
@@ -299,4 +376,17 @@ export default DottedStepper
299376
DottedStepper.propTypes = {
300377
steps: PropTypes.array,
301378
className: PropTypes.string,
379+
activeColor: PropTypes.string,
380+
inactiveBgColor: PropTypes.string,
381+
circleSize: PropTypes.number,
382+
gradientColors: PropTypes.array,
383+
showPercent: PropTypes.bool,
384+
}
385+
386+
DottedStepper.defaultProps = {
387+
activeColor: undefined,
388+
inactiveBgColor: undefined,
389+
circleSize: 44,
390+
gradientColors: ['#ff7a18', '#af002d', '#319197'],
391+
showPercent: true,
302392
}

src/pages/home/index.jsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,9 @@ const Home = () => {
363363
exportSvg(svgRef.current, svgFileName)
364364
}
365365

366+
const currentYear = new Date().getFullYear()
367+
const hopefulText = `${currentYear} 年,是充满希望的一年。让我们放下过去的包袱,轻装上阵。用积极的心态去面对生活,用坚定的信念去追逐梦想。相信自己,只要我们努力奋斗,就一定能创造出属于自己的精彩人生。让我们一起逐光前行,奔赴新程,书写属于我们的辉煌篇章!`
368+
366369
const mcRef = useRef(null)
367370
return (
368371
<FixTabPanel ref={scrollRef}>
@@ -828,7 +831,7 @@ const Home = () => {
828831
</BlurFade>
829832
</section>
830833
<section style={{ margin: '20px 0', fontSize: 24 }}>
831-
<GradualSpacing text="2025 年,是充满希望的一年。让我们放下过去的包袱,轻装上阵。用积极的心态去面对生活,用坚定的信念去追逐梦想。相信自己,只要我们努力奋斗,就一定能创造出属于自己的精彩人生。让我们一起逐光前行,奔赴新程,书写属于我们的辉煌篇章!" />
834+
<GradualSpacing text={hopefulText} />
832835
</section>
833836
<section style={{ margin: '20px 0' }}>
834837
<ContentPlaceholder />

0 commit comments

Comments
 (0)