feat: 更新 ESLint 和 Prettier 配置,增强代码质量管理
- 在 .gitignore 中添加 public/audio/* 以忽略音频文件。 - 更新 bun.lock,添加 eslint-config-prettier 和 eslint-plugin-prettier 依赖,确保 ESLint 与 Prettier 的兼容性。 - 修改 eslint.config.ts,整合 Prettier 规则,优化代码风格一致性。 - 在 package.json 中添加 lint:fix 和 format 脚本,简化代码格式化和修复流程。 - 更新 tailwind.config.ts 和其他组件文件,统一代码风格,提升可读性。
This commit is contained in:
1 parent
5d00adc1a5
commit
76c67b492e
216 files changed
+12454
-13976
No files matched your search
@@ -1,26 +1,26 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useAudioManager } from "@/hooks/useAudioManager";
|
||||
import { useState, useEffect } from 'react'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useAudioManager } from '@/hooks/useAudioManager'
|
||||
|
||||
export interface NarrationProps {
|
||||
/** 旁白文本 */
|
||||
text: string;
|
||||
text: string
|
||||
/** 是否可见 */
|
||||
visible: boolean;
|
||||
visible: boolean
|
||||
/** 关闭回调 */
|
||||
onClose: () => void;
|
||||
onClose: () => void
|
||||
/** 语音播报的音频文件路径(可选) */
|
||||
audioSrc?: string;
|
||||
audioSrc?: string
|
||||
/** 自定义样式类名 */
|
||||
className?: string;
|
||||
className?: string
|
||||
/** 是否启用逐字动画,默认 true */
|
||||
enableAnimation?: boolean;
|
||||
enableAnimation?: boolean
|
||||
/** 文字动画速度(毫秒/字),默认 100ms */
|
||||
animationSpeed?: number;
|
||||
animationSpeed?: number
|
||||
/** 覆盖层出现时是否自动播放音频(默认 true) */
|
||||
autoPlayAudio?: boolean;
|
||||
autoPlayAudio?: boolean
|
||||
/** 音频分组 id,相同 channel 内互斥,不同 channel 可同时播放;不传则全局互斥 */
|
||||
audioChannelId?: string;
|
||||
audioChannelId?: string
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -42,95 +42,95 @@ const Narration = ({
|
||||
autoPlayAudio = true,
|
||||
audioChannelId,
|
||||
}: NarrationProps) => {
|
||||
const [displayedText, setDisplayedText] = useState("");
|
||||
const [isAnimating, setIsAnimating] = useState(true);
|
||||
const [isSkipped, setIsSkipped] = useState(false);
|
||||
const [displayedText, setDisplayedText] = useState('')
|
||||
const [isAnimating, setIsAnimating] = useState(true)
|
||||
const [isSkipped, setIsSkipped] = useState(false)
|
||||
|
||||
// 旁白音频:默认自动播放,支持按 channel 分组互斥
|
||||
const { currentSrc, play, stop } = useAudioManager(
|
||||
audioChannelId
|
||||
? {
|
||||
mode: "channel",
|
||||
mode: 'channel',
|
||||
channelId: audioChannelId,
|
||||
}
|
||||
: { mode: "global" }
|
||||
);
|
||||
: { mode: 'global' }
|
||||
)
|
||||
|
||||
// 文字动画效果
|
||||
useEffect(() => {
|
||||
if (!visible) {
|
||||
setDisplayedText("");
|
||||
setIsAnimating(true);
|
||||
setIsSkipped(false);
|
||||
setDisplayedText('')
|
||||
setIsAnimating(true)
|
||||
setIsSkipped(false)
|
||||
// 旁白关闭时,如果当前正在播放该旁白音频,则停止
|
||||
if (audioSrc && currentSrc === audioSrc) {
|
||||
stop();
|
||||
stop()
|
||||
}
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
// 如果已经跳过,不再执行动画
|
||||
if (isSkipped) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
// 如果不启用动画,直接显示全部文本
|
||||
if (!enableAnimation) {
|
||||
setDisplayedText(text);
|
||||
setIsAnimating(false);
|
||||
return;
|
||||
setDisplayedText(text)
|
||||
setIsAnimating(false)
|
||||
return
|
||||
}
|
||||
|
||||
let currentIndex = 0;
|
||||
setDisplayedText("");
|
||||
setIsAnimating(true);
|
||||
let currentIndex = 0
|
||||
setDisplayedText('')
|
||||
setIsAnimating(true)
|
||||
|
||||
const timer = setInterval(() => {
|
||||
if (currentIndex < text.length) {
|
||||
setDisplayedText(text.slice(0, currentIndex + 1));
|
||||
currentIndex++;
|
||||
setDisplayedText(text.slice(0, currentIndex + 1))
|
||||
currentIndex++
|
||||
} else {
|
||||
setIsAnimating(false);
|
||||
clearInterval(timer);
|
||||
setIsAnimating(false)
|
||||
clearInterval(timer)
|
||||
}
|
||||
}, animationSpeed);
|
||||
}, animationSpeed)
|
||||
|
||||
return () => clearInterval(timer);
|
||||
}, [text, visible, enableAnimation, animationSpeed, isSkipped, audioSrc, currentSrc, stop]);
|
||||
return () => clearInterval(timer)
|
||||
}, [text, visible, enableAnimation, animationSpeed, isSkipped, audioSrc, currentSrc, stop])
|
||||
|
||||
// 跳过动画
|
||||
const handleSkip = () => {
|
||||
setDisplayedText(text);
|
||||
setIsAnimating(false);
|
||||
setIsSkipped(true);
|
||||
};
|
||||
setDisplayedText(text)
|
||||
setIsAnimating(false)
|
||||
setIsSkipped(true)
|
||||
}
|
||||
|
||||
// 旁白出现时自动播放音频
|
||||
useEffect(() => {
|
||||
if (!visible) return;
|
||||
if (!autoPlayAudio) return;
|
||||
if (!audioSrc) return;
|
||||
if (!visible) return
|
||||
if (!autoPlayAudio) return
|
||||
if (!audioSrc) return
|
||||
|
||||
play(audioSrc);
|
||||
}, [visible, autoPlayAudio, audioSrc, play]);
|
||||
play(audioSrc)
|
||||
}, [visible, autoPlayAudio, audioSrc, play])
|
||||
|
||||
// 停止音频
|
||||
const handleClose = () => {
|
||||
if (audioSrc && currentSrc === audioSrc) {
|
||||
stop();
|
||||
stop()
|
||||
}
|
||||
onClose();
|
||||
};
|
||||
onClose()
|
||||
}
|
||||
|
||||
if (!visible) return null;
|
||||
if (!visible) return null
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 flex items-center justify-center bg-black/80 z-50">
|
||||
<div
|
||||
className={cn(
|
||||
"w-[1600px] bg-black/90 rounded-[32px] px-20 py-16",
|
||||
"shadow-[0_20px_60px_rgba(240,190,117,0.8)] border-4 border-[#d4c5a9]",
|
||||
"relative",
|
||||
'w-[1600px] bg-black/90 rounded-[32px] px-20 py-16',
|
||||
'shadow-[0_20px_60px_rgba(240,190,117,0.8)] border-4 border-[#d4c5a9]',
|
||||
'relative',
|
||||
className
|
||||
)}
|
||||
>
|
||||
@@ -161,7 +161,7 @@ const Narration = ({
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default Narration;
|
||||
export default Narration
|
||||
@@ -1,2 +1,2 @@
|
||||
export { default as Narration } from "./Narration";
|
||||
export type { NarrationProps } from "./Narration";
|
||||
export { default as Narration } from './Narration'
|
||||
export type { NarrationProps } from './Narration'
|
||||
Reference in new issue
Block a user