完善了MD内部锚点和页面链接
parent
e9691882f0
commit
1d1f50ec10
|
|
@ -503,7 +503,7 @@ function PageTemplate() {
|
|||
- 顶部导航栏(搜索、消息、用户信息)
|
||||
- 内容区域(可滚动)
|
||||
|
||||
**详细文档**: [主框架页面设计规范](../docs/pages/main-layout.md)
|
||||
**详细文档**: [主框架页面设计规范](../docs/layouts/main-layout.md)
|
||||
|
||||
**主要特性**:
|
||||
- 基于 NEX Logo 的品牌配色 (#b8178d)
|
||||
|
|
|
|||
|
|
@ -150,7 +150,7 @@
|
|||
| 阴影 | 0 1px 4px rgba(0, 21, 41, 0.08) |
|
||||
| 位置 | sticky,top: 0 |
|
||||
| 层级 | z-index: 9 |
|
||||
| 内边距 | 0 24px |
|
||||
| 内边距 | 0 16px |
|
||||
|
||||
### 2.2 左侧区域
|
||||
|
||||
|
|
@ -223,14 +223,14 @@
|
|||
| 属性 | 值 |
|
||||
|------|---|
|
||||
| 背景色 | #f5f5f5 |
|
||||
| 内边距 | 24px |
|
||||
| 内边距 | 16px |
|
||||
| 高度 | calc(100vh - 64px) |
|
||||
| 滚动 | overflow-y: auto |
|
||||
|
||||
### 3.2 内容容器
|
||||
|
||||
- 最大宽度:根据业务需求,建议 1200-1600px
|
||||
- 内边距:24px
|
||||
- 内边距:16px
|
||||
- 背景色:根据内容类型,卡片为 #fff
|
||||
|
||||
### 3.3 滚动行为
|
||||
|
|
@ -239,6 +239,8 @@
|
|||
- 顶部导航栏和侧边栏保持固定
|
||||
- 滚动条样式与全局一致
|
||||
|
||||
详细设计见:[主内容区布局](./content-area-layout.md)
|
||||
|
||||
---
|
||||
|
||||
## 4. 响应式适配
|
||||
|
|
@ -378,63 +380,49 @@ MainLayout/
|
|||
|
||||
---
|
||||
|
||||
## 8. 示例页面
|
||||
## 8. 可访问性
|
||||
|
||||
### 8.1 概览页 (Overview)
|
||||
|
||||
作为主框架的示例页面,展示了:
|
||||
- 统计卡片布局
|
||||
- 图表展示
|
||||
- 数据可视化
|
||||
- 响应式栅格系统
|
||||
|
||||
详细设计见:[概览页设计文档](./overview.md)
|
||||
|
||||
---
|
||||
|
||||
## 9. 可访问性
|
||||
|
||||
### 9.1 键盘导航
|
||||
### 8.1 键盘导航
|
||||
|
||||
- 支持 Tab 键在可交互元素间切换
|
||||
- 支持 Enter 键激活菜单项
|
||||
- 支持方向键在菜单间导航
|
||||
|
||||
### 9.2 语义化标签
|
||||
### 8.2 语义化标签
|
||||
|
||||
- 使用 nav 标签包裹导航菜单
|
||||
- 使用 header 标签包裹顶部栏
|
||||
- 使用 main 标签包裹主内容区
|
||||
|
||||
### 9.3 对比度
|
||||
### 8.3 对比度
|
||||
|
||||
- 所有文本与背景对比度 ≥ 4.5:1
|
||||
- 图标与背景对比度 ≥ 3:1
|
||||
|
||||
---
|
||||
|
||||
## 10. 性能优化
|
||||
## 9. 性能优化
|
||||
|
||||
### 10.1 懒加载
|
||||
### 9.1 懒加载
|
||||
|
||||
- 页面组件使用 React.lazy 懒加载
|
||||
- 减少首屏加载时间
|
||||
|
||||
### 10.2 防抖优化
|
||||
### 9.2 防抖优化
|
||||
|
||||
- 搜索框输入使用防抖处理
|
||||
- 窗口大小变化使用节流处理
|
||||
|
||||
### 10.3 虚拟滚动
|
||||
### 9.3 虚拟滚动
|
||||
|
||||
- 菜单项较多时考虑虚拟滚动
|
||||
- 长列表使用虚拟化技术
|
||||
|
||||
---
|
||||
|
||||
## 11. 开发指南
|
||||
## 10. 开发指南
|
||||
|
||||
### 11.1 添加新菜单
|
||||
### 10.1 添加新菜单
|
||||
|
||||
1. 编辑 `src/constants/menuData.json`
|
||||
2. 添加菜单项配置
|
||||
|
|
@ -442,13 +430,13 @@ MainLayout/
|
|||
4. 创建对应的页面组件
|
||||
5. 在 `App.jsx` 中添加路由
|
||||
|
||||
### 11.2 自定义主题
|
||||
### 10.2 自定义主题
|
||||
|
||||
1. 编辑 `src/main.jsx` 中的 theme 配置
|
||||
2. 修改 `tailwind.config.js` 中的颜色系统
|
||||
3. 更新 `src/styles/globals.css` 中的 CSS 变量
|
||||
|
||||
### 11.3 扩展功能
|
||||
### 10.3 扩展功能
|
||||
|
||||
- 添加面包屑导航
|
||||
- 添加页签 (Tabs) 功能
|
||||
|
|
@ -457,7 +445,7 @@ MainLayout/
|
|||
|
||||
---
|
||||
|
||||
## 12. 常见问题
|
||||
## 11. 常见问题
|
||||
|
||||
### Q1: 如何修改侧边栏默认展开状态?
|
||||
|
||||
|
|
|
|||
|
|
@ -11,12 +11,14 @@
|
|||
"@ant-design/icons": "^5.2.6",
|
||||
"antd": "^5.12.0",
|
||||
"echarts": "^6.0.0",
|
||||
"github-slugger": "^2.0.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-router-dom": "^6.20.0",
|
||||
"rehype-highlight": "^7.0.2",
|
||||
"rehype-raw": "^7.0.0",
|
||||
"rehype-slug": "^6.0.0",
|
||||
"remark-gfm": "^4.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
@ -2844,6 +2846,12 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/github-slugger": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/github-slugger/-/github-slugger-2.0.0.tgz",
|
||||
"integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "10.4.5",
|
||||
"resolved": "https://registry.npmmirror.com/glob/-/glob-10.4.5.tgz",
|
||||
|
|
@ -3071,6 +3079,19 @@
|
|||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/hast-util-heading-rank": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/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.npmmirror.com/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz",
|
||||
|
|
@ -3178,6 +3199,19 @@
|
|||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/hast-util-to-string": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/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.npmmirror.com/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz",
|
||||
|
|
@ -6458,6 +6492,23 @@
|
|||
"url": "https://opencollective.com/unified"
|
||||
}
|
||||
},
|
||||
"node_modules/rehype-slug": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmmirror.com/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-gfm": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/remark-gfm/-/remark-gfm-4.0.1.tgz",
|
||||
|
|
|
|||
|
|
@ -14,12 +14,14 @@
|
|||
"@ant-design/icons": "^5.2.6",
|
||||
"antd": "^5.12.0",
|
||||
"echarts": "^6.0.0",
|
||||
"github-slugger": "^2.0.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-router-dom": "^6.20.0",
|
||||
"rehype-highlight": "^7.0.2",
|
||||
"rehype-raw": "^7.0.0",
|
||||
"rehype-slug": "^6.0.0",
|
||||
"remark-gfm": "^4.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import { useState, useEffect } from 'react'
|
||||
import { Layout, Menu, Spin } from 'antd'
|
||||
import { FileTextOutlined } from '@ant-design/icons'
|
||||
import { useState, useEffect, useRef } from 'react'
|
||||
import { Layout, Menu, Spin, FloatButton } from 'antd'
|
||||
import { FileTextOutlined, VerticalAlignTopOutlined } from '@ant-design/icons'
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
import rehypeRaw from 'rehype-raw'
|
||||
import rehypeSlug from 'rehype-slug'
|
||||
import rehypeHighlight from 'rehype-highlight'
|
||||
import 'highlight.js/styles/github.css'
|
||||
import docsMenuData from '../data/docsMenuData.json'
|
||||
|
|
@ -15,6 +16,21 @@ function DocsPage() {
|
|||
const [selectedKey, setSelectedKey] = useState('design-cookbook')
|
||||
const [markdownContent, setMarkdownContent] = useState('')
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [currentDocPath, setCurrentDocPath] = useState('')
|
||||
const contentRef = useRef(null)
|
||||
|
||||
// 构建文档路径到 key 的映射
|
||||
const buildPathToKeyMap = () => {
|
||||
const map = {}
|
||||
docsMenuData.forEach((group) => {
|
||||
group.children.forEach((item) => {
|
||||
map[item.path] = item.key
|
||||
})
|
||||
})
|
||||
return map
|
||||
}
|
||||
|
||||
const pathToKeyMap = buildPathToKeyMap()
|
||||
|
||||
// 构建菜单项
|
||||
const menuItems = docsMenuData.map((group) => ({
|
||||
|
|
@ -47,6 +63,12 @@ function DocsPage() {
|
|||
if (response.ok) {
|
||||
const text = await response.text()
|
||||
setMarkdownContent(text)
|
||||
setCurrentDocPath(path)
|
||||
|
||||
// 滚动到顶部
|
||||
if (contentRef.current) {
|
||||
contentRef.current.scrollTo({ top: 0, behavior: 'smooth' })
|
||||
}
|
||||
} else {
|
||||
setMarkdownContent('# 文档加载失败\n\n无法加载该文档,请稍后重试。')
|
||||
}
|
||||
|
|
@ -64,6 +86,90 @@ function DocsPage() {
|
|||
loadMarkdown(key)
|
||||
}
|
||||
|
||||
// 解析相对路径,返回绝对路径
|
||||
const resolvePath = (currentPath, relativePath) => {
|
||||
// 如果是绝对路径,直接返回
|
||||
if (relativePath.startsWith('/')) {
|
||||
return relativePath
|
||||
}
|
||||
|
||||
// 获取当前文档的目录
|
||||
const currentDir = currentPath.substring(0, currentPath.lastIndexOf('/'))
|
||||
|
||||
// 处理相对路径
|
||||
const parts = relativePath.split('/')
|
||||
const dirParts = currentDir.split('/')
|
||||
|
||||
for (const part of parts) {
|
||||
if (part === '..') {
|
||||
dirParts.pop()
|
||||
} else if (part !== '.' && part !== '') {
|
||||
dirParts.push(part)
|
||||
}
|
||||
}
|
||||
|
||||
return dirParts.join('/')
|
||||
}
|
||||
|
||||
// 处理文档链接点击
|
||||
const handleDocLinkClick = (href) => {
|
||||
// 解析相对路径为绝对路径
|
||||
const absolutePath = resolvePath(currentDocPath, href)
|
||||
|
||||
// 查找对应的文档 key
|
||||
const targetKey = pathToKeyMap[absolutePath]
|
||||
|
||||
if (targetKey) {
|
||||
setSelectedKey(targetKey)
|
||||
loadMarkdown(targetKey)
|
||||
} else {
|
||||
console.warn('未找到文档:', absolutePath)
|
||||
}
|
||||
}
|
||||
|
||||
// 自定义 ReactMarkdown 组件
|
||||
const markdownComponents = {
|
||||
// 自定义链接组件
|
||||
a: ({ node, href, children, ...props }) => {
|
||||
// 判断是否为内部 .md 文档链接
|
||||
if (href && href.endsWith('.md')) {
|
||||
return (
|
||||
<a
|
||||
{...props}
|
||||
href="#"
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
handleDocLinkClick(href)
|
||||
}}
|
||||
style={{ color: '#1677ff', cursor: 'pointer', textDecoration: 'underline' }}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
|
||||
// 判断是否为锚点链接(页面内跳转)
|
||||
if (href && href.startsWith('#')) {
|
||||
return (
|
||||
<a
|
||||
{...props}
|
||||
href={href}
|
||||
style={{ color: '#1677ff', cursor: 'pointer', textDecoration: 'underline' }}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
|
||||
// 外部链接
|
||||
return (
|
||||
<a href={href} target="_blank" rel="noopener noreferrer" {...props}>
|
||||
{children}
|
||||
</a>
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
// 初始加载默认文档
|
||||
useEffect(() => {
|
||||
loadMarkdown(selectedKey)
|
||||
|
|
@ -88,7 +194,7 @@ function DocsPage() {
|
|||
</Sider>
|
||||
|
||||
{/* 右侧内容 */}
|
||||
<Content className="docs-content">
|
||||
<Content className="docs-content" ref={contentRef}>
|
||||
<div className="docs-content-wrapper">
|
||||
{loading ? (
|
||||
<div className="docs-loading">
|
||||
|
|
@ -98,13 +204,26 @@ function DocsPage() {
|
|||
<div className="markdown-body">
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[remarkGfm]}
|
||||
rehypePlugins={[rehypeRaw, rehypeHighlight]}
|
||||
rehypePlugins={[rehypeRaw, rehypeSlug, rehypeHighlight]}
|
||||
components={markdownComponents}
|
||||
>
|
||||
{markdownContent}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 返回顶部按钮 */}
|
||||
<FloatButton
|
||||
icon={<VerticalAlignTopOutlined />}
|
||||
type="primary"
|
||||
style={{ right: 24 }}
|
||||
onClick={() => {
|
||||
if (contentRef.current) {
|
||||
contentRef.current.scrollTo({ top: 0, behavior: 'smooth' })
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Content>
|
||||
</Layout>
|
||||
</div>
|
||||
|
|
|
|||
30
yarn.lock
30
yarn.lock
|
|
@ -1629,6 +1629,11 @@ get-symbol-description@^1.1.0:
|
|||
es-errors "^1.3.0"
|
||||
get-intrinsic "^1.2.6"
|
||||
|
||||
github-slugger@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmmirror.com/github-slugger/-/github-slugger-2.0.0.tgz"
|
||||
integrity sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==
|
||||
|
||||
glob-parent@^5.1.2, glob-parent@~5.1.2:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz"
|
||||
|
|
@ -1749,6 +1754,13 @@ hast-util-from-parse5@^8.0.0:
|
|||
vfile-location "^5.0.0"
|
||||
web-namespaces "^2.0.0"
|
||||
|
||||
hast-util-heading-rank@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmmirror.com/hast-util-heading-rank/-/hast-util-heading-rank-3.0.0.tgz"
|
||||
integrity sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA==
|
||||
dependencies:
|
||||
"@types/hast" "^3.0.0"
|
||||
|
||||
hast-util-is-element@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmmirror.com/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz"
|
||||
|
|
@ -1816,6 +1828,13 @@ hast-util-to-parse5@^8.0.0:
|
|||
web-namespaces "^2.0.0"
|
||||
zwitch "^2.0.0"
|
||||
|
||||
hast-util-to-string@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.npmmirror.com/hast-util-to-string/-/hast-util-to-string-3.0.1.tgz"
|
||||
integrity sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==
|
||||
dependencies:
|
||||
"@types/hast" "^3.0.0"
|
||||
|
||||
hast-util-to-text@^4.0.0:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.npmmirror.com/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz"
|
||||
|
|
@ -3574,6 +3593,17 @@ rehype-raw@^7.0.0:
|
|||
hast-util-raw "^9.0.0"
|
||||
vfile "^6.0.0"
|
||||
|
||||
rehype-slug@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.npmmirror.com/rehype-slug/-/rehype-slug-6.0.0.tgz"
|
||||
integrity sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A==
|
||||
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"
|
||||
|
||||
remark-gfm@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.npmmirror.com/remark-gfm/-/remark-gfm-4.0.1.tgz"
|
||||
|
|
|
|||
Loading…
Reference in New Issue