rn-bundle-analyzer 1.1.0

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.
Files changed (54) hide show
  1. package/README.md +80 -0
  2. package/dist/builders/treeBuilder.d.ts +2 -0
  3. package/dist/builders/treeBuilder.js +47 -0
  4. package/dist/collectors/fileCollector.d.ts +2 -0
  5. package/dist/collectors/fileCollector.js +27 -0
  6. package/dist/collectors/inverseDependencyCollector.d.ts +2 -0
  7. package/dist/collectors/inverseDependencyCollector.js +29 -0
  8. package/dist/config.d.ts +12 -0
  9. package/dist/config.js +15 -0
  10. package/dist/fileInfoCollector.d.ts +4 -0
  11. package/dist/fileInfoCollector.js +44 -0
  12. package/dist/index.d.ts +3 -0
  13. package/dist/index.js +48 -0
  14. package/dist/templates/analyse.html +1082 -0
  15. package/dist/templates/analyse.pug +321 -0
  16. package/dist/templates/analyseTemplate.d.ts +3 -0
  17. package/dist/templates/analyseTemplate.js +41 -0
  18. package/dist/templates/icons-inline.js +27 -0
  19. package/dist/templates/lucideIcons.d.ts +2 -0
  20. package/dist/templates/lucideIcons.js +28 -0
  21. package/dist/templates/tailwindcss.js +83 -0
  22. package/dist/types.d.ts +41 -0
  23. package/dist/types.js +2 -0
  24. package/dist/utils/envSetup.d.ts +1 -0
  25. package/dist/utils/envSetup.js +21 -0
  26. package/dist/utils/fileUtils.d.ts +3 -0
  27. package/dist/utils/fileUtils.js +27 -0
  28. package/dist/writers/fileInfoWriter.d.ts +12 -0
  29. package/dist/writers/fileInfoWriter.js +42 -0
  30. package/dist/writers/inverseDependencyWriter.d.ts +2 -0
  31. package/dist/writers/inverseDependencyWriter.js +48 -0
  32. package/dist/writers/treeWriter.d.ts +2 -0
  33. package/dist/writers/treeWriter.js +26 -0
  34. package/index.js +1 -0
  35. package/package.json +24 -0
  36. package/scripts/compile-pug.js +17 -0
  37. package/src/builders/treeBuilder.ts +57 -0
  38. package/src/collectors/fileCollector.ts +24 -0
  39. package/src/collectors/inverseDependencyCollector.ts +32 -0
  40. package/src/config.ts +14 -0
  41. package/src/fileInfoCollector.ts +50 -0
  42. package/src/index.ts +15 -0
  43. package/src/templates/analyse-app.js +988 -0
  44. package/src/templates/analyse.pug +321 -0
  45. package/src/templates/analyseTemplate.ts +45 -0
  46. package/src/templates/icons-inline.js +27 -0
  47. package/src/templates/tailwindcss.js +83 -0
  48. package/src/types.ts +43 -0
  49. package/src/utils/envSetup.ts +18 -0
  50. package/src/utils/fileUtils.ts +21 -0
  51. package/src/writers/fileInfoWriter.ts +44 -0
  52. package/src/writers/inverseDependencyWriter.ts +50 -0
  53. package/src/writers/treeWriter.ts +24 -0
  54. package/tsconfig.json +20 -0
@@ -0,0 +1,1082 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>RN 包分析结果展示</title>
7
+ <script src="./tailwindcss.js"></script>
8
+ <script src="./icons-inline.js"></script>
9
+ <script src="./rn-package-analyse.js"></script>
10
+ <script src="./rn-package-tree-analyse.js"></script>
11
+ <script src="./rn-package-inverse-dependencies.js"></script>
12
+ <script>
13
+ tailwind.config = {
14
+ theme: {
15
+ extend: {
16
+ colors: {
17
+ border: "hsl(214.3 31.8% 91.4%)",
18
+ input: "hsl(214.3 31.8% 91.4%)",
19
+ ring: "hsl(222.2 84% 4.9%)",
20
+ background: "hsl(0 0% 100%)",
21
+ foreground: "hsl(222.2 84% 4.9%)",
22
+ primary: {
23
+ DEFAULT: "hsl(222.2 47.4% 11.2%)",
24
+ foreground: "hsl(210 40% 98%)",
25
+ },
26
+ secondary: {
27
+ DEFAULT: "hsl(210 40% 96%)",
28
+ foreground: "hsl(222.2 84% 4.9%)",
29
+ },
30
+ muted: {
31
+ DEFAULT: "hsl(210 40% 96%)",
32
+ foreground: "hsl(215.4 16.3% 46.9%)",
33
+ },
34
+ accent: {
35
+ DEFAULT: "hsl(210 40% 96%)",
36
+ foreground: "hsl(222.2 84% 4.9%)",
37
+ },
38
+ card: {
39
+ DEFAULT: "hsl(0 0% 100%)",
40
+ foreground: "hsl(222.2 84% 4.9%)",
41
+ },
42
+ },
43
+ }
44
+ }
45
+ }
46
+ </script>
47
+ <style>
48
+ .tree-node {
49
+ transition: all 0.2s ease;
50
+ }
51
+
52
+ .tree-node.collapsed .tree-children {
53
+ display: none;
54
+ }
55
+
56
+ .tree-toggle {
57
+ cursor: pointer;
58
+ user-select: none;
59
+ border-radius: 4px;
60
+ transition: background-color 0.2s ease;
61
+ }
62
+
63
+ .tree-toggle:hover {
64
+ background-color: hsl(210 40% 94%);
65
+ }
66
+
67
+ .tree-expand-icon {
68
+ transition: transform 0.2s ease;
69
+ display: inline-flex;
70
+ align-items: center;
71
+ justify-content: center;
72
+ }
73
+
74
+ .tree-node.collapsed .tree-expand-icon {
75
+ transform: rotate(0deg);
76
+ }
77
+
78
+ .tree-node:not(.collapsed) .tree-expand-icon {
79
+ transform: rotate(90deg);
80
+ }
81
+
82
+ .tree-children {
83
+ border-left: 1px solid hsl(214.3 31.8% 91.4%);
84
+ margin-left: 8px;
85
+ padding-left: 8px;
86
+ }
87
+
88
+ .tree-item {
89
+ display: flex;
90
+ align-items: center;
91
+ padding: 4px 8px;
92
+ border-radius: 4px;
93
+ transition: background-color 0.2s ease;
94
+ }
95
+
96
+ .tree-item:hover {
97
+ background-color: hsl(210 40% 96%);
98
+ }
99
+
100
+ .tree-folder-icon {
101
+ color: hsl(45 100% 51%);
102
+ }
103
+
104
+ .tree-file-icon {
105
+ color: hsl(215.4 16.3% 46.9%);
106
+ }
107
+
108
+ .progress-bar {
109
+ background: linear-gradient(90deg, #3b82f6 0%, #1d4ed8 100%);
110
+ }
111
+
112
+ .lucide-inline {
113
+ display: inline-block;
114
+ vertical-align: middle;
115
+ flex-shrink: 0;
116
+ }
117
+
118
+ </style>
119
+ </head>
120
+ <body class="bg-background text-foreground">
121
+ <div class="min-h-screen">
122
+ <header class="bg-card border-b border-border">
123
+ <div class="container mx-auto px-4 py-6">
124
+ <div class="flex items-center justify-between">
125
+ <div>
126
+ <h1 class="text-3xl font-bold">RN 包分析结果</h1>
127
+ <p class="text-muted-foreground mt-2">React Native 项目包大小分析与可视化</p>
128
+ </div>
129
+ <div class="flex items-center space-x-4">
130
+ <div class="text-right">
131
+ <div class="text-sm text-muted-foreground">总文件数</div>
132
+ <div class="text-2xl font-bold" id="total-files">-</div>
133
+ </div>
134
+ <div class="text-right">
135
+ <div class="text-sm text-muted-foreground">总大小</div>
136
+ <div class="text-2xl font-bold" id="total-size">-</div>
137
+ </div>
138
+ </div>
139
+ </div>
140
+ </div>
141
+ </header>
142
+ <main class="container mx-auto px-4 py-6">
143
+ <div class="border-b border-border mb-6">
144
+ <nav class="flex space-x-8">
145
+ <button class="tab-button active py-2 px-1 border-b-2 border-primary text-primary font-medium" data-tab="files"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide-inline inline w-4 h-4 mr-2"><path d="M3 5h.01" /> <path d="M3 12h.01" /> <path d="M3 19h.01" /> <path d="M8 5h13" /> <path d="M8 12h13" /> <path d="M8 19h13" /></svg>文件列表视图
146
+ </button>
147
+ <button class="tab-button py-2 px-1 border-b-2 border-transparent text-muted-foreground hover:text-foreground font-medium" data-tab="tree"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide-inline inline w-4 h-4 mr-2"><path d="M20 10a1 1 0 0 0 1-1V6a1 1 0 0 0-1-1h-2.5a1 1 0 0 1-.8-.4l-.9-1.2A1 1 0 0 0 15 3h-2a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1Z" /> <path d="M20 21a1 1 0 0 0 1-1v-3a1 1 0 0 0-1-1h-2.9a1 1 0 0 1-.88-.55l-.42-.85a1 1 0 0 0-.92-.6H13a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1Z" /> <path d="M3 5a2 2 0 0 0 2 2h3" /> <path d="M3 3v13a2 2 0 0 0 2 2h3" /></svg>目录树视图
148
+ </button>
149
+ <button class="tab-button py-2 px-1 border-b-2 border-transparent text-muted-foreground hover:text-foreground font-medium" data-tab="dependencies"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide-inline inline w-4 h-4 mr-2"><circle cx="12" cy="18" r="3" /> <circle cx="6" cy="6" r="3" /> <circle cx="18" cy="6" r="3" /> <path d="M18 9v2c0 .6-.4 1-1 1H7c-.6 0-1-.4-1-1V9" /> <path d="M12 12v3" /></svg>反向依赖分析
150
+ </button>
151
+ </nav>
152
+ </div>
153
+ <div class="tab-content" id="files-tab">
154
+ <div class="mb-6 flex flex-col sm:flex-row gap-4">
155
+ <div class="flex-1">
156
+ <div class="relative"><span class="absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground w-4 h-4 pointer-events-none"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide-inline w-4 h-4"><path d="m21 21-4.34-4.34" /> <circle cx="11" cy="11" r="8" /></svg></span>
157
+ <input class="w-full pl-10 pr-4 py-2 border border-input rounded-md bg-background focus:outline-none focus:ring-2 focus:ring-ring" id="search-input" type="text" placeholder="搜索文件名或路径...">
158
+ </div>
159
+ </div>
160
+ <div class="flex gap-2">
161
+ <select class="px-3 py-2 border border-input rounded-md bg-background focus:outline-none focus:ring-2 focus:ring-ring" id="extension-filter">
162
+ <option value="">所有文件类型</option>
163
+ </select>
164
+ <select class="px-3 py-2 border border-input rounded-md bg-background focus:outline-none focus:ring-2 focus:ring-ring" id="sort-select">
165
+ <option value="size-desc">大小 (大到小)</option>
166
+ <option value="size-asc">大小 (小到大)</option>
167
+ <option value="name-asc">名称 (A-Z)</option>
168
+ <option value="name-desc">名称 (Z-A)</option>
169
+ </select>
170
+ </div>
171
+ </div>
172
+ <div class="bg-card rounded-lg border border-border overflow-hidden">
173
+ <div class="overflow-x-auto">
174
+ <table class="w-full">
175
+ <thead class="bg-muted">
176
+ <tr>
177
+ <th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">文件路径</th>
178
+ <th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">大小</th>
179
+ <th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">类型</th>
180
+ <th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">修改时间</th>
181
+ <th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">反向依赖</th>
182
+ </tr>
183
+ </thead>
184
+ <tbody class="divide-y divide-border" id="files-table-body"></tbody>
185
+ </table>
186
+ </div>
187
+ </div>
188
+ <div class="mt-6 flex items-center justify-between">
189
+ <div class="text-sm text-muted-foreground">显示 <span id="files-range">0-0</span> 项,共 <span id="files-total">0</span> 项</div>
190
+ <div class="flex items-center space-x-2">
191
+ <button class="px-3 py-1 border border-border rounded text-sm hover:bg-accent disabled:opacity-50" id="prev-page">上一页</button><span class="px-3 py-1 text-sm" id="page-info">1 / 1</span>
192
+ <button class="px-3 py-1 border border-border rounded text-sm hover:bg-accent disabled:opacity-50" id="next-page">下一页</button>
193
+ </div>
194
+ </div>
195
+ </div>
196
+ <div class="tab-content hidden" id="tree-tab">
197
+ <div class="bg-card rounded-lg border border-border p-6">
198
+ <div class="mb-4 space-y-4">
199
+ <div class="flex flex-col sm:flex-row gap-4">
200
+ <input class="flex-1 px-4 py-2 border border-input rounded-md bg-background focus:outline-none focus:ring-2 focus:ring-ring" id="tree-search" type="text" placeholder="搜索目录或文件...">
201
+ <div class="flex gap-2">
202
+ <button class="px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors flex items-center" id="expand-all-btn"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide-inline w-4 h-4 mr-2"><path d="m7 6 5 5 5-5" /> <path d="m7 13 5 5 5-5" /></svg>全部展开
203
+ </button>
204
+ <button class="px-4 py-2 bg-secondary text-secondary-foreground rounded-md hover:bg-secondary/80 transition-colors flex items-center" id="collapse-all-btn"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide-inline w-4 h-4 mr-2"><path d="m17 11-5-5-5 5" /> <path d="m17 18-5-5-5 5" /></svg>全部折叠
205
+ </button>
206
+ </div>
207
+ </div>
208
+ </div>
209
+ <div class="font-mono text-sm" id="tree-container"></div>
210
+ </div>
211
+ </div>
212
+ <div class="tab-content hidden" id="dependencies-tab">
213
+ <div class="grid grid-cols-1 gap-6 mb-6 md:grid-cols-4">
214
+ <div class="bg-card rounded-lg border border-border p-4">
215
+ <div class="flex items-center"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide-inline w-8 h-8 text-blue-500 mr-3"><path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" /> <path d="M14 2v4a2 2 0 0 0 2 2h4" /> <path d="M10 9H8" /> <path d="M16 13H8" /> <path d="M16 17H8" /></svg>
216
+ <div>
217
+ <div class="text-2xl font-bold" id="dep-total-files">-</div>
218
+ <div class="text-sm text-muted-foreground">总文件数</div>
219
+ </div>
220
+ </div>
221
+ </div>
222
+ <div class="bg-card rounded-lg border border-border p-4">
223
+ <div class="flex items-center"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide-inline w-8 h-8 text-green-500 mr-3"><circle cx="12" cy="18" r="3" /> <circle cx="6" cy="6" r="3" /> <circle cx="18" cy="6" r="3" /> <path d="M18 9v2c0 .6-.4 1-1 1H7c-.6 0-1-.4-1-1V9" /> <path d="M12 12v3" /></svg>
224
+ <div>
225
+ <div class="text-2xl font-bold" id="dep-total-dependencies">-</div>
226
+ <div class="text-sm text-muted-foreground">总依赖关系数</div>
227
+ </div>
228
+ </div>
229
+ </div>
230
+ <div class="bg-card rounded-lg border border-border p-4">
231
+ <div class="flex items-center"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide-inline w-8 h-8 text-orange-500 mr-3"><path d="M16 7h6v6" /> <path d="m22 7-8.5 8.5-5-5L2 17" /></svg>
232
+ <div>
233
+ <div class="text-2xl font-bold" id="dep-average-dependencies">-</div>
234
+ <div class="text-sm text-muted-foreground">平均依赖数</div>
235
+ </div>
236
+ </div>
237
+ </div>
238
+ <div class="bg-card rounded-lg border border-border p-4">
239
+ <div class="flex items-center"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide-inline w-8 h-8 text-purple-500 mr-3"><path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z" /></svg>
240
+ <div>
241
+ <div class="text-2xl font-bold" id="dep-most-depended">-</div>
242
+ <div class="text-sm text-muted-foreground">最高依赖数</div>
243
+ </div>
244
+ </div>
245
+ </div>
246
+ </div>
247
+ <div class="bg-card rounded-lg border border-border p-6 mb-6">
248
+ <h3 class="text-lg font-semibold mb-4 flex items-center"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide-inline w-5 h-5 mr-2 text-red-500"><path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z" /></svg>热门依赖文件 Top 10
249
+ </h3>
250
+ <div class="space-y-3" id="top-dependencies-list"></div>
251
+ </div>
252
+ <div class="bg-card rounded-lg border border-border p-6">
253
+ <div class="mb-4 flex flex-col sm:flex-row gap-4">
254
+ <div class="flex-1">
255
+ <div class="relative"><span class="absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground w-4 h-4 pointer-events-none"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide-inline w-4 h-4"><path d="m21 21-4.34-4.34" /> <circle cx="11" cy="11" r="8" /></svg></span>
256
+ <input class="w-full pl-10 pr-4 py-2 border border-input rounded-md bg-background focus:outline-none focus:ring-2 focus:ring-ring" id="dep-search-input" type="text" placeholder="搜索文件路径...">
257
+ </div>
258
+ </div>
259
+ <div class="flex gap-2">
260
+ <select class="px-3 py-2 border border-input rounded-md bg-background focus:outline-none focus:ring-2 focus:ring-ring" id="dep-sort-select">
261
+ <option value="dependents-desc">依赖数 (高到低)</option>
262
+ <option value="dependents-asc">依赖数 (低到高)</option>
263
+ <option value="name-asc">文件名 (A-Z)</option>
264
+ <option value="name-desc">文件名 (Z-A)</option>
265
+ </select>
266
+ <select class="px-3 py-2 border border-input rounded-md bg-background focus:outline-none focus:ring-2 focus:ring-ring" id="dep-filter-select">
267
+ <option value="">所有文件</option>
268
+ <option value="src">源码文件</option>
269
+ <option value="node_modules">依赖包文件</option>
270
+ <option value="high-deps">高依赖文件 (>100)</option>
271
+ </select>
272
+ </div>
273
+ </div>
274
+ <div class="overflow-x-auto">
275
+ <table class="w-full">
276
+ <thead class="bg-muted">
277
+ <tr>
278
+ <th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">文件路径</th>
279
+ <th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">被依赖次数</th>
280
+ <th class="px-6 py-3 text-left text-xs font-medium text-muted-foreground uppercase tracking-wider">操作</th>
281
+ </tr>
282
+ </thead>
283
+ <tbody class="divide-y divide-border" id="dependencies-table-body"></tbody>
284
+ </table>
285
+ </div>
286
+ <div class="mt-6 flex items-center justify-between">
287
+ <div class="text-sm text-muted-foreground">显示 <span id="dep-range">0-0</span> 项,共 <span id="dep-total">0</span> 项</div>
288
+ <div class="flex items-center space-x-2">
289
+ <button class="px-3 py-1 border border-border rounded text-sm hover:bg-accent disabled:opacity-50" id="dep-prev-page">上一页</button><span class="px-3 py-1 text-sm" id="dep-page-info">1 / 1</span>
290
+ <button class="px-3 py-1 border border-border rounded text-sm hover:bg-accent disabled:opacity-50" id="dep-next-page">下一页</button>
291
+ </div>
292
+ </div>
293
+ </div>
294
+ </div>
295
+ </main>
296
+ </div>
297
+ </body>
298
+ <script> // 全局数据存储
299
+ let packageData = null;
300
+ let treeData = null;
301
+ let inverseDependencyData = null;
302
+ let filteredFiles = [];
303
+ let filteredDependencies = [];
304
+ let currentPage = 1;
305
+ let currentDepPage = 1;
306
+ const itemsPerPage = 50;
307
+
308
+ // 工具函数
309
+ function formatFileSize(bytes) {
310
+ if (bytes === 0) return '0 B';
311
+ const k = 1024;
312
+ const sizes = ['B', 'KB', 'MB', 'GB'];
313
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
314
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
315
+ }
316
+
317
+ function formatDate(dateString) {
318
+ const date = new Date(dateString);
319
+ return date.toLocaleDateString('zh-CN') + ' ' + date.toLocaleTimeString('zh-CN', { hour12: false });
320
+ }
321
+
322
+ function getFileExtension(filename) {
323
+ const lastDot = filename.lastIndexOf('.');
324
+ return lastDot > 0 ? filename.substring(lastDot) : '';
325
+ }
326
+
327
+ function getFileName(path) {
328
+ return path.split('/').pop();
329
+ }
330
+
331
+ function escapeForSingleQuoteJsString(value) {
332
+ return String(value)
333
+ .replace(/\\/g, '\\\\')
334
+ .replace(/'/g, "\\'");
335
+ }
336
+
337
+ // 数据加载和初始化
338
+ async function loadData() {
339
+ try {
340
+ packageData = window.packageAnalyseData || null;
341
+ treeData = window.treeAnalyseData || null;
342
+
343
+ // 加载反向依赖数据
344
+ inverseDependencyData = window.inverseDependencyData;
345
+
346
+ if (!packageData || !packageData.buildInfo || !Array.isArray(packageData.files)) {
347
+ throw new Error('packageAnalyseData 数据缺失或格式不正确');
348
+ }
349
+ if (!treeData || !Array.isArray(treeData.pathAnalyse)) {
350
+ throw new Error('treeAnalyseData 数据缺失或格式不正确');
351
+ }
352
+
353
+ initializeApp();
354
+ } catch (error) {
355
+ console.error('Error loading data:', error);
356
+
357
+ initializeApp();
358
+ }
359
+ }
360
+
361
+ // 应用初始化
362
+ function initializeApp() {
363
+ updateHeader();
364
+ setupEventListeners();
365
+ initializeFilesView();
366
+ initializeTreeView();
367
+ initializeDependenciesView();
368
+
369
+
370
+ // 初始化图标
371
+ lucide.createIcons();
372
+ }
373
+
374
+ // 更新头部信息
375
+ function updateHeader() {
376
+ document.getElementById('total-files').textContent = packageData.buildInfo.totalFiles.toLocaleString();
377
+ document.getElementById('total-size').textContent = formatFileSize(packageData.buildInfo.totalSize);
378
+ }
379
+
380
+ // 设置事件监听器
381
+ function setupEventListeners() {
382
+ // Tab切换
383
+ document.querySelectorAll('.tab-button').forEach(button => {
384
+ button.addEventListener('click', () => {
385
+ const tabName = button.dataset.tab;
386
+ switchTab(tabName);
387
+ });
388
+ });
389
+
390
+ // 搜索功能
391
+ document.getElementById('search-input').addEventListener('input', debounce(handleSearch, 300));
392
+ document.getElementById('tree-search').addEventListener('input', debounce(handleTreeSearch, 300));
393
+
394
+ // 树形控制按钮
395
+ document.getElementById('expand-all-btn').addEventListener('click', expandAllNodes);
396
+ document.getElementById('collapse-all-btn').addEventListener('click', collapseAllNodes);
397
+
398
+ // 筛选和排序
399
+ document.getElementById('extension-filter').addEventListener('change', handleFilter);
400
+ document.getElementById('sort-select').addEventListener('change', handleSort);
401
+
402
+ // 分页
403
+ document.getElementById('prev-page').addEventListener('click', () => changePage(-1));
404
+ document.getElementById('next-page').addEventListener('click', () => changePage(1));
405
+
406
+ // 反向依赖功能
407
+ document.getElementById('dep-search-input').addEventListener('input', debounce(handleDepSearch, 300));
408
+ document.getElementById('dep-sort-select').addEventListener('change', handleDepSort);
409
+ document.getElementById('dep-filter-select').addEventListener('change', handleDepFilter);
410
+ document.getElementById('dep-prev-page').addEventListener('click', () => changeDepPage(-1));
411
+ document.getElementById('dep-next-page').addEventListener('click', () => changeDepPage(1));
412
+ }
413
+
414
+ // 防抖函数
415
+ function debounce(func, wait) {
416
+ let timeout;
417
+ return function executedFunction(...args) {
418
+ const later = () => {
419
+ clearTimeout(timeout);
420
+ func(...args);
421
+ };
422
+ clearTimeout(timeout);
423
+ timeout = setTimeout(later, wait);
424
+ };
425
+ }
426
+
427
+ // Tab切换
428
+ function switchTab(tabName) {
429
+ // 更新按钮状态
430
+ document.querySelectorAll('.tab-button').forEach(btn => {
431
+ btn.classList.remove('active', 'border-primary', 'text-primary');
432
+ btn.classList.add('border-transparent', 'text-muted-foreground');
433
+ });
434
+
435
+ const activeBtn = document.querySelector(`[data-tab="${tabName}"]`);
436
+ activeBtn.classList.add('active', 'border-primary', 'text-primary');
437
+ activeBtn.classList.remove('border-transparent', 'text-muted-foreground');
438
+
439
+ // 切换内容
440
+ document.querySelectorAll('.tab-content').forEach(content => {
441
+ content.classList.add('hidden');
442
+ });
443
+ document.getElementById(`${tabName}-tab`).classList.remove('hidden');
444
+
445
+ // 重新初始化图标
446
+ lucide.createIcons();
447
+ }
448
+
449
+ // 文件视图初始化
450
+ function initializeFilesView() {
451
+ // 初始化扩展名过滤器
452
+ const extensions = Object.keys(packageData.fileTypeStats);
453
+ const filterSelect = document.getElementById('extension-filter');
454
+ extensions.forEach(ext => {
455
+ const option = document.createElement('option');
456
+ option.value = ext;
457
+ option.textContent = `${ext} (${packageData.fileTypeStats[ext].count})`;
458
+ filterSelect.appendChild(option);
459
+ });
460
+
461
+ // 初始化文件列表
462
+ filteredFiles = [...packageData.files];
463
+ updateFilesTable();
464
+ }
465
+
466
+ // 搜索处理
467
+ function handleSearch() {
468
+ const searchTerm = document.getElementById('search-input').value.toLowerCase();
469
+ const extensionFilter = document.getElementById('extension-filter').value;
470
+
471
+ filteredFiles = packageData.files.filter(file => {
472
+ const matchesSearch = file.path.toLowerCase().includes(searchTerm);
473
+ const matchesExtension = !extensionFilter || file.extension === extensionFilter;
474
+ return matchesSearch && matchesExtension;
475
+ });
476
+
477
+ currentPage = 1;
478
+ updateFilesTable();
479
+ }
480
+
481
+ // 筛选处理
482
+ function handleFilter() {
483
+ handleSearch(); // 重新应用所有筛选条件
484
+ }
485
+
486
+ // 排序处理
487
+ function handleSort() {
488
+ const sortValue = document.getElementById('sort-select').value;
489
+
490
+ filteredFiles.sort((a, b) => {
491
+ switch (sortValue) {
492
+ case 'size-desc':
493
+ return b.size - a.size;
494
+ case 'size-asc':
495
+ return a.size - b.size;
496
+ case 'name-asc':
497
+ return a.path.localeCompare(b.path);
498
+ case 'name-desc':
499
+ return b.path.localeCompare(a.path);
500
+ default:
501
+ return 0;
502
+ }
503
+ });
504
+
505
+ updateFilesTable();
506
+ }
507
+
508
+ // 更新文件表格
509
+ function updateFilesTable() {
510
+ const tbody = document.getElementById('files-table-body');
511
+ const startIndex = (currentPage - 1) * itemsPerPage;
512
+ const endIndex = startIndex + itemsPerPage;
513
+ const pageFiles = filteredFiles.slice(startIndex, endIndex);
514
+
515
+ tbody.innerHTML = pageFiles.map(file => {
516
+ const dependencyCount = getDependencyCount(file.path);
517
+ return `
518
+ <tr class="hover:bg-accent">
519
+ <td class="px-6 py-4">
520
+ <div class="flex items-center">
521
+ <i data-lucide="file" class="w-4 h-4 mr-2 text-muted-foreground"></i>
522
+ <span class="font-mono text-sm break-all">${file.path}</span>
523
+ </div>
524
+ </td>
525
+ <td class="px-6 py-4">
526
+ <div class="flex items-center">
527
+ <span class="font-medium">${formatFileSize(file.size)}</span>
528
+ <div class="ml-2 w-16 h-2 bg-muted rounded-full overflow-hidden">
529
+ <div class="progress-bar h-full" style="width: ${Math.min(100, (file.size / packageData.files[0].size) * 100)}%"></div>
530
+ </div>
531
+ </div>
532
+ </td>
533
+ <td class="px-6 py-4">
534
+ <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-secondary text-secondary-foreground">
535
+ ${file.extension || 'Unknown'}
536
+ </span>
537
+ </td>
538
+ <td class="px-6 py-4 text-sm text-muted-foreground">
539
+ ${formatDate(file.lastModified)}
540
+ </td>
541
+ <td class="px-6 py-4">
542
+ <div class="flex items-center space-x-2">
543
+ ${dependencyCount > 0 ? `
544
+ <button title="查看反向依赖" class="px-2 py-1 bg-blue-500 text-white rounded text-xs hover:bg-blue-600 transition-colors flex items-center"
545
+ onclick="showDependentsList('${escapeForSingleQuoteJsString(file.path)}')">
546
+ <i data-lucide="git-fork" class="w-3 h-3 mr-1"></i>
547
+ ${dependencyCount}
548
+ </button>
549
+ ` : `
550
+ <span class="text-xs text-muted-foreground">无依赖</span>
551
+ `}
552
+ </div>
553
+ </td>
554
+ </tr>
555
+ `;
556
+ }).join('');
557
+
558
+ // 更新分页信息
559
+ updatePagination();
560
+
561
+ // 重新初始化图标
562
+ lucide.createIcons();
563
+ }
564
+
565
+ // 更新分页
566
+ function updatePagination() {
567
+ const totalPages = Math.ceil(filteredFiles.length / itemsPerPage);
568
+ if (filteredFiles.length === 0) {
569
+ document.getElementById('files-range').textContent = '0-0';
570
+ document.getElementById('files-total').textContent = '0';
571
+ document.getElementById('page-info').textContent = '0 / 0';
572
+ document.getElementById('prev-page').disabled = true;
573
+ document.getElementById('next-page').disabled = true;
574
+ return;
575
+ }
576
+ const startItem = (currentPage - 1) * itemsPerPage + 1;
577
+ const endItem = Math.min(currentPage * itemsPerPage, filteredFiles.length);
578
+
579
+ document.getElementById('files-range').textContent = `${startItem}-${endItem}`;
580
+ document.getElementById('files-total').textContent = filteredFiles.length;
581
+ document.getElementById('page-info').textContent = `${currentPage} / ${totalPages}`;
582
+
583
+ document.getElementById('prev-page').disabled = currentPage === 1;
584
+ document.getElementById('next-page').disabled = currentPage === totalPages;
585
+ }
586
+
587
+ // 翻页
588
+ function changePage(direction) {
589
+ const totalPages = Math.ceil(filteredFiles.length / itemsPerPage);
590
+ const newPage = currentPage + direction;
591
+
592
+ if (newPage >= 1 && newPage <= totalPages) {
593
+ currentPage = newPage;
594
+ updateFilesTable();
595
+ }
596
+ }
597
+
598
+ // 树形视图初始化
599
+ function initializeTreeView() {
600
+ renderTree();
601
+ }
602
+
603
+ // 渲染树形结构
604
+ function renderTree() {
605
+ const container = document.getElementById('tree-container');
606
+ container.innerHTML = renderTreeNode(treeData.pathAnalyse, 0);
607
+
608
+ // 添加点击事件
609
+ container.addEventListener('click', handleTreeClick);
610
+
611
+ // 重新初始化图标
612
+ lucide.createIcons();
613
+ }
614
+
615
+ // 渲染树节点
616
+ function renderTreeNode(nodes, level) {
617
+ if (!nodes || !Array.isArray(nodes)) return '';
618
+
619
+ return nodes.map(node => {
620
+ const hasChildren = node.child && node.child.length > 0;
621
+ const sizePercent = (node.ratio * 100).toFixed(1);
622
+ const pathName = node.path.split('/').pop();
623
+ const dependencyCount = !hasChildren ? getDependencyCount(node.path) : 0;
624
+
625
+ return `
626
+ <div class="tree-node collapsed" data-path="${node.path}">
627
+ <div class="tree-item ${hasChildren ? 'tree-toggle' : ''} flex items-center">
628
+ <span class="tree-expand-icon w-4 h-4 flex items-center justify-center mr-1">
629
+ ${hasChildren ? '<i data-lucide="chevron-right" class="w-3 h-3"></i>' : '<span class="w-3"></span>'}
630
+ </span>
631
+ <i data-lucide="${hasChildren ? 'folder' : 'file'}" class="w-4 h-4 mr-2 ${hasChildren ? 'tree-folder-icon' : 'tree-file-icon'}"></i>
632
+ <span class="flex-1 font-medium">${pathName}</span>
633
+ <span class="text-sm text-muted-foreground ml-2 font-mono">${formatFileSize(node.size)}</span>
634
+ <span class="text-xs text-muted-foreground ml-2 opacity-75">(${sizePercent}%)</span>
635
+ ${!hasChildren && dependencyCount > 0 ? `
636
+ <button title="查看反向依赖" class="ml-2 px-1 py-0.5 bg-blue-500 text-white rounded text-xs hover:bg-blue-600 transition-colors flex items-center"
637
+ onclick="event.stopPropagation(); showDependentsList('${escapeForSingleQuoteJsString(node.path)}')">
638
+ <i data-lucide="git-fork" class="w-2.5 h-2.5 mr-1"></i>
639
+ ${dependencyCount}
640
+ </button>
641
+ ` : !hasChildren ? `
642
+ <span class="ml-2 text-xs text-muted-foreground opacity-50">无依赖</span>
643
+ ` : ''}
644
+ </div>
645
+ ${hasChildren ? `<div class="tree-children">${renderTreeNode(node.child, level + 1)}</div>` : ''}
646
+ </div>
647
+ `;
648
+ }).join('');
649
+ }
650
+
651
+ // 处理树形结构点击
652
+ function handleTreeClick(e) {
653
+ const toggle = e.target.closest('.tree-toggle');
654
+ if (!toggle) return;
655
+
656
+ e.preventDefault();
657
+ e.stopPropagation();
658
+
659
+ const node = toggle.closest('.tree-node');
660
+ const children = node.querySelector('.tree-children');
661
+
662
+ if (!children) return;
663
+
664
+ if (node.classList.contains('collapsed')) {
665
+ // 展开节点
666
+ node.classList.remove('collapsed');
667
+ children.style.display = 'block';
668
+ } else {
669
+ // 折叠节点
670
+ node.classList.add('collapsed');
671
+ children.style.display = 'none';
672
+ }
673
+
674
+ // 重新初始化图标以确保正确显示
675
+ lucide.createIcons();
676
+ }
677
+
678
+ // 树形搜索
679
+ function handleTreeSearch() {
680
+ const searchTerm = document.getElementById('tree-search').value.toLowerCase();
681
+ const nodes = document.querySelectorAll('.tree-node');
682
+
683
+ if (searchTerm === '') {
684
+ // 重置所有节点显示状态
685
+ nodes.forEach(node => {
686
+ node.style.display = 'block';
687
+ node.classList.add('collapsed'); // 重新折叠所有节点
688
+ const children = node.querySelector('.tree-children');
689
+ if (children) {
690
+ children.style.display = 'none';
691
+ }
692
+ });
693
+ } else {
694
+ // 搜索匹配
695
+ nodes.forEach(node => {
696
+ const path = node.dataset.path.toLowerCase();
697
+ const pathName = node.dataset.path.split('/').pop().toLowerCase();
698
+ const matches = path.includes(searchTerm) || pathName.includes(searchTerm);
699
+
700
+ if (matches) {
701
+ node.style.display = 'block';
702
+ // 展开所有父节点
703
+ expandParentNodes(node);
704
+ } else {
705
+ node.style.display = 'none';
706
+ }
707
+ });
708
+ }
709
+
710
+ // 重新初始化图标
711
+ lucide.createIcons();
712
+ }
713
+
714
+ // 展开父节点的辅助函数
715
+ function expandParentNodes(node) {
716
+ let parent = node.parentElement ? node.parentElement.closest('.tree-node') : null;
717
+ while (parent) {
718
+ parent.style.display = 'block';
719
+ parent.classList.remove('collapsed');
720
+ const children = parent.querySelector('.tree-children');
721
+ if (children) {
722
+ children.style.display = 'block';
723
+ }
724
+ parent = parent.parentElement.closest('.tree-node');
725
+ }
726
+ }
727
+
728
+ // 全部展开
729
+ function expandAllNodes() {
730
+ const nodes = document.querySelectorAll('.tree-node');
731
+ nodes.forEach(node => {
732
+ node.classList.remove('collapsed');
733
+ node.style.display = 'block';
734
+ const children = node.querySelector('.tree-children');
735
+ if (children) {
736
+ children.style.display = 'block';
737
+ }
738
+ });
739
+ lucide.createIcons();
740
+ }
741
+
742
+ // 全部折叠
743
+ function collapseAllNodes() {
744
+ const nodes = document.querySelectorAll('.tree-node');
745
+ nodes.forEach(node => {
746
+ node.classList.add('collapsed');
747
+ const children = node.querySelector('.tree-children');
748
+ if (children) {
749
+ children.style.display = 'none';
750
+ }
751
+ });
752
+ lucide.createIcons();
753
+ }
754
+
755
+
756
+ // ===== 反向依赖功能函数 =====
757
+
758
+ // 获取文件的依赖数量
759
+ function getDependencyCount(filePath) {
760
+ if (!inverseDependencyData || !inverseDependencyData.inverseDependencies) {
761
+ return 0;
762
+ }
763
+
764
+ const dependency = inverseDependencyData.inverseDependencies.find(dep => dep.path === filePath);
765
+ return dependency ? dependency.dependents.length : 0;
766
+ }
767
+
768
+ // 反向依赖视图初始化
769
+ function initializeDependenciesView() {
770
+ if (!inverseDependencyData) {
771
+ console.warn('反向依赖数据未加载');
772
+ return;
773
+ }
774
+
775
+ // 更新统计信息
776
+ updateDependenciesStats();
777
+
778
+ // 更新热门依赖列表
779
+ updateTopDependencies();
780
+
781
+ // 初始化依赖关系表格
782
+ filteredDependencies = [...inverseDependencyData.inverseDependencies];
783
+ updateDependenciesTable();
784
+ }
785
+
786
+ // 更新反向依赖统计信息
787
+ function updateDependenciesStats() {
788
+ const summary = inverseDependencyData.summary;
789
+ document.getElementById('dep-total-files').textContent = summary.totalFiles.toLocaleString();
790
+ document.getElementById('dep-total-dependencies').textContent = summary.totalDependencies.toLocaleString();
791
+ document.getElementById('dep-average-dependencies').textContent = summary.averageDependencies.toFixed(2);
792
+ document.getElementById('dep-most-depended').textContent = summary.mostDependedFiles[0]?.dependentCount || 0;
793
+ }
794
+
795
+ // 更新热门依赖文件列表
796
+ function updateTopDependencies() {
797
+ const topList = document.getElementById('top-dependencies-list');
798
+ const topFiles = inverseDependencyData.summary.mostDependedFiles.slice(0, 10);
799
+
800
+ topList.innerHTML = topFiles.map((file, index) => {
801
+ const fileName = getFileName(file.path);
802
+ const isNodeModule = file.path.includes('node_modules');
803
+ const percentage = ((file.dependentCount / inverseDependencyData.summary.totalDependencies) * 100).toFixed(2);
804
+
805
+ return `
806
+ <div class="flex items-center justify-between p-3 bg-muted/50 rounded-lg hover:bg-muted transition-colors">
807
+ <div class="flex items-center flex-1 min-w-0">
808
+ <div class="w-8 h-8 rounded-full bg-gradient-to-r from-blue-500 to-purple-500 flex items-center justify-center text-white text-sm font-bold mr-3">
809
+ ${index + 1}
810
+ </div>
811
+ <div class="flex-1 min-w-0">
812
+ <div class="font-medium text-sm truncate" title="${file.path}">
813
+ ${fileName}
814
+ </div>
815
+ <div class="text-xs text-muted-foreground truncate">
816
+ ${isNodeModule ? '📦 ' : '📄 '}${file.path}
817
+ </div>
818
+ </div>
819
+ </div>
820
+ <div class="text-right ml-4">
821
+ <div class="text-lg font-bold text-primary">${file.dependentCount}</div>
822
+ <div class="text-xs text-muted-foreground">${percentage}%</div>
823
+ </div>
824
+ </div>
825
+ `;
826
+ }).join('');
827
+ }
828
+
829
+ // 反向依赖搜索处理
830
+ function handleDepSearch() {
831
+ const searchTerm = document.getElementById('dep-search-input').value.toLowerCase();
832
+ const filterValue = document.getElementById('dep-filter-select').value;
833
+
834
+ filteredDependencies = inverseDependencyData.inverseDependencies.filter(dep => {
835
+ const matchesSearch = dep.path.toLowerCase().includes(searchTerm);
836
+ let matchesFilter = true;
837
+
838
+ switch (filterValue) {
839
+ case 'src':
840
+ matchesFilter = dep.path.startsWith('src/');
841
+ break;
842
+ case 'node_modules':
843
+ matchesFilter = dep.path.includes('node_modules');
844
+ break;
845
+ case 'high-deps':
846
+ matchesFilter = dep.dependents.length > 100;
847
+ break;
848
+ }
849
+
850
+ return matchesSearch && matchesFilter;
851
+ });
852
+
853
+ currentDepPage = 1;
854
+ updateDependenciesTable();
855
+ }
856
+
857
+ // 反向依赖筛选处理
858
+ function handleDepFilter() {
859
+ handleDepSearch(); // 重新应用所有筛选条件
860
+ }
861
+
862
+ // 反向依赖排序处理
863
+ function handleDepSort() {
864
+ const sortValue = document.getElementById('dep-sort-select').value;
865
+
866
+ filteredDependencies.sort((a, b) => {
867
+ switch (sortValue) {
868
+ case 'dependents-desc':
869
+ return b.dependents.length - a.dependents.length;
870
+ case 'dependents-asc':
871
+ return a.dependents.length - b.dependents.length;
872
+ case 'name-asc':
873
+ return a.path.localeCompare(b.path);
874
+ case 'name-desc':
875
+ return b.path.localeCompare(a.path);
876
+ default:
877
+ return 0;
878
+ }
879
+ });
880
+
881
+ updateDependenciesTable();
882
+ }
883
+
884
+ // 更新反向依赖表格
885
+ function updateDependenciesTable() {
886
+ const tbody = document.getElementById('dependencies-table-body');
887
+ const startIndex = (currentDepPage - 1) * itemsPerPage;
888
+ const endIndex = startIndex + itemsPerPage;
889
+ const pageDeps = filteredDependencies.slice(startIndex, endIndex);
890
+
891
+ tbody.innerHTML = pageDeps.map(dep => {
892
+ const fileName = getFileName(dep.path);
893
+ const isNodeModule = dep.path.includes('node_modules');
894
+ const dependentCount = dep.dependents.length;
895
+
896
+ return `
897
+ <tr class="hover:bg-accent">
898
+ <td class="px-6 py-4">
899
+ <div class="flex items-center">
900
+ <i data-lucide="${isNodeModule ? 'package' : 'file'}" class="w-4 h-4 mr-2 text-muted-foreground"></i>
901
+ <div>
902
+ <div class="font-medium text-sm">${fileName}</div>
903
+ <div class="text-xs text-muted-foreground font-mono break-all">${dep.path}</div>
904
+ </div>
905
+ </div>
906
+ </td>
907
+ <td class="px-6 py-4">
908
+ <div class="flex items-center">
909
+ <span class="text-lg font-bold text-primary mr-2">${dependentCount}</span>
910
+ <div class="flex-1 bg-muted rounded-full h-2 overflow-hidden">
911
+ <div class="bg-gradient-to-r from-blue-500 to-purple-500 h-full transition-all duration-300"
912
+ style="width: ${Math.min(100, (dependentCount / inverseDependencyData.summary.mostDependedFiles[0].dependentCount) * 100)}%"></div>
913
+ </div>
914
+ </div>
915
+ </td>
916
+ <td class="px-6 py-4">
917
+ <button class="px-3 py-1 bg-primary text-primary-foreground rounded text-sm hover:bg-primary/90 transition-colors"
918
+ onclick="showDependentsList('${escapeForSingleQuoteJsString(dep.path)}')">
919
+ 查看依赖
920
+ </button>
921
+ </td>
922
+ </tr>
923
+ `;
924
+ }).join('');
925
+
926
+ // 更新分页信息
927
+ updateDepPagination();
928
+
929
+ // 重新初始化图标
930
+ lucide.createIcons();
931
+ }
932
+
933
+ // 更新反向依赖分页
934
+ function updateDepPagination() {
935
+ const totalPages = Math.ceil(filteredDependencies.length / itemsPerPage);
936
+ if (filteredDependencies.length === 0) {
937
+ document.getElementById('dep-range').textContent = '0-0';
938
+ document.getElementById('dep-total').textContent = '0';
939
+ document.getElementById('dep-page-info').textContent = '0 / 0';
940
+ document.getElementById('dep-prev-page').disabled = true;
941
+ document.getElementById('dep-next-page').disabled = true;
942
+ return;
943
+ }
944
+ const startItem = (currentDepPage - 1) * itemsPerPage + 1;
945
+ const endItem = Math.min(currentDepPage * itemsPerPage, filteredDependencies.length);
946
+
947
+ document.getElementById('dep-range').textContent = `${startItem}-${endItem}`;
948
+ document.getElementById('dep-total').textContent = filteredDependencies.length;
949
+ document.getElementById('dep-page-info').textContent = `${currentDepPage} / ${totalPages}`;
950
+
951
+ document.getElementById('dep-prev-page').disabled = currentDepPage === 1;
952
+ document.getElementById('dep-next-page').disabled = currentDepPage === totalPages;
953
+ }
954
+
955
+ // 反向依赖翻页
956
+ function changeDepPage(direction) {
957
+ const totalPages = Math.ceil(filteredDependencies.length / itemsPerPage);
958
+ const newPage = currentDepPage + direction;
959
+
960
+ if (newPage >= 1 && newPage <= totalPages) {
961
+ currentDepPage = newPage;
962
+ updateDependenciesTable();
963
+ }
964
+ }
965
+
966
+ // 显示依赖列表弹窗
967
+ function showDependentsList(filePath, existingModal = null) {
968
+ const dep = inverseDependencyData.inverseDependencies.find(d => d.path === filePath);
969
+ if (!dep) {
970
+ if (existingModal) {
971
+ // 如果文件没有依赖关系,显示提示
972
+ updateModalContent(existingModal, filePath, [], 0, true);
973
+ }
974
+ return;
975
+ }
976
+
977
+ const fileName = getFileName(filePath);
978
+ const dependentsList = dep.dependents.slice(0, 500); // 限制显示前500个
979
+
980
+ if (existingModal) {
981
+ // 更新现有弹窗内容
982
+ updateModalContent(existingModal, filePath, dependentsList, dep.dependents.length, false);
983
+ } else {
984
+ // 创建新弹窗
985
+ const modal = document.createElement('div');
986
+ modal.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 p-4';
987
+ modal.innerHTML = createModalHTML(filePath, dependentsList, dep.dependents.length, false);
988
+
989
+ document.body.appendChild(modal);
990
+ lucide.createIcons();
991
+
992
+ // 点击背景关闭
993
+ modal.addEventListener('click', (e) => {
994
+ if (e.target === modal) {
995
+ modal.remove();
996
+ }
997
+ });
998
+ }
999
+ }
1000
+
1001
+ // 创建弹窗HTML内容
1002
+ function createModalHTML(filePath, dependentsList, totalCount, noDependencies) {
1003
+ const fileName = getFileName(filePath);
1004
+
1005
+ return `
1006
+ <div class="bg-card rounded-lg border border-border max-w-4xl w-full max-h-[80vh] overflow-hidden">
1007
+ <div class="p-6 border-b border-border">
1008
+ <div class="flex items-center justify-between">
1009
+ <div>
1010
+ <h3 class="text-lg font-semibold">${fileName} 的反向依赖关系</h3>
1011
+ <p class="text-sm text-muted-foreground mt-1">
1012
+ ${filePath}
1013
+ </p>
1014
+ <p class="text-sm text-muted-foreground mt-1">
1015
+ ${noDependencies ? '此文件没有被其他文件依赖' : `共 ${totalCount} 个文件依赖此文件`}
1016
+ </p>
1017
+ </div>
1018
+ <button onclick="this.closest('.fixed').remove()" class="text-muted-foreground hover:text-foreground">
1019
+ <i data-lucide="x" class="w-5 h-5"></i>
1020
+ </button>
1021
+ </div>
1022
+ </div>
1023
+ <div class="p-6 overflow-y-auto max-h-96">
1024
+ ${noDependencies ? `
1025
+ <div class="text-center py-8">
1026
+ <i data-lucide="info" class="w-12 h-12 text-muted-foreground mx-auto mb-4"></i>
1027
+ <p class="text-muted-foreground">此文件没有被其他文件依赖</p>
1028
+ </div>
1029
+ ` : `
1030
+ <div class="space-y-2">
1031
+ ${dependentsList.map(dependent => {
1032
+ const depFileName = getFileName(dependent);
1033
+ const isNodeModule = dependent.includes('node_modules');
1034
+ const depCount = getDependencyCount(dependent);
1035
+ return `
1036
+ <div class="flex items-center p-2 bg-muted/50 rounded hover:bg-muted transition-colors">
1037
+ <i data-lucide="${isNodeModule ? 'package' : 'file'}" class="w-4 h-4 mr-2 text-muted-foreground"></i>
1038
+ <div class="flex-1 min-w-0">
1039
+ <div class="font-medium text-sm">${depFileName}</div>
1040
+ <div class="text-xs text-muted-foreground font-mono truncate">${dependent}</div>
1041
+ </div>
1042
+ ${depCount > 0 ? `
1043
+ <button title="查看反向依赖" class="ml-2 px-2 py-1 bg-blue-500 text-white rounded text-xs hover:bg-blue-600 transition-colors flex items-center"
1044
+ onclick="showDependentsList('${escapeForSingleQuoteJsString(dependent)}', this.closest('.fixed'))">
1045
+ <i data-lucide="git-fork" class="w-3 h-3 mr-1"></i>
1046
+ ${depCount}
1047
+ </button>
1048
+ ` : `
1049
+ <span class="ml-2 text-xs text-muted-foreground">无依赖</span>
1050
+ `}
1051
+ </div>
1052
+ `;
1053
+ }).join('')}
1054
+ ${totalCount > 500 ? `
1055
+ <div class="text-center text-muted-foreground text-sm py-2">
1056
+ 还有 ${totalCount - 500} 个依赖文件未显示...
1057
+ </div>
1058
+ ` : ''}
1059
+ </div>
1060
+ `}
1061
+ </div>
1062
+ </div>
1063
+ `;
1064
+ }
1065
+
1066
+ // 更新弹窗内容
1067
+ function updateModalContent(modal, filePath, dependentsList, totalCount, noDependencies) {
1068
+ const modalContent = modal.querySelector('.bg-card');
1069
+ const tempDiv = document.createElement('div');
1070
+ tempDiv.innerHTML = createModalHTML(filePath, dependentsList, totalCount, noDependencies);
1071
+ const newContent = tempDiv.querySelector('.bg-card');
1072
+ modalContent.innerHTML = newContent.innerHTML;
1073
+ lucide.createIcons();
1074
+ }
1075
+
1076
+ // 页面加载完成后初始化
1077
+ document.addEventListener('DOMContentLoaded', () => {
1078
+ loadData();
1079
+ });
1080
+
1081
+ </script>
1082
+ </html>