md2ui 1.0.2 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # md2ui
2
2
 
3
+ [![GitHub](https://img.shields.io/badge/GitHub-devsneed%2Fmd2ui-blue?logo=github)](https://github.com/devsneed/md2ui)
4
+
3
5
  一个轻量级的文档渲染系统,将本地 Markdown 文档转换为美观的 HTML 页面。
4
6
 
5
7
  ## 核心特性
@@ -63,7 +65,7 @@ your-docs/
63
65
 
64
66
  ```bash
65
67
  # 克隆项目
66
- git clone https://github.com/user/md2ui.git
68
+ git clone https://github.com/devsneed/md2ui.git
67
69
  cd md2ui
68
70
 
69
71
  # 安装依赖
@@ -81,8 +83,8 @@ pnpm dev
81
83
  ### 本地测试 CLI
82
84
 
83
85
  ```bash
84
- # 链接到全局
85
- pnpm link --global
86
+ # 安装到全局
87
+ npm install -g .
86
88
 
87
89
  # 在任意目录测试
88
90
  cd /path/to/test/docs
package/bin/md2ui.js CHANGED
@@ -10,16 +10,19 @@ const __filename = fileURLToPath(import.meta.url)
10
10
  const __dirname = dirname(__filename)
11
11
  const pkgRoot = resolve(__dirname, '..')
12
12
 
13
+ // 导入共享配置
14
+ const { config } = await import(resolve(pkgRoot, 'src/config.js'))
15
+
13
16
  // 用户执行命令的目录
14
17
  const userDir = process.cwd()
15
18
 
16
19
  // 解析命令行参数
17
20
  const args = process.argv.slice(2)
18
- let port = 3000
21
+ let port = config.defaultPort
19
22
 
20
23
  for (let i = 0; i < args.length; i++) {
21
24
  if (args[i] === '-p' || args[i] === '--port') {
22
- port = parseInt(args[i + 1]) || 3000
25
+ port = parseInt(args[i + 1]) || config.defaultPort
23
26
  }
24
27
  }
25
28
 
@@ -70,7 +73,7 @@ function scanDocs(dir, basePath = '', level = 0) {
70
73
  order: match ? parseInt(match[1]) : 999,
71
74
  type: 'folder',
72
75
  level,
73
- expanded: true,
76
+ expanded: config.folderExpanded,
74
77
  children
75
78
  })
76
79
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "md2ui",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "type": "module",
5
5
  "description": "将本地 Markdown 文档转换为美观的 HTML 页面",
6
6
  "author": "",
package/src/App.vue CHANGED
@@ -2,7 +2,15 @@
2
2
  <div class="container">
3
3
  <aside v-if="!sidebarCollapsed" class="sidebar" :style="{ width: sidebarWidth + 'px' }">
4
4
  <div class="logo">
5
- <Logo @go-home="loadReadme" />
5
+ <div class="logo-group">
6
+ <Logo @go-home="loadFirstDoc" />
7
+ <a href="https://github.com/devsneed/md2ui" target="_blank" class="github-link" title="GitHub">
8
+ <Github :size="14" />
9
+ </a>
10
+ </div>
11
+ <button class="sidebar-toggle" @click="sidebarCollapsed = true" title="收起导航">
12
+ <PanelLeftClose :size="16" />
13
+ </button>
6
14
  </div>
7
15
  <nav class="nav-menu">
8
16
  <div class="nav-section">
@@ -30,20 +38,14 @@
30
38
  <button v-if="sidebarCollapsed" class="expand-btn expand-btn-left" @click="sidebarCollapsed = false" title="展开导航">
31
39
  <PanelLeftOpen :size="16" />
32
40
  </button>
33
- <button v-else class="collapse-btn collapse-btn-left" @click="sidebarCollapsed = true" title="收起导航">
34
- <PanelLeftClose :size="16" />
35
- </button>
36
41
  <main class="content" @scroll="handleScroll" @click="handleContentClick">
37
42
  <article class="markdown-content" v-html="htmlContent"></article>
38
43
  </main>
39
44
  <div v-if="!tocCollapsed && tocItems.length > 0" class="resizer resizer-right" @mousedown="startResize('right', $event)"></div>
40
- <TableOfContents :tocItems="tocItems" :activeHeading="activeHeading" :collapsed="tocCollapsed" :width="tocWidth" @scroll-to="scrollToHeading" />
45
+ <TableOfContents :tocItems="tocItems" :activeHeading="activeHeading" :collapsed="tocCollapsed" :width="tocWidth" @toggle="tocCollapsed = !tocCollapsed" @scroll-to="scrollToHeading" />
41
46
  <button v-if="tocCollapsed && tocItems.length > 0" class="expand-btn expand-btn-right" @click="tocCollapsed = false" title="展开目录">
42
47
  <PanelRightOpen :size="16" />
43
48
  </button>
44
- <button v-else-if="tocItems.length > 0" class="collapse-btn collapse-btn-right" @click="tocCollapsed = true" title="收起目录">
45
- <PanelRightClose :size="16" />
46
- </button>
47
49
  <transition name="fade">
48
50
  <button v-if="showBackToTop" class="back-to-top" @click="scrollToTop" title="返回顶部">
49
51
  <ArrowUp :size="20" />
@@ -56,7 +58,7 @@
56
58
 
57
59
  <script setup>
58
60
  import { ref, onMounted } from 'vue'
59
- import { ChevronsDownUp, ChevronsUpDown, ArrowUp, PanelLeftOpen, PanelLeftClose, PanelRightOpen, PanelRightClose } from 'lucide-vue-next'
61
+ import { ChevronsDownUp, ChevronsUpDown, ArrowUp, PanelLeftOpen, PanelLeftClose, PanelRightOpen, Github } from 'lucide-vue-next'
60
62
  import Logo from './components/Logo.vue'
61
63
  import TreeNode from './components/TreeNode.vue'
62
64
  import TableOfContents from './components/TableOfContents.vue'
@@ -81,16 +83,11 @@ async function loadDocsList() {
81
83
  docsList.value = await getDocsList()
82
84
  }
83
85
 
84
- async function loadReadme() {
85
- currentDoc.value = ''
86
- try {
87
- const response = await fetch('/README.md')
88
- if (response.ok) {
89
- const content = await response.text()
90
- await renderMarkdown(content)
91
- }
92
- } catch (error) {
93
- console.error('加载首页失败:', error)
86
+ // 加载第一个文档
87
+ function loadFirstDoc() {
88
+ const firstDoc = findFirstDoc(docsList.value)
89
+ if (firstDoc) {
90
+ loadDoc(firstDoc.key)
94
91
  }
95
92
  }
96
93
 
@@ -164,8 +161,46 @@ function handleContentClick(event) {
164
161
  }
165
162
  }
166
163
 
164
+ // 查找第一个文档
165
+ function findFirstDoc(items) {
166
+ for (const item of items) {
167
+ if (item.type === 'file') return item
168
+ if (item.type === 'folder' && item.children) {
169
+ const found = findFirstDoc(item.children)
170
+ if (found) return found
171
+ }
172
+ }
173
+ return null
174
+ }
175
+
176
+ // 显示无文档提示
177
+ function showEmptyMessage() {
178
+ renderMarkdown(`# 当前目录没有 Markdown 文档
179
+
180
+ 请在当前目录下添加 \`.md\` 文件,然后刷新页面。
181
+
182
+ ## 文档组织示例
183
+
184
+ \`\`\`
185
+ your-docs/
186
+ ├── 00-快速开始.md
187
+ ├── 01-功能特性.md
188
+ └── 02-进阶指南/
189
+ ├── 01-目录结构.md
190
+ └── 02-自定义配置.md
191
+ \`\`\`
192
+ `)
193
+ }
194
+
167
195
  onMounted(async () => {
168
196
  await loadDocsList()
169
- await loadReadme()
197
+
198
+ // 如果有文档,加载第一个;否则显示提示
199
+ const firstDoc = findFirstDoc(docsList.value)
200
+ if (firstDoc) {
201
+ await loadDoc(firstDoc.key)
202
+ } else {
203
+ showEmptyMessage()
204
+ }
170
205
  })
171
206
  </script>
package/src/api/docs.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { config } from '../config.js'
2
+
1
3
  // 构建目录树结构(支持多层嵌套)
2
4
  function buildTree(files) {
3
5
  const root = { children: [] }
@@ -23,7 +25,7 @@ function buildTree(files) {
23
25
  order: match ? parseInt(match[1]) : 999,
24
26
  level: i,
25
27
  children: [],
26
- expanded: false
28
+ expanded: config.folderExpanded
27
29
  }
28
30
  currentLevel.children.push(folder)
29
31
  }
@@ -21,15 +21,15 @@ defineEmits(['go-home'])
21
21
  gap: 10px;
22
22
  color: #111827;
23
23
  cursor: pointer;
24
- transition: opacity 0.2s;
24
+ transition: all 0.15s;
25
25
  }
26
26
 
27
27
  .logo-container:hover {
28
- opacity: 0.7;
28
+ color: #3eaf7c;
29
29
  }
30
30
 
31
31
  .logo-container:active {
32
- opacity: 0.5;
32
+ transform: scale(0.96);
33
33
  }
34
34
 
35
35
  .logo-icon {
@@ -1,8 +1,13 @@
1
1
  <template>
2
2
  <aside class="toc-sidebar" v-if="tocItems.length > 0 && !collapsed" :style="{ width: width + 'px' }">
3
3
  <div class="toc-header">
4
- <List :size="16" />
5
- <span>目录</span>
4
+ <div class="toc-title">
5
+ <List :size="16" />
6
+ <span>目录</span>
7
+ </div>
8
+ <button class="toc-toggle" @click="$emit('toggle')" title="收起目录">
9
+ <PanelRightClose :size="16" />
10
+ </button>
6
11
  </div>
7
12
  <nav class="toc-nav">
8
13
  <a
@@ -19,7 +24,7 @@
19
24
  </template>
20
25
 
21
26
  <script setup>
22
- import { List } from 'lucide-vue-next'
27
+ import { List, PanelRightClose } from 'lucide-vue-next'
23
28
 
24
29
  defineProps({
25
30
  tocItems: {
@@ -40,5 +45,5 @@ defineProps({
40
45
  }
41
46
  })
42
47
 
43
- defineEmits(['scroll-to'])
48
+ defineEmits(['scroll-to', 'toggle'])
44
49
  </script>
@@ -7,12 +7,14 @@
7
7
  :class="{ expanded: item.expanded }"
8
8
  :style="{ paddingLeft: `${20 + item.level * 16}px` }"
9
9
  @click="$emit('toggle', item)"
10
+ @mouseenter="showTooltip($event, item.label)"
11
+ @mouseleave="hideTooltip"
10
12
  >
11
13
  <ChevronRight v-if="!item.expanded" class="nav-icon chevron-icon" :size="16" />
12
14
  <ChevronDown v-else class="nav-icon chevron-icon" :size="16" />
13
15
  <Folder v-if="!item.expanded" class="nav-icon folder-icon" :size="16" />
14
16
  <FolderOpen v-else class="nav-icon folder-icon" :size="16" />
15
- <span>{{ item.label }}</span>
17
+ <span class="nav-label">{{ item.label }}</span>
16
18
  </div>
17
19
 
18
20
  <!-- 递归渲染子节点 -->
@@ -34,9 +36,11 @@
34
36
  :class="{ active: currentDoc === item.key }"
35
37
  :style="{ paddingLeft: `${20 + item.level * 16}px` }"
36
38
  @click="$emit('select', item.key)"
39
+ @mouseenter="showTooltip($event, item.label)"
40
+ @mouseleave="hideTooltip"
37
41
  >
38
42
  <FileText class="nav-icon file-icon" :size="16" />
39
- <span>{{ item.label }}</span>
43
+ <span class="nav-label">{{ item.label }}</span>
40
44
  </div>
41
45
  </div>
42
46
  </template>
@@ -56,6 +60,55 @@ defineProps({
56
60
  })
57
61
 
58
62
  defineEmits(['toggle', 'select'])
63
+
64
+ let tooltipEl = null
65
+
66
+ function showTooltip(event, text) {
67
+ hideTooltip()
68
+
69
+ const target = event.currentTarget
70
+ const label = target.querySelector('.nav-label')
71
+
72
+ // 只有文字被截断时才显示 tooltip
73
+ if (label && label.scrollWidth <= label.clientWidth) return
74
+
75
+ tooltipEl = document.createElement('div')
76
+ tooltipEl.className = 'nav-tooltip'
77
+ tooltipEl.textContent = text
78
+ tooltipEl.style.cssText = `
79
+ position: fixed;
80
+ background: #1f2328;
81
+ color: #fff;
82
+ padding: 6px 10px;
83
+ border-radius: 6px;
84
+ font-size: 12px;
85
+ z-index: 9999;
86
+ max-width: 300px;
87
+ word-break: break-all;
88
+ user-select: text;
89
+ cursor: text;
90
+ box-shadow: 0 2px 8px rgba(0,0,0,0.2);
91
+ `
92
+
93
+ document.body.appendChild(tooltipEl)
94
+
95
+ const rect = target.getBoundingClientRect()
96
+ tooltipEl.style.left = `${rect.right + 8}px`
97
+ tooltipEl.style.top = `${rect.top}px`
98
+
99
+ // 防止超出屏幕右侧
100
+ const tooltipRect = tooltipEl.getBoundingClientRect()
101
+ if (tooltipRect.right > window.innerWidth - 10) {
102
+ tooltipEl.style.left = `${rect.left - tooltipRect.width - 8}px`
103
+ }
104
+ }
105
+
106
+ function hideTooltip() {
107
+ if (tooltipEl) {
108
+ tooltipEl.remove()
109
+ tooltipEl = null
110
+ }
111
+ }
59
112
  </script>
60
113
 
61
114
 
package/src/config.js ADDED
@@ -0,0 +1,8 @@
1
+ // 共享配置 - bin/md2ui.js 和 src/api/docs.js 共用
2
+ export const config = {
3
+ // 默认端口
4
+ defaultPort: 3000,
5
+
6
+ // 文件夹默认展开状态
7
+ folderExpanded: false
8
+ }
package/src/style.css CHANGED
@@ -5,13 +5,12 @@
5
5
  }
6
6
 
7
7
  body {
8
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", Arial, sans-serif;
9
- line-height: 1.75;
10
- color: #1a202c;
11
- background: #f7fafc;
8
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Noto Sans", Helvetica, Arial, sans-serif;
9
+ line-height: 1.6;
10
+ color: #1f2328;
11
+ background: #ffffff;
12
12
  -webkit-font-smoothing: antialiased;
13
- -moz-osx-font-smoothing: grayscale;
14
- font-weight: 400;
13
+ font-size: 16px;
15
14
  }
16
15
 
17
16
  #app {
@@ -27,19 +26,59 @@ body {
27
26
  .sidebar {
28
27
  min-width: 200px;
29
28
  max-width: 400px;
30
- background: #ffffff;
31
- border-right: 1px solid #e2e8f0;
29
+ background: #f6f8fa;
30
+ border-right: 1px solid #d0d7de;
32
31
  overflow-y: auto;
33
32
  flex-shrink: 0;
34
- box-shadow: 2px 0 8px rgba(0, 0, 0, 0.02);
35
33
  }
36
34
 
37
35
  .logo {
38
- padding: 24px 24px;
39
- border-bottom: 1px solid #e2e8f0;
40
- background: #fafbfd;
36
+ padding: 16px;
37
+ border-bottom: 1px solid #d0d7de;
38
+ display: flex;
39
+ align-items: center;
40
+ justify-content: space-between;
41
+ }
42
+
43
+ .logo-group {
41
44
  display: flex;
42
45
  align-items: center;
46
+ gap: 8px;
47
+ }
48
+
49
+ .github-link {
50
+ display: flex;
51
+ align-items: center;
52
+ justify-content: center;
53
+ width: 24px;
54
+ height: 24px;
55
+ color: #ffffff;
56
+ background: #3eaf7c;
57
+ border-radius: 6px;
58
+ transition: all 0.15s;
59
+ }
60
+
61
+ .github-link:hover {
62
+ background: #2d8a5e;
63
+ }
64
+
65
+ .sidebar-toggle {
66
+ display: flex;
67
+ align-items: center;
68
+ justify-content: center;
69
+ width: 28px;
70
+ height: 28px;
71
+ border: 1px solid #d0d7de;
72
+ background: #ffffff;
73
+ color: #656d76;
74
+ cursor: pointer;
75
+ border-radius: 6px;
76
+ transition: all 0.1s;
77
+ }
78
+
79
+ .sidebar-toggle:hover {
80
+ background: #f6f8fa;
81
+ color: #1f2328;
43
82
  }
44
83
 
45
84
  .nav-menu {
@@ -50,12 +89,12 @@ body {
50
89
  display: flex;
51
90
  align-items: center;
52
91
  justify-content: space-between;
53
- padding: 20px 24px 8px;
54
- font-size: 11px;
55
- color: #718096;
92
+ padding: 12px 16px 8px;
93
+ font-size: 12px;
94
+ color: #656d76;
56
95
  font-weight: 600;
57
96
  text-transform: uppercase;
58
- letter-spacing: 0.05em;
97
+ letter-spacing: 0.02em;
59
98
  }
60
99
 
61
100
  .nav-actions {
@@ -67,60 +106,66 @@ body {
67
106
  display: flex;
68
107
  align-items: center;
69
108
  justify-content: center;
70
- width: 20px;
71
- height: 20px;
109
+ width: 24px;
110
+ height: 24px;
72
111
  border: none;
73
112
  background: transparent;
74
- color: #a0aec0;
113
+ color: #656d76;
75
114
  cursor: pointer;
76
- border-radius: 3px;
77
- transition: all 0.15s;
115
+ border-radius: 6px;
116
+ transition: all 0.1s;
78
117
  padding: 0;
79
118
  }
80
119
 
81
120
  .action-btn:hover {
82
- background: #edf2f7;
83
- color: #2d3748;
121
+ background: #d0d7de;
122
+ color: #1f2328;
84
123
  }
85
124
 
86
125
  .nav-item {
87
126
  display: flex;
88
127
  align-items: center;
89
- padding: 9px 24px;
90
- margin: 1px 0;
128
+ padding: 6px 16px;
91
129
  cursor: pointer;
92
- transition: all 0.15s;
93
- color: #4a5568;
94
- font-size: 13px;
95
- border-left: 2px solid transparent;
130
+ transition: all 0.1s;
131
+ color: #1f2328;
132
+ font-size: 14px;
133
+ border-radius: 6px;
134
+ margin: 1px 8px;
135
+ white-space: nowrap;
136
+ overflow: hidden;
137
+ text-overflow: ellipsis;
96
138
  }
97
139
 
98
140
  .nav-item:hover {
99
- background: #f7fafc;
100
- color: #1a202c;
141
+ background: #d0d7de;
101
142
  }
102
143
 
103
144
  .nav-item.active {
104
- background: #ebf4ff;
105
- color: #2b6cb0;
145
+ background: #e6f7e6;
146
+ color: #3eaf7c;
106
147
  font-weight: 500;
107
- border-left-color: #3182ce;
148
+ }
149
+
150
+ .nav-label {
151
+ overflow: hidden;
152
+ text-overflow: ellipsis;
153
+ white-space: nowrap;
108
154
  }
109
155
 
110
156
  /* 图标样式 */
111
157
  .nav-icon {
112
158
  flex-shrink: 0;
113
- margin-right: 10px;
114
- color: #a0aec0;
115
- transition: all 0.15s;
159
+ margin-right: 8px;
160
+ color: #656d76;
116
161
  }
117
162
 
118
163
  .nav-item:hover .nav-icon {
119
- color: #2d3748;
164
+ color: #1f2328;
120
165
  }
121
166
 
122
167
  .nav-item.active .nav-icon {
123
- color: #3182ce;
168
+ color: #3eaf7c;
124
169
  }
125
170
 
126
171
  .chevron-icon {
@@ -128,29 +173,20 @@ body {
128
173
  }
129
174
 
130
175
  .folder-icon {
131
- color: #ed8936;
176
+ color: #3eaf7c;
132
177
  }
133
178
 
134
179
  .file-icon {
135
- color: #a0aec0;
180
+ color: #656d76;
136
181
  }
137
182
 
138
183
  .home-icon {
139
- color: #4299e1;
184
+ color: #3eaf7c;
140
185
  }
141
186
 
142
187
  /* 文件夹样式 */
143
188
  .nav-folder {
144
189
  font-weight: 500;
145
- color: #2d3748;
146
- }
147
-
148
- .nav-folder:hover {
149
- background: #f7fafc;
150
- }
151
-
152
- .nav-folder:hover .folder-icon {
153
- color: #dd6b20;
154
190
  }
155
191
 
156
192
  /* 多级目录样式 */
@@ -164,7 +200,6 @@ body {
164
200
 
165
201
  .nav-item.level-2 {
166
202
  font-size: 13px;
167
- opacity: 0.9;
168
203
  }
169
204
 
170
205
  /* 内容区域样式 */
@@ -180,305 +215,143 @@ body {
180
215
  max-width: none;
181
216
  margin: 0 auto;
182
217
  background: #ffffff;
183
- padding: 64px 80px;
218
+ padding: 32px 48px;
184
219
  }
185
220
 
186
221
  /* Markdown 内容样式 */
187
222
  .markdown-content h1 {
188
223
  font-size: 32px;
189
- margin-bottom: 32px;
190
- padding-bottom: 16px;
191
- border-bottom: 2px solid #e2e8f0;
192
- color: #1a202c;
193
- font-weight: 700;
194
- letter-spacing: -0.02em;
224
+ margin-bottom: 16px;
225
+ padding-bottom: 8px;
226
+ border-bottom: 1px solid #d0d7de;
227
+ color: #1f2328;
228
+ font-weight: 600;
195
229
  line-height: 1.25;
196
230
  }
197
231
 
198
232
  .markdown-content h2 {
199
233
  font-size: 24px;
200
- margin-top: 56px;
201
- margin-bottom: 20px;
202
- color: #2d3748;
234
+ margin-top: 32px;
235
+ margin-bottom: 16px;
236
+ padding-bottom: 8px;
237
+ border-bottom: 1px solid #d0d7de;
238
+ color: #1f2328;
203
239
  font-weight: 600;
204
- letter-spacing: -0.01em;
205
- line-height: 1.35;
240
+ line-height: 1.25;
206
241
  }
207
242
 
208
243
  .markdown-content h3 {
209
- font-size: 18px;
210
- margin-top: 40px;
244
+ font-size: 20px;
245
+ margin-top: 24px;
211
246
  margin-bottom: 16px;
212
- color: #2d3748;
247
+ color: #1f2328;
213
248
  font-weight: 600;
214
- line-height: 1.5;
249
+ line-height: 1.25;
215
250
  }
216
251
 
217
252
  .markdown-content h4 {
218
253
  font-size: 16px;
219
- margin-top: 32px;
220
- margin-bottom: 12px;
221
- color: #4a5568;
254
+ margin-top: 24px;
255
+ margin-bottom: 16px;
256
+ color: #1f2328;
222
257
  font-weight: 600;
223
258
  }
224
259
 
225
260
  .markdown-content p {
226
- margin-bottom: 20px;
227
- color: #2d3748;
228
- font-size: 15px;
229
- line-height: 1.75;
261
+ margin-bottom: 16px;
262
+ color: #1f2328;
230
263
  }
231
264
 
232
265
  .markdown-content ul,
233
266
  .markdown-content ol {
234
- margin-bottom: 20px;
235
- padding-left: 24px;
267
+ margin-bottom: 16px;
268
+ padding-left: 2em;
236
269
  }
237
270
 
238
271
  .markdown-content li {
239
- margin-bottom: 8px;
240
- color: #2d3748;
241
- font-size: 15px;
242
- line-height: 1.75;
272
+ margin-bottom: 4px;
273
+ color: #1f2328;
243
274
  }
244
275
 
245
276
  .markdown-content code {
246
- background: #fef2f2;
247
- padding: 3px 8px;
248
- border-radius: 4px;
249
- font-family: "SF Mono", "Consolas", "Monaco", "Courier New", monospace;
250
- font-size: 13px;
251
- color: #dc2626;
252
- border: 1px solid #fecaca;
253
- font-weight: 500;
277
+ background: rgba(175, 184, 193, 0.2);
278
+ padding: 0.2em 0.4em;
279
+ border-radius: 6px;
280
+ font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
281
+ font-size: 85%;
282
+ color: #1f2328;
254
283
  }
255
284
 
256
285
  .markdown-content pre {
257
- background: #1e1e1e;
258
- padding: 20px 24px;
286
+ background: #24292f;
287
+ padding: 16px;
259
288
  border-radius: 6px;
260
289
  overflow-x: auto;
261
- margin-bottom: 24px;
262
- border: 1px solid #2d2d2d;
263
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
290
+ margin-bottom: 16px;
264
291
  }
265
292
 
266
293
  .markdown-content pre code {
267
294
  background: none;
268
- color: #d4d4d4;
295
+ color: #e6edf3;
269
296
  padding: 0;
270
- border: none;
271
- font-size: 13px;
272
- line-height: 1.7;
273
- display: block;
274
- font-weight: 400;
297
+ font-size: 85%;
298
+ line-height: 1.45;
275
299
  }
276
300
 
277
301
  .markdown-content a {
278
- color: #3182ce;
279
- text-decoration: underline;
280
- text-decoration-color: #90cdf4;
281
- text-underline-offset: 2px;
282
- transition: all 0.15s;
283
- font-weight: 500;
302
+ color: #3eaf7c;
303
+ text-decoration: none;
284
304
  }
285
305
 
286
306
  .markdown-content a:hover {
287
- color: #2c5282;
288
- text-decoration-color: #3182ce;
307
+ text-decoration: underline;
289
308
  }
290
309
 
291
- /* 表格容器 - 支持横向滚动 */
310
+ /* 表格样式 */
292
311
  .markdown-content .table-wrapper {
293
312
  width: 100%;
294
313
  overflow-x: auto;
295
- margin-bottom: 32px;
296
- border-radius: 8px;
297
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
314
+ margin-bottom: 16px;
298
315
  }
299
316
 
300
317
  .markdown-content table {
301
318
  width: 100%;
302
319
  border-collapse: collapse;
303
- margin-bottom: 0;
304
- border: 1px solid #cbd5e0;
305
- font-size: 14px;
306
- border-radius: 8px;
307
- overflow: hidden;
308
- table-layout: auto;
309
- background: #ffffff;
320
+ border-spacing: 0;
310
321
  }
311
322
 
312
323
  .markdown-content table th,
313
324
  .markdown-content table td {
314
- border: 1px solid #e2e8f0;
315
- padding: 14px 18px;
316
- text-align: left;
317
- vertical-align: top;
318
- word-wrap: break-word;
319
- word-break: break-word;
320
- min-width: 100px;
321
- line-height: 1.6;
325
+ border: 1px solid #d0d7de;
326
+ padding: 6px 13px;
322
327
  }
323
328
 
324
329
  .markdown-content table th {
325
- background: linear-gradient(to bottom, #f7fafc 0%, #edf2f7 100%);
326
- font-weight: 600;
327
- color: #2d3748;
328
- font-size: 13px;
329
- letter-spacing: 0.02em;
330
- white-space: nowrap;
331
- border-bottom: 2px solid #cbd5e0;
332
- position: sticky;
333
- top: 0;
334
- z-index: 10;
335
- }
336
-
337
- /* 第一列(表A)适中宽度 */
338
- .markdown-content table th:first-child,
339
- .markdown-content table td:first-child {
340
- width: 15%;
341
- min-width: 140px;
342
- font-weight: 500;
343
- }
344
-
345
- /* 第二列(关系类型)窄一些 */
346
- .markdown-content table th:nth-child(2),
347
- .markdown-content table td:nth-child(2) {
348
- width: 8%;
349
- min-width: 70px;
350
- text-align: center;
351
- font-weight: 500;
352
- }
353
-
354
- /* 第三列(表B)适中宽度 */
355
- .markdown-content table th:nth-child(3),
356
- .markdown-content table td:nth-child(3) {
357
- width: 15%;
358
- min-width: 140px;
359
- font-weight: 500;
360
- }
361
-
362
- /* 第四列(关联字段)较宽 */
363
- .markdown-content table th:nth-child(4),
364
- .markdown-content table td:nth-child(4) {
365
- width: 22%;
366
- min-width: 180px;
367
- font-family: "SF Mono", "Consolas", "Monaco", monospace;
368
- font-size: 13px;
369
- color: #4a5568;
370
- }
371
-
372
- /* 最后一列(说明)最宽,自动填充剩余空间 */
373
- .markdown-content table th:last-child,
374
- .markdown-content table td:last-child {
375
- width: 40%;
376
- min-width: 220px;
377
- color: #4a5568;
378
- }
379
-
380
- .markdown-content table tbody tr {
381
- transition: all 0.15s ease;
382
- border-bottom: 1px solid #f1f5f9;
383
- }
384
-
385
- .markdown-content table tbody tr:hover {
386
- background-color: #f8fafc;
387
- box-shadow: inset 0 0 0 1px #e2e8f0;
388
- }
389
-
390
- .markdown-content table tbody tr:last-child {
391
- border-bottom: none;
392
- }
393
-
394
- /* 字段名样式 */
395
- .markdown-content table .field-name-cell {
396
- font-family: "SF Mono", "Consolas", "Monaco", monospace;
397
- color: #dc2626;
398
- font-weight: 500;
399
- font-size: 13px;
400
- background-color: #fef2f2;
401
- }
402
-
403
- /* 必填标记样式 */
404
- .markdown-content table .required-yes {
405
- color: #2d3748;
330
+ background: #f6f8fa;
406
331
  font-weight: 600;
407
- text-align: center;
408
332
  }
409
333
 
410
- /* 表格滚动条样式 */
411
- .markdown-content .table-wrapper::-webkit-scrollbar {
412
- height: 8px;
413
- }
414
-
415
- .markdown-content .table-wrapper::-webkit-scrollbar-track {
416
- background: #f1f5f9;
417
- border-radius: 4px;
418
- }
419
-
420
- .markdown-content .table-wrapper::-webkit-scrollbar-thumb {
421
- background: #cbd5e0;
422
- border-radius: 4px;
423
- transition: background 0.2s;
424
- }
425
-
426
- .markdown-content .table-wrapper::-webkit-scrollbar-thumb:hover {
427
- background: #94a3b8;
334
+ .markdown-content table tr:nth-child(2n) {
335
+ background: #f6f8fa;
428
336
  }
429
337
 
430
338
  .markdown-content blockquote {
431
- border-left: 3px solid #4299e1;
432
- padding-left: 20px;
433
- margin: 24px 0;
434
- color: #4a5568;
435
- font-style: italic;
436
- background: #ebf8ff;
437
- padding: 16px 20px;
438
- border-radius: 4px;
339
+ border-left: 4px solid #d0d7de;
340
+ padding: 0 16px;
341
+ margin: 0 0 16px;
342
+ color: #656d76;
439
343
  }
440
344
 
441
345
  .markdown-content img {
442
346
  max-width: 100%;
443
347
  height: auto;
444
- border-radius: 4px;
445
- margin: 16px 0;
446
- transition: all 0.2s ease;
447
- }
448
-
449
- .markdown-content img:hover {
450
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
451
- transform: translateY(-2px);
452
- }
453
-
454
- /* 可放大图片样式 */
455
- .zoomable-image {
456
- cursor: zoom-in !important;
457
- position: relative;
458
- }
459
-
460
- .zoomable-image::after {
461
- content: '🔍';
462
- position: absolute;
463
- top: 8px;
464
- right: 8px;
465
- background: rgba(0, 0, 0, 0.7);
466
- color: white;
467
- padding: 4px 8px;
468
- border-radius: 4px;
469
- font-size: 12px;
470
- opacity: 0;
471
- transition: opacity 0.2s ease;
472
- pointer-events: none;
473
- }
474
-
475
- .zoomable-image:hover::after {
476
- opacity: 1;
348
+ border-radius: 6px;
349
+ margin: 8px 0;
477
350
  }
478
351
 
479
352
  /* Mermaid 图表样式 */
480
353
  .markdown-content .mermaid {
481
- margin: 24px 0;
354
+ margin: 16px 0;
482
355
  text-align: center;
483
356
  }
484
357
 
@@ -488,189 +361,167 @@ body {
488
361
  }
489
362
 
490
363
  .markdown-content .mermaid-error {
491
- color: #f56c6c;
492
- background: #fef0f0;
364
+ color: #cf222e;
365
+ background: #ffebe9;
493
366
  padding: 12px;
494
- border-radius: 4px;
495
- border: 1px solid #fde2e2;
496
- font-size: 14px;
367
+ border-radius: 6px;
368
+ border: 1px solid #ff8182;
497
369
  }
498
370
 
499
371
  /* 滚动条样式 */
500
372
  .sidebar::-webkit-scrollbar,
501
373
  .content::-webkit-scrollbar {
502
- width: 6px;
374
+ width: 8px;
503
375
  }
504
376
 
505
377
  .sidebar::-webkit-scrollbar-thumb,
506
378
  .content::-webkit-scrollbar-thumb {
507
- background: #dcdfe6;
508
- border-radius: 3px;
379
+ background: #d0d7de;
380
+ border-radius: 4px;
509
381
  }
510
382
 
511
383
  .sidebar::-webkit-scrollbar-thumb:hover,
512
384
  .content::-webkit-scrollbar-thumb:hover {
513
- background: #c0c4cc;
385
+ background: #afb8c1;
514
386
  }
515
387
 
516
388
  /* 返回顶部按钮 */
517
389
  .back-to-top {
518
390
  position: fixed;
519
- right: 32px;
520
- bottom: 32px;
521
- width: 56px;
522
- height: 56px;
391
+ right: 24px;
392
+ bottom: 24px;
393
+ width: 48px;
394
+ height: 48px;
523
395
  display: flex;
524
396
  flex-direction: column;
525
397
  align-items: center;
526
398
  justify-content: center;
527
- gap: 4px;
399
+ gap: 2px;
528
400
  background: #ffffff;
529
- color: #3182ce;
530
- border: 1px solid #cbd5e0;
531
- border-radius: 8px;
401
+ color: #656d76;
402
+ border: 1px solid #d0d7de;
403
+ border-radius: 6px;
532
404
  cursor: pointer;
533
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
534
- transition: all 0.2s;
405
+ box-shadow: 0 1px 3px rgba(31, 35, 40, 0.12);
406
+ transition: all 0.1s;
535
407
  z-index: 1000;
536
408
  }
537
409
 
538
410
  .back-to-top:hover {
539
- background: #3182ce;
540
- color: #ffffff;
541
- border-color: #3182ce;
542
- box-shadow: 0 6px 16px rgba(49, 130, 206, 0.3);
543
- transform: translateY(-2px);
544
- }
545
-
546
- .back-to-top:active {
547
- transform: translateY(0) scale(0.95);
411
+ background: #e6f7e6;
412
+ color: #3eaf7c;
413
+ border-color: #3eaf7c;
548
414
  }
549
415
 
550
416
  .progress-text {
551
- font-size: 14px;
552
- font-weight: 600;
553
- line-height: 1;
554
- color: inherit;
417
+ font-size: 12px;
418
+ font-weight: 500;
555
419
  }
556
420
 
557
421
  /* 淡入淡出动画 */
558
422
  .fade-enter-active,
559
423
  .fade-leave-active {
560
- transition: opacity 0.3s, transform 0.3s;
424
+ transition: opacity 0.2s;
561
425
  }
562
426
 
563
427
  .fade-enter-from,
564
428
  .fade-leave-to {
565
429
  opacity: 0;
566
- transform: translateY(10px);
567
430
  }
568
431
 
569
432
  /* 右侧目录样式 */
570
433
  .toc-sidebar {
571
434
  min-width: 200px;
572
- max-width: 400px;
435
+ max-width: 300px;
573
436
  background: #ffffff;
574
- border-left: 1px solid #e2e8f0;
437
+ border-left: 1px solid #d0d7de;
575
438
  overflow-y: auto;
576
439
  flex-shrink: 0;
577
- padding: 64px 0 32px;
578
- box-shadow: -2px 0 8px rgba(0, 0, 0, 0.02);
440
+ padding: 32px 0;
579
441
  }
580
442
 
581
443
  .toc-header {
444
+ display: flex;
445
+ align-items: center;
446
+ justify-content: space-between;
447
+ padding: 0 16px 12px;
448
+ border-bottom: 1px solid #d0d7de;
449
+ }
450
+
451
+ .toc-title {
582
452
  display: flex;
583
453
  align-items: center;
584
454
  gap: 8px;
585
- padding: 0 24px 16px;
586
- font-size: 11px;
455
+ font-size: 12px;
587
456
  font-weight: 600;
588
- color: #718096;
589
- border-bottom: 1px solid #e2e8f0;
590
- letter-spacing: 0.05em;
457
+ color: #1f2328;
591
458
  text-transform: uppercase;
459
+ letter-spacing: 0.02em;
460
+ }
461
+
462
+ .toc-toggle {
463
+ display: flex;
464
+ align-items: center;
465
+ justify-content: center;
466
+ width: 28px;
467
+ height: 28px;
468
+ border: 1px solid #d0d7de;
469
+ background: #ffffff;
470
+ color: #656d76;
471
+ cursor: pointer;
472
+ border-radius: 6px;
473
+ transition: all 0.1s;
474
+ }
475
+
476
+ .toc-toggle:hover {
477
+ background: #f6f8fa;
478
+ color: #1f2328;
592
479
  }
593
480
 
594
481
  .toc-nav {
595
- padding: 16px 0;
482
+ padding: 12px 0;
596
483
  }
597
484
 
598
485
  .toc-item {
599
486
  display: block;
600
- padding: 6px 24px;
601
- color: #718096;
487
+ padding: 4px 16px;
488
+ color: #656d76;
602
489
  text-decoration: none;
603
490
  font-size: 12px;
604
- line-height: 1.6;
605
- transition: all 0.15s;
491
+ line-height: 1.5;
492
+ transition: all 0.1s;
606
493
  border-left: 2px solid transparent;
607
- overflow: hidden;
608
- text-overflow: ellipsis;
609
- white-space: nowrap;
610
494
  }
611
495
 
612
496
  .toc-item:hover {
613
- color: #2d3748;
614
- background: #f7fafc;
497
+ color: #3eaf7c;
615
498
  }
616
499
 
617
500
  .toc-item.active {
618
- color: #3182ce;
619
- border-left-color: #3182ce;
501
+ color: #3eaf7c;
502
+ border-left-color: #3eaf7c;
620
503
  font-weight: 500;
621
- background: #ebf8ff;
622
504
  }
623
505
 
624
- /* 不同层级的缩进 */
625
506
  .toc-item.toc-level-1 {
626
- padding-left: 24px;
627
- font-size: 13px;
507
+ padding-left: 16px;
628
508
  font-weight: 500;
629
- color: #4a5568;
630
- margin-top: 12px;
509
+ color: #1f2328;
510
+ margin-top: 8px;
631
511
  }
632
512
 
633
513
  .toc-item.toc-level-2 {
634
- padding-left: 32px;
635
- font-size: 12px;
636
- margin-top: 2px;
514
+ padding-left: 24px;
637
515
  }
638
516
 
639
517
  .toc-item.toc-level-3 {
640
- padding-left: 40px;
518
+ padding-left: 32px;
641
519
  font-size: 11px;
642
520
  }
643
521
 
644
522
  .toc-item.toc-level-4 {
645
- padding-left: 56px;
646
- font-size: 12px;
647
- opacity: 0.9;
648
- }
649
-
650
- .toc-item.toc-level-5 {
651
- padding-left: 68px;
652
- font-size: 11px;
653
- opacity: 0.85;
654
- }
655
-
656
- .toc-item.toc-level-6 {
657
- padding-left: 80px;
523
+ padding-left: 40px;
658
524
  font-size: 11px;
659
- opacity: 0.8;
660
- }
661
-
662
- /* 目录滚动条样式 */
663
- .toc-sidebar::-webkit-scrollbar {
664
- width: 6px;
665
- }
666
-
667
- .toc-sidebar::-webkit-scrollbar-thumb {
668
- background: #dcdfe6;
669
- border-radius: 3px;
670
- }
671
-
672
- .toc-sidebar::-webkit-scrollbar-thumb:hover {
673
- background: #c0c4cc;
674
525
  }
675
526
 
676
527
  /* 拖拽条样式 */
@@ -679,40 +530,22 @@ body {
679
530
  background: transparent;
680
531
  cursor: col-resize;
681
532
  flex-shrink: 0;
682
- position: relative;
683
- transition: background 0.15s;
684
- }
685
-
686
- .resizer::before {
687
- content: '';
688
- position: absolute;
689
- top: 0;
690
- bottom: 0;
691
- left: -2px;
692
- right: -2px;
533
+ transition: background 0.1s;
693
534
  }
694
535
 
695
536
  .resizer:hover {
696
- background: #e5e7eb;
537
+ background: #d0d7de;
697
538
  }
698
539
 
699
540
  .resizer:active {
700
- background: #111827;
701
- }
702
-
703
- .resizer-left {
704
- margin-left: -1px;
705
- }
706
-
707
- .resizer-right {
708
- margin-right: -1px;
541
+ background: #3eaf7c;
709
542
  }
710
543
 
711
544
  /* 收起/展开按钮 */
712
545
  .collapse-btn,
713
546
  .expand-btn {
714
547
  position: fixed;
715
- top: 70px;
548
+ top: 16px;
716
549
  z-index: 100;
717
550
  width: 32px;
718
551
  height: 32px;
@@ -720,30 +553,21 @@ body {
720
553
  align-items: center;
721
554
  justify-content: center;
722
555
  background: #ffffff;
723
- border: 1px solid #cbd5e0;
556
+ border: 1px solid #d0d7de;
724
557
  border-radius: 6px;
725
558
  cursor: pointer;
726
- color: #718096;
727
- transition: all 0.2s;
728
- box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
559
+ color: #656d76;
560
+ transition: all 0.1s;
561
+ box-shadow: 0 1px 3px rgba(31, 35, 40, 0.12);
729
562
  }
730
563
 
731
564
  .collapse-btn:hover,
732
565
  .expand-btn:hover {
733
- color: #3182ce;
734
- border-color: #3182ce;
735
- box-shadow: 0 4px 12px rgba(49, 130, 206, 0.15);
736
- transform: translateY(-1px);
737
- }
738
-
739
- .collapse-btn-left {
740
- left: 16px;
741
- }
742
-
743
- .collapse-btn-right {
744
- right: 16px;
566
+ background: #f6f8fa;
567
+ color: #1f2328;
745
568
  }
746
569
 
570
+ .collapse-btn-left,
747
571
  .expand-btn-left {
748
572
  left: 16px;
749
573
  }
@@ -768,18 +592,14 @@ body {
768
592
  width: 240px;
769
593
  }
770
594
 
771
- .content {
772
- padding: 20px;
773
- }
774
-
775
595
  .markdown-content {
776
- padding: 20px;
596
+ padding: 16px;
777
597
  }
778
598
 
779
599
  .back-to-top {
780
- right: 20px;
781
- bottom: 20px;
782
- width: 48px;
783
- height: 48px;
600
+ right: 16px;
601
+ bottom: 16px;
602
+ width: 40px;
603
+ height: 40px;
784
604
  }
785
- }
605
+ }
package/vite.config.js CHANGED
@@ -1,10 +1,11 @@
1
1
  import { defineConfig } from 'vite'
2
2
  import vue from '@vitejs/plugin-vue'
3
+ import { config } from './src/config.js'
3
4
 
4
5
  export default defineConfig({
5
6
  plugins: [vue()],
6
7
  server: {
7
- port: 3000
8
+ port: config.defaultPort
8
9
  },
9
10
  optimizeDeps: {
10
11
  include: ['vue', 'marked', 'mermaid']