[feat]:[FL-218][操作文档展示页面样式优化]
Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
Generated
+136
@@ -1181,12 +1181,44 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/github-slugger": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz",
|
||||
"integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/grapheme-splitter": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz",
|
||||
"integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/hast-util-heading-rank": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/hast-util-heading-rank/-/hast-util-heading-rank-3.0.0.tgz",
|
||||
"integrity": "sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/hast": "^3.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/hast-util-is-element": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz",
|
||||
"integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/hast": "^3.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/hast-util-to-jsx-runtime": {
|
||||
"version": "2.3.6",
|
||||
"resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz",
|
||||
@@ -1214,6 +1246,35 @@
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/hast-util-to-string": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-3.0.1.tgz",
|
||||
"integrity": "sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/hast": "^3.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/hast-util-to-text": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz",
|
||||
"integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/hast": "^3.0.0",
|
||||
"@types/unist": "^3.0.0",
|
||||
"hast-util-is-element": "^3.0.0",
|
||||
"unist-util-find-after": "^5.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/hast-util-whitespace": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz",
|
||||
@@ -1227,6 +1288,15 @@
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/highlight.js": {
|
||||
"version": "11.11.1",
|
||||
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz",
|
||||
"integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/html-url-attributes": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz",
|
||||
@@ -1441,6 +1511,21 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/lowlight": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lowlight/-/lowlight-3.3.0.tgz",
|
||||
"integrity": "sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/hast": "^3.0.0",
|
||||
"devlop": "^1.0.0",
|
||||
"highlight.js": "~11.11.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/mdast-util-from-markdown": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz",
|
||||
@@ -2908,6 +2993,40 @@
|
||||
"redux": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/rehype-highlight": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/rehype-highlight/-/rehype-highlight-7.0.2.tgz",
|
||||
"integrity": "sha512-k158pK7wdC2qL3M5NcZROZ2tR/l7zOzjxXd5VGdcfIyoijjQqpHd3JKtYSBDpDZ38UI2WJWuFAtkMDxmx5kstA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/hast": "^3.0.0",
|
||||
"hast-util-to-text": "^4.0.0",
|
||||
"lowlight": "^3.0.0",
|
||||
"unist-util-visit": "^5.0.0",
|
||||
"vfile": "^6.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/rehype-slug": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/rehype-slug/-/rehype-slug-6.0.0.tgz",
|
||||
"integrity": "sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/hast": "^3.0.0",
|
||||
"github-slugger": "^2.0.0",
|
||||
"hast-util-heading-rank": "^3.0.0",
|
||||
"hast-util-to-string": "^3.0.0",
|
||||
"unist-util-visit": "^5.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/remark-parse": {
|
||||
"version": "11.0.0",
|
||||
"resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz",
|
||||
@@ -3136,6 +3255,20 @@
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/unist-util-find-after": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz",
|
||||
"integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/unist": "^3.0.0",
|
||||
"unist-util-is": "^6.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/unist-util-is": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz",
|
||||
@@ -3309,6 +3442,7 @@
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"framer-motion": "^12.38.0",
|
||||
"highlight.js": "^11.11.1",
|
||||
"jszip": "^3.10.1",
|
||||
"next": "16.2.3",
|
||||
"react": "19.2.4",
|
||||
@@ -3316,6 +3450,8 @@
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-redux": "^9.3.0",
|
||||
"recharts": "^3.8.1",
|
||||
"rehype-highlight": "^7.0.2",
|
||||
"rehype-slug": "^6.0.0",
|
||||
"reselect": "^5.2.0",
|
||||
"tailwind-merge": "^3.5.0"
|
||||
},
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"framer-motion": "^12.38.0",
|
||||
"highlight.js": "^11.11.1",
|
||||
"jszip": "^3.10.1",
|
||||
"next": "16.2.3",
|
||||
"react": "19.2.4",
|
||||
@@ -25,6 +26,8 @@
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-redux": "^9.3.0",
|
||||
"recharts": "^3.8.1",
|
||||
"rehype-highlight": "^7.0.2",
|
||||
"rehype-slug": "^6.0.0",
|
||||
"reselect": "^5.2.0",
|
||||
"tailwind-merge": "^3.5.0"
|
||||
},
|
||||
|
||||
@@ -5,10 +5,13 @@ import {
|
||||
Card,
|
||||
Drawer,
|
||||
Empty,
|
||||
Input,
|
||||
Menu,
|
||||
Spin,
|
||||
Skeleton,
|
||||
Typography,
|
||||
Button,
|
||||
Image,
|
||||
Tooltip,
|
||||
type CardProps,
|
||||
} from "antd";
|
||||
import {
|
||||
@@ -17,10 +20,23 @@ import {
|
||||
MenuOutlined,
|
||||
MenuFoldOutlined,
|
||||
MenuUnfoldOutlined,
|
||||
SearchOutlined,
|
||||
CopyOutlined,
|
||||
LinkOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { useCallback, useEffect, useState, type ComponentType, type RefAttributes } from "react";
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useState,
|
||||
useRef,
|
||||
useMemo,
|
||||
type ComponentType,
|
||||
type RefAttributes,
|
||||
} from "react";
|
||||
import type { MenuProps } from "antd";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import rehypeHighlight from "rehype-highlight";
|
||||
import rehypeSlug from "rehype-slug";
|
||||
|
||||
import { useAuth } from "@/components/auth-provider";
|
||||
import { useMobileDetection } from "@/hooks/use-mobile-detection";
|
||||
@@ -30,17 +46,35 @@ import type {
|
||||
DocumentChapterTreeItem,
|
||||
} from "@/types/document";
|
||||
|
||||
const { Title, Paragraph } = Typography;
|
||||
const { Title, Paragraph, Text } = Typography;
|
||||
const AntCard = Card as unknown as ComponentType<CardProps & RefAttributes<HTMLDivElement>>;
|
||||
|
||||
type MenuItem = Required<MenuProps>["items"][number];
|
||||
|
||||
function flattenDocuments(chapters: DocumentChapterTreeItem[]): { id: number; title: string; chapterPath: string }[] {
|
||||
const result: { id: number; title: string; chapterPath: string }[] = [];
|
||||
const walk = (items: DocumentChapterTreeItem[], path: string) => {
|
||||
for (const chapter of items) {
|
||||
const currentPath = path ? `${path} / ${chapter.name}` : chapter.name;
|
||||
for (const doc of chapter.documents?.filter((d) => d.status === "published") ?? []) {
|
||||
result.push({ id: doc.id, title: doc.title, chapterPath: currentPath });
|
||||
}
|
||||
if (chapter.children) walk(chapter.children, currentPath);
|
||||
}
|
||||
};
|
||||
walk(chapters, "");
|
||||
return result;
|
||||
}
|
||||
|
||||
export default function DocsViewPage() {
|
||||
const { user, fetchWithAuth, hasPermission } = useAuth();
|
||||
const isMobile = useMobileDetection();
|
||||
const [selectedDocumentId, setSelectedDocumentId] = useState<number | null>(null);
|
||||
const [siderCollapsed, setSiderCollapsed] = useState(false);
|
||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [copiedCodeBlockId, setCopiedCodeBlockId] = useState<string | null>(null);
|
||||
const contentRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const canRead = hasPermission("document.read") || true;
|
||||
|
||||
@@ -65,46 +99,89 @@ export default function DocsViewPage() {
|
||||
enabled: !!selectedDocumentId && !!user && canRead,
|
||||
});
|
||||
|
||||
const convertToMenuItems = useCallback((chapters: DocumentChapterTreeItem[]): MenuItem[] => {
|
||||
const convert = (chapters: DocumentChapterTreeItem[]): MenuItem[] => {
|
||||
const documentIndex = useMemo(() => (treeData ? flattenDocuments(treeData) : []), [treeData]);
|
||||
|
||||
const filteredMenuItems = useMemo(() => {
|
||||
if (!treeData) return [];
|
||||
|
||||
const filterTree = (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")
|
||||
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) => ({
|
||||
const matchedDocs = searchQuery
|
||||
? publishedDocs.filter(
|
||||
(doc) =>
|
||||
doc.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
chapter.name.toLowerCase().includes(searchQuery.toLowerCase()),
|
||||
)
|
||||
: publishedDocs;
|
||||
|
||||
const childItems = chapter.children ? filterTree(chapter.children) : [];
|
||||
|
||||
if (searchQuery && matchedDocs.length === 0 && childItems.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const docItems: MenuItem[] = matchedDocs.map((doc) => ({
|
||||
key: `doc-${doc.id}`,
|
||||
icon: <FileTextOutlined />,
|
||||
label: doc.title,
|
||||
label: (
|
||||
<span className="admin-docs-view-menu-doc-title" data-searching={!!searchQuery}>
|
||||
{searchQuery ? highlightMatch(doc.title, searchQuery) : doc.title}
|
||||
</span>
|
||||
),
|
||||
}));
|
||||
|
||||
const childItems = hasChildren ? convert(chapter.children) : [];
|
||||
|
||||
return {
|
||||
key: `chapter-${chapter.id}`,
|
||||
icon: <FolderOutlined />,
|
||||
label: chapter.name,
|
||||
label: (
|
||||
<span className="admin-docs-view-menu-chapter-name" data-searching={!!searchQuery}>
|
||||
{searchQuery ? highlightMatch(chapter.name, searchQuery) : chapter.name}
|
||||
</span>
|
||||
),
|
||||
children: [...docItems, ...childItems],
|
||||
};
|
||||
});
|
||||
})
|
||||
.filter(Boolean) as MenuItem[];
|
||||
};
|
||||
return convert(chapters);
|
||||
}, []);
|
||||
|
||||
const handleMenuClick: MenuProps["onClick"] = useCallback((e) => {
|
||||
if (e.key.startsWith("doc-")) {
|
||||
const docId = parseInt(e.key.replace("doc-", ""), 10);
|
||||
setSelectedDocumentId(docId);
|
||||
setMobileMenuOpen(false);
|
||||
}
|
||||
}, []);
|
||||
return filterTree(treeData);
|
||||
}, [treeData, searchQuery]);
|
||||
|
||||
function highlightMatch(text: string, query: string): React.ReactNode {
|
||||
const lower = text.toLowerCase();
|
||||
const q = query.toLowerCase();
|
||||
const idx = lower.indexOf(q);
|
||||
if (idx === -1) return text;
|
||||
return (
|
||||
<>
|
||||
{text.slice(0, idx)}
|
||||
<mark className="admin-docs-view-search-highlight">{text.slice(idx, idx + q.length)}</mark>
|
||||
{text.slice(idx + q.length)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const handleMenuClick: MenuProps["onClick"] = useCallback(
|
||||
(e) => {
|
||||
if (e.key.startsWith("doc-")) {
|
||||
const docId = parseInt(e.key.replace("doc-", ""), 10);
|
||||
setSelectedDocumentId(docId);
|
||||
setMobileMenuOpen(false);
|
||||
contentRef.current?.scrollTo({ top: 0, behavior: "smooth" });
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const selectFirstDocument = useCallback(() => {
|
||||
if (!treeData || treeData.length === 0 || selectedDocumentId) return;
|
||||
@@ -132,6 +209,16 @@ export default function DocsViewPage() {
|
||||
selectFirstDocument();
|
||||
}, [selectFirstDocument]);
|
||||
|
||||
const handleCopyCode = async (code: string, blockId: string) => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(code);
|
||||
setCopiedCodeBlockId(blockId);
|
||||
setTimeout(() => setCopiedCodeBlockId(null), 2000);
|
||||
} catch {
|
||||
// silent
|
||||
}
|
||||
};
|
||||
|
||||
if (!user || !canRead) {
|
||||
return (
|
||||
<div className="flex min-h-0 flex-1 flex-col">
|
||||
@@ -144,30 +231,64 @@ export default function DocsViewPage() {
|
||||
);
|
||||
}
|
||||
|
||||
const skeletonMenu = (
|
||||
<div className="admin-docs-view-skeleton-menu">
|
||||
{[0, 1, 2, 3, 4, 5].map((i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="admin-docs-view-skeleton-item"
|
||||
style={i % 3 === 0 ? {} : { paddingLeft: 20 }}
|
||||
>
|
||||
<Skeleton.Input active size="small" block style={{ width: `${50 + (i % 3) * 15}%` }} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
const menuContent = (
|
||||
<>
|
||||
<div className="admin-docs-view-sider-header">
|
||||
{!siderCollapsed && (
|
||||
<Title level={4} style={{ margin: 0, fontSize: '16px', fontWeight: 600 }}>
|
||||
操作文档
|
||||
</Title>
|
||||
<>
|
||||
<Title level={4} style={{ margin: 0, fontSize: "16px", fontWeight: 600 }}>
|
||||
操作文档
|
||||
</Title>
|
||||
{!treeLoading && treeData && treeData.length > 0 && (
|
||||
<div className="admin-docs-view-search-wrapper">
|
||||
<Input
|
||||
className="admin-docs-view-search-input"
|
||||
placeholder="搜索文档..."
|
||||
prefix={<SearchOutlined />}
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
allowClear
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{treeLoading ? (
|
||||
<div className="admin-docs-view-sider-loading">
|
||||
<Spin />
|
||||
</div>
|
||||
skeletonMenu
|
||||
) : treeData && treeData.length > 0 ? (
|
||||
<div className="admin-docs-view-sider-menu">
|
||||
<Menu
|
||||
mode="inline"
|
||||
items={convertToMenuItems(treeData)}
|
||||
onClick={handleMenuClick}
|
||||
selectedKeys={selectedDocumentId ? [`doc-${selectedDocumentId}`] : []}
|
||||
style={{ borderRight: 0, background: "transparent" }}
|
||||
inlineCollapsed={siderCollapsed && !isMobile}
|
||||
/>
|
||||
</div>
|
||||
<>
|
||||
{searchQuery && (
|
||||
<div className="admin-docs-view-search-results-count">
|
||||
{`找到 ${documentIndex.filter((d) => d.title.toLowerCase().includes(searchQuery.toLowerCase())).length} 个结果`}
|
||||
</div>
|
||||
)}
|
||||
<div className="admin-docs-view-sider-menu">
|
||||
<Menu
|
||||
mode="inline"
|
||||
items={filteredMenuItems}
|
||||
onClick={handleMenuClick}
|
||||
selectedKeys={selectedDocumentId ? [`doc-${selectedDocumentId}`] : []}
|
||||
style={{ borderRight: 0, background: "transparent" }}
|
||||
inlineCollapsed={siderCollapsed && !isMobile}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className="admin-docs-view-sider-empty">
|
||||
<Empty description="暂无文档" image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
||||
@@ -175,27 +296,41 @@ export default function DocsViewPage() {
|
||||
)}
|
||||
{!isMobile && (
|
||||
<div className="admin-docs-view-sider-footer">
|
||||
<Button
|
||||
aria-label={siderCollapsed ? "展开菜单" : "收起菜单"}
|
||||
icon={siderCollapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
|
||||
type="text"
|
||||
onClick={() => setSiderCollapsed((prev) => !prev)}
|
||||
/>
|
||||
<Tooltip title={siderCollapsed ? "展开菜单" : "收起菜单"}>
|
||||
<Button
|
||||
aria-label={siderCollapsed ? "展开菜单" : "收起菜单"}
|
||||
icon={siderCollapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
|
||||
type="text"
|
||||
onClick={() => setSiderCollapsed((prev) => !prev)}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
const skeletonContent = (
|
||||
<div className="admin-docs-view-skeleton-content">
|
||||
<Skeleton active paragraph={{ rows: 1 }} title={{ width: "60%" }} />
|
||||
<div style={{ marginTop: 24 }}>
|
||||
<Skeleton active paragraph={{ rows: 3 }} />
|
||||
</div>
|
||||
<div style={{ marginTop: 24 }}>
|
||||
<Skeleton active paragraph={{ rows: 5 }} />
|
||||
</div>
|
||||
<div style={{ marginTop: 24 }}>
|
||||
<Skeleton active paragraph={{ rows: 4 }} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex min-h-0 flex-1 flex-col">
|
||||
<AntCard
|
||||
className="admin-docs-view-page-card"
|
||||
extra={
|
||||
isMobile ? (
|
||||
<Button
|
||||
icon={<MenuOutlined />}
|
||||
onClick={() => setMobileMenuOpen(true)}
|
||||
>
|
||||
<Button icon={<MenuOutlined />} onClick={() => setMobileMenuOpen(true)}>
|
||||
目录
|
||||
</Button>
|
||||
) : null
|
||||
@@ -208,12 +343,9 @@ export default function DocsViewPage() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="admin-docs-view-content">
|
||||
<div className="admin-docs-view-content" ref={contentRef}>
|
||||
{documentLoading ? (
|
||||
<div className="admin-docs-view-loading">
|
||||
<Spin size="large" />
|
||||
<Typography.Text type="secondary">加载文档中...</Typography.Text>
|
||||
</div>
|
||||
skeletonContent
|
||||
) : selectedDocument ? (
|
||||
<AntCard className="admin-docs-view-document-card" bordered={false}>
|
||||
<div className="admin-docs-view-document-header">
|
||||
@@ -223,67 +355,106 @@ export default function DocsViewPage() {
|
||||
</div>
|
||||
<div className="admin-docs-view-markdown-content">
|
||||
<ReactMarkdown
|
||||
rehypePlugins={[rehypeSlug, rehypeHighlight]}
|
||||
components={{
|
||||
h1: ({ children }) => (
|
||||
<Title level={2} style={{ marginTop: 32, marginBottom: 16 }}>
|
||||
h1: ({ children, id }) => (
|
||||
<Title level={2} className="admin-docs-view-heading-anchor" id={id}>
|
||||
<a href={`#${id}`} className="admin-docs-view-heading-link" aria-label="Heading anchor">
|
||||
<LinkOutlined />
|
||||
</a>
|
||||
{children}
|
||||
</Title>
|
||||
),
|
||||
h2: ({ children }) => (
|
||||
<Title level={3} style={{ marginTop: 28, marginBottom: 14 }}>
|
||||
h2: ({ children, id }) => (
|
||||
<Title level={3} className="admin-docs-view-heading-anchor" id={id}>
|
||||
<a href={`#${id}`} className="admin-docs-view-heading-link" aria-label="Heading anchor">
|
||||
<LinkOutlined />
|
||||
</a>
|
||||
{children}
|
||||
</Title>
|
||||
),
|
||||
h3: ({ children }) => (
|
||||
<Title level={4} style={{ marginTop: 24, marginBottom: 12 }}>
|
||||
h3: ({ children, id }) => (
|
||||
<Title level={4} className="admin-docs-view-heading-anchor" id={id}>
|
||||
<a href={`#${id}`} className="admin-docs-view-heading-link" aria-label="Heading anchor">
|
||||
<LinkOutlined />
|
||||
</a>
|
||||
{children}
|
||||
</Title>
|
||||
),
|
||||
h4: ({ children }) => (
|
||||
<Title level={5} style={{ marginTop: 20, marginBottom: 10 }}>
|
||||
h4: ({ children, id }) => (
|
||||
<Title level={5} className="admin-docs-view-heading-anchor" id={id}>
|
||||
<a href={`#${id}`} className="admin-docs-view-heading-link" aria-label="Heading anchor">
|
||||
<LinkOutlined />
|
||||
</a>
|
||||
{children}
|
||||
</Title>
|
||||
),
|
||||
p: ({ children }) => (
|
||||
<Paragraph style={{ marginBottom: 16 }}>
|
||||
{children}
|
||||
</Paragraph>
|
||||
<Paragraph style={{ marginBottom: 16, lineHeight: 1.8 }}>{children}</Paragraph>
|
||||
),
|
||||
ul: ({ children }) => (
|
||||
<ul style={{ marginBottom: 16, paddingLeft: 24 }}>
|
||||
{children}
|
||||
</ul>
|
||||
<ul style={{ marginBottom: 16, paddingLeft: 24, lineHeight: 1.8 }}>{children}</ul>
|
||||
),
|
||||
ol: ({ children }) => (
|
||||
<ol style={{ marginBottom: 16, paddingLeft: 24 }}>
|
||||
{children}
|
||||
</ol>
|
||||
),
|
||||
li: ({ children }) => (
|
||||
<li style={{ marginBottom: 8 }}>
|
||||
{children}
|
||||
</li>
|
||||
<ol style={{ marginBottom: 16, paddingLeft: 24, lineHeight: 1.8 }}>{children}</ol>
|
||||
),
|
||||
li: ({ children }) => <li style={{ marginBottom: 8 }}>{children}</li>,
|
||||
blockquote: ({ children }) => (
|
||||
<blockquote className="admin-docs-view-blockquote">
|
||||
{children}
|
||||
</blockquote>
|
||||
<blockquote className="admin-docs-view-blockquote">{children}</blockquote>
|
||||
),
|
||||
code: ({ children, className }) => {
|
||||
const isBlock = className?.includes("language-");
|
||||
const codeText = String(children).replace(/\n$/, "");
|
||||
const blockId = `code-${Math.random().toString(36).slice(2, 8)}`;
|
||||
return isBlock ? (
|
||||
<pre className="admin-docs-view-code-block">
|
||||
<code>{children}</code>
|
||||
</pre>
|
||||
<div className="admin-docs-view-code-block-wrapper">
|
||||
<div className="admin-docs-view-code-block-header">
|
||||
<span className="admin-docs-view-code-lang">
|
||||
{className?.replace("language-", "") || "code"}
|
||||
</span>
|
||||
<Button
|
||||
type="text"
|
||||
size="small"
|
||||
className="admin-docs-view-copy-btn"
|
||||
icon={<CopyOutlined />}
|
||||
onClick={() => handleCopyCode(codeText, blockId)}
|
||||
>
|
||||
{copiedCodeBlockId === blockId ? "已复制" : "复制"}
|
||||
</Button>
|
||||
</div>
|
||||
<pre className="admin-docs-view-code-block">
|
||||
<code className={className}>{children}</code>
|
||||
</pre>
|
||||
</div>
|
||||
) : (
|
||||
<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>
|
||||
<table className="admin-docs-view-table">{children}</table>
|
||||
</div>
|
||||
),
|
||||
img: ({ src, alt }) => (
|
||||
<div className="admin-docs-view-image-wrapper">
|
||||
<Image
|
||||
src={src as string}
|
||||
alt={alt || ""}
|
||||
className="admin-docs-view-content-image"
|
||||
placeholder={
|
||||
<div className="admin-docs-view-image-placeholder">
|
||||
<Skeleton.Image active />
|
||||
</div>
|
||||
}
|
||||
preview={{
|
||||
mask: <div className="admin-docs-view-image-preview-mask">点击预览</div>,
|
||||
}}
|
||||
/>
|
||||
{alt && (
|
||||
<Text type="secondary" className="admin-docs-view-image-caption">
|
||||
{alt}
|
||||
</Text>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
}}
|
||||
@@ -308,16 +479,30 @@ export default function DocsViewPage() {
|
||||
title="文档目录"
|
||||
placement="left"
|
||||
open={isMobile && mobileMenuOpen}
|
||||
width={280}
|
||||
width={300}
|
||||
onClose={() => setMobileMenuOpen(false)}
|
||||
styles={{ body: { padding: 0 } }}
|
||||
>
|
||||
<Menu
|
||||
mode="inline"
|
||||
items={convertToMenuItems(treeData || [])}
|
||||
onClick={handleMenuClick}
|
||||
selectedKeys={selectedDocumentId ? [`doc-${selectedDocumentId}`] : []}
|
||||
style={{ borderRight: 0 }}
|
||||
/>
|
||||
<div className="admin-docs-view-mobile-drawer-content">
|
||||
{!treeLoading && treeData && treeData.length > 0 && (
|
||||
<div className="admin-docs-view-search-wrapper" style={{ padding: "12px 16px" }}>
|
||||
<Input
|
||||
placeholder="搜索文档..."
|
||||
prefix={<SearchOutlined />}
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
allowClear
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<Menu
|
||||
mode="inline"
|
||||
items={filteredMenuItems}
|
||||
onClick={handleMenuClick}
|
||||
selectedKeys={selectedDocumentId ? [`doc-${selectedDocumentId}`] : []}
|
||||
style={{ borderRight: 0 }}
|
||||
/>
|
||||
</div>
|
||||
</Drawer>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1698,6 +1698,424 @@ body {
|
||||
padding: 8px 12px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
|
||||
/* ======================================
|
||||
docs-view 新增优化样式
|
||||
====================================== */
|
||||
|
||||
/* 搜索框 */
|
||||
.admin-docs-view-search-wrapper {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.admin-docs-view-search-input {
|
||||
border-radius: var(--ant-border-radius);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.admin-docs-view-search-input:focus {
|
||||
box-shadow: 0 0 0 2px color-mix(in srgb, var(--ant-color-primary) 20%, transparent);
|
||||
}
|
||||
|
||||
/* 搜索结果计数 */
|
||||
.admin-docs-view-search-results-count {
|
||||
font-size: 12px;
|
||||
color: var(--ant-color-text-secondary);
|
||||
padding: 4px 16px 0;
|
||||
}
|
||||
|
||||
/* 搜索高亮 */
|
||||
.admin-docs-view-search-highlight {
|
||||
background: color-mix(in srgb, var(--ant-color-primary) 30%, transparent);
|
||||
color: var(--ant-color-primary);
|
||||
padding: 0 2px;
|
||||
border-radius: 2px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* 骨架屏目录 */
|
||||
.admin-docs-view-skeleton-menu {
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.admin-docs-view-skeleton-item {
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
/* 骨架屏内容 */
|
||||
.admin-docs-view-skeleton-content {
|
||||
max-width: 900px;
|
||||
margin: 0 auto;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
/* 菜单项文档标题(搜索模式) */
|
||||
.admin-docs-view-menu-doc-title,
|
||||
.admin-docs-view-menu-chapter-name {
|
||||
font-size: 14px;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
/* 标题锚点 */
|
||||
.admin-docs-view-heading-anchor {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.admin-docs-view-heading-link {
|
||||
position: absolute;
|
||||
left: -24px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
opacity: 0;
|
||||
color: var(--ant-color-text-secondary);
|
||||
font-size: 14px;
|
||||
transition: opacity 0.2s, color 0.2s;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 20px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.admin-docs-view-heading-anchor:hover .admin-docs-view-heading-link {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.admin-docs-view-heading-link:hover {
|
||||
color: var(--ant-color-primary);
|
||||
}
|
||||
|
||||
/* 代码块包装(带标题栏) */
|
||||
.admin-docs-view-code-block-wrapper {
|
||||
margin: 20px 0;
|
||||
border-radius: var(--ant-border-radius);
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--ant-color-border-secondary);
|
||||
}
|
||||
|
||||
.admin-docs-view-code-block-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 6px 12px;
|
||||
background: var(--fquiz-theme-table-header-bg);
|
||||
border-bottom: 1px solid var(--ant-color-border-secondary);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.admin-docs-view-code-lang {
|
||||
color: var(--ant-color-text-secondary);
|
||||
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
||||
text-transform: uppercase;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.admin-docs-view-copy-btn {
|
||||
font-size: 12px;
|
||||
color: var(--ant-color-text-secondary);
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
.admin-docs-view-copy-btn:hover {
|
||||
color: var(--ant-color-primary);
|
||||
}
|
||||
|
||||
/* 代码块(在包装器内调整) */
|
||||
.admin-docs-view-code-block-wrapper .admin-docs-view-code-block {
|
||||
margin: 0;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
/* 图片包装 */
|
||||
.admin-docs-view-image-wrapper {
|
||||
margin: 20px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.admin-docs-view-content-image {
|
||||
border-radius: var(--ant-border-radius);
|
||||
max-width: 100%;
|
||||
cursor: zoom-in;
|
||||
transition: box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.admin-docs-view-content-image:hover {
|
||||
box-shadow: 0 4px 16px color-mix(in srgb, var(--fquiz-theme-text-primary) 12%, transparent);
|
||||
}
|
||||
|
||||
.admin-docs-view-image-placeholder {
|
||||
min-height: 120px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--ant-color-fill-alter);
|
||||
border-radius: var(--ant-border-radius);
|
||||
}
|
||||
|
||||
.admin-docs-view-image-preview-mask {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: color-mix(in srgb, black 30%, transparent);
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
border-radius: var(--ant-border-radius);
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.admin-docs-view-image-wrapper:hover .admin-docs-view-image-preview-mask {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.admin-docs-view-image-caption {
|
||||
display: block;
|
||||
margin-top: 8px;
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 表格增强:交替行背景 */
|
||||
.admin-docs-view-table tbody tr:nth-child(even) {
|
||||
background: color-mix(in srgb, var(--ant-color-primary) 3%, transparent);
|
||||
}
|
||||
|
||||
.admin-docs-view-table tbody tr:hover {
|
||||
background: color-mix(in srgb, var(--ant-color-primary) 8%, transparent);
|
||||
}
|
||||
|
||||
/* 内容区滚动条美化 */
|
||||
.admin-docs-view-content::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.admin-docs-view-content::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.admin-docs-view-content::-webkit-scrollbar-thumb {
|
||||
background: var(--fquiz-scrollbar-thumb);
|
||||
border-radius: 4px;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.admin-docs-view-content::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--fquiz-scrollbar-thumb-hover);
|
||||
}
|
||||
|
||||
/* 侧栏菜单滚动条美化 */
|
||||
.admin-docs-view-sider-menu::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.admin-docs-view-sider-menu::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.admin-docs-view-sider-menu::-webkit-scrollbar-thumb {
|
||||
background: var(--fquiz-scrollbar-thumb);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.admin-docs-view-sider-menu::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--fquiz-scrollbar-thumb-hover);
|
||||
}
|
||||
|
||||
/* 移动端 Drawer 内容 */
|
||||
.admin-docs-view-mobile-drawer-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* 引用块增强 */
|
||||
.admin-docs-view-blockquote {
|
||||
margin: 20px 0;
|
||||
padding: 16px 20px;
|
||||
border-left: 4px solid var(--ant-color-primary);
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
color-mix(in srgb, var(--ant-color-primary) 6%, transparent) 0%,
|
||||
color-mix(in srgb, var(--ant-color-primary) 2%, transparent) 100%
|
||||
);
|
||||
border-radius: 0 var(--ant-border-radius) var(--ant-border-radius) 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.admin-docs-view-blockquote::before {
|
||||
content: '"';
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
left: 8px;
|
||||
font-size: 32px;
|
||||
color: color-mix(in srgb, var(--ant-color-primary) 20%, transparent);
|
||||
font-family: Georgia, serif;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* 暗色主题适配 */
|
||||
:root[data-fquiz-theme="dark"] .admin-docs-view-code-block-header {
|
||||
background: color-mix(in srgb, var(--ant-color-bg-container) 80%, black);
|
||||
border-bottom-color: color-mix(in srgb, var(--ant-color-border-secondary) 50%, transparent);
|
||||
}
|
||||
|
||||
:root[data-fquiz-theme="dark"] .admin-docs-view-table tbody tr:nth-child(even) {
|
||||
background: color-mix(in srgb, black 15%, transparent);
|
||||
}
|
||||
|
||||
:root[data-fquiz-theme="dark"] .admin-docs-view-table tbody tr:hover {
|
||||
background: color-mix(in srgb, var(--ant-color-primary) 12%, transparent);
|
||||
}
|
||||
|
||||
:root[data-fquiz-theme="dark"] .admin-docs-view-image-placeholder {
|
||||
background: color-mix(in srgb, var(--ant-color-bg-container) 80%, black);
|
||||
}
|
||||
|
||||
:root[data-fquiz-theme="dark"] .admin-docs-view-blockquote {
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
color-mix(in srgb, var(--ant-color-primary) 10%, transparent) 0%,
|
||||
color-mix(in srgb, var(--ant-color-primary) 4%, transparent) 100%
|
||||
);
|
||||
}
|
||||
|
||||
/* 移动端增强 */
|
||||
@media (max-width: 767px) {
|
||||
.admin-docs-view-document-header {
|
||||
padding-bottom: 12px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.admin-docs-view-markdown-content {
|
||||
font-size: 15px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.admin-docs-view-image-caption {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.admin-docs-view-blockquote {
|
||||
padding: 12px 16px;
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
.admin-docs-view-heading-link {
|
||||
left: -20px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ======================================
|
||||
highlight.js 语法高亮主题
|
||||
====================================== */
|
||||
.admin-docs-view-code-block code {
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
font-family: 'Consolas', 'Monaco', 'Courier New', 'JetBrains Mono', monospace;
|
||||
font-size: 14px;
|
||||
color: var(--ant-color-text);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Light theme */
|
||||
.admin-docs-view-code-block .hljs {
|
||||
color: #24292e;
|
||||
}
|
||||
|
||||
.admin-docs-view-code-block .hljs-keyword,
|
||||
.admin-docs-view-code-block .hljs-selector-tag,
|
||||
.admin-docs-view-code-block .hljs-type { color: #d73a49; }
|
||||
|
||||
.admin-docs-view-code-block .hljs-string,
|
||||
.admin-docs-view-code-block .hljs-addition { color: #032f62; }
|
||||
|
||||
.admin-docs-view-code-block .hljs-number,
|
||||
.admin-docs-view-code-block .hljs-literal { color: #005cc5; }
|
||||
|
||||
.admin-docs-view-code-block .hljs-comment,
|
||||
.admin-docs-view-code-block .hljs-quote { color: #6a737d; font-style: italic; }
|
||||
|
||||
.admin-docs-view-code-block .hljs-built_in,
|
||||
.admin-docs-view-code-block .hljs-title.class_ { color: #e36209; }
|
||||
|
||||
.admin-docs-view-code-block .hljs-title.function_,
|
||||
.admin-docs-view-code-block .hljs-title { color: #6f42c1; }
|
||||
|
||||
.admin-docs-view-code-block .hljs-attr,
|
||||
.admin-docs-view-code-block .hljs-attribute { color: #005cc5; }
|
||||
|
||||
.admin-docs-view-code-block .hljs-variable,
|
||||
.admin-docs-view-code-block .hljs-template-variable { color: #e36209; }
|
||||
|
||||
.admin-docs-view-code-block .hljs-regexp { color: #032f62; }
|
||||
|
||||
.admin-docs-view-code-block .hljs-tag { color: #22863a; }
|
||||
|
||||
.admin-docs-view-code-block .hljs-name { color: #22863a; }
|
||||
|
||||
.admin-docs-view-code-block .hljs-selector-class { color: #6f42c1; }
|
||||
|
||||
.admin-docs-view-code-block .hljs-meta { color: #005cc5; }
|
||||
|
||||
/* Dark theme */
|
||||
:root[data-fquiz-theme="dark"] .admin-docs-view-code-block .hljs {
|
||||
color: #e6e6e6;
|
||||
}
|
||||
|
||||
:root[data-fquiz-theme="dark"] .admin-docs-view-code-block .hljs-keyword,
|
||||
:root[data-fquiz-theme="dark"] .admin-docs-view-code-block .hljs-selector-tag,
|
||||
:root[data-fquiz-theme="dark"] .admin-docs-view-code-block .hljs-type { color: #ff7b72; }
|
||||
|
||||
:root[data-fquiz-theme="dark"] .admin-docs-view-code-block .hljs-string,
|
||||
:root[data-fquiz-theme="dark"] .admin-docs-view-code-block .hljs-addition { color: #a5d6ff; }
|
||||
|
||||
:root[data-fquiz-theme="dark"] .admin-docs-view-code-block .hljs-number,
|
||||
:root[data-fquiz-theme="dark"] .admin-docs-view-code-block .hljs-literal { color: #79c0ff; }
|
||||
|
||||
:root[data-fquiz-theme="dark"] .admin-docs-view-code-block .hljs-comment,
|
||||
:root[data-fquiz-theme="dark"] .admin-docs-view-code-block .hljs-quote { color: #8b949e; font-style: italic; }
|
||||
|
||||
:root[data-fquiz-theme="dark"] .admin-docs-view-code-block .hljs-built_in,
|
||||
:root[data-fquiz-theme="dark"] .admin-docs-view-code-block .hljs-title.class_ { color: #ffa657; }
|
||||
|
||||
:root[data-fquiz-theme="dark"] .admin-docs-view-code-block .hljs-title.function_,
|
||||
:root[data-fquiz-theme="dark"] .admin-docs-view-code-block .hljs-title { color: #d2a8ff; }
|
||||
|
||||
:root[data-fquiz-theme="dark"] .admin-docs-view-code-block .hljs-attr,
|
||||
:root[data-fquiz-theme="dark"] .admin-docs-view-code-block .hljs-attribute { color: #79c0ff; }
|
||||
|
||||
:root[data-fquiz-theme="dark"] .admin-docs-view-code-block .hljs-variable,
|
||||
:root[data-fquiz-theme="dark"] .admin-docs-view-code-block .hljs-template-variable { color: #ffa657; }
|
||||
|
||||
:root[data-fquiz-theme="dark"] .admin-docs-view-code-block .hljs-regexp { color: #a5d6ff; }
|
||||
|
||||
:root[data-fquiz-theme="dark"] .admin-docs-view-code-block .hljs-tag { color: #7ee787; }
|
||||
|
||||
:root[data-fquiz-theme="dark"] .admin-docs-view-code-block .hljs-name { color: #7ee787; }
|
||||
|
||||
:root[data-fquiz-theme="dark"] .admin-docs-view-code-block .hljs-selector-class { color: #d2a8ff; }
|
||||
|
||||
:root[data-fquiz-theme="dark"] .admin-docs-view-code-block .hljs-meta { color: #79c0ff; }
|
||||
|
||||
/* 内联引用:侧栏当前文档高亮增强 */
|
||||
.ant-menu-item-selected {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.ant-menu-item-selected::after {
|
||||
opacity: 1 !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* 表格边框优化 - 仅外边框,上下圆角对齐 */
|
||||
|
||||
Reference in New Issue
Block a user