[feat]:[FL-179][优化文档查看页面布局和样式]

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
chengkai3
2026-06-22 23:58:32 +08:00
parent 265fe741a8
commit 3c500d1397
2 changed files with 286 additions and 82 deletions
+100 -42
View File
@@ -66,33 +66,36 @@ export default function DocsViewPage() {
});
const convertToMenuItems = useCallback((chapters: DocumentChapterTreeItem[]): MenuItem[] => {
return chapters
.filter((chapter) => {
const hasPublishedDocs = chapter.documents?.some((doc) => doc.status === "published");
const hasPublishedChildren = chapter.children?.some((child) =>
child.documents?.some((doc) => doc.status === "published")
);
return hasPublishedDocs || hasPublishedChildren;
})
.map((chapter) => {
const hasChildren = chapter.children && chapter.children.length > 0;
const publishedDocs = chapter.documents?.filter((doc) => doc.status === "published") || [];
const convert = (chapters: DocumentChapterTreeItem[]): MenuItem[] => {
return chapters
.filter((chapter) => {
const hasPublishedDocs = chapter.documents?.some((doc) => doc.status === "published");
const hasPublishedChildren = chapter.children?.some((child) =>
child.documents?.some((doc) => doc.status === "published")
);
return hasPublishedDocs || hasPublishedChildren;
})
.map((chapter) => {
const hasChildren = chapter.children && chapter.children.length > 0;
const publishedDocs = chapter.documents?.filter((doc) => doc.status === "published") || [];
const docItems: MenuItem[] = publishedDocs.map((doc) => ({
key: `doc-${doc.id}`,
icon: <FileTextOutlined />,
label: doc.title,
}));
const docItems: MenuItem[] = publishedDocs.map((doc) => ({
key: `doc-${doc.id}`,
icon: <FileTextOutlined />,
label: doc.title,
}));
const childItems = hasChildren ? convertToMenuItems(chapter.children) : [];
const childItems = hasChildren ? convert(chapter.children) : [];
return {
key: `chapter-${chapter.id}`,
icon: <FolderOutlined />,
label: chapter.name,
children: [...docItems, ...childItems],
};
});
return {
key: `chapter-${chapter.id}`,
icon: <FolderOutlined />,
label: chapter.name,
children: [...docItems, ...childItems],
};
});
};
return convert(chapters);
}, []);
const handleMenuClick: MenuProps["onClick"] = useCallback((e) => {
@@ -144,10 +147,14 @@ export default function DocsViewPage() {
const menuContent = (
<>
<div className="admin-docs-view-sider-header">
{!siderCollapsed && <Title level={4} style={{ margin: 0 }}></Title>}
{!siderCollapsed && (
<Title level={4} style={{ margin: 0, fontSize: '16px', fontWeight: 600 }}>
</Title>
)}
</div>
{treeLoading ? (
<div style={{ padding: "24px", textAlign: "center" }}>
<div className="admin-docs-view-sider-loading">
<Spin />
</div>
) : treeData && treeData.length > 0 ? (
@@ -162,7 +169,7 @@ export default function DocsViewPage() {
/>
</div>
) : (
<div style={{ padding: "24px" }}>
<div className="admin-docs-view-sider-empty">
<Empty description="暂无文档" image={Empty.PRESENTED_IMAGE_SIMPLE} />
</div>
)}
@@ -204,31 +211,82 @@ export default function DocsViewPage() {
<div className="admin-docs-view-content">
{documentLoading ? (
<div className="flex items-center justify-center" style={{ minHeight: 300 }}>
<div className="flex flex-col items-center gap-3">
<Spin size="large" />
<Typography.Text type="secondary">...</Typography.Text>
</div>
<div className="admin-docs-view-loading">
<Spin size="large" />
<Typography.Text type="secondary">...</Typography.Text>
</div>
) : selectedDocument ? (
<AntCard className="admin-docs-view-document-card">
<Title level={2}>{selectedDocument.title}</Title>
<AntCard className="admin-docs-view-document-card" bordered={false}>
<div className="admin-docs-view-document-header">
<Title level={2} style={{ marginBottom: 8 }}>
{selectedDocument.title}
</Title>
</div>
<div className="admin-docs-view-markdown-content">
<ReactMarkdown
components={{
h1: ({ children }) => <Title level={2}>{children}</Title>,
h2: ({ children }) => <Title level={3}>{children}</Title>,
h3: ({ children }) => <Title level={4}>{children}</Title>,
h4: ({ children }) => <Title level={5}>{children}</Title>,
p: ({ children }) => <Paragraph>{children}</Paragraph>,
h1: ({ children }) => (
<Title level={2} style={{ marginTop: 32, marginBottom: 16 }}>
{children}
</Title>
),
h2: ({ children }) => (
<Title level={3} style={{ marginTop: 28, marginBottom: 14 }}>
{children}
</Title>
),
h3: ({ children }) => (
<Title level={4} style={{ marginTop: 24, marginBottom: 12 }}>
{children}
</Title>
),
h4: ({ children }) => (
<Title level={5} style={{ marginTop: 20, marginBottom: 10 }}>
{children}
</Title>
),
p: ({ children }) => (
<Paragraph style={{ marginBottom: 16 }}>
{children}
</Paragraph>
),
ul: ({ children }) => (
<ul style={{ marginBottom: 16, paddingLeft: 24 }}>
{children}
</ul>
),
ol: ({ children }) => (
<ol style={{ marginBottom: 16, paddingLeft: 24 }}>
{children}
</ol>
),
li: ({ children }) => (
<li style={{ marginBottom: 8 }}>
{children}
</li>
),
blockquote: ({ children }) => (
<blockquote className="admin-docs-view-blockquote">
{children}
</blockquote>
),
code: ({ children, className }) => {
const isBlock = className?.includes("language-");
return isBlock ? (
<pre><code>{children}</code></pre>
<pre className="admin-docs-view-code-block">
<code>{children}</code>
</pre>
) : (
<code>{children}</code>
<code className="admin-docs-view-inline-code">{children}</code>
);
},
table: ({ children }) => (
<div className="admin-docs-view-table-wrapper">
<table className="admin-docs-view-table">
{children}
</table>
</div>
),
}}
>
{selectedDocument.content}
@@ -236,7 +294,7 @@ export default function DocsViewPage() {
</div>
</AntCard>
) : (
<div className="flex items-center justify-center" style={{ minHeight: 300 }}>
<div className="admin-docs-view-empty">
<Empty
description="请从左侧目录选择要查看的文档"
image={Empty.PRESENTED_IMAGE_SIMPLE}
+186 -40
View File
@@ -1451,14 +1451,18 @@ body {
display: flex;
min-height: 0;
flex: 1;
gap: 0;
}
.admin-docs-view-sider {
display: flex;
flex-direction: column;
width: 280px;
border-right: 1px solid var(--ant-color-border-secondary);
background: var(--fquiz-theme-bg-container);
overflow-y: auto;
background: var(--fquiz-theme-bg-elevated);
overflow: hidden;
flex-shrink: 0;
transition: width 0.3s ease;
}
.admin-docs-view-sider.collapsed {
@@ -1466,14 +1470,31 @@ body {
}
.admin-docs-view-sider-header {
padding: 16px;
padding: 20px 16px;
border-bottom: 1px solid var(--ant-color-border-secondary);
background: color-mix(in srgb, var(--fquiz-theme-primary) 4%, transparent);
background: color-mix(in srgb, var(--fquiz-theme-primary) 5%, transparent);
flex-shrink: 0;
}
.admin-docs-view-sider-menu {
flex: 1;
min-height: 0;
overflow-y: auto;
padding: 8px 0;
}
.admin-docs-view-sider-loading {
display: flex;
justify-content: center;
align-items: center;
padding: 40px 24px;
}
.admin-docs-view-sider-empty {
display: flex;
justify-content: center;
align-items: center;
padding: 40px 24px;
}
.admin-docs-view-sider-footer {
@@ -1481,70 +1502,176 @@ body {
justify-content: center;
border-top: 1px solid var(--ant-color-border-secondary);
padding: 12px;
background: var(--fquiz-theme-bg-elevated);
flex-shrink: 0;
}
.admin-docs-view-content {
min-height: 0;
flex: 1;
overflow-y: auto;
padding: 24px;
background: var(--fquiz-theme-bg-container);
padding: 32px;
background: var(--fquiz-theme-bg-layout);
}
.admin-docs-view-loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 16px;
min-height: 400px;
}
.admin-docs-view-empty {
display: flex;
align-items: center;
justify-content: center;
min-height: 400px;
}
.admin-docs-view-document-card {
border-color: color-mix(in srgb, var(--fquiz-theme-primary) 20%, var(--ant-color-border-secondary));
background: linear-gradient(
180deg,
color-mix(in srgb, var(--fquiz-theme-bg-container) 98%, var(--fquiz-theme-primary) 2%) 0%,
var(--fquiz-theme-bg-container) 100%
);
box-shadow: 0 4px 12px color-mix(in srgb, var(--fquiz-theme-text-primary) 6%, transparent);
max-width: 900px;
margin: 0 auto;
border: none;
border-radius: var(--ant-border-radius-lg);
background: var(--fquiz-theme-bg-container);
box-shadow: 0 2px 8px color-mix(in srgb, var(--fquiz-theme-text-primary) 4%, transparent);
}
.admin-docs-view-document-card > .ant-card-head {
border-bottom-color: color-mix(in srgb, var(--fquiz-theme-primary) 15%, var(--ant-color-border-secondary));
background: color-mix(in srgb, var(--fquiz-theme-primary) 4%, transparent);
.admin-docs-view-document-header {
padding-bottom: 16px;
border-bottom: 2px solid var(--ant-color-border-secondary);
margin-bottom: 32px;
}
.admin-docs-view-markdown-content {
margin-top: 24px;
line-height: 1.8;
line-height: 1.75;
font-size: 15px;
color: var(--ant-color-text);
}
.admin-docs-view-markdown-content pre {
.admin-docs-view-markdown-content > *:first-child {
margin-top: 0;
}
.admin-docs-view-markdown-content > *:last-child {
margin-bottom: 0;
}
.admin-docs-view-markdown-content strong {
font-weight: 600;
color: var(--ant-color-text);
}
.admin-docs-view-markdown-content a {
color: var(--ant-color-primary);
text-decoration: none;
transition: color 0.2s;
}
.admin-docs-view-markdown-content a:hover {
color: var(--ant-color-primary-hover);
text-decoration: underline;
}
.admin-docs-view-markdown-content img {
max-width: 100%;
height: auto;
border-radius: var(--ant-border-radius);
margin: 20px 0;
box-shadow: 0 2px 8px color-mix(in srgb, var(--fquiz-theme-text-primary) 8%, transparent);
}
.admin-docs-view-markdown-content hr {
border: none;
border-top: 1px solid var(--ant-color-border-secondary);
margin: 32px 0;
}
.admin-docs-view-blockquote {
margin: 20px 0;
padding: 12px 20px;
border-left: 4px solid var(--ant-color-primary);
background: color-mix(in srgb, var(--ant-color-primary) 6%, transparent);
border-radius: 0 var(--ant-border-radius) var(--ant-border-radius) 0;
}
.admin-docs-view-blockquote p {
margin-bottom: 0 !important;
}
.admin-docs-view-code-block {
background: var(--ant-color-bg-layout);
padding: 12px;
border-radius: 4px;
overflow: auto;
padding: 16px 20px;
border-radius: var(--ant-border-radius);
overflow-x: auto;
margin: 20px 0;
border: 1px solid var(--ant-color-border-secondary);
line-height: 1.6;
}
.admin-docs-view-markdown-content code {
background: var(--ant-color-bg-layout);
padding: 2px 6px;
border-radius: 3px;
font-family: monospace;
}
.admin-docs-view-markdown-content pre code {
.admin-docs-view-code-block code {
background: transparent;
padding: 0;
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
font-size: 14px;
color: var(--ant-color-text);
}
.admin-docs-view-inline-code {
background: color-mix(in srgb, var(--ant-color-primary) 10%, transparent);
color: var(--ant-color-primary);
padding: 2px 8px;
border-radius: 4px;
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
font-size: 0.9em;
font-weight: 500;
}
.admin-docs-view-table-wrapper {
overflow-x: auto;
margin: 20px 0;
}
.admin-docs-view-table {
width: 100%;
border-collapse: collapse;
border: 1px solid var(--ant-color-border-secondary);
border-radius: var(--ant-border-radius);
overflow: hidden;
}
.admin-docs-view-table th,
.admin-docs-view-table td {
padding: 12px 16px;
border: 1px solid var(--ant-color-border-secondary);
text-align: left;
}
.admin-docs-view-table th {
background: var(--fquiz-theme-table-header-bg);
font-weight: 600;
color: var(--ant-color-text);
}
.admin-docs-view-table tr:hover {
background: color-mix(in srgb, var(--ant-color-primary) 4%, transparent);
}
/* 暗色主题适配 */
:root[data-fquiz-theme="dark"] .admin-docs-view-document-card {
border-color: color-mix(in srgb, var(--fquiz-theme-primary) 30%, var(--ant-color-border-secondary)) !important;
background: linear-gradient(
180deg,
color-mix(in srgb, var(--ant-color-bg-container) 94%, var(--fquiz-theme-primary) 6%) 0%,
var(--ant-color-bg-container) 100%
) !important;
box-shadow: 0 6px 16px color-mix(in srgb, black 35%, transparent) !important;
background: var(--ant-color-bg-container);
box-shadow: 0 2px 8px color-mix(in srgb, black 30%, transparent);
}
:root[data-fquiz-theme="dark"] .admin-docs-view-document-card > .ant-card-head {
border-bottom-color: color-mix(in srgb, var(--fquiz-theme-primary) 22%, var(--ant-color-border-secondary)) !important;
background: color-mix(in srgb, var(--fquiz-theme-primary) 8%, transparent) !important;
:root[data-fquiz-theme="dark"] .admin-docs-view-code-block {
background: color-mix(in srgb, var(--ant-color-bg-container) 70%, black);
border-color: color-mix(in srgb, var(--ant-color-border-secondary) 60%, transparent);
}
:root[data-fquiz-theme="dark"] .admin-docs-view-table th {
background: color-mix(in srgb, var(--ant-color-bg-container) 85%, black);
}
/* 移动端适配 */
@@ -1552,6 +1679,25 @@ body {
.admin-docs-view-content {
padding: 16px;
}
.admin-docs-view-document-card {
border-radius: var(--ant-border-radius);
}
.admin-docs-view-markdown-content {
font-size: 14px;
}
.admin-docs-view-code-block {
padding: 12px 16px;
font-size: 13px;
}
.admin-docs-view-table th,
.admin-docs-view-table td {
padding: 8px 12px;
font-size: 14px;
}
}
/* 表格边框优化 - 仅外边框,上下圆角对齐 */