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
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:
@@ -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;
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
@@ -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 });
|
||||
},
|
||||
}));
|
||||
Reference in New Issue
Block a user