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:
zhongyidian committed 2026-03-05 13:50:47 +08:00
1 parent 5d00adc1a5
commit 76c67b492e
216 files changed
+12454 -13976

No files matched your search

@@ -1,17 +1,17 @@
import { useState, useEffect, useMemo, useCallback, useRef } from "react";
import { AnimatePresence, motion } from "framer-motion";
import { cn, getStaticImage } from "@/lib/utils";
import { Image, DragFill } from "@/components";
import type { DragFillResult } from "@/components";
import { ErrorAlert, SuccessAlert } from "@/components/layout";
import { useState, useEffect, useMemo, useCallback, useRef } from 'react'
import { AnimatePresence, motion } from 'framer-motion'
import { cn, getStaticImage } from '@/lib/utils'
import { Image, DragFill } from '@/components'
import type { DragFillResult } from '@/components'
import { ErrorAlert, SuccessAlert } from '@/components/layout'
export interface TowelSalesDialogProps {
/** 是否可见 */
visible?: boolean;
visible?: boolean
/** 关闭回调 */
onClose?: () => void;
onClose?: () => void
/** 完成回调 */
onComplete?: () => void;
onComplete?: () => void
}
/**
@@ -21,151 +21,143 @@ export interface TowelSalesDialogProps {
* - 拖拽话术到毛巾上,点亮对应的毛巾
* - 3个都点亮后完成
*/
const TowelSalesDialog = ({
visible = true,
onClose,
onComplete,
}: TowelSalesDialogProps) => {
const maskSrc = getStaticImage("components/蒙版遮罩");
const backSrc = getStaticImage("components/关闭");
const TowelSalesDialog = ({ visible = true, onClose, onComplete }: TowelSalesDialogProps) => {
const maskSrc = getStaticImage('components/蒙版遮罩')
const backSrc = getStaticImage('components/关闭')
// 毛巾图片资源(未点亮)
const towel1 = getStaticImage("huangyanpei/reflection/scene4/12_002");
const towel2 = getStaticImage("huangyanpei/reflection/scene4/12_003");
const towel3 = getStaticImage("huangyanpei/reflection/scene4/12_004");
const towel1 = getStaticImage('huangyanpei/reflection/scene4/12_002')
const towel2 = getStaticImage('huangyanpei/reflection/scene4/12_003')
const towel3 = getStaticImage('huangyanpei/reflection/scene4/12_004')
// 毛巾图片资源(点亮)
const towel1Lit = getStaticImage("huangyanpei/reflection/scene4/12_002_02");
const towel2Lit = getStaticImage("huangyanpei/reflection/scene4/12_003_02");
const towel3Lit = getStaticImage("huangyanpei/reflection/scene4/12_004_03");
const towel1Lit = getStaticImage('huangyanpei/reflection/scene4/12_002_02')
const towel2Lit = getStaticImage('huangyanpei/reflection/scene4/12_003_02')
const towel3Lit = getStaticImage('huangyanpei/reflection/scene4/12_004_03')
const [mounted, setMounted] = useState(false);
const [value, setValue] = useState<DragFillResult[]>([]);
const [errorVisible, setErrorVisible] = useState(false);
const [successVisible, setSuccessVisible] = useState(false);
const completedRef = useRef(false);
const [mounted, setMounted] = useState(false)
const [value, setValue] = useState<DragFillResult[]>([])
const [errorVisible, setErrorVisible] = useState(false)
const [successVisible, setSuccessVisible] = useState(false)
const completedRef = useRef(false)
// SSR 兼容
useEffect(() => {
setMounted(true);
}, []);
setMounted(true)
}, [])
// 重置游戏状态
useEffect(() => {
if (visible) {
setValue([]);
completedRef.current = false;
setErrorVisible(false);
setSuccessVisible(false);
setValue([])
completedRef.current = false
setErrorVisible(false)
setSuccessVisible(false)
}
}, [visible]);
}, [visible])
// 检查是否完成(3个毛巾都点亮)
useEffect(() => {
if (value.length === 3 && !completedRef.current) {
completedRef.current = true;
completedRef.current = true
// 显示成功提示
setSuccessVisible(true);
setSuccessVisible(true)
}
}, [value]);
}, [value])
const handleClose = useCallback(
(e: React.MouseEvent<HTMLImageElement>) => {
e.stopPropagation();
onClose?.();
e.stopPropagation()
onClose?.()
},
[onClose]
);
)
// 关闭错误提示
const handleErrorClose = useCallback(() => {
setErrorVisible(false);
}, []);
setErrorVisible(false)
}, [])
// 关闭成功提示
const handleSuccessContinue = useCallback(() => {
setSuccessVisible(false);
setSuccessVisible(false)
// 关闭提示后,完成并关闭弹窗
onComplete?.();
onClose?.();
}, [onComplete, onClose]);
onComplete?.()
onClose?.()
}, [onComplete, onClose])
// 可选话术选项
const options = useMemo(
() => [
{
value: "option1",
label: "这是中华职校生产的国货,质量比洋货还好!",
value: 'option1',
label: '这是中华职校生产的国货,质量比洋货还好!',
},
{
value: "option2",
label: "价格便宜,量大从优,诚信经营绝不掺假",
value: 'option2',
label: '价格便宜,量大从优,诚信经营绝不掺假',
},
{
value: "option3",
label: "买国货就是支持实业救国,为国家出一份力!",
value: 'option3',
label: '买国货就是支持实业救国,为国家出一份力!',
},
{
value: "option4",
label: "我只管赚钱,东西好不好不管哦",
value: 'option4',
label: '我只管赚钱,东西好不好不管哦',
},
],
[]
);
)
// 转换为 DragFill 的 blanks
const blanks = useMemo(
() => [
{ id: "towel1", label: "毛巾1" },
{ id: "towel2", label: "毛巾2" },
{ id: "towel3", label: "毛巾3" },
{ id: 'towel1', label: '毛巾1' },
{ id: 'towel2', label: '毛巾2' },
{ id: 'towel3', label: '毛巾3' },
],
[]
);
)
// 处理拖拽结果变化
const handleValueChange = useCallback((newValue: DragFillResult[]) => {
// 检查是否选择了错误的选项(option4)
const hasOption4 = newValue.some(
(result) => result.optionValue === "option4"
);
const hasOption4 = newValue.some((result) => result.optionValue === 'option4')
if (hasOption4) {
// 显示错误提示
setErrorVisible(true);
setErrorVisible(true)
// 移除 option4,保留其他选项
const filteredValue = newValue.filter(
(result) => result.optionValue !== "option4"
);
setValue(filteredValue);
const filteredValue = newValue.filter((result) => result.optionValue !== 'option4')
setValue(filteredValue)
} else {
// 限制最多3个
if (newValue.length <= 3) {
setValue(newValue);
setValue(newValue)
}
}
}, []);
}, [])
// 检查毛巾是否被点亮
const isTowelLit = useCallback(
(towelId: string) => {
return value.some((result) => result.blankId === towelId);
return value.some((result) => result.blankId === towelId)
},
[value]
);
)
// 样式
const overlayStyle = useMemo<React.CSSProperties>(
() => ({
position: "fixed",
position: 'fixed',
inset: 0,
zIndex: 9999,
pointerEvents: "none",
pointerEvents: 'none',
}),
[]
);
)
if (!mounted) return null;
if (!mounted) return null
const content = (
<AnimatePresence>
@@ -176,11 +168,11 @@ const TowelSalesDialog = ({
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.3, ease: "easeInOut" }}
transition={{ duration: 0.3, ease: 'easeInOut' }}
>
<div
className={cn("absolute inset-0 flex flex-col", "bg-black/70")}
style={{ pointerEvents: "auto" }}
className={cn('absolute inset-0 flex flex-col', 'bg-black/70')}
style={{ pointerEvents: 'auto' }}
>
{/* 背景蒙版 */}
{maskSrc && (
@@ -224,7 +216,7 @@ const TowelSalesDialog = ({
className="relative w-full flex items-center justify-center transition-all duration-300 rounded-2xl"
>
<img
src={isTowelLit("towel1") ? towel1Lit : towel1}
src={isTowelLit('towel1') ? towel1Lit : towel1}
draggable={false}
alt="毛巾1"
className="w-full h-full object-contain"
@@ -237,7 +229,7 @@ const TowelSalesDialog = ({
className="relative w-full flex items-center justify-center transition-all duration-300 rounded-2xl"
>
<img
src={isTowelLit("towel2") ? towel2Lit : towel2}
src={isTowelLit('towel2') ? towel2Lit : towel2}
draggable={false}
alt="毛巾2"
className="w-full h-full object-contain"
@@ -250,7 +242,7 @@ const TowelSalesDialog = ({
className="relative w-full flex items-center justify-center transition-all duration-300 rounded-2xl"
>
<img
src={isTowelLit("towel3") ? towel3Lit : towel3}
src={isTowelLit('towel3') ? towel3Lit : towel3}
draggable={false}
alt="毛巾3"
className="w-full h-full object-contain"
@@ -275,32 +267,30 @@ const TowelSalesDialog = ({
showSubmitButton={false}
className="w-full"
renderOption={(option, isUsed) => {
const index = options.findIndex(
(opt) => opt.value === option.value
);
const index = options.findIndex((opt) => opt.value === option.value)
return (
<div
className={cn(
"px-10 py-10 rounded-2xl border-3 text-3xl font-medium transition-all select-none",
'px-10 py-10 rounded-2xl border-3 text-3xl font-medium transition-all select-none',
isUsed
? "border-[#8b7355] bg-[#3d3329] text-[#a89378] cursor-not-allowed opacity-50"
: "border-[#79471f] bg-black/20 text-white cursor-move hover:border-[#f0be75] hover:shadow-[0_12px_40px_rgba(240,190,117,0.7)]"
? 'border-[#8b7355] bg-[#3d3329] text-[#a89378] cursor-not-allowed opacity-50'
: 'border-[#79471f] bg-black/20 text-white cursor-move hover:border-[#f0be75] hover:shadow-[0_12px_40px_rgba(240,190,117,0.7)]'
)}
>
{index + 1}. {option.label}
</div>
);
)
}}
renderDragging={(option) => {
return (
<div className="w-[638px] h-[90px] flex items-center justify-center rounded-2xl border-3 text-2xl font-medium border-[#f0be75] bg-black/40 text-white shadow-[0_12px_40px_rgba(240,190,117,0.7)]">
{option.label}
</div>
);
)
}}
renderBlank={() => {
// 返回隐藏元素,实际的空白区域在页面中通过 data-blank-id 定位
return <div className="hidden" />;
return <div className="hidden" />
}}
/>
</div>
@@ -327,9 +317,9 @@ const TowelSalesDialog = ({
</motion.div>
)}
</AnimatePresence>
);
)
return content;
};
return content
}
export default TowelSalesDialog;
export default TowelSalesDialog