From 0689bd956f15edefe992e1180b922e4c4903d464 Mon Sep 17 00:00:00 2001 From: CI Bot Date: Sat, 27 Jun 2026 07:34:29 +0800 Subject: [PATCH] refactor(web): remove workspace-related frontend code after backend removal --- apps/web/src/App.tsx | 5 +- apps/web/src/api/assets.ts | 12 - apps/web/src/api/editPlans.ts | 7 +- apps/web/src/api/generation.ts | 3 - apps/web/src/api/projectTitles.ts | 3 +- apps/web/src/api/projects.ts | 6 +- apps/web/src/api/subscription.ts | 17 +- apps/web/src/api/tasks.ts | 1 - apps/web/src/api/workspace.ts | 132 ----------- .../components/business/InviteMemberModal.tsx | 83 ------- .../src/components/business/MemberList.tsx | 135 ----------- apps/web/src/components/layout/Header.tsx | 7 +- apps/web/src/components/layout/Sidebar.tsx | 6 +- apps/web/src/hooks/useAuth.ts | 2 +- apps/web/src/hooks/useWorkspace.ts | 66 ------ apps/web/src/pages/admin/AdminComingSoon.tsx | 3 +- apps/web/src/pages/admin/LogViewer.tsx | 5 +- apps/web/src/pages/admin/SystemMonitor.tsx | 2 +- apps/web/src/pages/auth/Login.tsx | 2 +- .../subscription/UpgradeSubscription.tsx | 9 +- .../src/pages/workspace/WorkspaceDetail.tsx | 211 ------------------ .../web/src/pages/workspace/WorkspaceList.css | 202 ----------------- .../web/src/pages/workspace/WorkspaceList.tsx | 125 ----------- apps/web/src/router/index.tsx | 12 +- apps/web/src/store/workspaceStore.ts | 43 ---- .../test/components/WorkspaceList.test.tsx | 64 ------ 26 files changed, 29 insertions(+), 1134 deletions(-) delete mode 100644 apps/web/src/api/workspace.ts delete mode 100644 apps/web/src/components/business/InviteMemberModal.tsx delete mode 100644 apps/web/src/components/business/MemberList.tsx delete mode 100644 apps/web/src/hooks/useWorkspace.ts delete mode 100644 apps/web/src/pages/workspace/WorkspaceDetail.tsx delete mode 100644 apps/web/src/pages/workspace/WorkspaceList.css delete mode 100644 apps/web/src/pages/workspace/WorkspaceList.tsx delete mode 100644 apps/web/src/store/workspaceStore.ts delete mode 100644 apps/web/src/test/components/WorkspaceList.test.tsx diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index 8338d6664..31e644b8e 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -22,10 +22,7 @@ function App() {

快速链接

- - 📁 - 工作空间 - + Projects (Coming Soon) 📋 我的项目 diff --git a/apps/web/src/api/assets.ts b/apps/web/src/api/assets.ts index c278caf79..3fef14bf4 100644 --- a/apps/web/src/api/assets.ts +++ b/apps/web/src/api/assets.ts @@ -5,7 +5,6 @@ import apiClient from './client'; export interface AssetItem { id: string; - workspace_id: string; project_id: string; library_id: string; name: string; @@ -20,7 +19,6 @@ export interface AssetItem { export interface AssetLibraryItem { id: string; - workspace_id: string; project_id: string; name: string; kind: 'video' | 'voice' | 'image'; @@ -30,7 +28,6 @@ export interface AssetLibraryItem { export interface IngestJob { id: string; - workspace_id: string; project_id: string; library_id: string; storage_key: string; @@ -41,7 +38,6 @@ export interface IngestJob { export interface ClassificationJob { id: string; - workspace_id: string; project_id: string; asset_id: string; status: 'pending' | 'processing' | 'completed' | 'failed'; @@ -51,7 +47,6 @@ export interface ClassificationJob { } export interface ProjectAssetDiagnosis { - workspace_id: string; project_id: string; readiness_score: number; readiness_label: string; @@ -82,7 +77,6 @@ export const getAssetLibraries = async (projectId: string): Promise => { const prepared = await prepareDirectUpload({ - workspace_id: data.workspace_id, project_id: data.project_id, library_id: data.library_id, filename: data.file.name, @@ -175,7 +165,6 @@ export const uploadAssetDirect = async (data: { } return completeDirectUpload({ - workspace_id: data.workspace_id, project_id: data.project_id, library_id: data.library_id, storage_key: prepared.storage_key, @@ -188,7 +177,6 @@ export const getIngestJob = async (jobId: string): Promise => { }; export const submitClassificationJob = async (data: { - workspace_id: string; project_id: string; asset_id: string; }): Promise => { diff --git a/apps/web/src/api/editPlans.ts b/apps/web/src/api/editPlans.ts index 98dd71cf6..c1ee65dda 100644 --- a/apps/web/src/api/editPlans.ts +++ b/apps/web/src/api/editPlans.ts @@ -7,7 +7,6 @@ export type EditingMode = "one-take" | "pip" | "voiceover" | "voice_pip"; export interface EditTemplateItem { id: string; - workspace_id: string; project_id: string; name: string; description: string; @@ -30,7 +29,6 @@ export interface EditPlanClipItem { export interface EditPlanItem { id: string; - workspace_id: string; project_id: string; template_id: string; asset_library_id: string; @@ -43,13 +41,12 @@ export interface EditPlanItem { updated_at?: string; } -export const getEditTemplates = async (projectId: string, workspaceId: string): Promise => { - const response = await apiClient.get(`/projects/${projectId}/edit-plans/templates`, { params: { workspace_id: workspaceId } }); +export const getEditTemplates = async (projectId: string): Promise => { + const response = await apiClient.get(`/projects/${projectId}/edit-plans/templates`, ); return response.data; }; export const createEditPlan = async ({ projectId, data }: { projectId: string; data: { - workspace_id: string; asset_library_id: string; template_id?: string; title_id?: string; diff --git a/apps/web/src/api/generation.ts b/apps/web/src/api/generation.ts index 83e4f6f9c..536166dda 100644 --- a/apps/web/src/api/generation.ts +++ b/apps/web/src/api/generation.ts @@ -6,7 +6,6 @@ import type { EditingMode } from "./editPlans"; export interface GenerationTaskItem { id: string; - workspace_id: string; project_id: string; asset_library_id: string; strategy_id?: string | null; @@ -23,7 +22,6 @@ export interface GenerationTaskItem { export interface GeneratedVideoItem { id: string; - workspace_id: string; project_id: string; generation_task_id: string; name: string; @@ -41,7 +39,6 @@ export interface GeneratedVideoItem { } export const createGenerationTask = async (data: { - workspace_id: string; project_id: string; asset_library_id: string; strategy_id?: string; diff --git a/apps/web/src/api/projectTitles.ts b/apps/web/src/api/projectTitles.ts index c5c04921a..753605a63 100644 --- a/apps/web/src/api/projectTitles.ts +++ b/apps/web/src/api/projectTitles.ts @@ -2,7 +2,6 @@ import apiClient from './client'; export interface ProjectTitleItem { id: string; - workspace_id: string; project_id: string; text: string; category: 'default' | 'marketing' | 'tutorial' | 'story' | 'promo'; @@ -18,7 +17,7 @@ export const getProjectTitles = async (projectId: string, activeOnly = false): P return response.data.items; }; -export const createProjectTitle = async (projectId: string, data: { workspace_id: string; text: string; category?: ProjectTitleItem['category']; favorite?: boolean }): Promise => { +export const createProjectTitle = async (projectId: string, data: { text: string; category?: ProjectTitleItem['category']; favorite?: boolean }): Promise => { const response = await apiClient.post(`/projects/${projectId}/titles`, data); return response.data; }; diff --git a/apps/web/src/api/projects.ts b/apps/web/src/api/projects.ts index 4c9707fa5..ea1195de2 100644 --- a/apps/web/src/api/projects.ts +++ b/apps/web/src/api/projects.ts @@ -2,13 +2,11 @@ import apiClient from './client'; export interface ProjectItem { id: string; - workspace_id: string; name: string; description: string; } export interface CreateProjectRequest { - workspace_id: string; name: string; description?: string; } @@ -18,8 +16,8 @@ export const getProject = async (projectId: string): Promise => { return response.data; }; -export const getProjects = async (workspaceId: string): Promise => { - const response = await apiClient.get('/projects', { params: { workspace_id: workspaceId } }); +export const getProjects = async (): Promise => { + const response = await apiClient.get('/projects'); return response.data.items; }; diff --git a/apps/web/src/api/subscription.ts b/apps/web/src/api/subscription.ts index 7e68799c3..8bab2c8ca 100644 --- a/apps/web/src/api/subscription.ts +++ b/apps/web/src/api/subscription.ts @@ -35,18 +35,16 @@ export const getPlans = async (): Promise => { // 获取当前订阅 export const getCurrentSubscription = async ( - workspaceId: string -): Promise => { - const response = await apiClient.get(`/workspaces/${workspaceId}/subscription`); + ): Promise => { + const response = await apiClient.get('/subscription'); return response.data; }; // 升级订阅 export const upgradeSubscription = async ( - workspaceId: string, plan: 'pro' | 'enterprise' ): Promise<{ message: string }> => { - const response = await apiClient.post(`/workspaces/${workspaceId}/subscription/upgrade`, { + const response = await apiClient.post('/subscription/upgrade', { plan, }); return response.data; @@ -54,14 +52,13 @@ export const upgradeSubscription = async ( // 取消订阅 export const cancelSubscription = async ( - workspaceId: string -): Promise<{ message: string }> => { - const response = await apiClient.post(`/workspaces/${workspaceId}/subscription/cancel`); + ): Promise<{ message: string }> => { + const response = await apiClient.post('/subscription/cancel'); return response.data; }; // 获取配额状态 -export const getQuotaStatus = async (workspaceId: string): Promise => { - const response = await apiClient.get(`/workspaces/${workspaceId}/quota`); +export const getQuotaStatus = async (): Promise => { + const response = await apiClient.get('/quota'); return response.data; }; diff --git a/apps/web/src/api/tasks.ts b/apps/web/src/api/tasks.ts index 22a67b9d2..6a4a19ec6 100644 --- a/apps/web/src/api/tasks.ts +++ b/apps/web/src/api/tasks.ts @@ -3,7 +3,6 @@ import apiClient from './client'; export interface ProjectTaskItem { id: string; task_type: 'ingest' | 'generation' | string; - workspace_id: string; project_id: string; status: string; progress: number; diff --git a/apps/web/src/api/workspace.ts b/apps/web/src/api/workspace.ts deleted file mode 100644 index a2a306fce..000000000 --- a/apps/web/src/api/workspace.ts +++ /dev/null @@ -1,132 +0,0 @@ -/** - * 工作空间相关 API - */ -import apiClient from './client'; - -export interface Workspace { - id: string; - workspace_id: string; - name: string; - owner_user_id?: string; - subscription_plan: 'free' | 'pro' | 'enterprise'; - subscription_status: 'active' | 'inactive' | 'expired'; - created_at?: string; - max_projects?: number; - max_storage_gb?: number; - used_storage_gb?: number; - member_count?: number; - user_role?: string; -} - -export interface CreateWorkspaceRequest { - name: string; - subscription_plan?: 'free' | 'pro' | 'enterprise'; -} - -export interface WorkspaceMember { - id: string; - member_id: string; - user_id: string; - email: string; - username: string; - display_name?: string; - role: 'owner' | 'admin' | 'member' | 'viewer'; - joined_at: string; -} - -export interface InviteMemberRequest { - email: string; - role: 'admin' | 'member' | 'viewer'; -} - -export interface InviteMemberResponse { - invitation_id: string; - invitee_email: string; - role: 'admin' | 'member' | 'viewer'; - expires_at: string; -} - -type WorkspaceApiResponse = Omit & { - id?: string; - workspace_id?: string; -}; - -type WorkspaceMemberApiResponse = Omit & { - id?: string; - member_id?: string; -}; - -const normalizeWorkspace = (workspace: WorkspaceApiResponse): Workspace => { - const workspaceId = workspace.id || workspace.workspace_id || ''; - return { - ...workspace, - id: workspaceId, - workspace_id: workspaceId, - subscription_status: workspace.subscription_status || 'active', - }; -}; - -const normalizeMember = (member: WorkspaceMemberApiResponse): WorkspaceMember => { - const memberId = member.member_id || member.id || ''; - return { - ...member, - id: member.user_id || memberId, - member_id: memberId, - }; -}; - -export const getWorkspaces = async (): Promise => { - const response = await apiClient.get('/workspaces'); - const payload = response.data; - const workspaces = Array.isArray(payload) ? payload : payload.workspaces || []; - return workspaces.map(normalizeWorkspace); -}; - -export const getWorkspace = async (id: string): Promise => { - const response = await apiClient.get(`/workspaces/${id}`); - return normalizeWorkspace(response.data); -}; - -export const createWorkspace = async ( - data: CreateWorkspaceRequest -): Promise => { - const response = await apiClient.post('/workspaces', data); - return normalizeWorkspace(response.data); -}; - -export const getMembers = async (workspaceId: string): Promise => { - const response = await apiClient.get(`/workspaces/${workspaceId}/members`); - const payload = response.data; - const members = Array.isArray(payload) ? payload : payload.members || []; - return members.map(normalizeMember); -}; - -export const inviteMember = async ( - workspaceId: string, - data: InviteMemberRequest -): Promise => { - const response = await apiClient.post(`/workspaces/${workspaceId}/members/invite`, data); - return response.data; -}; - -export const removeMember = async ( - workspaceId: string, - memberId: string -): Promise => { - await apiClient.delete(`/workspaces/${workspaceId}/members/${memberId}`); -}; - -export const updateMemberRole = async ( - workspaceId: string, - memberId: string, - role: 'admin' | 'member' | 'viewer' -): Promise<{ user_id: string; old_role: string; new_role: string }> => { - const response = await apiClient.patch(`/workspaces/${workspaceId}/members/${memberId}/role`, { - role, - }); - return response.data; -}; - -export const leaveWorkspace = async (_workspaceId: string): Promise<{ message: string }> => { - throw new Error('主动退出工作空间暂未开放'); -}; diff --git a/apps/web/src/components/business/InviteMemberModal.tsx b/apps/web/src/components/business/InviteMemberModal.tsx deleted file mode 100644 index 4c276413e..000000000 --- a/apps/web/src/components/business/InviteMemberModal.tsx +++ /dev/null @@ -1,83 +0,0 @@ -/** - * 邀请成员弹窗 - V21 UI - */ -import React from 'react'; -import { Modal, Form, Input, Select, message } from 'antd'; -import { useInviteMember } from '@/hooks/useWorkspace'; -import './business.css'; - -interface InviteMemberModalProps { - workspaceId: string; - open: boolean; - onClose: () => void; -} - -const InviteMemberModal: React.FC = ({ - 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 ( - -
- - - - - - - 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', - width: 100, - render: (_: any, record: any) => { - if (record.role === 'owner') { - return -; - } - return ( - - removeMutation.mutate(record.id)} - okText="确定" - cancelText="取消" - > - - - - ); - }, - }, - ]; - - return ( -
- - - ); -}; - -export default MemberList; diff --git a/apps/web/src/components/layout/Header.tsx b/apps/web/src/components/layout/Header.tsx index 3e54aac11..5e238b356 100644 --- a/apps/web/src/components/layout/Header.tsx +++ b/apps/web/src/components/layout/Header.tsx @@ -30,7 +30,7 @@ const Header: React.FC = () => { : []; return [ - { key: 'home', label: '首页', path: '/workspaces' }, + { key: 'home', label: '首页', path: '/' }, { key: 'subscription', label: '订阅', path: '/subscription' }, ...projectNav, ]; @@ -61,15 +61,14 @@ const Header: React.FC = () => { return (
-
diff --git a/apps/web/src/pages/workspace/WorkspaceDetail.tsx b/apps/web/src/pages/workspace/WorkspaceDetail.tsx deleted file mode 100644 index 0d95e4cc6..000000000 --- a/apps/web/src/pages/workspace/WorkspaceDetail.tsx +++ /dev/null @@ -1,211 +0,0 @@ -/** - * 工作空间详情页面 - V21 UI - */ -import React, { useState } from 'react'; -import { useParams, useNavigate } from 'react-router-dom'; -import { Tabs, Button, Descriptions, Card, List, Modal, Form, Input, message, Alert } from 'antd'; -import { PlusOutlined, TeamOutlined } from '@ant-design/icons'; -import { useWorkspace } from '@/hooks/useWorkspace'; -import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; -import { createProject, getProjects } from '@/api/projects'; -import MemberList from '@/components/business/MemberList'; -import InviteMemberModal from '@/components/business/InviteMemberModal'; -import PermissionMatrix from '@/components/business/PermissionMatrix'; -import '@/components/business/business.css'; - -const WorkspaceDetail: React.FC = () => { - const { id } = useParams<{ id: string }>(); - const navigate = useNavigate(); - const { data: workspace, isLoading } = useWorkspace(id!); - const queryClient = useQueryClient(); - const [inviteModalOpen, setInviteModalOpen] = useState(false); - const [createProjectOpen, setCreateProjectOpen] = useState(false); - const [projectForm] = Form.useForm<{ name: string; description?: string }>(); - - const projectsQuery = useQuery({ - queryKey: ['projects', id], - queryFn: () => getProjects(id!), - enabled: !!id, - }); - - const createProjectMutation = useMutation({ - mutationFn: createProject, - onSuccess: (project) => { - message.success('项目创建成功'); - setCreateProjectOpen(false); - projectForm.resetFields(); - queryClient.invalidateQueries({ queryKey: ['projects', id] }); - navigate(`/projects/${project.id}/assets`, { state: { workspaceId: id } }); - }, - onError: (error: any) => { - message.error(error.response?.data?.detail || '项目创建失败,请稍后重试'); - }, - }); - - const handleCreateProject = async () => { - const values = await projectForm.validateFields(); - createProjectMutation.mutate({ - workspace_id: id!, - name: values.name, - description: values.description || '', - }); - }; - - if (isLoading) return
加载中...
; - if (!workspace) return
工作空间不存在
; - - const items = [ - { - key: 'overview', - label: '概览', - children: ( -
- setCreateProjectOpen(true)}> - 创建项目 - - } - > - - {workspace.name} - {workspace.subscription_plan} - {workspace.subscription_status} - - {workspace.created_at - ? new Date(workspace.created_at).toLocaleDateString() - : '暂未返回'} - - - - - - {projectsQuery.isError && ( - - )} - ( - navigate(`/projects/${project.id}/assets`, { state: { workspaceId: id } })}> - 素材管理 - , - , - , - , - , - ]} - > - - - )} - /> - - - -
- ), - }, - { - key: 'members', - label: ( - <> - 成员管理 - - ), - children: ( -
-
-
-

工作空间成员

-

管理有权访问此工作空间的成员

-
- -
-
- -
-
- -
-
- ), - }, - { - key: 'settings', - label: '设置', - children:

工作空间设置(待开发)

, - }, - ]; - - return ( -
-
-
-

{workspace.name}

-

工作空间详情与成员管理

-
-
- - setCreateProjectOpen(false)} - className="xx-modal" - > - - - - - - - - - - setInviteModalOpen(false)} - /> -
- ); -}; - -export default WorkspaceDetail; -export const Component = WorkspaceDetail; diff --git a/apps/web/src/pages/workspace/WorkspaceList.css b/apps/web/src/pages/workspace/WorkspaceList.css deleted file mode 100644 index 92599a760..000000000 --- a/apps/web/src/pages/workspace/WorkspaceList.css +++ /dev/null @@ -1,202 +0,0 @@ -/* V21 工作空间列表页面 */ -.xx-workspace-list { - max-width: 1200px; - margin: 0 auto; -} - -/* Hero 区域 - V21 设计 */ -.xx-hero { - text-align: center; - padding: 60px 24px 80px; - position: relative; -} - -.xx-hero::before { - content: ''; - position: absolute; - top: 0; - left: 50%; - transform: translateX(-50%); - width: 300px; - height: 300px; - background: radial-gradient(circle, rgba(79,70,229,0.15), transparent 70%); - border-radius: 50%; - filter: blur(40px); -} - -.xx-hero h1 { - font-size: 42px; - font-weight: 900; - color: var(--slate, #0f172a); - margin: 0 0 16px; - letter-spacing: -0.02em; - position: relative; -} - -.xx-hero p { - font-size: 18px; - color: var(--muted, #64748b); - margin: 0 0 32px; - max-width: 560px; - margin-left: auto; - margin-right: auto; - line-height: 1.6; -} - -/* 功能卡片网格 - V21 */ -.xx-features-grid { - display: grid; - grid-template-columns: repeat(4, 1fr); - gap: 20px; - margin-bottom: 60px; -} - -@media (max-width: 1200px) { - .xx-features-grid { - grid-template-columns: repeat(2, 1fr); - } -} - -@media (max-width: 768px) { - .xx-features-grid { - grid-template-columns: 1fr; - } -} - -.xx-feature-card { - background: rgba(255,255,255,0.94); - border: 1px solid rgba(226,232,240,0.95); - border-radius: 24px; - padding: 28px; - text-align: center; - transition: all 0.25s; - cursor: pointer; -} - -.xx-feature-card:hover { - transform: translateY(-4px); - box-shadow: 0 26px 64px rgba(15,23,42,0.14); - border-color: var(--indigo, #4f46e5); -} - -.xx-feature-icon { - width: 56px; - height: 56px; - margin: 0 auto 16px; - background: linear-gradient(135deg, #6366f1, #4f46e5); - border-radius: 16px; - display: flex; - align-items: center; - justify-content: center; - font-size: 24px; -} - -.xx-feature-card h3 { - font-size: 16px; - font-weight: 850; - color: var(--slate, #0f172a); - margin: 0 0 8px; -} - -.xx-feature-card p { - font-size: 13px; - color: var(--muted, #64748b); - margin: 0; -} - -/* 页面头部 */ -.xx-page-head { - display: flex; - justify-content: space-between; - align-items: flex-start; - gap: 18px; - margin-bottom: 22px; -} - -.xx-page-head h2 { - font-size: 28px; - margin: 0 0 6px; - color: var(--slate, #0f172a); -} - -.xx-page-head p { - margin: 0; - color: var(--muted, #64748b); - font-size: 14px; -} - -.xx-grid4 { - display: grid; - grid-template-columns: repeat(4, 1fr); - gap: 16px; -} - -@media (max-width: 1200px) { - .xx-grid4 { - grid-template-columns: repeat(3, 1fr); - } -} - -@media (max-width: 768px) { - .xx-grid4 { - grid-template-columns: repeat(2, 1fr); - } -} - -@media (max-width: 480px) { - .xx-grid4 { - grid-template-columns: 1fr; - } -} - -.xx-workspace-card { - border: 1px solid var(--line, #e2e8f0); - background: rgba(255, 255, 255, 0.94); - border-radius: 24px; - padding: 18px; - cursor: pointer; - transition: all 0.25s; -} - -.xx-workspace-card:hover { - border-color: var(--indigo, #4f46e5); - box-shadow: 0 26px 64px rgba(15,23,42,0.14); - transform: translateY(-2px); -} - -.xx-workspace-name { - font-size: 16px; - font-weight: 850; - color: var(--slate, #0f172a); - margin-bottom: 12px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.xx-workspace-plan { - display: inline-block; - font-size: 12px; - font-weight: 850; - padding: 4px 10px; - border-radius: 10px; - background: var(--indigo-soft, #eef2ff); - color: var(--indigo-dark, #4338ca); - margin-bottom: 12px; -} - -.xx-workspace-date { - font-size: 12px; - color: #94a3b8; -} - -.xx-empty-state { - text-align: center; - padding: 60px 24px; - color: var(--muted, #64748b); -} - -.xx-empty-state p { - font-size: 16px; - margin: 12px 0; -} diff --git a/apps/web/src/pages/workspace/WorkspaceList.tsx b/apps/web/src/pages/workspace/WorkspaceList.tsx deleted file mode 100644 index 717b4fb25..000000000 --- a/apps/web/src/pages/workspace/WorkspaceList.tsx +++ /dev/null @@ -1,125 +0,0 @@ -/** - * 工作空间列表页面 - V21 UI - */ -import React from 'react'; -import { Button, Modal, Form, Input, message } from 'antd'; -import { useNavigate } from 'react-router-dom'; -import { useCreateWorkspace, useWorkspaces } from '@/hooks/useWorkspace'; -import './WorkspaceList.css'; - -const WorkspaceList: React.FC = () => { - const navigate = useNavigate(); - const { data: workspaces, isLoading } = useWorkspaces(); - const createWorkspaceMutation = useCreateWorkspace(); - const [form] = Form.useForm<{ name: string }>(); - const [createOpen, setCreateOpen] = React.useState(false); - - const openCreateModal = () => setCreateOpen(true); - - const handleCreateWorkspace = async () => { - const values = await form.validateFields(); - try { - const workspace = await createWorkspaceMutation.mutateAsync({ name: values.name }); - message.success('创建工作空间成功'); - setCreateOpen(false); - form.resetFields(); - navigate(`/workspaces/${workspace.id}`); - } catch (createError: any) { - message.error(createError.response?.data?.detail || '创建失败'); - } - }; - - // 功能卡片数据 - const features = [ - { id: 'assets', icon: '🎬', title: '素材管理', desc: '上传管理视频素材' }, - { id: 'titles', icon: '✍️', title: '标题库', desc: '创作吸引眼球的标题' }, - { id: 'voices', icon: '🎙️', title: '配音库', desc: '选择优质配音音色' }, - { id: 'generation', icon: '✨', title: '剪辑计划', desc: 'AI智能生成成片' }, - ]; - - return ( -
- {/* Hero 区域 - V21 设计 */} -
-

小虾剪辑 SaaS

-

专业的AI视频剪辑平台,一站式管理素材、标题、配音,轻松创作优质内容

- -
- - {/* 功能卡片 - V21 设计 */} -
- {features.map((feature) => ( -
-
{feature.icon}
-

{feature.title}

-

{feature.desc}

-
- ))} -
- - {/* 工作空间列表 */} -
-
-

我的工作空间

-

选择或创建一个工作空间开始创作

-
-
- -
- {workspaces?.map((workspace) => ( -
navigate(`/workspaces/${workspace.id}`)} - > -
{workspace.name}
-
- {workspace.subscription_plan === 'enterprise' ? '企业版' : workspace.subscription_plan === 'pro' ? 'Pro' : 'Free'} -
-
- {workspace.created_at ? new Date(workspace.created_at).toLocaleDateString() : ''} -
-
- ))} -
- - {!isLoading && workspaces?.length === 0 && ( -
-
🚀
-

还没有工作空间

- -
- )} - - setCreateOpen(false)} - > -
- - - - -
-
- ); -}; - -export default WorkspaceList; -export const Component = WorkspaceList; diff --git a/apps/web/src/router/index.tsx b/apps/web/src/router/index.tsx index 5f9727e2a..ae63b22dc 100644 --- a/apps/web/src/router/index.tsx +++ b/apps/web/src/router/index.tsx @@ -50,15 +50,7 @@ export const router = createBrowserRouter([ children: [ { index: true, - element: , - }, - { - path: 'workspaces', - lazy: () => import('@/pages/workspace/WorkspaceList'), - }, - { - path: 'workspaces/:id', - lazy: () => import('@/pages/workspace/WorkspaceDetail'), + element: , }, { path: 'projects/:id/assets', @@ -89,7 +81,7 @@ export const router = createBrowserRouter([ lazy: () => import('@/pages/subscription/Plans'), }, { - path: 'subscription/upgrade/:workspaceId', + path: 'subscription/upgrade', lazy: () => import('@/pages/subscription/UpgradeSubscription'), }, { diff --git a/apps/web/src/store/workspaceStore.ts b/apps/web/src/store/workspaceStore.ts deleted file mode 100644 index eab0d2188..000000000 --- a/apps/web/src/store/workspaceStore.ts +++ /dev/null @@ -1,43 +0,0 @@ -/** - * 工作空间状态管理 - */ -import { create } from 'zustand'; -import { Workspace } from '@/api/workspace'; - -interface WorkspaceState { - currentWorkspace: Workspace | null; - workspaces: Workspace[]; - - // Actions - setCurrentWorkspace: (workspace: Workspace | null) => void; - setWorkspaces: (workspaces: Workspace[]) => void; - addWorkspace: (workspace: Workspace) => void; - removeWorkspace: (workspaceId: string) => void; -} - -export const useWorkspaceStore = create((set) => ({ - currentWorkspace: null, - workspaces: [], - - setCurrentWorkspace: (workspace) => { - set({ currentWorkspace: workspace }); - }, - - setWorkspaces: (workspaces) => { - set({ workspaces }); - }, - - addWorkspace: (workspace) => { - set((state) => ({ - workspaces: [...state.workspaces, workspace], - })); - }, - - removeWorkspace: (workspaceId) => { - set((state) => ({ - workspaces: state.workspaces.filter((w) => w.id !== workspaceId), - currentWorkspace: - state.currentWorkspace?.id === workspaceId ? null : state.currentWorkspace, - })); - }, -})); diff --git a/apps/web/src/test/components/WorkspaceList.test.tsx b/apps/web/src/test/components/WorkspaceList.test.tsx deleted file mode 100644 index 466bad0b5..000000000 --- a/apps/web/src/test/components/WorkspaceList.test.tsx +++ /dev/null @@ -1,64 +0,0 @@ -/** - * WorkspaceList 组件单元测试 - */ -import { render, screen, waitFor } from '@testing-library/react'; -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { BrowserRouter } from 'react-router-dom'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import WorkspaceList from '@/pages/workspace/WorkspaceList'; -import * as workspaceApi from '@/api/workspace'; - -vi.mock('@/api/workspace'); - -const renderWorkspaceList = () => { - const queryClient = new QueryClient({ - defaultOptions: { - queries: { retry: false }, - }, - }); - - return render( - - - - - - ); -}; - -describe('WorkspaceList', () => { - beforeEach(() => { - vi.clearAllMocks(); - }); - - it('should render workspace list', async () => { - const mockWorkspaces: workspaceApi.Workspace[] = [ - { - id: '1', - name: 'Test Workspace', - owner_user_id: 'user-1', - subscription_plan: 'free', - subscription_status: 'active', - created_at: '2024-01-01', - }, - ]; - - vi.mocked(workspaceApi.getWorkspaces).mockResolvedValue(mockWorkspaces); - - renderWorkspaceList(); - - await waitFor(() => { - expect(screen.getByText('Test Workspace')).toBeInTheDocument(); - }); - }); - - it('should show create workspace button', async () => { - vi.mocked(workspaceApi.getWorkspaces).mockResolvedValue([]); - - renderWorkspaceList(); - - await waitFor(() => { - expect(screen.getByText('创建工作空间')).toBeInTheDocument(); - }); - }); -}); -- 2.54.0