fix(frontend): 深度健康检查 - 清理死代码和修复合并冲突
Tests / test (pull_request) Failing after 0s
Tests / lint (pull_request) Failing after 0s
CI/CD Pipeline / Validate Code Quality And Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Lint (push) Has been cancelled
Deploy / Deploy Staging (push) Has been cancelled
Deploy / Build Production Runtime Images (push) Has been cancelled
Deploy / Deploy Production (push) Has been cancelled
Deploy / Production Browser E2E (push) Has been cancelled
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Has been cancelled
CI/CD Pipeline / Frontend Lint (pull_request) Has been cancelled

- 删除未使用的API模块: projects.ts, generation.ts, subscription.ts, projectTitles.ts
- 删除未使用的业务组件: InviteMemberModal.tsx, MemberList.tsx, PermissionMatrix.tsx
- 删除未引用的布局组件: Sidebar.tsx
- 删除未引用的状态管理: uiStore.ts
- 修复 business.css 中残留的git合并冲突标记
- 移除 business.css 中重复的注释行

所有删除的文件均经过引用检查,确认无任何页面/组件/hook引用
This commit is contained in:
CI Test
2026-06-28 10:34:00 +08:00
parent 9c287145a1
commit 5c46fe648d
10 changed files with 0 additions and 654 deletions
-77
View File
@@ -1,77 +0,0 @@
/**
* 视频生成 API
*/
import apiClient from "./client";
import type { EditingMode } from "./editPlans";
export interface GenerationTaskItem {
id: string;
project_id: string;
asset_library_id: string;
strategy_id?: string | null;
voice_library_id?: string | null;
edit_plan_id?: string | null;
editing_mode?: EditingMode;
status: "pending" | "running" | "completed" | "failed" | "cancelled";
progress: number;
result_count: number;
error_message?: string | null;
created_at?: string;
updated_at?: string;
}
export interface GeneratedVideoItem {
id: string;
project_id: string;
generation_task_id: string;
name: string;
file_url: string;
file_size: number;
duration: number;
thumbnail_url?: string | null;
width: number;
height: number;
fps: number;
status?: string;
review_status?: "pending_review" | "approved" | "rejected";
generation_params?: Record<string, unknown>;
editing_mode?: EditingMode;
}
export const createGenerationTask = async (data: {
project_id: string;
asset_library_id: string;
strategy_id?: string;
voice_library_id?: string;
edit_plan_id?: string;
editing_mode?: EditingMode;
created_by_user_id?: string;
}): Promise<GenerationTaskItem> => {
const response = await apiClient.post("/generation/tasks", data);
return response.data;
};
export const getGenerationTask = async (taskId: string): Promise<GenerationTaskItem> => {
const response = await apiClient.get(`/generation/tasks/${taskId}`);
return response.data;
};
export const getGenerationResults = async (taskId: string): Promise<GeneratedVideoItem[]> => {
const response = await apiClient.get(`/generation/tasks/${taskId}/results`);
return response.data.items;
};
export const getGeneratedVideos = async (projectId: string): Promise<GeneratedVideoItem[]> => {
const response = await apiClient.get("/generated-videos", { params: { project_id: projectId } });
return response.data.items;
};
export const updateGeneratedVideoReviewStatus = async (videoId: string, reviewStatus: "pending_review" | "approved" | "rejected"): Promise<GeneratedVideoItem> => {
const response = await apiClient.patch(`/generated-videos/${videoId}/review`, { review_status: reviewStatus });
return response.data;
};
export const getGeneratedVideoDownloadUrl = async (videoId: string): Promise<string> => {
const response = await apiClient.get(`/generated-videos/${videoId}/download-url`);
return response.data.download_url;
};
-28
View File
@@ -1,28 +0,0 @@
import apiClient from './client';
export interface ProjectTitleItem {
id: string;
project_id: string;
text: string;
category: 'default' | 'marketing' | 'tutorial' | 'story' | 'promo';
favorite: boolean;
usage_count: number;
is_active: boolean;
created_at: string;
updated_at: string;
}
export const getProjectTitles = async (projectId: string, activeOnly = false): Promise<ProjectTitleItem[]> => {
const response = await apiClient.get(`/projects/${projectId}/titles`, { params: { active_only: activeOnly } });
return response.data.items;
};
export const createProjectTitle = async (projectId: string, data: { text: string; category?: ProjectTitleItem['category']; favorite?: boolean }): Promise<ProjectTitleItem> => {
const response = await apiClient.post(`/projects/${projectId}/titles`, data);
return response.data;
};
export const updateProjectTitle = async (titleId: string, data: { text?: string; category?: ProjectTitleItem['category']; favorite?: boolean; is_active?: boolean }): Promise<ProjectTitleItem> => {
const response = await apiClient.patch(`/project-titles/${titleId}`, data);
return response.data;
};
-27
View File
@@ -1,27 +0,0 @@
import apiClient from './client';
export interface ProjectItem {
id: string;
name: string;
description: string;
}
export interface CreateProjectRequest {
name: string;
description?: string;
}
export const getProject = async (projectId: string): Promise<ProjectItem> => {
const response = await apiClient.get(`/projects/${projectId}`);
return response.data;
};
export const getProjects = async (): Promise<ProjectItem[]> => {
const response = await apiClient.get('/projects');
return response.data.items;
};
export const createProject = async (data: CreateProjectRequest): Promise<ProjectItem> => {
const response = await apiClient.post('/projects', data);
return response.data;
};
-64
View File
@@ -1,64 +0,0 @@
/**
* 订阅相关 API
*/
import apiClient from './client';
// 类型定义
export interface SubscriptionPlan {
name: 'free' | 'pro' | 'enterprise';
display_name: string;
price: number;
currency: string;
max_projects: number;
max_storage_gb: number;
features: string[];
}
export interface QuotaStatus {
projects: {
used: number;
limit: number;
status: 'normal' | 'warning' | 'critical' | 'exceeded';
};
storage: {
used_gb: number;
limit_gb: number;
status: 'normal' | 'warning' | 'critical' | 'exceeded';
};
}
// 获取所有订阅计划
export const getPlans = async (): Promise<SubscriptionPlan[]> => {
const response = await apiClient.get('/subscriptions/plans');
return response.data;
};
// 获取当前订阅
export const getCurrentSubscription = async (
): Promise<SubscriptionPlan> => {
const response = await apiClient.get('/subscription');
return response.data;
};
// 升级订阅
export const upgradeSubscription = async (
plan: 'pro' | 'enterprise'
): Promise<{ message: string }> => {
const response = await apiClient.post('/subscription/upgrade', {
plan,
});
return response.data;
};
// 取消订阅
export const cancelSubscription = async (
): Promise<{ message: string }> => {
const response = await apiClient.post('/subscription/cancel');
return response.data;
};
// 获取配额状态
export const getQuotaStatus = async (): Promise<QuotaStatus> => {
const response = await apiClient.get('/quota');
return response.data;
};
@@ -1,80 +0,0 @@
/**
* 邀请成员弹窗
*/
import React from 'react';
import { Modal, Form, Input, Select, message } from 'antd';
import { useInviteMember } from '@/hooks/useWorkspace';
interface InviteMemberModalProps {
workspaceId: string;
open: boolean;
onClose: () => void;
}
const InviteMemberModal: React.FC<InviteMemberModalProps> = ({
workspaceId,
open,
onClose,
}) => {
const [form] = Form.useForm();
const inviteMutation = useInviteMember(workspaceId);
const handleOk = async () => {
try {
const values = await form.validateFields();
await inviteMutation.mutateAsync(values);
message.success('邀请已发送!');
form.resetFields();
onClose();
} catch (error) {
// 验证失败或请求失败
}
};
const handleCancel = () => {
form.resetFields();
onClose();
};
return (
<Modal
title="邀请成员"
open={open}
onOk={handleOk}
onCancel={handleCancel}
confirmLoading={inviteMutation.isPending}
okText="发送邀请"
cancelText="取消"
>
<Form form={form} layout="vertical">
<Form.Item
name="email"
label="邮箱地址"
rules={[
{ required: true, message: '请输入邮箱地址' },
{ type: 'email', message: '请输入有效的邮箱地址' },
]}
>
<Input placeholder="member@example.com" />
</Form.Item>
<Form.Item
name="role"
label="角色"
initialValue="member"
rules={[{ required: true, message: '请选择角色' }]}
>
<Select
options={[
{ value: 'admin', label: '管理员 - 可以管理成员和项目' },
{ value: 'member', label: '成员 - 可以创建和管理项目' },
{ value: 'viewer', label: '访客 - 只读权限' },
]}
/>
</Form.Item>
</Form>
</Modal>
);
};
export default InviteMemberModal;
@@ -1,129 +0,0 @@
/**
* 成员列表组件
*/
import React from 'react';
import { Table, Button, Tag, Space, Popconfirm, message, Select } from 'antd';
import { DeleteOutlined } from '@ant-design/icons';
import { useWorkspaceMembers } from '@/hooks/useWorkspace';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { removeMember, updateMemberRole } from '@/api/workspace';
interface MemberListProps {
workspaceId: string;
}
const MemberList: React.FC<MemberListProps> = ({ workspaceId }) => {
const { data: members, isLoading } = useWorkspaceMembers(workspaceId);
const queryClient = useQueryClient();
const removeMutation = useMutation({
mutationFn: (memberId: string) => removeMember(workspaceId, memberId),
onSuccess: () => {
message.success('成员已移除');
queryClient.invalidateQueries({ queryKey: ['workspaceMembers', workspaceId] });
},
});
const updateRoleMutation = useMutation({
mutationFn: ({ memberId, role }: { memberId: string; role: string }) =>
updateMemberRole(workspaceId, memberId, role as any),
onSuccess: () => {
message.success('角色已更新');
queryClient.invalidateQueries({ queryKey: ['workspaceMembers', workspaceId] });
},
});
const getRoleTag = (role: string) => {
const roleConfig: Record<string, { color: string; text: string }> = {
owner: { color: 'gold', text: '拥有者' },
admin: { color: 'blue', text: '管理员' },
member: { color: 'green', text: '成员' },
viewer: { color: 'default', text: '访客' },
};
const config = roleConfig[role] || roleConfig.member;
return <Tag color={config.color}>{config.text}</Tag>;
};
const columns = [
{
title: '用户名',
dataIndex: 'username',
key: 'username',
},
{
title: '邮箱',
dataIndex: 'email',
key: 'email',
},
{
title: '角色',
dataIndex: 'role',
key: 'role',
render: (role: string, record: any) => {
if (role === 'owner') {
return getRoleTag(role);
}
return (
<Select
value={role}
style={{ width: 120 }}
onChange={(newRole) =>
updateRoleMutation.mutate({ memberId: record.id, role: newRole })
}
options={[
{ value: 'admin', label: '管理员' },
{ value: 'member', label: '成员' },
{ value: 'viewer', label: '访客' },
]}
/>
);
},
},
{
title: '加入时间',
dataIndex: 'joined_at',
key: 'joined_at',
render: (date: string) => new Date(date).toLocaleDateString(),
},
{
title: '操作',
key: 'action',
render: (_: any, record: any) => {
if (record.role === 'owner') {
return <span style={{ color: '#999' }}>-</span>;
}
return (
<Space>
<Popconfirm
title="确定移除该成员?"
onConfirm={() => removeMutation.mutate(record.id)}
okText="确定"
cancelText="取消"
>
<Button
type="text"
danger
icon={<DeleteOutlined />}
loading={removeMutation.isPending}
>
</Button>
</Popconfirm>
</Space>
);
},
},
];
return (
<Table
columns={columns}
dataSource={members}
loading={isLoading}
rowKey="id"
pagination={{ pageSize: 10 }}
/>
);
};
export default MemberList;
@@ -1,100 +0,0 @@
/**
* 权限矩阵展示组件 - V21 UI
*/
import React from 'react';
import { Card, Table } from 'antd';
import { CheckOutlined, CloseOutlined } from '@ant-design/icons';
import './business.css';
const PermissionMatrix: React.FC = () => {
const permissions = [
{ action: '查看工作空间', owner: true, admin: true, member: true, viewer: true },
{ action: '创建项目', owner: true, admin: true, member: true, viewer: false },
{ action: '编辑项目', owner: true, admin: true, member: true, viewer: false },
{ action: '删除项目', owner: true, admin: true, member: false, viewer: false },
{ action: '邀请成员', owner: true, admin: true, member: false, viewer: false },
{ action: '移除成员', owner: true, admin: true, member: false, viewer: false },
{ action: '修改成员角色', owner: true, admin: true, member: false, viewer: false },
{ action: '升级订阅', owner: true, admin: false, member: false, viewer: false },
{ action: '删除工作空间', owner: true, admin: false, member: false, viewer: false },
];
const columns = [
{
title: '操作',
dataIndex: 'action',
key: 'action',
fixed: 'left' as const,
width: 160,
render: (text: string) => <span style={{ fontWeight: 500 }}>{text}</span>,
},
{
title: '拥有者',
dataIndex: 'owner',
key: 'owner',
align: 'center' as const,
width: 100,
render: (value: boolean) =>
value ? (
<CheckOutlined style={{ color: '#16a34a', fontSize: '18px' }} />
) : (
<CloseOutlined style={{ color: '#dc2626', fontSize: '16px' }} />
),
},
{
title: '管理员',
dataIndex: 'admin',
key: 'admin',
align: 'center' as const,
width: 100,
render: (value: boolean) =>
value ? (
<CheckOutlined style={{ color: '#16a34a', fontSize: '18px' }} />
) : (
<CloseOutlined style={{ color: '#dc2626', fontSize: '16px' }} />
),
},
{
title: '成员',
dataIndex: 'member',
key: 'member',
align: 'center' as const,
width: 100,
render: (value: boolean) =>
value ? (
<CheckOutlined style={{ color: '#16a34a', fontSize: '18px' }} />
) : (
<CloseOutlined style={{ color: '#dc2626', fontSize: '16px' }} />
),
},
{
title: '访客',
dataIndex: 'viewer',
key: 'viewer',
align: 'center' as const,
width: 100,
render: (value: boolean) =>
value ? (
<CheckOutlined style={{ color: '#16a34a', fontSize: '18px' }} />
) : (
<CloseOutlined style={{ color: '#dc2626', fontSize: '16px' }} />
),
},
];
return (
<Card title="权限说明" className="xx-card">
<div className="xx-table-wrapper">
<Table
columns={columns}
dataSource={permissions}
pagination={false}
rowKey="action"
size="middle"
/>
</div>
</Card>
);
};
export default PermissionMatrix;
@@ -89,7 +89,6 @@
overflow: hidden;
}
<<<<<<< HEAD
/* 表格包装器 */
.xx-table-wrapper {
border-radius: 16px;
@@ -223,7 +222,6 @@
box-shadow: 0 8px 24px rgba(79,70,229,0.1);
}
<<<<<<< HEAD
/* ==================== 进度条 ==================== */
.xx-progress {
margin-top: 12px;
@@ -231,7 +229,6 @@
/* ==================== Ant Design 覆盖样式 ==================== */
/* Table overrides */
/* ==================== Ant Design 覆盖样式 ==================== */
.ant-table-wrapper .ant-table-thead > tr > th {
background: #f8fafc !important;
font-weight: 700 !important;
@@ -249,7 +246,6 @@
background: #fafbfc !important;
}
<<<<<<< HEAD
/* Card overrides */
.ant-card {
border-radius: 22px !important;
@@ -272,7 +268,6 @@
padding: 20px 24px !important;
}
<<<<<<< HEAD
/* Modal overrides */
.ant-modal-content {
border-radius: 22px !important;
@@ -298,7 +293,6 @@
padding: 16px 24px !important;
}
<<<<<<< HEAD
/* Button overrides */
.ant-btn-primary {
background: linear-gradient(135deg, #6366f1, #4f46e5) !important;
@@ -316,7 +310,6 @@
transform: translateY(-1px);
}
<<<<<<< HEAD
/* Tag overrides */
.ant-tag {
border-radius: 10px !important;
@@ -324,7 +317,6 @@
font-weight: 500 !important;
}
<<<<<<< HEAD
/* Select overrides */
.ant-select-selector {
border-radius: 14px !important;
@@ -340,7 +332,6 @@
box-shadow: 0 0 0 3px rgba(79,70,229,0.1) !important;
}
<<<<<<< HEAD
/* Input overrides */
.ant-input {
border-radius: 14px !important;
@@ -357,7 +348,6 @@
box-shadow: 0 0 0 3px rgba(79,70,229,0.1) !important;
}
<<<<<<< HEAD
/* Progress overrides */
.ant-progress-inner {
background: #f1f5f9 !important;
-107
View File
@@ -1,107 +0,0 @@
/**
* 侧边栏导航
*/
import React from 'react';
import { Layout, Menu } from 'antd';
import {
HomeOutlined,
AppstoreOutlined,
TeamOutlined,
CrownOutlined,
UserOutlined,
DashboardOutlined,
} from '@ant-design/icons';
import { useNavigate, useLocation } from 'react-router-dom';
import type { MenuProps } from 'antd';
const { Sider } = Layout;
interface SidebarProps {
collapsed: boolean;
}
const Sidebar: React.FC<SidebarProps> = ({ collapsed }) => {
const navigate = useNavigate();
const location = useLocation();
const menuItems: MenuProps['items'] = [
{
key: '/',
icon: <HomeOutlined />,
label: '首页',
onClick: () => navigate('/'),
},
{
key: '/projects',
icon: <AppstoreOutlined />,
label: '项目',
disabled: true,
},
{
key: '/members',
icon: <TeamOutlined />,
label: '成员管理',
disabled: true,
},
{
key: '/subscription',
icon: <CrownOutlined />,
label: '订阅管理',
onClick: () => navigate('/subscription'),
},
{
key: '/admin',
icon: <DashboardOutlined />,
label: 'Admin(暂未开放)',
disabled: true,
},
{
key: '/profile',
icon: <UserOutlined />,
label: '个人中心',
onClick: () => navigate('/profile'),
},
];
// 根据当前路径选中菜单项
const selectedKey = '/' + location.pathname.split('/')[1];
return (
<Sider
collapsible
collapsed={collapsed}
trigger={null}
width={200}
style={{
overflow: 'auto',
height: '100vh',
position: 'sticky',
left: 0,
top: 0,
bottom: 0,
}}
>
<div
style={{
height: 64,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: '#fff',
fontSize: '18px',
fontWeight: 'bold',
}}
>
{collapsed ? '🦐' : '小虾 SaaS'}
</div>
<Menu
theme="dark"
mode="inline"
selectedKeys={[selectedKey]}
items={menuItems}
/>
</Sider>
);
};
export default Sidebar;
-32
View File
@@ -1,32 +0,0 @@
/**
* UI 状态管理
* 管理全局 UI 状态(侧边栏、加载状态等)
*/
import { create } from 'zustand';
interface UIState {
sidebarCollapsed: boolean;
loading: boolean;
// Actions
toggleSidebar: () => void;
setSidebarCollapsed: (collapsed: boolean) => void;
setLoading: (loading: boolean) => void;
}
export const useUIStore = create<UIState>((set) => ({
sidebarCollapsed: false,
loading: false,
toggleSidebar: () => {
set((state) => ({ sidebarCollapsed: !state.sidebarCollapsed }));
},
setSidebarCollapsed: (collapsed) => {
set({ sidebarCollapsed: collapsed });
},
setLoading: (loading) => {
set({ loading });
},
}));