+
-
- 存储空间
+
+ 存储空间
{getStatusTag(quota.storage.status)}
-
+
{quota.storage.used_gb.toFixed(2)} / {quota.storage.limit_gb} GB
diff --git a/apps/web/src/components/business/business.css b/apps/web/src/components/business/business.css
new file mode 100644
index 000000000..4bd15b81f
--- /dev/null
+++ b/apps/web/src/components/business/business.css
@@ -0,0 +1,345 @@
+/* V21 业务组件统一样式 */
+
+/* ==================== 按钮 ==================== */
+.xx-primary-btn {
+ background: linear-gradient(135deg, #6366f1, #4f46e5) !important;
+ color: white !important;
+ border: none !important;
+ border-radius: 14px !important;
+ padding: 10px 20px !important;
+ font-weight: 600 !important;
+ box-shadow: 0 14px 26px rgba(79,70,229,0.22) !important;
+ transition: all 0.2s !important;
+ cursor: pointer;
+ height: auto !important;
+}
+
+.xx-primary-btn:hover {
+ box-shadow: 0 18px 34px rgba(79,70,229,0.28) !important;
+ transform: translateY(-1px);
+}
+
+.xx-ghost-btn {
+ background: transparent !important;
+ color: #4f46e5 !important;
+ border: 2px solid #4f46e5 !important;
+ border-radius: 14px !important;
+ padding: 8px 18px !important;
+ font-weight: 600 !important;
+ transition: all 0.2s !important;
+ cursor: pointer;
+ height: auto !important;
+}
+
+.xx-ghost-btn:hover {
+ background: #eef2ff !important;
+}
+
+/* ==================== 卡片 ==================== */
+.xx-card {
+ background: rgba(255,255,255,0.94);
+ border: 1px solid rgba(226,232,240,0.95);
+ border-radius: 22px;
+ box-shadow: 0 24px 70px rgba(15,23,42,0.09);
+ padding: 24px;
+ margin-bottom: 20px;
+ transition: all 0.25s;
+}
+
+.xx-card:hover {
+ box-shadow: 0 26px 64px rgba(15,23,42,0.14);
+ transform: translateY(-2px);
+}
+
+/* ==================== 页面结构 ==================== */
+.xx-page {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 24px;
+}
+
+.xx-page-head {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ gap: 18px;
+ margin-bottom: 28px;
+}
+
+.xx-page-head h2 {
+ font-size: 26px;
+ font-weight: 850;
+ color: #0f172a;
+ margin: 0 0 8px;
+}
+
+.xx-page-head p {
+ font-size: 14px;
+ color: #64748b;
+ margin: 0;
+}
+
+/* ==================== 表格样式 ==================== */
+.xx-table-card {
+ background: rgba(255,255,255,0.94);
+ border: 1px solid rgba(226,232,240,0.95);
+ border-radius: 22px;
+ box-shadow: 0 24px 70px rgba(15,23,42,0.09);
+ padding: 20px;
+ overflow: hidden;
+}
+
+.xx-table-wrapper {
+ border-radius: 16px;
+ overflow: hidden;
+}
+
+/* ==================== 标签/Tag ==================== */
+.xx-tag {
+ padding: 4px 12px;
+ border-radius: 10px;
+ font-size: 13px;
+ font-weight: 500;
+}
+
+.xx-tag-indigo {
+ background: #eef2ff;
+ color: #4f46e5;
+ border: 1px solid #c7d2fe;
+}
+
+.xx-tag-success {
+ background: #f0fdf4;
+ color: #16a34a;
+ border: 1px solid #bbf7d0;
+}
+
+.xx-tag-warning {
+ background: #fffbeb;
+ color: #d97706;
+ border: 1px solid #fde68a;
+}
+
+.xx-tag-error {
+ background: #fef2f2;
+ color: #dc2626;
+ border: 1px solid #fecaca;
+}
+
+/* ==================== 搜索栏 ==================== */
+.xx-search-bar {
+ margin-bottom: 20px;
+}
+
+.xx-search-input {
+ width: 100%;
+ padding: 12px 18px;
+ border: 2px solid #e2e8f0;
+ border-radius: 14px;
+ font-size: 14px;
+ background: white;
+ transition: all 0.2s;
+ outline: none;
+}
+
+.xx-search-input:focus {
+ border-color: #4f46e5;
+ box-shadow: 0 0 0 4px rgba(79,70,229,0.1);
+}
+
+/* ==================== Modal ==================== */
+.xx-modal .ant-modal-content {
+ border-radius: 22px;
+ padding: 24px;
+}
+
+.xx-modal .ant-modal-header {
+ border-radius: 22px 22px 0 0;
+ padding: 20px 24px;
+ border-bottom: 1px solid #e2e8f0;
+}
+
+.xx-modal .ant-modal-title {
+ font-size: 18px;
+ font-weight: 700;
+ color: #0f172a;
+}
+
+.xx-modal .ant-modal-footer {
+ border-top: 1px solid #e2e8f0;
+ padding: 16px 24px;
+}
+
+/* ==================== 空状态 ==================== */
+.xx-empty-state {
+ text-align: center;
+ padding: 48px 24px;
+ color: #64748b;
+}
+
+.xx-empty-state-icon {
+ font-size: 48px;
+ margin-bottom: 16px;
+}
+
+/* ==================== 网格布局 ==================== */
+.xx-grid-2 {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ gap: 20px;
+}
+
+.xx-grid-3 {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 20px;
+}
+
+.xx-grid-4 {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 20px;
+}
+
+@media (max-width: 768px) {
+ .xx-grid-2, .xx-grid-3, .xx-grid-4 {
+ grid-template-columns: 1fr;
+ }
+}
+
+/* ==================== 配额展示 ==================== */
+.xx-quota-item {
+ padding: 20px;
+ background: white;
+ border: 1px solid #e2e8f0;
+ border-radius: 16px;
+ transition: all 0.2s;
+}
+
+.xx-quota-item:hover {
+ border-color: #4f46e5;
+ box-shadow: 0 8px 24px rgba(79,70,229,0.1);
+}
+
+/* ==================== Ant Design 覆盖样式 ==================== */
+.ant-table-wrapper .ant-table-thead > tr > th {
+ background: #f8fafc !important;
+ font-weight: 700 !important;
+ color: #0f172a !important;
+ border-bottom: 2px solid #e2e8f0 !important;
+ padding: 14px 16px !important;
+}
+
+.ant-table-wrapper .ant-table-tbody > tr > td {
+ padding: 14px 16px !important;
+ border-bottom: 1px solid #f1f5f9 !important;
+}
+
+.ant-table-wrapper .ant-table-tbody > tr:hover > td {
+ background: #fafbfc !important;
+}
+
+.ant-card {
+ border-radius: 22px !important;
+ border: 1px solid rgba(226,232,240,0.95) !important;
+}
+
+.ant-card-head {
+ border-bottom: 1px solid #e2e8f0 !important;
+ min-height: 52px !important;
+ padding: 0 24px !important;
+}
+
+.ant-card-head-title {
+ font-weight: 700 !important;
+ font-size: 16px !important;
+ color: #0f172a !important;
+}
+
+.ant-card-body {
+ padding: 20px 24px !important;
+}
+
+.ant-modal-content {
+ border-radius: 22px !important;
+ overflow: hidden;
+}
+
+.ant-modal-header {
+ padding: 20px 24px !important;
+ background: white !important;
+}
+
+.ant-modal-title {
+ font-weight: 700 !important;
+ font-size: 18px !important;
+ color: #0f172a !important;
+}
+
+.ant-modal-body {
+ padding: 24px !important;
+}
+
+.ant-modal-footer {
+ padding: 16px 24px !important;
+}
+
+.ant-btn-primary {
+ background: linear-gradient(135deg, #6366f1, #4f46e5) !important;
+ border: none !important;
+ border-radius: 14px !important;
+ box-shadow: 0 14px 26px rgba(79,70,229,0.22) !important;
+ height: auto !important;
+ padding: 10px 20px !important;
+ font-weight: 600 !important;
+}
+
+.ant-btn-primary:hover {
+ background: linear-gradient(135deg, #6366f1, #4f46e5) !important;
+ box-shadow: 0 18px 34px rgba(79,70,229,0.28) !important;
+ transform: translateY(-1px);
+}
+
+.ant-tag {
+ border-radius: 10px !important;
+ padding: 4px 12px !important;
+ font-weight: 500 !important;
+}
+
+.ant-select-selector {
+ border-radius: 14px !important;
+ border-color: #e2e8f0 !important;
+}
+
+.ant-select:not(.ant-select-disabled):hover .ant-select-selector {
+ border-color: #4f46e5 !important;
+}
+
+.ant-select-focused .ant-select-selector {
+ border-color: #4f46e5 !important;
+ box-shadow: 0 0 0 3px rgba(79,70,229,0.1) !important;
+}
+
+.ant-input {
+ border-radius: 14px !important;
+ border-color: #e2e8f0 !important;
+ padding: 10px 14px !important;
+}
+
+.ant-input:hover {
+ border-color: #4f46e5 !important;
+}
+
+.ant-input:focus {
+ border-color: #4f46e5 !important;
+ box-shadow: 0 0 0 3px rgba(79,70,229,0.1) !important;
+}
+
+.ant-progress-inner {
+ background: #f1f5f9 !important;
+ border-radius: 10px !important;
+}
+
+.ant-progress-bg {
+ border-radius: 10px !important;
+}
diff --git a/apps/web/src/pages/workspace/ProjectTasks.css b/apps/web/src/pages/workspace/ProjectTasks.css
new file mode 100644
index 000000000..253efb84a
--- /dev/null
+++ b/apps/web/src/pages/workspace/ProjectTasks.css
@@ -0,0 +1,53 @@
+/* V21 项目任务中心页面 */
+.xx-project-tasks {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 24px;
+}
+
+/* 任务列表项 */
+.xx-task-item {
+ padding: 20px 0 !important;
+ border-bottom: 1px solid #f1f5f9 !important;
+ transition: all 0.2s;
+}
+
+.xx-task-item:last-child {
+ border-bottom: none !important;
+}
+
+.xx-task-item:hover {
+ background: #fafbfc;
+}
+
+/* 任务标题行 */
+.xx-task-title {
+ align-items: center;
+ gap: 10px;
+}
+
+.xx-task-type {
+ font-weight: 600;
+ color: #0f172a;
+ font-size: 15px;
+}
+
+/* 任务描述 */
+.xx-task-desc {
+ margin-top: 12px;
+}
+
+/* V21 标签 */
+.xx-tag {
+ padding: 4px 12px;
+ border-radius: 10px;
+ font-size: 13px;
+ font-weight: 500;
+ margin: 0 !important;
+}
+
+.xx-tag-indigo {
+ background: #eef2ff;
+ color: #4f46e5;
+ border: 1px solid #c7d2fe;
+}
diff --git a/apps/web/src/pages/workspace/ProjectTasks.tsx b/apps/web/src/pages/workspace/ProjectTasks.tsx
index 86a41de62..fbebb02c1 100644
--- a/apps/web/src/pages/workspace/ProjectTasks.tsx
+++ b/apps/web/src/pages/workspace/ProjectTasks.tsx
@@ -1,15 +1,20 @@
+/**
+ * 项目任务中心 - V21 UI
+ */
import React from 'react';
import { Alert, Button, Card, Empty, List, Progress, Space, Tag, Typography } from 'antd';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useParams } from 'react-router-dom';
import { getProjectTasks, retryProjectTask } from '@/api/tasks';
+import '@/components/business/business.css';
+import './ProjectTasks.css';
const statusMap: Record
= {
pending: { label: '排队中', color: 'default' },
- processing: { label: '处理中', color: 'blue' },
- running: { label: '运行中', color: 'blue' },
- completed: { label: '已完成', color: 'green' },
- failed: { label: '失败', color: 'red' },
+ processing: { label: '处理中', color: 'processing' },
+ running: { label: '运行中', color: 'processing' },
+ completed: { label: '已完成', color: 'success' },
+ failed: { label: '失败', color: 'error' },
cancelled: { label: '已取消', color: 'default' },
};
@@ -31,49 +36,83 @@ const ProjectTasks: React.FC = () => {
});
const retryMutation = useMutation({
- mutationFn: ({ taskType, sourceId }: { taskType: string; sourceId: string }) => retryProjectTask(taskType, sourceId),
+ mutationFn: ({ taskType, sourceId }: { taskType: string; sourceId: string }) =>
+ retryProjectTask(taskType, sourceId),
onSuccess: () => queryClient.invalidateQueries({ queryKey: ['project-tasks', projectId] }),
});
return (
-
-
tasksQuery.refetch()}>刷新}>
- {tasksQuery.isError && }
+
+
+
+
+
+
+
+ {tasksQuery.isError && (
+
+ )}
{tasksQuery.data?.some((task) => task.retryable) && (
)}
}}
+ locale={{
+ emptyText: (
+
+
📋
+
暂无任务,上传素材或发起生成后会出现在这里
+
+ ),
+ }}
renderItem={(task) => {
const status = statusMap[task.status] || { label: task.status, color: 'default' };
return (
-
+
- {taskTypeLabels[task.task_type] || task.task_type}
- {status.label}
- {task.current_step}
- {task.retryable ? (
-
);
};
diff --git a/apps/web/src/pages/workspace/WorkspaceDetail.tsx b/apps/web/src/pages/workspace/WorkspaceDetail.tsx
index 6ac291b06..0d95e4cc6 100644
--- a/apps/web/src/pages/workspace/WorkspaceDetail.tsx
+++ b/apps/web/src/pages/workspace/WorkspaceDetail.tsx
@@ -1,5 +1,5 @@
/**
- * 工作空间详情页面
+ * 工作空间详情页面 - V21 UI
*/
import React, { useState } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
@@ -11,6 +11,7 @@ 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 }>();
@@ -50,20 +51,20 @@ const WorkspaceDetail: React.FC = () => {
});
};
- if (isLoading) return 加载中...
;
- if (!workspace) return 工作空间不存在
;
+ if (isLoading) return ;
+ if (!workspace) return ;
const items = [
{
key: 'overview',
label: '概览',
children: (
-
+
setCreateProjectOpen(true)}>
+ setCreateProjectOpen(true)}>
创建项目
}
@@ -80,7 +81,7 @@ const WorkspaceDetail: React.FC = () => {
-
+
{projectsQuery.isError && (
)}
@@ -131,18 +132,25 @@ const WorkspaceDetail: React.FC = () => {
>
),
children: (
-
-
+
+
+
+
工作空间成员
+
管理有权访问此工作空间的成员
+
}
onClick={() => setInviteModalOpen(true)}
>
邀请成员
-
-
@@ -151,13 +159,18 @@ const WorkspaceDetail: React.FC = () => {
{
key: 'settings',
label: '设置',
- children:
工作空间设置(待开发)
,
+ children:
,
},
];
return (
-
-
{workspace.name}
+
+
+
+
{workspace.name}
+
工作空间详情与成员管理
+
+
{
confirmLoading={createProjectMutation.isPending}
onOk={handleCreateProject}
onCancel={() => setCreateProjectOpen(false)}
+ className="xx-modal"
>