refactor(#783): 统一API文件命名风格为kebab-case #788

Merged
xiaoxia merged 4 commits from refactor/frontend-naming-convention into develop 2026-07-23 22:34:57 +08:00
65 changed files with 140 additions and 59 deletions
Executable → Regular
View File
Executable → Regular
View File
+2 -2
View File
@@ -8,8 +8,8 @@
* - POST /api/v1/templates/{id}/toggle-favorite — 收藏/取消收藏
*/
import apiClient from "./client"
import type { TitleConfig, SubtitleConfig, BgmConfig } from "./editingPlanner"
import type { EditPlanConfig } from "./templateEditor"
import type { TitleConfig, SubtitleConfig, BgmConfig } from "./editing-planner"
import type { EditPlanConfig } from "./template-editor"
/* ──────────── 类型定义 ──────────── */
@@ -12,8 +12,8 @@
import React, { useState, useMemo, useCallback, useRef, useEffect } from "react"
import "./AssetSelector.css"
import { Input, Select, Button } from "@/components/ui"
import type { MediaAsset } from "@/api/templateEditor"
import { MATERIAL_TYPE_LABELS, MATERIAL_TYPE_ICONS, QUALITY_OPTIONS } from "@/api/templateEditor"
import type { MediaAsset } from "@/api/template-editor"
import { MATERIAL_TYPE_LABELS, MATERIAL_TYPE_ICONS, QUALITY_OPTIONS } from "@/api/template-editor"
/* ──────────── 类型 ──────────── */
View File
View File
+2 -2
View File
@@ -12,8 +12,8 @@
*/
import React, { useState, useCallback, useRef, useEffect } from "react"
import { Modal, Button } from "@/components/ui"
import { createVoiceClone, toVoiceClone } from "@/api/voiceClone"
import type { VoiceClone } from "@/api/voiceClone"
import { createVoiceClone, toVoiceClone } from "@/api/voice-clone"
import type { VoiceClone } from "@/api/voice-clone"
import { uploadAsset } from "@/api/assets"
import "./clone-modal.css"
View File
Executable → Regular
View File
+2 -2
View File
@@ -5,8 +5,8 @@
* 全部完成(ready / failed)后停止轮询。
*/
import { useState, useEffect, useCallback, useRef } from "react"
import { getVoiceClones } from "@/api/voiceClone"
import type { VoiceClone } from "@/api/voiceClone"
import { getVoiceClones } from "@/api/voice-clone"
import type { VoiceClone } from "@/api/voice-clone"
const POLL_INTERVAL = 3000 // 3 秒
View File
View File
View File
View File
@@ -11,7 +11,7 @@ import type {
TemplateCategory,
TemplateMode,
SaveTemplatePayload,
} from "@/api/editingPlanner"
} from "@/api/editing-planner"
import {
getEditingTemplates,
getEditingTemplate,
@@ -19,9 +19,9 @@ import {
updateEditingTemplate,
getTemplateCategories,
MODE_LABELS,
} from "@/api/editingPlanner"
import type { MediaAsset, TransitionEffect, TitleConfig } from "@/api/templateEditor"
import { getMediaAssets, getEditPlan, getEditPlanClips } from "@/api/templateEditor"
} from "@/api/editing-planner"
import type { MediaAsset, TransitionEffect, TitleConfig } from "@/api/template-editor"
import { getMediaAssets, getEditPlan, getEditPlanClips } from "@/api/template-editor"
import { useUndoRedo } from "./hooks/useUndoRedo"
import type {
ClipData,
@@ -4,9 +4,9 @@
*/
import React, { useRef, useState, useCallback } from "react"
import { useNavigate } from "react-router-dom"
import type { TemplateMode } from "@/api/editingPlanner"
import type { TemplateMode } from "@/api/editing-planner"
import type { ClipData, ClipType } from "../types"
import { TRANSITION_OPTIONS } from "@/api/templateEditor"
import { TRANSITION_OPTIONS } from "@/api/template-editor"
import type { AssetItem } from "@/api/assets"
interface SubtitleSettings {
View File
@@ -4,8 +4,8 @@
*/
import React from "react"
import { CloseOutlined, InboxOutlined } from "@ant-design/icons"
import type { EditPlanGeneration } from "@/api/templateEditor"
import { PLAN_STATUS_LABELS } from "@/api/templateEditor"
import type { EditPlanGeneration } from "@/api/template-editor"
import { PLAN_STATUS_LABELS } from "@/api/template-editor"
interface GenerationHistoryModalProps {
open: boolean
@@ -7,7 +7,7 @@ import React, { useCallback } from "react"
import { Drawer } from "antd"
import type { IntroOutroConfig, IntroOutroItem, IntroOutroKind, TransitionType } from "../types"
import { DEFAULT_INTRO_OUTRO } from "../types"
import { TRANSITION_OPTIONS } from "@/api/templateEditor"
import { TRANSITION_OPTIONS } from "@/api/template-editor"
/* ──────────── 常量 ──────────── */
@@ -3,10 +3,10 @@
* Tab 切换:模板列表 + 素材库
*/
import React, { useState } from "react"
import type { EditingTemplate } from "@/api/editingPlanner"
import { MODE_LABELS } from "@/api/editingPlanner"
import type { MediaAsset } from "@/api/templateEditor"
import AssetSelector from "@/components/AssetSelector/AssetSelector"
import type { EditingTemplate } from "@/api/editing-planner"
import { MODE_LABELS } from "@/api/editing-planner"
import type { MediaAsset } from "@/api/template-editor"
import AssetSelector from "@/components/asset-selector/AssetSelector"
interface MediaPanelProps {
templates: EditingTemplate[]
View File
@@ -5,7 +5,7 @@
*/
import React from "react"
import type { ClipData, ClipType } from "../types"
import type { TitleConfig } from "@/api/templateEditor"
import type { TitleConfig } from "@/api/template-editor"
import type { CoverConfig } from "../types"
interface SubtitleSettings {
@@ -4,7 +4,7 @@
*/
import React from "react"
import { Modal, Input, Select } from "@/components/ui"
import type { TemplateCategory } from "@/api/editingPlanner"
import type { TemplateCategory } from "@/api/editing-planner"
interface SaveModalProps {
open: boolean
View File
@@ -10,7 +10,7 @@
*/
import React, { useState, useRef, useCallback, useEffect, useLayoutEffect, useMemo } from "react"
import type { ClipData, ClipType, TrimConfig } from "../types"
import { TRANSITION_OPTIONS } from "@/api/templateEditor"
import { TRANSITION_OPTIONS } from "@/api/template-editor"
interface TimelinePanelProps {
clips: ClipData[]
@@ -5,7 +5,7 @@
*/
import React, { useCallback } from "react"
import { Drawer, Slider } from "antd"
import { TRANSITION_OPTIONS } from "@/api/templateEditor"
import { TRANSITION_OPTIONS } from "@/api/template-editor"
import type { TransitionConfig, TransitionType } from "../types"
import { DEFAULT_TRANSITION } from "../types"
@@ -18,7 +18,7 @@ import type {
CreateEditPlanClipRequest,
UpdateEditPlanClipRequest,
ClipReorderItem,
} from "@/api/templateEditor"
} from "@/api/template-editor"
import {
getEditPlanClips,
createEditPlanClip,
@@ -27,7 +27,7 @@ import {
reorderEditPlanClips,
batchDeleteEditPlanClips,
createClipsFromAssets,
} from "@/api/templateEditor"
} from "@/api/template-editor"
import { useUndoRedo } from "./useUndoRedo"
const QUERY_KEY = "editPlanClips"
View File
View File
+5 -5
View File
@@ -30,15 +30,15 @@ import {
getGenerationTaskResults,
getGenerationStatus,
getEditPlan,
} from "@/api/templateEditor"
import type { GeneratedVideo, EditPlanConfig, TitleConfig } from "@/api/templateEditor"
} from "@/api/template-editor"
import type { GeneratedVideo, EditPlanConfig, TitleConfig } from "@/api/template-editor"
import type { CoverConfig } from "../editing-planner/types"
import { getEditingTemplates } from "@/api/editingPlanner"
import { getEditingTemplates } from "@/api/editing-planner"
import { getTitles } from "@/api/titles"
import { fetchPresetVoices } from "@/api/voices"
import type { PresetVoiceItem } from "@/api/voices"
import { formatDuration } from "@/api/voiceClone"
import type { VoiceClone } from "@/api/voiceClone"
import { formatDuration } from "@/api/voice-clone"
import type { VoiceClone } from "@/api/voice-clone"
import CloneModal from "@/components/voice/CloneModal"
import { synthesizeSpeech, getTTSJobStatus, saveTtsToLibrary } from "@/api/tts"
import { getTags, createTag } from "@/api/tags"
View File
View File
View File
@@ -40,7 +40,7 @@ import {
MODE_COLORS,
type EditingTemplate,
type TemplateMode,
} from "@/api/editingPlanner"
} from "@/api/editing-planner"
import "./MyTemplates.css"
const { Title, Text } = Typography
+2 -2
View File
@@ -19,8 +19,8 @@ import { Button, Modal, Input, Tooltip } from "@/components/ui"
import type { ButtonProps } from "antd"
import PageHead from "@/components/layout/PageHead"
import { useCloneProgress } from "@/hooks/useCloneProgress"
import { deleteVoiceClone, updateVoiceClone, formatDuration } from "@/api/voiceClone"
import type { VoiceClone, VoiceCloneStatus } from "@/api/voiceClone"
import { deleteVoiceClone, updateVoiceClone, formatDuration } from "@/api/voice-clone"
import type { VoiceClone, VoiceCloneStatus } from "@/api/voice-clone"
import "./my-voices.css"
/* ============================================================
View File
View File
View File
View File
@@ -25,7 +25,7 @@ import {
updateVoiceClone,
formatDuration,
type VoiceClone as VoiceCloneType,
} from "@/api/voiceClone"
} from "@/api/voice-clone"
import "./voice-clone.css"
/* ── 状态配置 ─────────────────────────────────────────── */
+1 -1
View File
@@ -34,7 +34,7 @@ import {
retryVoiceClone,
toVoiceClone,
type VoiceClone,
} from "@/api/voiceClone"
} from "@/api/voice-clone"
import { synthesizeSpeech, getTTSJobStatus, saveTtsToLibrary } from "@/api/tts"
import {
getAssetsByKind,
View File
@@ -23,7 +23,7 @@ import {
copyEditPlan,
getMediaAssets,
getMediaAsset,
} from "@/api/templateEditor"
} from "@/api/template-editor"
const mockGet = vi.fn()
const mockPost = vi.fn()
@@ -7,7 +7,7 @@ import {
deleteEditingTemplate,
getTemplateCategories,
generateFromTemplate,
} from "@/api/editingPlanner"
} from "@/api/editing-planner"
const mockGet = vi.fn()
const mockPost = vi.fn()
@@ -1,6 +1,6 @@
import { describe, expect, it } from "vitest"
import { toVoiceClone, formatDuration } from "@/api/voiceClone"
import type { VoiceCloneProfile } from "@/api/voiceClone"
import { toVoiceClone, formatDuration } from "@/api/voice-clone"
import type { VoiceCloneProfile } from "@/api/voice-clone"
describe("formatDuration", () => {
it("should format seconds correctly", () => {
@@ -8,7 +8,7 @@ import {
updateVoiceClone,
getVoiceCloneStatus,
retryVoiceClone,
} from "@/api/voiceClone"
} from "@/api/voice-clone"
const mockGet = vi.fn()
const mockPost = vi.fn()
@@ -1,8 +1,8 @@
import React from "react"
import { describe, it, expect, vi } from "vitest"
import { render, screen } from "@testing-library/react"
import AssetSelector from "@/components/AssetSelector/AssetSelector"
import type { MediaAsset } from "@/api/templateEditor"
import AssetSelector from "@/components/asset-selector/AssetSelector"
import type { MediaAsset } from "@/api/template-editor"
vi.mock("@/components/ui", () => ({
Input: ({ placeholder }: any) => <input placeholder={placeholder} />,
@@ -18,7 +18,7 @@ vi.mock("@/components/ui", () => ({
Button: ({ children, onClick }: any) => <button onClick={onClick}>{children}</button>,
}))
vi.mock("@/components/AssetSelector/AssetSelector.css", () => ({}))
vi.mock("@/components/asset-selector/AssetSelector.css", () => ({}))
const mockAssets: MediaAsset[] = [
{
@@ -3,7 +3,7 @@ import { describe, expect, it, vi } from "vitest"
import { render } from "@testing-library/react"
import CloneModal from "@/components/voice/CloneModal"
vi.mock("@/api/voiceClone", () => ({
vi.mock("@/api/voice-clone", () => ({
createVoiceClone: vi.fn(),
toVoiceClone: vi.fn(),
}))
View File
@@ -4,7 +4,7 @@ import { useCloneProgress } from "@/hooks/useCloneProgress"
// mock API
const mockGetVoiceClones = vi.fn()
vi.mock("@/api/voiceClone", () => ({
vi.mock("@/api/voice-clone", () => ({
getVoiceClones: (...args: unknown[]) => mockGetVoiceClones(...args),
VoiceCloneStatus: { READY: "ready" },
}))
View File
@@ -137,7 +137,7 @@ vi.mock("@/components/ui", () => ({
}))
// === API mocks ===
vi.mock("@/api/editingPlanner", () => ({
vi.mock("@/api/editing-planner", () => ({
getEditingTemplates: vi.fn().mockResolvedValue({ items: [], total: 0 }),
getEditingTemplate: vi.fn().mockResolvedValue({}),
createEditingTemplate: vi.fn().mockResolvedValue({}),
@@ -146,7 +146,7 @@ vi.mock("@/api/editingPlanner", () => ({
MODE_LABELS: { pip: "画中画", intro_outro: "片头片尾", watermark: "水印" },
}))
vi.mock("@/api/templateEditor", () => ({
vi.mock("@/api/template-editor", () => ({
getMediaAssets: vi.fn().mockResolvedValue({ items: [] }),
getEditPlanGenerations: vi.fn().mockResolvedValue({ items: [] }),
generateCover: vi.fn().mockResolvedValue({}),
@@ -204,7 +204,7 @@ vi.mock("@/api/voices", () => ({
fetchPresetVoices: vi.fn().mockResolvedValue({ items: [] }),
}))
vi.mock("@/api/editingPlanner", () => ({
vi.mock("@/api/editing-planner", () => ({
getEditingTemplates: vi.fn().mockResolvedValue({ items: [], total: 0 }),
MODE_LABELS: { pip: "画中画" },
}))
@@ -213,7 +213,7 @@ vi.mock("@/api/titles", () => ({
getTitles: vi.fn().mockResolvedValue({ items: [] }),
}))
vi.mock("@/api/templateEditor", () => ({
vi.mock("@/api/template-editor", () => ({
generateEditPlan: vi.fn().mockResolvedValue({
plan_id: "test-plan",
generation_task_id: "test-task",
@@ -234,7 +234,7 @@ vi.mock("@/api/templateEditor", () => ({
getGenerationTaskResults: vi.fn().mockResolvedValue({ items: [] }),
}))
vi.mock("@/api/voiceClone", () => ({
vi.mock("@/api/voice-clone", () => ({
formatDuration: vi.fn((s: number) => `${s}s`),
}))
+1 -1
View File
@@ -40,7 +40,7 @@ vi.mock("@ant-design/icons", () => ({
PlusOutlined: () => <span>PlusOutlined</span>,
}))
vi.mock("@/api/editingPlanner", () => ({
vi.mock("@/api/editing-planner", () => ({
getEditingTemplates: vi.fn(),
getTemplateCategories: vi.fn().mockResolvedValue([]),
deleteEditingTemplate: vi.fn(),
+1 -1
View File
@@ -73,7 +73,7 @@ vi.mock("antd", () => ({
RadioGroup: ({ children }: any) => <div>{children}</div>,
}))
vi.mock("@/api/voiceClone", () => ({
vi.mock("@/api/voice-clone", () => ({
getVoiceCloneList: vi.fn().mockResolvedValue({ items: [], total: 0 }),
deleteVoiceClone: vi.fn().mockResolvedValue({ success: true }),
createVoiceClone: vi.fn().mockResolvedValue({ success: true }),
+1 -1
View File
@@ -73,7 +73,7 @@ vi.mock("antd", () => ({
RadioGroup: ({ children }: any) => <div>{children}</div>,
}))
vi.mock("@/api/voiceClone", () => ({
vi.mock("@/api/voice-clone", () => ({
getVoiceCloneList: vi.fn().mockResolvedValue({ items: [], total: 0 }),
createVoiceClone: vi.fn().mockResolvedValue({ success: true, id: "1" }),
deleteVoiceClone: vi.fn().mockResolvedValue({ success: true }),
@@ -109,7 +109,7 @@ vi.mock("@ant-design/icons", () => ({
vi.mock("@/store/authStore", () => ({
useAuthStore: (sel: any) => sel({ user: { id: "1" }, isAuthenticated: true }),
}))
vi.mock("@/api/voiceClone", () => ({
vi.mock("@/api/voice-clone", () => ({
createVoiceClone: vi.fn().mockResolvedValue({ success: true }),
deleteVoiceClone: vi.fn().mockResolvedValue({ success: true }),
retryVoiceClone: vi.fn().mockResolvedValue({ success: true }),
@@ -32,7 +32,7 @@ vi.mock("antd", () => ({
},
}))
vi.mock("@/api/templateEditor", () => ({
vi.mock("@/api/template-editor", () => ({
getEditPlanClips: vi.fn().mockResolvedValue({ items: [], total: 0 }),
createEditPlanClip: vi.fn().mockResolvedValue({}),
updateEditPlanClip: vi.fn().mockResolvedValue({}),
View File
Regular → Executable
+83 -2
View File
@@ -445,5 +445,86 @@ def list_all_assets() -> list[Asset]:
---
**最后更新**: 2026-06-15
**版本**: v1.0
## 8. 前端规范(React + TypeScript
### 8.1 文件命名规范
| 类型 | 风格 | 示例 | 说明 |
|------|------|------|------|
| **目录名** | kebab-case | `editing-planner/`, `asset-selector/` | 全小写,多单词用短横线连接 |
| **组件文件** | PascalCase | `AssetSelector.tsx`, `MediaPanel.tsx` | 与组件导出名一致 |
| **页面组件** | PascalCase | `EditingPlanner.tsx`, `MyTemplates.tsx` | 放在 kebab-case 目录中 |
| **API 文件** | kebab-case | `template-editor.ts`, `voice-clone.ts` | 与 RESTful 资源路径风格一致 |
| **Hooks** | camelCase (use前缀) | `useAuth.ts`, `useCloneProgress.ts` | React 官方惯例 |
| **Store** | kebab-case | `auth-store.ts`, `ui-store.ts` | |
| **工具函数/helpers** | kebab-case | `format-duration.ts`, `date-utils.ts` | |
| **类型定义** | kebab-case | `types.ts`, `subtitle-types.ts` | 目录内类型定义可用 `types.ts` |
| **常量** | UPPER_SNAKE_CASE | `MAX_UPLOAD_SIZE`, `API_BASE_URL` | |
| **测试文件** | 与被测文件同名 + `.test` | `auth.test.ts`, `AssetSelector.test.tsx` | 放在 `test/` 目录下,保持相同相对路径 |
### 8.2 组件命名
- 组件名使用 **PascalCase**,与文件名一致
- 默认导出组件名与文件名相同
- 高阶组件/包装器用 `with` 前缀:`withAuth(Component)`
- 渲染属性组件用 `Render` 后缀:`UserRender`
```tsx
// ✅ 正确
// 文件: AssetSelector.tsx
const AssetSelector: React.FC<AssetSelectorProps> = ({ assets }) => { ... };
export default AssetSelector;
// ❌ 错误
// 文件: asset-selector.tsx
const assetSelector = () => { ... };
```
### 8.3 变量与函数命名
- **变量/函数**camelCase
- **布尔变量**:用 `is/has/should/can` 前缀
- **事件处理函数**:用 `handle` 前缀 + 事件名
- **事件 handler prop**:用 `on` 前缀
```tsx
// ✅ 正确
const isLoading = true;
const hasError = false;
const handleSubmit = () => { ... };
<Button onClick={onClick} />
```
### 8.4 导入路径
- 使用 `@/` 别名引用 `src/` 下的文件
- 同一目录内用相对路径 `./`
- 导入顺序:React → 第三方库 → @/内部模块 → 相对路径 → 样式
```tsx
import React, { useState } from 'react';
import { Button, Modal } from 'antd';
import { useAuth } from '@/hooks/useAuth';
import { Asset } from '@/api/asset-selector';
import { MediaPanel } from './MediaPanel';
import './AssetSelector.css';
```
### 8.5 CSS/样式命名
- CSS Modules / CSS 类名:kebab-case
- styled-componentsPascalCase(与组件一致)
- Tailwind 工具类遵循官方惯例
```css
/* ✅ 正确 */
.asset-selector { ... }
.asset-item { ... }
.asset-item--active { ... }
```
---
**最后更新**: 2026-07-23
**版本**: v1.1