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

+82 -92
View File
@@ -1,32 +1,32 @@
import { useState, useEffect } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { cn, getBackgroundImage, getStaticImage } from "@/lib/utils";
import { usePuzzleStore } from "@/store/puzzleStore";
import { getPuzzlePieceData } from "@/config/puzzleData";
import { getPuzzlePiecePath, getPuzzleViewBox } from "@/config/puzzlePaths";
import { Image } from "@/components";
import { useState, useEffect } from 'react'
import { useNavigate, useParams } from 'react-router-dom'
import { cn, getBackgroundImage, getStaticImage } from '@/lib/utils'
import { usePuzzleStore } from '@/store/puzzleStore'
import { getPuzzlePieceData } from '@/config/puzzleData'
import { getPuzzlePiecePath, getPuzzleViewBox } from '@/config/puzzlePaths'
import { Image } from '@/components'
/**
* 将英文故居名称转换为中文显示名称
*/
const getResidenceDisplayName = (residence: string): string => {
const nameMap: Record<string, string> = {
chenyun: "陈云",
huangyanpei: "黄炎培",
luxun: "鲁迅",
sunzhongshan: "孙中山",
};
chenyun: '陈云',
huangyanpei: '黄炎培',
luxun: '鲁迅',
sunzhongshan: '孙中山',
}
return nameMap[residence] || residence;
};
return nameMap[residence] || residence
}
export interface PuzzleSystemProps {
/** 是否可见 */
visible: boolean;
visible: boolean
/** 关闭回调 */
onClose: () => void;
onClose: () => void
/** 故居名称,如 'chenyun' */
residence: string;
residence: string
}
/**
@@ -42,24 +42,24 @@ const PuzzlePieceSvg = ({
onHover,
onLeave,
}: {
residence: string;
pieceId: number;
isUnlocked: boolean;
isSelected: boolean;
isHovered: boolean;
onClick: () => void;
onHover: (id: number) => void;
onLeave: () => void;
residence: string
pieceId: number
isUnlocked: boolean
isSelected: boolean
isHovered: boolean
onClick: () => void
onHover: (id: number) => void
onLeave: () => void
}) => {
const pathData = getPuzzlePiecePath(residence, pieceId);
const pathData = getPuzzlePiecePath(residence, pieceId)
if (!pathData) return null;
if (!pathData) return null
// 为每个碎片生成唯一的pattern ID
const patternId = `pattern-${residence}-${pieceId}`;
const patternId = `pattern-${residence}-${pieceId}`
// 碎片图片路径
const pieceImage = getStaticImage(`puzzle/${residence}/${pieceId}`);
const pieceImage = getStaticImage(`puzzle/${residence}/${pieceId}`)
return (
<>
@@ -81,9 +81,9 @@ const PuzzlePieceSvg = ({
onMouseEnter={() => (isUnlocked ? onHover(pieceId) : undefined)}
onMouseLeave={onLeave}
style={{
cursor: isUnlocked ? "pointer" : "not-allowed",
cursor: isUnlocked ? 'pointer' : 'not-allowed',
}}
className={cn(isUnlocked ? "puzzle-piece" : "")}
className={cn(isUnlocked ? 'puzzle-piece' : '')}
>
<defs>
{/* Safari/iOS 对 SVG image 的 CSS filter 支持有限,使用滤镜定义保证生效 */}
@@ -116,7 +116,7 @@ const PuzzlePieceSvg = ({
preserveAspectRatio="none"
filter={isUnlocked ? undefined : `url(#${patternId}-dim)`}
style={{
transition: "filter 220ms ease",
transition: 'filter 220ms ease',
}}
/>
</pattern>
@@ -126,14 +126,14 @@ const PuzzlePieceSvg = ({
<path
d={pathData}
fill={`url(#${patternId})`}
stroke={isUnlocked ? (isSelected ? "#75d1fb" : "#fff") : "#888"}
stroke={isUnlocked ? (isSelected ? '#75d1fb' : '#fff') : '#888'}
strokeWidth={isUnlocked ? (isSelected ? 3 : isHovered ? 2.3 : 2) : 1}
opacity={isUnlocked ? 1 : 0.8}
/>
</g>
</>
);
};
)
}
/**
* 拼图系统组件
@@ -143,36 +143,36 @@ const PuzzlePieceSvg = ({
* - 点击碎片可跳转到对应页面
*/
const PuzzleSystem = ({ visible, onClose, residence }: PuzzleSystemProps) => {
const navigate = useNavigate();
const { moduleName } = useParams<{ moduleName: string }>();
const { isPuzzleUnlocked, getPuzzleCollectProgress } = usePuzzleStore();
const [selectedPiece, setSelectedPiece] = useState<number | null>(null);
const [hoveredPiece, setHoveredPiece] = useState<number | null>(null);
const navigate = useNavigate()
const { moduleName } = useParams<{ moduleName: string }>()
const { isPuzzleUnlocked, getPuzzleCollectProgress } = usePuzzleStore()
const [selectedPiece, setSelectedPiece] = useState<number | null>(null)
const [hoveredPiece, setHoveredPiece] = useState<number | null>(null)
// 当弹窗关闭时重置选中状态
useEffect(() => {
if (!visible) {
setSelectedPiece(null);
setSelectedPiece(null)
}
}, [visible]);
}, [visible])
if (!visible) return null;
if (!visible) return null
// 背景图片
const backgroundImage = getStaticImage("puzzle/底图");
const backgroundImage = getStaticImage('puzzle/底图')
// 处理碎片点击
const handlePieceClick = (pieceId: number) => {
const isUnlocked = isPuzzleUnlocked(residence, pieceId);
const isUnlocked = isPuzzleUnlocked(residence, pieceId)
if (isUnlocked) {
// 显示选中效果,更新详情框
setSelectedPiece(pieceId);
setSelectedPiece(pieceId)
} else {
// 未解锁的碎片,可以显示提示或不做任何操作
// 暂时不做处理,或者可以添加提示
}
};
}
// 处理前往按钮点击
const handleGoToPage = () => {
@@ -180,40 +180,36 @@ const PuzzleSystem = ({ visible, onClose, residence }: PuzzleSystemProps) => {
// console.log(residence, selectedPiece, moduleName);
// const route = getPuzzleRoute(residence, selectedPiece, moduleName);
// console.log(route);
const route = selectedPieceData?.route;
const route = selectedPieceData?.route
if (route) {
navigate(`/${moduleName}/${residence}/${route}`);
onClose();
navigate(`/${moduleName}/${residence}/${route}`)
onClose()
}
}
};
}
// 获取当前选中碎片的数据
const selectedPieceData = selectedPiece
? getPuzzlePieceData(residence, selectedPiece)
: null;
const selectedPieceData = selectedPiece ? getPuzzlePieceData(residence, selectedPiece) : null
// 生成16块碎片(1-16)
const pieces = Array.from({ length: 16 }, (_, i) => i + 1);
const pieces = Array.from({ length: 16 }, (_, i) => i + 1)
// 将悬停的碎片放到最后渲染,保证 hover 层级在最上
const renderPieces =
hoveredPiece != null
? [...pieces.filter((p) => p !== hoveredPiece), hoveredPiece]
: pieces;
hoveredPiece != null ? [...pieces.filter((p) => p !== hoveredPiece), hoveredPiece] : pieces
// 获取收集进度
const collectProgress = getPuzzleCollectProgress(residence);
const collectProgress = getPuzzleCollectProgress(residence)
return (
<div
className="fixed inset-0 z-[100] flex items-center justify-center"
style={{
background: "rgba(0, 0, 0, 0.8)",
background: 'rgba(0, 0, 0, 0.8)',
}}
onClick={(e) => {
// 点击背景关闭
if (e.target === e.currentTarget) {
onClose();
onClose()
}
}}
>
@@ -227,7 +223,7 @@ const PuzzleSystem = ({ visible, onClose, residence }: PuzzleSystemProps) => {
{/* 关闭按钮 */}
<Image
className="absolute right-8 top-8 z-10"
src={getStaticImage("components/关闭")}
src={getStaticImage('components/关闭')}
alt="关闭"
button
onClick={onClose}
@@ -236,27 +232,27 @@ const PuzzleSystem = ({ visible, onClose, residence }: PuzzleSystemProps) => {
{/* 收集进度 */}
<div
className="absolute left-1/2 -translate-x-1/2 top-8 z-10 w-[708px] h-[107px] px-12 flex justify-between items-center"
style={{ backgroundImage: getBackgroundImage("puzzle/收集进度") }}
style={{ backgroundImage: getBackgroundImage('puzzle/收集进度') }}
>
<div className="relative">
<span
className={cn(
"text-white text-2xl font-bold",
"[text-shadow:_-4px_-4px_0_#ae6400,_4px_-4px_0_#ae6400,_-4px_4px_0_#ae6400,_4px_4px_0_#ae6400,_-5px_0_0_#ae6400,_5px_0_0_#ae6400,_0_-5px_0_#ae6400,_0_5px_0_#ae6400,_0_0_20px_rgba(255,215,0,1),_0_0_40px_rgba(255,215,0,0.8),_0_0_60px_rgba(255,215,0,0.5)]"
'text-white text-2xl font-bold',
'[text-shadow:_-4px_-4px_0_#ae6400,_4px_-4px_0_#ae6400,_-4px_4px_0_#ae6400,_4px_4px_0_#ae6400,_-5px_0_0_#ae6400,_5px_0_0_#ae6400,_0_-5px_0_#ae6400,_0_5px_0_#ae6400,_0_0_20px_rgba(255,215,0,1),_0_0_40px_rgba(255,215,0,0.8),_0_0_60px_rgba(255,215,0,0.5)]'
)}
>
{getResidenceDisplayName(residence)}故居碎片
</span>
<img
className="absolute -top-10 left-0 -z-10"
src={getStaticImage("puzzle/碎片装饰")}
src={getStaticImage('puzzle/碎片装饰')}
alt="碎片装饰"
/>
</div>
<div className="text-[#490000] text-4xl font-bold">
{/* 收集进度 */}
{collectProgress.toFixed(2) == "1.00"
? "已集齐"
{collectProgress.toFixed(2) == '1.00'
? '已集齐'
: `收集进度${collectProgress.toFixed(2)}%`}
</div>
</div>
@@ -271,12 +267,12 @@ const PuzzleSystem = ({ visible, onClose, residence }: PuzzleSystemProps) => {
viewBox={getPuzzleViewBox(residence)}
className="w-full h-full"
style={{
filter: "drop-shadow(0px 2px 4px rgba(0,0,0,0.3))",
overflow: "visible",
filter: 'drop-shadow(0px 2px 4px rgba(0,0,0,0.3))',
overflow: 'visible',
}}
>
{renderPieces.map((pieceId) => {
const isUnlocked = isPuzzleUnlocked(residence, pieceId);
const isUnlocked = isPuzzleUnlocked(residence, pieceId)
return (
<PuzzlePieceSvg
@@ -290,7 +286,7 @@ const PuzzleSystem = ({ visible, onClose, residence }: PuzzleSystemProps) => {
onHover={(id) => setHoveredPiece(id)}
onLeave={() => setHoveredPiece(null)}
/>
);
)
})}
</svg>
</div>
@@ -300,36 +296,30 @@ const PuzzleSystem = ({ visible, onClose, residence }: PuzzleSystemProps) => {
<div
className="w-[657px] h-[776px] bg-no-repeat bg-cover p-10 text-center"
style={{
backgroundImage: getBackgroundImage("puzzle/详情框"),
backgroundImage: getBackgroundImage('puzzle/详情框'),
}}
>
{/* 标题 */}
{selectedPieceData && (
<div
className={cn(
"text-white text-5xl font-bold whitespace-nowrap mt-8 mb-4 space-y-4",
"[text-shadow:_-4px_-4px_0_#ae6400,_4px_-4px_0_#ae6400,_-4px_4px_0_#ae6400,_4px_4px_0_#ae6400,_-5px_0_0_#ae6400,_5px_0_0_#ae6400,_0_-5px_0_#ae6400,_0_5px_0_#ae6400,_0_0_20px_rgba(255,215,0,1),_0_0_40px_rgba(255,215,0,0.8),_0_0_60px_rgba(255,215,0,0.5)]"
'text-white text-5xl font-bold whitespace-nowrap mt-8 mb-4 space-y-4',
'[text-shadow:_-4px_-4px_0_#ae6400,_4px_-4px_0_#ae6400,_-4px_4px_0_#ae6400,_4px_4px_0_#ae6400,_-5px_0_0_#ae6400,_5px_0_0_#ae6400,_0_-5px_0_#ae6400,_0_5px_0_#ae6400,_0_0_20px_rgba(255,215,0,1),_0_0_40px_rgba(255,215,0,0.8),_0_0_60px_rgba(255,215,0,0.5)]'
)}
>
{/* 空格换行 */}
{selectedPieceData.title
.split(" ")
.map((word: string, index: number) => (
<div key={index}>
{word}
{index <
selectedPieceData.title.split(" ").length - 1 &&
" "}
</div>
))}
{selectedPieceData.title.split(' ').map((word: string, index: number) => (
<div key={index}>
{word}
{index < selectedPieceData.title.split(' ').length - 1 && ' '}
</div>
))}
</div>
)}
{/* 对应页面图片 */}
<div className="max-h-[500px] min-h-[400px] flex items-center justify-center">
<img
src={getStaticImage(
`puzzle/${selectedPieceData.detailImage}`
)}
src={getStaticImage(`puzzle/${selectedPieceData.detailImage}`)}
alt={selectedPieceData.title}
className="max-w-full max-h-full object-contain"
/>
@@ -338,7 +328,7 @@ const PuzzleSystem = ({ visible, onClose, residence }: PuzzleSystemProps) => {
{selectedPieceData && (
<Image
className="mt-4"
src={getStaticImage("puzzle/点击前往浏览")}
src={getStaticImage('puzzle/点击前往浏览')}
alt="前往按钮"
button
onClick={handleGoToPage}
@@ -351,7 +341,7 @@ const PuzzleSystem = ({ visible, onClose, residence }: PuzzleSystemProps) => {
</div>
</div>
</div>
);
};
)
}
export default PuzzleSystem;
export default PuzzleSystem