md2ui 1.0.1 → 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.1",
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
  /* 内容区域样式 */
@@ -176,308 +211,147 @@ body {
176
211
  }
177
212
 
178
213
  .markdown-content {
179
- max-width: 880px;
214
+ width: 90%;
215
+ max-width: none;
180
216
  margin: 0 auto;
181
217
  background: #ffffff;
182
- padding: 64px 80px;
218
+ padding: 32px 48px;
183
219
  }
184
220
 
185
221
  /* Markdown 内容样式 */
186
222
  .markdown-content h1 {
187
223
  font-size: 32px;
188
- margin-bottom: 32px;
189
- padding-bottom: 16px;
190
- border-bottom: 2px solid #e2e8f0;
191
- color: #1a202c;
192
- font-weight: 700;
193
- 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;
194
229
  line-height: 1.25;
195
230
  }
196
231
 
197
232
  .markdown-content h2 {
198
233
  font-size: 24px;
199
- margin-top: 56px;
200
- margin-bottom: 20px;
201
- color: #2d3748;
234
+ margin-top: 32px;
235
+ margin-bottom: 16px;
236
+ padding-bottom: 8px;
237
+ border-bottom: 1px solid #d0d7de;
238
+ color: #1f2328;
202
239
  font-weight: 600;
203
- letter-spacing: -0.01em;
204
- line-height: 1.35;
240
+ line-height: 1.25;
205
241
  }
206
242
 
207
243
  .markdown-content h3 {
208
- font-size: 18px;
209
- margin-top: 40px;
244
+ font-size: 20px;
245
+ margin-top: 24px;
210
246
  margin-bottom: 16px;
211
- color: #2d3748;
247
+ color: #1f2328;
212
248
  font-weight: 600;
213
- line-height: 1.5;
249
+ line-height: 1.25;
214
250
  }
215
251
 
216
252
  .markdown-content h4 {
217
253
  font-size: 16px;
218
- margin-top: 32px;
219
- margin-bottom: 12px;
220
- color: #4a5568;
254
+ margin-top: 24px;
255
+ margin-bottom: 16px;
256
+ color: #1f2328;
221
257
  font-weight: 600;
222
258
  }
223
259
 
224
260
  .markdown-content p {
225
- margin-bottom: 20px;
226
- color: #2d3748;
227
- font-size: 15px;
228
- line-height: 1.75;
261
+ margin-bottom: 16px;
262
+ color: #1f2328;
229
263
  }
230
264
 
231
265
  .markdown-content ul,
232
266
  .markdown-content ol {
233
- margin-bottom: 20px;
234
- padding-left: 24px;
267
+ margin-bottom: 16px;
268
+ padding-left: 2em;
235
269
  }
236
270
 
237
271
  .markdown-content li {
238
- margin-bottom: 8px;
239
- color: #2d3748;
240
- font-size: 15px;
241
- line-height: 1.75;
272
+ margin-bottom: 4px;
273
+ color: #1f2328;
242
274
  }
243
275
 
244
276
  .markdown-content code {
245
- background: #fef2f2;
246
- padding: 3px 8px;
247
- border-radius: 4px;
248
- font-family: "SF Mono", "Consolas", "Monaco", "Courier New", monospace;
249
- font-size: 13px;
250
- color: #dc2626;
251
- border: 1px solid #fecaca;
252
- 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;
253
283
  }
254
284
 
255
285
  .markdown-content pre {
256
- background: #1e1e1e;
257
- padding: 20px 24px;
286
+ background: #24292f;
287
+ padding: 16px;
258
288
  border-radius: 6px;
259
289
  overflow-x: auto;
260
- margin-bottom: 24px;
261
- border: 1px solid #2d2d2d;
262
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
290
+ margin-bottom: 16px;
263
291
  }
264
292
 
265
293
  .markdown-content pre code {
266
294
  background: none;
267
- color: #d4d4d4;
295
+ color: #e6edf3;
268
296
  padding: 0;
269
- border: none;
270
- font-size: 13px;
271
- line-height: 1.7;
272
- display: block;
273
- font-weight: 400;
297
+ font-size: 85%;
298
+ line-height: 1.45;
274
299
  }
275
300
 
276
301
  .markdown-content a {
277
- color: #3182ce;
278
- text-decoration: underline;
279
- text-decoration-color: #90cdf4;
280
- text-underline-offset: 2px;
281
- transition: all 0.15s;
282
- font-weight: 500;
302
+ color: #3eaf7c;
303
+ text-decoration: none;
283
304
  }
284
305
 
285
306
  .markdown-content a:hover {
286
- color: #2c5282;
287
- text-decoration-color: #3182ce;
307
+ text-decoration: underline;
288
308
  }
289
309
 
290
- /* 表格容器 - 支持横向滚动 */
310
+ /* 表格样式 */
291
311
  .markdown-content .table-wrapper {
292
312
  width: 100%;
293
313
  overflow-x: auto;
294
- margin-bottom: 32px;
295
- border-radius: 8px;
296
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
314
+ margin-bottom: 16px;
297
315
  }
298
316
 
299
317
  .markdown-content table {
300
318
  width: 100%;
301
319
  border-collapse: collapse;
302
- margin-bottom: 0;
303
- border: 1px solid #cbd5e0;
304
- font-size: 14px;
305
- border-radius: 8px;
306
- overflow: hidden;
307
- table-layout: auto;
308
- background: #ffffff;
320
+ border-spacing: 0;
309
321
  }
310
322
 
311
323
  .markdown-content table th,
312
324
  .markdown-content table td {
313
- border: 1px solid #e2e8f0;
314
- padding: 14px 18px;
315
- text-align: left;
316
- vertical-align: top;
317
- word-wrap: break-word;
318
- word-break: break-word;
319
- min-width: 100px;
320
- line-height: 1.6;
325
+ border: 1px solid #d0d7de;
326
+ padding: 6px 13px;
321
327
  }
322
328
 
323
329
  .markdown-content table th {
324
- background: linear-gradient(to bottom, #f7fafc 0%, #edf2f7 100%);
325
- font-weight: 600;
326
- color: #2d3748;
327
- font-size: 13px;
328
- letter-spacing: 0.02em;
329
- white-space: nowrap;
330
- border-bottom: 2px solid #cbd5e0;
331
- position: sticky;
332
- top: 0;
333
- z-index: 10;
334
- }
335
-
336
- /* 第一列(表A)适中宽度 */
337
- .markdown-content table th:first-child,
338
- .markdown-content table td:first-child {
339
- width: 15%;
340
- min-width: 140px;
341
- font-weight: 500;
342
- }
343
-
344
- /* 第二列(关系类型)窄一些 */
345
- .markdown-content table th:nth-child(2),
346
- .markdown-content table td:nth-child(2) {
347
- width: 8%;
348
- min-width: 70px;
349
- text-align: center;
350
- font-weight: 500;
351
- }
352
-
353
- /* 第三列(表B)适中宽度 */
354
- .markdown-content table th:nth-child(3),
355
- .markdown-content table td:nth-child(3) {
356
- width: 15%;
357
- min-width: 140px;
358
- font-weight: 500;
359
- }
360
-
361
- /* 第四列(关联字段)较宽 */
362
- .markdown-content table th:nth-child(4),
363
- .markdown-content table td:nth-child(4) {
364
- width: 22%;
365
- min-width: 180px;
366
- font-family: "SF Mono", "Consolas", "Monaco", monospace;
367
- font-size: 13px;
368
- color: #4a5568;
369
- }
370
-
371
- /* 最后一列(说明)最宽,自动填充剩余空间 */
372
- .markdown-content table th:last-child,
373
- .markdown-content table td:last-child {
374
- width: 40%;
375
- min-width: 220px;
376
- color: #4a5568;
377
- }
378
-
379
- .markdown-content table tbody tr {
380
- transition: all 0.15s ease;
381
- border-bottom: 1px solid #f1f5f9;
382
- }
383
-
384
- .markdown-content table tbody tr:hover {
385
- background-color: #f8fafc;
386
- box-shadow: inset 0 0 0 1px #e2e8f0;
387
- }
388
-
389
- .markdown-content table tbody tr:last-child {
390
- border-bottom: none;
391
- }
392
-
393
- /* 字段名样式 */
394
- .markdown-content table .field-name-cell {
395
- font-family: "SF Mono", "Consolas", "Monaco", monospace;
396
- color: #dc2626;
397
- font-weight: 500;
398
- font-size: 13px;
399
- background-color: #fef2f2;
400
- }
401
-
402
- /* 必填标记样式 */
403
- .markdown-content table .required-yes {
404
- color: #2d3748;
330
+ background: #f6f8fa;
405
331
  font-weight: 600;
406
- text-align: center;
407
332
  }
408
333
 
409
- /* 表格滚动条样式 */
410
- .markdown-content .table-wrapper::-webkit-scrollbar {
411
- height: 8px;
412
- }
413
-
414
- .markdown-content .table-wrapper::-webkit-scrollbar-track {
415
- background: #f1f5f9;
416
- border-radius: 4px;
417
- }
418
-
419
- .markdown-content .table-wrapper::-webkit-scrollbar-thumb {
420
- background: #cbd5e0;
421
- border-radius: 4px;
422
- transition: background 0.2s;
423
- }
424
-
425
- .markdown-content .table-wrapper::-webkit-scrollbar-thumb:hover {
426
- background: #94a3b8;
334
+ .markdown-content table tr:nth-child(2n) {
335
+ background: #f6f8fa;
427
336
  }
428
337
 
429
338
  .markdown-content blockquote {
430
- border-left: 3px solid #4299e1;
431
- padding-left: 20px;
432
- margin: 24px 0;
433
- color: #4a5568;
434
- font-style: italic;
435
- background: #ebf8ff;
436
- padding: 16px 20px;
437
- border-radius: 4px;
339
+ border-left: 4px solid #d0d7de;
340
+ padding: 0 16px;
341
+ margin: 0 0 16px;
342
+ color: #656d76;
438
343
  }
439
344
 
440
345
  .markdown-content img {
441
346
  max-width: 100%;
442
347
  height: auto;
443
- border-radius: 4px;
444
- margin: 16px 0;
445
- transition: all 0.2s ease;
446
- }
447
-
448
- .markdown-content img:hover {
449
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
450
- transform: translateY(-2px);
451
- }
452
-
453
- /* 可放大图片样式 */
454
- .zoomable-image {
455
- cursor: zoom-in !important;
456
- position: relative;
457
- }
458
-
459
- .zoomable-image::after {
460
- content: '🔍';
461
- position: absolute;
462
- top: 8px;
463
- right: 8px;
464
- background: rgba(0, 0, 0, 0.7);
465
- color: white;
466
- padding: 4px 8px;
467
- border-radius: 4px;
468
- font-size: 12px;
469
- opacity: 0;
470
- transition: opacity 0.2s ease;
471
- pointer-events: none;
472
- }
473
-
474
- .zoomable-image:hover::after {
475
- opacity: 1;
348
+ border-radius: 6px;
349
+ margin: 8px 0;
476
350
  }
477
351
 
478
352
  /* Mermaid 图表样式 */
479
353
  .markdown-content .mermaid {
480
- margin: 24px 0;
354
+ margin: 16px 0;
481
355
  text-align: center;
482
356
  }
483
357
 
@@ -487,189 +361,167 @@ body {
487
361
  }
488
362
 
489
363
  .markdown-content .mermaid-error {
490
- color: #f56c6c;
491
- background: #fef0f0;
364
+ color: #cf222e;
365
+ background: #ffebe9;
492
366
  padding: 12px;
493
- border-radius: 4px;
494
- border: 1px solid #fde2e2;
495
- font-size: 14px;
367
+ border-radius: 6px;
368
+ border: 1px solid #ff8182;
496
369
  }
497
370
 
498
371
  /* 滚动条样式 */
499
372
  .sidebar::-webkit-scrollbar,
500
373
  .content::-webkit-scrollbar {
501
- width: 6px;
374
+ width: 8px;
502
375
  }
503
376
 
504
377
  .sidebar::-webkit-scrollbar-thumb,
505
378
  .content::-webkit-scrollbar-thumb {
506
- background: #dcdfe6;
507
- border-radius: 3px;
379
+ background: #d0d7de;
380
+ border-radius: 4px;
508
381
  }
509
382
 
510
383
  .sidebar::-webkit-scrollbar-thumb:hover,
511
384
  .content::-webkit-scrollbar-thumb:hover {
512
- background: #c0c4cc;
385
+ background: #afb8c1;
513
386
  }
514
387
 
515
388
  /* 返回顶部按钮 */
516
389
  .back-to-top {
517
390
  position: fixed;
518
- right: 32px;
519
- bottom: 32px;
520
- width: 56px;
521
- height: 56px;
391
+ right: 24px;
392
+ bottom: 24px;
393
+ width: 48px;
394
+ height: 48px;
522
395
  display: flex;
523
396
  flex-direction: column;
524
397
  align-items: center;
525
398
  justify-content: center;
526
- gap: 4px;
399
+ gap: 2px;
527
400
  background: #ffffff;
528
- color: #3182ce;
529
- border: 1px solid #cbd5e0;
530
- border-radius: 8px;
401
+ color: #656d76;
402
+ border: 1px solid #d0d7de;
403
+ border-radius: 6px;
531
404
  cursor: pointer;
532
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
533
- transition: all 0.2s;
405
+ box-shadow: 0 1px 3px rgba(31, 35, 40, 0.12);
406
+ transition: all 0.1s;
534
407
  z-index: 1000;
535
408
  }
536
409
 
537
410
  .back-to-top:hover {
538
- background: #3182ce;
539
- color: #ffffff;
540
- border-color: #3182ce;
541
- box-shadow: 0 6px 16px rgba(49, 130, 206, 0.3);
542
- transform: translateY(-2px);
543
- }
544
-
545
- .back-to-top:active {
546
- transform: translateY(0) scale(0.95);
411
+ background: #e6f7e6;
412
+ color: #3eaf7c;
413
+ border-color: #3eaf7c;
547
414
  }
548
415
 
549
416
  .progress-text {
550
- font-size: 14px;
551
- font-weight: 600;
552
- line-height: 1;
553
- color: inherit;
417
+ font-size: 12px;
418
+ font-weight: 500;
554
419
  }
555
420
 
556
421
  /* 淡入淡出动画 */
557
422
  .fade-enter-active,
558
423
  .fade-leave-active {
559
- transition: opacity 0.3s, transform 0.3s;
424
+ transition: opacity 0.2s;
560
425
  }
561
426
 
562
427
  .fade-enter-from,
563
428
  .fade-leave-to {
564
429
  opacity: 0;
565
- transform: translateY(10px);
566
430
  }
567
431
 
568
432
  /* 右侧目录样式 */
569
433
  .toc-sidebar {
570
434
  min-width: 200px;
571
- max-width: 400px;
435
+ max-width: 300px;
572
436
  background: #ffffff;
573
- border-left: 1px solid #e2e8f0;
437
+ border-left: 1px solid #d0d7de;
574
438
  overflow-y: auto;
575
439
  flex-shrink: 0;
576
- padding: 64px 0 32px;
577
- box-shadow: -2px 0 8px rgba(0, 0, 0, 0.02);
440
+ padding: 32px 0;
578
441
  }
579
442
 
580
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 {
581
452
  display: flex;
582
453
  align-items: center;
583
454
  gap: 8px;
584
- padding: 0 24px 16px;
585
- font-size: 11px;
455
+ font-size: 12px;
586
456
  font-weight: 600;
587
- color: #718096;
588
- border-bottom: 1px solid #e2e8f0;
589
- letter-spacing: 0.05em;
457
+ color: #1f2328;
590
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;
591
479
  }
592
480
 
593
481
  .toc-nav {
594
- padding: 16px 0;
482
+ padding: 12px 0;
595
483
  }
596
484
 
597
485
  .toc-item {
598
486
  display: block;
599
- padding: 6px 24px;
600
- color: #718096;
487
+ padding: 4px 16px;
488
+ color: #656d76;
601
489
  text-decoration: none;
602
490
  font-size: 12px;
603
- line-height: 1.6;
604
- transition: all 0.15s;
491
+ line-height: 1.5;
492
+ transition: all 0.1s;
605
493
  border-left: 2px solid transparent;
606
- overflow: hidden;
607
- text-overflow: ellipsis;
608
- white-space: nowrap;
609
494
  }
610
495
 
611
496
  .toc-item:hover {
612
- color: #2d3748;
613
- background: #f7fafc;
497
+ color: #3eaf7c;
614
498
  }
615
499
 
616
500
  .toc-item.active {
617
- color: #3182ce;
618
- border-left-color: #3182ce;
501
+ color: #3eaf7c;
502
+ border-left-color: #3eaf7c;
619
503
  font-weight: 500;
620
- background: #ebf8ff;
621
504
  }
622
505
 
623
- /* 不同层级的缩进 */
624
506
  .toc-item.toc-level-1 {
625
- padding-left: 24px;
626
- font-size: 13px;
507
+ padding-left: 16px;
627
508
  font-weight: 500;
628
- color: #4a5568;
629
- margin-top: 12px;
509
+ color: #1f2328;
510
+ margin-top: 8px;
630
511
  }
631
512
 
632
513
  .toc-item.toc-level-2 {
633
- padding-left: 32px;
634
- font-size: 12px;
635
- margin-top: 2px;
514
+ padding-left: 24px;
636
515
  }
637
516
 
638
517
  .toc-item.toc-level-3 {
639
- padding-left: 40px;
518
+ padding-left: 32px;
640
519
  font-size: 11px;
641
520
  }
642
521
 
643
522
  .toc-item.toc-level-4 {
644
- padding-left: 56px;
645
- font-size: 12px;
646
- opacity: 0.9;
647
- }
648
-
649
- .toc-item.toc-level-5 {
650
- padding-left: 68px;
651
- font-size: 11px;
652
- opacity: 0.85;
653
- }
654
-
655
- .toc-item.toc-level-6 {
656
- padding-left: 80px;
523
+ padding-left: 40px;
657
524
  font-size: 11px;
658
- opacity: 0.8;
659
- }
660
-
661
- /* 目录滚动条样式 */
662
- .toc-sidebar::-webkit-scrollbar {
663
- width: 6px;
664
- }
665
-
666
- .toc-sidebar::-webkit-scrollbar-thumb {
667
- background: #dcdfe6;
668
- border-radius: 3px;
669
- }
670
-
671
- .toc-sidebar::-webkit-scrollbar-thumb:hover {
672
- background: #c0c4cc;
673
525
  }
674
526
 
675
527
  /* 拖拽条样式 */
@@ -678,40 +530,22 @@ body {
678
530
  background: transparent;
679
531
  cursor: col-resize;
680
532
  flex-shrink: 0;
681
- position: relative;
682
- transition: background 0.15s;
683
- }
684
-
685
- .resizer::before {
686
- content: '';
687
- position: absolute;
688
- top: 0;
689
- bottom: 0;
690
- left: -2px;
691
- right: -2px;
533
+ transition: background 0.1s;
692
534
  }
693
535
 
694
536
  .resizer:hover {
695
- background: #e5e7eb;
537
+ background: #d0d7de;
696
538
  }
697
539
 
698
540
  .resizer:active {
699
- background: #111827;
700
- }
701
-
702
- .resizer-left {
703
- margin-left: -1px;
704
- }
705
-
706
- .resizer-right {
707
- margin-right: -1px;
541
+ background: #3eaf7c;
708
542
  }
709
543
 
710
544
  /* 收起/展开按钮 */
711
545
  .collapse-btn,
712
546
  .expand-btn {
713
547
  position: fixed;
714
- top: 70px;
548
+ top: 16px;
715
549
  z-index: 100;
716
550
  width: 32px;
717
551
  height: 32px;
@@ -719,30 +553,21 @@ body {
719
553
  align-items: center;
720
554
  justify-content: center;
721
555
  background: #ffffff;
722
- border: 1px solid #cbd5e0;
556
+ border: 1px solid #d0d7de;
723
557
  border-radius: 6px;
724
558
  cursor: pointer;
725
- color: #718096;
726
- transition: all 0.2s;
727
- 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);
728
562
  }
729
563
 
730
564
  .collapse-btn:hover,
731
565
  .expand-btn:hover {
732
- color: #3182ce;
733
- border-color: #3182ce;
734
- box-shadow: 0 4px 12px rgba(49, 130, 206, 0.15);
735
- transform: translateY(-1px);
736
- }
737
-
738
- .collapse-btn-left {
739
- left: 16px;
740
- }
741
-
742
- .collapse-btn-right {
743
- right: 16px;
566
+ background: #f6f8fa;
567
+ color: #1f2328;
744
568
  }
745
569
 
570
+ .collapse-btn-left,
746
571
  .expand-btn-left {
747
572
  left: 16px;
748
573
  }
@@ -767,18 +592,14 @@ body {
767
592
  width: 240px;
768
593
  }
769
594
 
770
- .content {
771
- padding: 20px;
772
- }
773
-
774
595
  .markdown-content {
775
- padding: 20px;
596
+ padding: 16px;
776
597
  }
777
598
 
778
599
  .back-to-top {
779
- right: 20px;
780
- bottom: 20px;
781
- width: 48px;
782
- height: 48px;
600
+ right: 16px;
601
+ bottom: 16px;
602
+ width: 40px;
603
+ height: 40px;
783
604
  }
784
- }
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']