diff --git a/CLAUDE.md b/CLAUDE.md index 692052d..107760f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -22,9 +22,7 @@ pnpm run start pnpm run lint # 数据库操作 -pnpm prisma generate # 生成 Prisma client 到 src/generated/prisma -pnpm prisma db push # 推送 schema 变更到数据库 -pnpm prisma studio # 打开 Prisma Studio 查看数据库 +# 不要进行数据库操作,让用户操作数据库 ``` ## 技术栈 @@ -102,31 +100,6 @@ src/modules/{module}/ **LLM 集成**: 使用智谱 AI API 进行翻译和 IPA 生成。通过环境变量 `ZHIPU_API_KEY` 和 `ZHIPU_MODEL_NAME` 配置。 -### 环境变量 - -需要在 `.env.local` 中配置: - -```env -# LLM 集成(智谱 AI 用于翻译和 IPA 生成) -ZHIPU_API_KEY=your-api-key -ZHIPU_MODEL_NAME=your-model-name - -# 阿里云千问 TTS(文本转语音) -DASHSCORE_API_KEY=your-dashscore-api-key - -# 认证 -BETTER_AUTH_SECRET=your-secret -BETTER_AUTH_URL=http://localhost:3000 -GITHUB_CLIENT_ID=your-client-id -GITHUB_CLIENT_SECRET=your-client-secret - -# 数据库 -DATABASE_URL=postgresql://username:password@localhost:5432/database_name -``` - -## 重要配置细节 - -- **Prisma client 输出**: 自定义目录 `src/generated/prisma`(不是默认的 `node_modules/.prisma`) - **Standalone 输出**: 为 Docker 部署配置 - **React Compiler**: 在 `next.config.ts` 中启用以自动优化 - **HTTPS 开发**: 开发服务器使用 `--experimental-https` 标志 @@ -147,7 +120,6 @@ DATABASE_URL=postgresql://username:password@localhost:5432/database_name ## 开发注意事项 - 使用 pnpm,而不是 npm 或 yarn -- schema 变更后,先运行 `pnpm prisma generate` 再运行 `pnpm prisma db push` - 应用使用 TypeScript 严格模式 - 确保类型安全 - 所有面向用户的文本都需要国际化 - **优先使用 Server Components**,只在需要交互时使用 Client Components diff --git a/src/app/(features)/alphabet/AlphabetCard.tsx b/src/app/(features)/alphabet/AlphabetCard.tsx index b303490..0d5435d 100644 --- a/src/app/(features)/alphabet/AlphabetCard.tsx +++ b/src/app/(features)/alphabet/AlphabetCard.tsx @@ -3,11 +3,11 @@ import { useState, useEffect, useCallback } from "react"; import { useTranslations } from "next-intl"; import { Letter, SupportedAlphabets } from "@/lib/interfaces"; -import { IconClick, CircleToggleButton, CircleButton, PrimaryButton } from "@/components/ui/buttons"; +import { IconClick, CircleToggleButton, CircleButton, PrimaryButton } from "@/design-system/base/button"; import { IMAGES } from "@/config/images"; import { ChevronLeft, ChevronRight } from "lucide-react"; import { PageLayout } from "@/components/ui/PageLayout"; -import { Card } from "@/components/ui/Card"; +import { Card } from "@/design-system/base/card"; interface AlphabetCardProps { alphabet: Letter[]; @@ -103,7 +103,7 @@ export function AlphabetCard({ alphabet, alphabetType, onBack }: AlphabetCardPro {/* 右上角返回按钮 - outside the white card */}
{/* 上一个按钮 */} - + {/* 中间区域:随机按钮 */} @@ -202,7 +202,7 @@ export function AlphabetCard({ alphabet, alphabetType, onBack }: AlphabetCardPro {/* 下一个按钮 */} - +
diff --git a/src/app/(features)/alphabet/MemoryCard.tsx b/src/app/(features)/alphabet/MemoryCard.tsx index 8b29e41..faaaf04 100644 --- a/src/app/(features)/alphabet/MemoryCard.tsx +++ b/src/app/(features)/alphabet/MemoryCard.tsx @@ -1,5 +1,5 @@ -import { LightButton } from "@/components/ui/buttons"; -import { IconClick } from "@/components/ui/buttons"; +import { LightButton } from "@/design-system/base/button"; +import { IconClick } from "@/design-system/base/button"; import { IMAGES } from "@/config/images"; import { Letter, SupportedAlphabets } from "@/lib/interfaces"; import { @@ -45,10 +45,10 @@ export function MemoryCard({ className="w-full flex justify-center items-center" onKeyDown={(e: KeyboardEvent) => e.preventDefault()} > -
+
setChosenAlphabet(null)} @@ -64,13 +64,13 @@ export function MemoryCard({
setMore(!more)} diff --git a/src/app/(features)/alphabet/page.tsx b/src/app/(features)/alphabet/page.tsx index 4e594e0..c1031a0 100644 --- a/src/app/(features)/alphabet/page.tsx +++ b/src/app/(features)/alphabet/page.tsx @@ -4,7 +4,7 @@ import { useState, useEffect } from "react"; import { useTranslations } from "next-intl"; import { Letter, SupportedAlphabets } from "@/lib/interfaces"; import { PageLayout } from "@/components/ui/PageLayout"; -import { LightButton } from "@/components/ui/buttons"; +import { LightButton } from "@/design-system/base/button"; import { AlphabetCard } from "./AlphabetCard"; export default function Alphabet() { diff --git a/src/app/(features)/dictionary/SearchForm.tsx b/src/app/(features)/dictionary/SearchForm.tsx index 71bf3b8..687b7a9 100644 --- a/src/app/(features)/dictionary/SearchForm.tsx +++ b/src/app/(features)/dictionary/SearchForm.tsx @@ -1,7 +1,7 @@ "use client"; -import { LightButton } from "@/components/ui/buttons"; -import { Input } from "@/components/ui/Input"; +import { LightButton } from "@/design-system/base/button"; +import { Input } from "@/design-system/base/input"; import { useTranslations } from "next-intl"; import { useState } from "react"; import { useRouter } from "next/navigation"; diff --git a/src/app/(features)/dictionary/SearchResult.client.tsx b/src/app/(features)/dictionary/SearchResult.client.tsx index 08c7131..97609f2 100644 --- a/src/app/(features)/dictionary/SearchResult.client.tsx +++ b/src/app/(features)/dictionary/SearchResult.client.tsx @@ -1,7 +1,7 @@ "use client"; import { Plus, RefreshCw } from "lucide-react"; -import { CircleButton, LightButton } from "@/components/ui/buttons"; +import { CircleButton, LightButton } from "@/design-system/base/button"; import { toast } from "sonner"; import { actionCreatePair } from "@/modules/folder/folder-aciton"; import { TSharedItem } from "@/shared/dictionary-type"; diff --git a/src/app/(features)/memorize/FolderSelector.tsx b/src/app/(features)/memorize/FolderSelector.tsx index 09e74f9..91c1475 100644 --- a/src/app/(features)/memorize/FolderSelector.tsx +++ b/src/app/(features)/memorize/FolderSelector.tsx @@ -6,7 +6,7 @@ import Link from "next/link"; import { Folder as Fd } from "lucide-react"; import { TSharedFolderWithTotalPairs } from "@/shared/folder-type"; import { PageLayout } from "@/components/ui/PageLayout"; -import { PrimaryButton } from "@/components/ui/buttons"; +import { PrimaryButton } from "@/design-system/base/button"; interface FolderSelectorProps { folders: TSharedFolderWithTotalPairs[]; @@ -37,7 +37,7 @@ const FolderSelector: React.FC = ({ folders }) => { {t("selectFolder")} {/* 文件夹列表 */} -
+
{folders .toSorted((a, b) => a.id - b.id) .map((folder) => ( @@ -50,7 +50,7 @@ const FolderSelector: React.FC = ({ folders }) => { > {/* 文件夹图标 */}
- +
{/* 文件夹信息 */}
diff --git a/src/app/(features)/memorize/Memorize.tsx b/src/app/(features)/memorize/Memorize.tsx index e6bb377..48846da 100644 --- a/src/app/(features)/memorize/Memorize.tsx +++ b/src/app/(features)/memorize/Memorize.tsx @@ -1,7 +1,7 @@ "use client"; import { useState } from "react"; -import { LinkButton, CircleToggleButton, LightButton } from "@/components/ui/buttons"; +import { LinkButton, CircleToggleButton, LightButton } from "@/design-system/base/button"; import { useAudioPlayer } from "@/hooks/useAudioPlayer"; import { getTTSUrl, TTS_SUPPORTED_LANGUAGES } from "@/lib/bigmodel/tts"; import { useTranslations } from "next-intl"; @@ -29,7 +29,7 @@ const Memorize: React.FC = ({ textPairs }) => { if (textPairs.length === 0) { return ( - +

{t("noTextPairs")}

); diff --git a/src/app/(features)/srt-player/VideoPlayer/VideoPanel.tsx b/src/app/(features)/srt-player/VideoPlayer/VideoPanel.tsx index b9161d6..26db463 100644 --- a/src/app/(features)/srt-player/VideoPlayer/VideoPanel.tsx +++ b/src/app/(features)/srt-player/VideoPlayer/VideoPanel.tsx @@ -1,6 +1,6 @@ import { useState, useRef, forwardRef, useEffect, useCallback } from "react"; import { SubtitleDisplay } from "./SubtitleDisplay"; -import { LightButton } from "@/components/ui/buttons"; +import { LightButton } from "@/design-system/base/button"; import { RangeInput } from "@/components/ui/RangeInput"; import { getIndex, parseSrt, getNearistIndex } from "../subtitle"; import { useTranslations } from "next-intl"; diff --git a/src/app/(features)/srt-player/components/atoms/FileInput.tsx b/src/app/(features)/srt-player/components/atoms/FileInput.tsx index 079a7ae..fcf0e43 100644 --- a/src/app/(features)/srt-player/components/atoms/FileInput.tsx +++ b/src/app/(features)/srt-player/components/atoms/FileInput.tsx @@ -1,7 +1,7 @@ "use client"; import React, { useRef } from "react"; -import { Button } from "@/components/ui/Button"; +import { Button } from "@/design-system/base/button"; import { FileInputProps } from "../../types/controls"; interface FileInputComponentProps extends FileInputProps { diff --git a/src/app/(features)/srt-player/components/atoms/PlayButton.tsx b/src/app/(features)/srt-player/components/atoms/PlayButton.tsx index e967859..32534ef 100644 --- a/src/app/(features)/srt-player/components/atoms/PlayButton.tsx +++ b/src/app/(features)/srt-player/components/atoms/PlayButton.tsx @@ -2,7 +2,7 @@ import React from "react"; import { useTranslations } from "next-intl"; -import { LightButton } from "@/components/ui/buttons"; +import { LightButton } from "@/design-system/base/button"; import { PlayButtonProps } from "../../types/player"; export function PlayButton({ isPlaying, onToggle, disabled, className }: PlayButtonProps) { diff --git a/src/app/(features)/srt-player/components/atoms/SpeedControl.tsx b/src/app/(features)/srt-player/components/atoms/SpeedControl.tsx index 3664557..d266e92 100644 --- a/src/app/(features)/srt-player/components/atoms/SpeedControl.tsx +++ b/src/app/(features)/srt-player/components/atoms/SpeedControl.tsx @@ -1,7 +1,7 @@ "use client"; import React from "react"; -import { LightButton } from "@/components/ui/buttons"; +import { LightButton } from "@/design-system/base/button"; import { SpeedControlProps } from "../../types/player"; import { getPlaybackRateOptions, getPlaybackRateLabel } from "../../utils/timeUtils"; diff --git a/src/app/(features)/srt-player/components/compounds/ControlBar.tsx b/src/app/(features)/srt-player/components/compounds/ControlBar.tsx index 75276db..b24470c 100644 --- a/src/app/(features)/srt-player/components/compounds/ControlBar.tsx +++ b/src/app/(features)/srt-player/components/compounds/ControlBar.tsx @@ -3,7 +3,7 @@ import React from "react"; import { useTranslations } from "next-intl"; import { ChevronLeft, ChevronRight, RotateCcw, Pause } from "lucide-react"; -import { LightButton } from "@/components/ui/buttons"; +import { LightButton } from "@/design-system/base/button"; import { ControlBarProps } from "../../types/controls"; import { PlayButton } from "../atoms/PlayButton"; import { SpeedControl } from "../atoms/SpeedControl"; diff --git a/src/app/(features)/srt-player/components/compounds/UploadZone.tsx b/src/app/(features)/srt-player/components/compounds/UploadZone.tsx index 45b063b..450850e 100644 --- a/src/app/(features)/srt-player/components/compounds/UploadZone.tsx +++ b/src/app/(features)/srt-player/components/compounds/UploadZone.tsx @@ -4,7 +4,7 @@ import React from "react"; import { useTranslations } from "next-intl"; import { toast } from "sonner"; import { Video, FileText } from "lucide-react"; -import { LightButton } from "@/components/ui/buttons"; +import { LightButton } from "@/design-system/base/button"; import { FileUploadProps } from "../../types/controls"; import { useFileUpload } from "../../hooks/useFileUpload"; diff --git a/src/app/(features)/srt-player/page.tsx b/src/app/(features)/srt-player/page.tsx index c245655..8184861 100644 --- a/src/app/(features)/srt-player/page.tsx +++ b/src/app/(features)/srt-player/page.tsx @@ -15,7 +15,7 @@ import { SubtitleArea } from "./components/compounds/SubtitleArea"; import { ControlBar } from "./components/compounds/ControlBar"; import { UploadZone } from "./components/compounds/UploadZone"; import { SeekBar } from "./components/atoms/SeekBar"; -import { LightButton } from "@/components/ui/buttons"; +import { LightButton } from "@/design-system/base/button"; export default function SrtPlayerPage() { const t = useTranslations("home"); @@ -119,7 +119,7 @@ export default function SrtPlayerPage() {
{/* 视频播放器区域 */} -
+
{(!state.video.url || !state.subtitle.url || state.subtitle.data.length === 0) && (
diff --git a/src/app/(features)/text-speaker/SaveList.tsx b/src/app/(features)/text-speaker/SaveList.tsx index 15282f8..98674f5 100644 --- a/src/app/(features)/text-speaker/SaveList.tsx +++ b/src/app/(features)/text-speaker/SaveList.tsx @@ -6,7 +6,7 @@ import { TextSpeakerArraySchema, TextSpeakerItemSchema, } from "@/lib/interfaces"; -import { IconClick } from "@/components/ui/buttons"; +import { IconClick } from "@/design-system/base/button"; import { IMAGES } from "@/config/images"; import { useTranslations } from "next-intl"; import { getLocalStorageOperator } from "@/lib/browser/localStorageOperators"; @@ -24,7 +24,7 @@ function TextCard({ item, handleUse, handleDel }: TextCardProps) { handleDel(item); }; return ( -
+
{item.text} @@ -39,7 +39,7 @@ function TextCard({ item, handleUse, handleDel }: TextCardProps) { alt="delete" onClick={onDelClick} className="place-self-center" - size={42} + size="lg" >
@@ -81,7 +81,7 @@ export function SaveList({ show = false, handleUse }: SaveListProps) { if (show) return (
@@ -89,14 +89,14 @@ export function SaveList({ show = false, handleUse }: SaveListProps) { src={IMAGES.refresh} alt="refresh" onClick={refresh} - size={48} + size="lg" className="" >
diff --git a/src/app/(features)/text-speaker/page.tsx b/src/app/(features)/text-speaker/page.tsx index 182ad1b..d683e47 100644 --- a/src/app/(features)/text-speaker/page.tsx +++ b/src/app/(features)/text-speaker/page.tsx @@ -1,7 +1,7 @@ "use client"; -import { LightButton } from "@/components/ui/buttons"; -import { IconClick } from "@/components/ui/buttons"; +import { LightButton } from "@/design-system/base/button"; +import { IconClick } from "@/design-system/base/button"; import { IMAGES } from "@/config/images"; import { useAudioPlayer } from "@/hooks/useAudioPlayer"; import { @@ -222,7 +222,7 @@ export default function TextSpeakerPage() { {/* 文本输入区域 */}
{/* 文本输入框 */} @@ -242,37 +242,37 @@ export default function TextSpeakerPage() {
{/* 速度调节面板 */} {showSpeedAdjust && ( -
+
{/* 自动暂停按钮 */} { setAutopause(!autopause); if (objurlRef) { @@ -303,7 +303,7 @@ export default function TextSpeakerPage() { > {/* 速度调节按钮 */} setShowSpeedAdjust(!showSpeedAdjust)} src={IMAGES.speed} alt="speed" @@ -311,7 +311,7 @@ export default function TextSpeakerPage() { > {/* 保存按钮 */} {/* 保存列表 */} {showSaveList && ( -
+
)} diff --git a/src/app/(features)/translator/page.tsx b/src/app/(features)/translator/page.tsx index 527b9f0..2cf5f6b 100644 --- a/src/app/(features)/translator/page.tsx +++ b/src/app/(features)/translator/page.tsx @@ -1,7 +1,6 @@ "use client"; -import { LightButton, PrimaryButton } from "@/components/ui/buttons"; -import { IconClick } from "@/components/ui/buttons"; +import { LightButton, PrimaryButton, IconClick } from "@/design-system/base/button"; import { IMAGES } from "@/config/images"; import { useAudioPlayer } from "@/hooks/useAudioPlayer"; import { useTranslations } from "next-intl"; @@ -101,7 +100,7 @@ export default function TranslatorPage() { {/* Card Component - Left Side */}
{/* ICard1 Component */} -
+