xzx-icon-vue2 0.0.6 → 0.1.1
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 +25 -0
- package/bin/cli.js +57 -0
- package/lib/index.js +1 -1
- package/lib/index.umd.js +1 -1
- package/package.json +16 -11
- package/preview/index.html +579 -0
- package/preview/server.js +145 -0
package/README.md
CHANGED
@@ -26,6 +26,31 @@ import XzxIconVue2 from 'xzx-icon-vue2'
|
|
26
26
|
Vue.use(XzxIconVue2)
|
27
27
|
```
|
28
28
|
|
29
|
+
### 预览所有图标
|
30
|
+
|
31
|
+
安装后可以使用CLI命令启动本地预览服务器:
|
32
|
+
|
33
|
+
```bash
|
34
|
+
# 使用 npx
|
35
|
+
npx xzx-icon-vue2 preview
|
36
|
+
|
37
|
+
# 或者全局安装后使用
|
38
|
+
npm install -g xzx-icon-vue2
|
39
|
+
xzx-icon-vue2 preview
|
40
|
+
|
41
|
+
# 使用 yarn
|
42
|
+
yarn xzx-icon-vue2 preview
|
43
|
+
|
44
|
+
# 使用 pnpm
|
45
|
+
pnpm xzx-icon-vue2 preview
|
46
|
+
```
|
47
|
+
|
48
|
+
预览功能特性:
|
49
|
+
- 🔍 **实时搜索**:按 `/` 快速聚焦搜索框,ESC 清空搜索
|
50
|
+
- 🏷️ **分类筛选**:按类型快速筛选图标
|
51
|
+
- 📋 **一键复制**:点击图标即可复制Vue代码
|
52
|
+
- 📱 **响应式设计**:支持移动端浏览
|
53
|
+
|
29
54
|
### CDN 使用
|
30
55
|
|
31
56
|
```html
|
package/bin/cli.js
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
#!/usr/bin/env node
|
2
|
+
|
3
|
+
const { spawn } = require('child_process')
|
4
|
+
const path = require('path')
|
5
|
+
|
6
|
+
const command = process.argv[2]
|
7
|
+
|
8
|
+
if (command === 'preview') {
|
9
|
+
console.log('🚀 启动图标预览服务器...')
|
10
|
+
|
11
|
+
const serverPath = path.join(__dirname, '../preview/server.js')
|
12
|
+
const child = spawn('node', [serverPath], {
|
13
|
+
stdio: 'inherit',
|
14
|
+
cwd: process.cwd()
|
15
|
+
})
|
16
|
+
|
17
|
+
child.on('error', (err) => {
|
18
|
+
console.error('❌ 启动预览服务器失败:', err.message)
|
19
|
+
process.exit(1)
|
20
|
+
})
|
21
|
+
|
22
|
+
child.on('exit', (code) => {
|
23
|
+
if (code !== 0) {
|
24
|
+
console.error(`❌ 预览服务器退出,代码: ${code}`)
|
25
|
+
process.exit(code)
|
26
|
+
}
|
27
|
+
})
|
28
|
+
|
29
|
+
// 优雅关闭
|
30
|
+
process.on('SIGINT', () => {
|
31
|
+
console.log('\n👋 关闭预览服务器...')
|
32
|
+
child.kill('SIGINT')
|
33
|
+
process.exit(0)
|
34
|
+
})
|
35
|
+
|
36
|
+
process.on('SIGTERM', () => {
|
37
|
+
child.kill('SIGTERM')
|
38
|
+
process.exit(0)
|
39
|
+
})
|
40
|
+
|
41
|
+
} else {
|
42
|
+
console.log(`
|
43
|
+
🎨 XZX Icon Vue2 CLI
|
44
|
+
|
45
|
+
用法:
|
46
|
+
xzx-icon-vue2 preview 启动图标预览服务器
|
47
|
+
|
48
|
+
选项:
|
49
|
+
-h, --help 显示帮助信息
|
50
|
+
-v, --version 显示版本信息
|
51
|
+
`)
|
52
|
+
|
53
|
+
if (command === '-v' || command === '--version') {
|
54
|
+
const packageJson = require('../package.json')
|
55
|
+
console.log(packageJson.version)
|
56
|
+
}
|
57
|
+
}
|
package/lib/index.js
CHANGED
package/lib/index.umd.js
CHANGED
package/package.json
CHANGED
@@ -1,15 +1,20 @@
|
|
1
1
|
{
|
2
2
|
"name": "xzx-icon-vue2",
|
3
|
-
"version": "0.
|
4
|
-
"description": "Vue2
|
3
|
+
"version": "0.1.1",
|
4
|
+
"description": "基于 Vue2 的内联 SVG 图标组件库",
|
5
5
|
"main": "lib/index.js",
|
6
6
|
"module": "lib/index.esm.js",
|
7
|
-
"unpkg": "lib/index.umd.js",
|
8
|
-
"jsdelivr": "lib/index.umd.js",
|
7
|
+
"unpkg": "lib/index.umd.min.js",
|
8
|
+
"jsdelivr": "lib/index.umd.min.js",
|
9
9
|
"types": "lib/index.d.ts",
|
10
|
+
"bin": {
|
11
|
+
"xzx-icon-vue2": "bin/cli.js"
|
12
|
+
},
|
10
13
|
"files": [
|
11
14
|
"lib",
|
12
15
|
"src",
|
16
|
+
"bin",
|
17
|
+
"preview",
|
13
18
|
"README.md"
|
14
19
|
],
|
15
20
|
"scripts": {
|
@@ -37,14 +42,14 @@
|
|
37
42
|
},
|
38
43
|
"devDependencies": {
|
39
44
|
"@babel/core": "^7.23.0",
|
40
|
-
"@babel/plugin-transform-object-rest-spread": "^7.22.15",
|
41
45
|
"@babel/preset-env": "^7.23.0",
|
42
|
-
"rollup": "^
|
43
|
-
"rollup
|
44
|
-
"rollup
|
45
|
-
"rollup
|
46
|
-
"
|
47
|
-
"
|
46
|
+
"@rollup/plugin-babel": "^6.0.4",
|
47
|
+
"@rollup/plugin-commonjs": "^25.0.7",
|
48
|
+
"@rollup/plugin-node-resolve": "^15.2.3",
|
49
|
+
"@rollup/plugin-terser": "^0.4.4",
|
50
|
+
"express": "^4.18.2",
|
51
|
+
"open": "^8.4.2",
|
52
|
+
"rollup": "^4.5.0"
|
48
53
|
},
|
49
54
|
"repository": {
|
50
55
|
"type": "git",
|
@@ -0,0 +1,579 @@
|
|
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>XZX Icon Vue2 - 图标预览</title>
|
7
|
+
<style>
|
8
|
+
* {
|
9
|
+
margin: 0;
|
10
|
+
padding: 0;
|
11
|
+
box-sizing: border-box;
|
12
|
+
}
|
13
|
+
|
14
|
+
body {
|
15
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
16
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
17
|
+
min-height: 100vh;
|
18
|
+
color: #333;
|
19
|
+
}
|
20
|
+
|
21
|
+
.container {
|
22
|
+
max-width: 1200px;
|
23
|
+
margin: 0 auto;
|
24
|
+
padding: 20px;
|
25
|
+
}
|
26
|
+
|
27
|
+
.header {
|
28
|
+
text-align: center;
|
29
|
+
margin-bottom: 40px;
|
30
|
+
color: white;
|
31
|
+
}
|
32
|
+
|
33
|
+
.header h1 {
|
34
|
+
font-size: 3rem;
|
35
|
+
margin-bottom: 10px;
|
36
|
+
text-shadow: 0 2px 4px rgba(0,0,0,0.3);
|
37
|
+
}
|
38
|
+
|
39
|
+
.header p {
|
40
|
+
font-size: 1.2rem;
|
41
|
+
opacity: 0.9;
|
42
|
+
}
|
43
|
+
|
44
|
+
.stats {
|
45
|
+
background: rgba(255,255,255,0.2);
|
46
|
+
-webkit-backdrop-filter: blur(10px);
|
47
|
+
backdrop-filter: blur(10px);
|
48
|
+
border-radius: 12px;
|
49
|
+
padding: 20px;
|
50
|
+
margin-bottom: 30px;
|
51
|
+
color: white;
|
52
|
+
}
|
53
|
+
|
54
|
+
.stats-grid {
|
55
|
+
display: grid;
|
56
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
57
|
+
gap: 20px;
|
58
|
+
text-align: center;
|
59
|
+
}
|
60
|
+
|
61
|
+
.stat-item h3 {
|
62
|
+
font-size: 2rem;
|
63
|
+
margin-bottom: 5px;
|
64
|
+
}
|
65
|
+
|
66
|
+
.controls {
|
67
|
+
background: rgba(255,255,255,0.95);
|
68
|
+
-webkit-backdrop-filter: blur(10px);
|
69
|
+
backdrop-filter: blur(10px);
|
70
|
+
border-radius: 12px;
|
71
|
+
padding: 20px;
|
72
|
+
margin-bottom: 30px;
|
73
|
+
box-shadow: 0 8px 32px rgba(0,0,0,0.1);
|
74
|
+
}
|
75
|
+
|
76
|
+
.search-box {
|
77
|
+
position: relative;
|
78
|
+
margin-bottom: 20px;
|
79
|
+
}
|
80
|
+
|
81
|
+
.search-box input {
|
82
|
+
width: 100%;
|
83
|
+
padding: 15px 50px 15px 20px;
|
84
|
+
border: 2px solid #e1e5e9;
|
85
|
+
border-radius: 8px;
|
86
|
+
font-size: 16px;
|
87
|
+
transition: all 0.3s ease;
|
88
|
+
}
|
89
|
+
|
90
|
+
.search-box input:focus {
|
91
|
+
outline: none;
|
92
|
+
border-color: #667eea;
|
93
|
+
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
|
94
|
+
}
|
95
|
+
|
96
|
+
.search-icon {
|
97
|
+
position: absolute;
|
98
|
+
right: 15px;
|
99
|
+
top: 50%;
|
100
|
+
transform: translateY(-50%);
|
101
|
+
color: #666;
|
102
|
+
}
|
103
|
+
|
104
|
+
.filters {
|
105
|
+
display: flex;
|
106
|
+
gap: 10px;
|
107
|
+
flex-wrap: wrap;
|
108
|
+
align-items: center;
|
109
|
+
}
|
110
|
+
|
111
|
+
.filter-tag {
|
112
|
+
padding: 8px 16px;
|
113
|
+
background: #f5f5f5;
|
114
|
+
border: 2px solid transparent;
|
115
|
+
border-radius: 20px;
|
116
|
+
cursor: pointer;
|
117
|
+
transition: all 0.3s ease;
|
118
|
+
font-size: 14px;
|
119
|
+
}
|
120
|
+
|
121
|
+
.filter-tag:hover {
|
122
|
+
background: #e1e5e9;
|
123
|
+
}
|
124
|
+
|
125
|
+
.filter-tag.active {
|
126
|
+
background: #667eea;
|
127
|
+
color: white;
|
128
|
+
}
|
129
|
+
|
130
|
+
.icons-grid {
|
131
|
+
background: white;
|
132
|
+
border-radius: 12px;
|
133
|
+
padding: 30px;
|
134
|
+
box-shadow: 0 8px 32px rgba(0,0,0,0.1);
|
135
|
+
display: grid;
|
136
|
+
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
|
137
|
+
gap: 20px;
|
138
|
+
}
|
139
|
+
|
140
|
+
.icon-item {
|
141
|
+
text-align: center;
|
142
|
+
padding: 20px;
|
143
|
+
border: 2px solid #f5f5f5;
|
144
|
+
border-radius: 8px;
|
145
|
+
cursor: pointer;
|
146
|
+
transition: all 0.3s ease;
|
147
|
+
position: relative;
|
148
|
+
}
|
149
|
+
|
150
|
+
.icon-item:hover {
|
151
|
+
border-color: #667eea;
|
152
|
+
transform: translateY(-2px);
|
153
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
154
|
+
}
|
155
|
+
|
156
|
+
.icon-item.copied {
|
157
|
+
border-color: #52c41a;
|
158
|
+
background: #f6ffed;
|
159
|
+
}
|
160
|
+
|
161
|
+
.icon-display {
|
162
|
+
font-size: 32px;
|
163
|
+
margin-bottom: 10px;
|
164
|
+
color: #333;
|
165
|
+
height: 40px;
|
166
|
+
display: flex;
|
167
|
+
align-items: center;
|
168
|
+
justify-content: center;
|
169
|
+
}
|
170
|
+
|
171
|
+
.icon-name {
|
172
|
+
font-size: 12px;
|
173
|
+
color: #666;
|
174
|
+
word-break: break-all;
|
175
|
+
line-height: 1.3;
|
176
|
+
}
|
177
|
+
|
178
|
+
.copy-tooltip {
|
179
|
+
position: absolute;
|
180
|
+
top: -30px;
|
181
|
+
left: 50%;
|
182
|
+
transform: translateX(-50%);
|
183
|
+
background: #333;
|
184
|
+
color: white;
|
185
|
+
padding: 5px 10px;
|
186
|
+
border-radius: 4px;
|
187
|
+
font-size: 12px;
|
188
|
+
opacity: 0;
|
189
|
+
transition: opacity 0.3s ease;
|
190
|
+
pointer-events: none;
|
191
|
+
white-space: nowrap;
|
192
|
+
}
|
193
|
+
|
194
|
+
.copy-tooltip.show {
|
195
|
+
opacity: 1;
|
196
|
+
}
|
197
|
+
|
198
|
+
.no-results {
|
199
|
+
text-align: center;
|
200
|
+
padding: 60px 20px;
|
201
|
+
color: #999;
|
202
|
+
}
|
203
|
+
|
204
|
+
.no-results .icon {
|
205
|
+
font-size: 64px;
|
206
|
+
margin-bottom: 20px;
|
207
|
+
opacity: 0.5;
|
208
|
+
}
|
209
|
+
|
210
|
+
.loading {
|
211
|
+
text-align: center;
|
212
|
+
padding: 60px 20px;
|
213
|
+
color: #666;
|
214
|
+
}
|
215
|
+
|
216
|
+
.loading .spinner {
|
217
|
+
display: inline-block;
|
218
|
+
width: 40px;
|
219
|
+
height: 40px;
|
220
|
+
border: 4px solid #f3f3f3;
|
221
|
+
border-top: 4px solid #667eea;
|
222
|
+
border-radius: 50%;
|
223
|
+
animation: spin 1s linear infinite;
|
224
|
+
margin-bottom: 20px;
|
225
|
+
}
|
226
|
+
|
227
|
+
@keyframes spin {
|
228
|
+
0% { transform: rotate(0deg); }
|
229
|
+
100% { transform: rotate(360deg); }
|
230
|
+
}
|
231
|
+
|
232
|
+
.footer {
|
233
|
+
text-align: center;
|
234
|
+
margin-top: 40px;
|
235
|
+
color: rgba(255,255,255,0.8);
|
236
|
+
}
|
237
|
+
|
238
|
+
.footer a {
|
239
|
+
color: rgba(255,255,255,0.9);
|
240
|
+
text-decoration: none;
|
241
|
+
}
|
242
|
+
|
243
|
+
.footer a:hover {
|
244
|
+
text-decoration: underline;
|
245
|
+
}
|
246
|
+
|
247
|
+
@media (max-width: 768px) {
|
248
|
+
.header h1 {
|
249
|
+
font-size: 2rem;
|
250
|
+
}
|
251
|
+
|
252
|
+
.icons-grid {
|
253
|
+
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
|
254
|
+
gap: 15px;
|
255
|
+
padding: 20px;
|
256
|
+
}
|
257
|
+
|
258
|
+
.icon-item {
|
259
|
+
padding: 15px;
|
260
|
+
}
|
261
|
+
|
262
|
+
.icon-display {
|
263
|
+
font-size: 24px;
|
264
|
+
height: 30px;
|
265
|
+
}
|
266
|
+
}
|
267
|
+
</style>
|
268
|
+
</head>
|
269
|
+
<body>
|
270
|
+
<div class="container">
|
271
|
+
<header class="header">
|
272
|
+
<h1>🎨 XZX Icon Vue2</h1>
|
273
|
+
<p>精美的 Vue2 图标组件库</p>
|
274
|
+
</header>
|
275
|
+
|
276
|
+
<div class="stats">
|
277
|
+
<div class="stats-grid">
|
278
|
+
<div class="stat-item">
|
279
|
+
<h3 id="total-count">-</h3>
|
280
|
+
<p>总图标数</p>
|
281
|
+
</div>
|
282
|
+
<div class="stat-item">
|
283
|
+
<h3 id="filtered-count">-</h3>
|
284
|
+
<p>当前显示</p>
|
285
|
+
</div>
|
286
|
+
<div class="stat-item">
|
287
|
+
<h3>Vue2</h3>
|
288
|
+
<p>兼容版本</p>
|
289
|
+
</div>
|
290
|
+
</div>
|
291
|
+
</div>
|
292
|
+
|
293
|
+
<div class="controls">
|
294
|
+
<div class="search-box">
|
295
|
+
<input
|
296
|
+
type="text"
|
297
|
+
id="search-input"
|
298
|
+
placeholder="🔍 搜索图标名称... (按 / 快速聚焦,ESC 清空)"
|
299
|
+
autocomplete="off"
|
300
|
+
>
|
301
|
+
<div class="search-icon">⌕</div>
|
302
|
+
</div>
|
303
|
+
|
304
|
+
<div class="filters">
|
305
|
+
<span style="color: #666; margin-right: 10px;">快速筛选:</span>
|
306
|
+
<div class="filter-tag active" data-filter="all">全部</div>
|
307
|
+
<div class="filter-tag" data-filter="filled">填充类</div>
|
308
|
+
<div class="filter-tag" data-filter="one">单色类</div>
|
309
|
+
<div class="filter-tag" data-filter="arrow">箭头类</div>
|
310
|
+
<div class="filter-tag" data-filter="file">文件类</div>
|
311
|
+
<div class="filter-tag" data-filter="edit">编辑类</div>
|
312
|
+
<div class="filter-tag" data-filter="close">关闭类</div>
|
313
|
+
</div>
|
314
|
+
</div>
|
315
|
+
|
316
|
+
<div id="icons-container">
|
317
|
+
<div class="loading">
|
318
|
+
<div class="spinner"></div>
|
319
|
+
<p>正在加载图标...</p>
|
320
|
+
</div>
|
321
|
+
</div>
|
322
|
+
|
323
|
+
<footer class="footer">
|
324
|
+
<p>
|
325
|
+
基于 <a href="https://www.npmjs.com/package/@xzx-design/icons-svg" target="_blank" rel="noopener">@xzx-design/icons-svg</a> 构建
|
326
|
+
| 点击图标复制名称
|
327
|
+
</p>
|
328
|
+
</footer>
|
329
|
+
</div>
|
330
|
+
|
331
|
+
<!-- Vue2 -->
|
332
|
+
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
|
333
|
+
<!-- 本地图标库 -->
|
334
|
+
<script src="/lib/index.umd.js"></script>
|
335
|
+
|
336
|
+
<script>
|
337
|
+
// 全局变量存储Vue实例
|
338
|
+
let vueApp = null
|
339
|
+
|
340
|
+
// 复制图标代码功能
|
341
|
+
function copyIconCode(iconName, element) {
|
342
|
+
// 只复制图标名称,不包含标签
|
343
|
+
const code = iconName
|
344
|
+
|
345
|
+
// 创建临时元素复制到剪贴板
|
346
|
+
const textarea = document.createElement('textarea')
|
347
|
+
textarea.value = code
|
348
|
+
document.body.appendChild(textarea)
|
349
|
+
textarea.select()
|
350
|
+
document.execCommand('copy')
|
351
|
+
document.body.removeChild(textarea)
|
352
|
+
|
353
|
+
// 显示复制成功提示
|
354
|
+
element.classList.add('copied')
|
355
|
+
const tooltip = element.querySelector('.copy-tooltip')
|
356
|
+
tooltip.textContent = '已复制!'
|
357
|
+
tooltip.classList.add('show')
|
358
|
+
|
359
|
+
setTimeout(() => {
|
360
|
+
element.classList.remove('copied')
|
361
|
+
tooltip.classList.remove('show')
|
362
|
+
tooltip.textContent = '点击复制名称'
|
363
|
+
}, 1500)
|
364
|
+
|
365
|
+
// 更新统计
|
366
|
+
if (vueApp) {
|
367
|
+
vueApp.copyCount++
|
368
|
+
}
|
369
|
+
}
|
370
|
+
|
371
|
+
// 页面加载完成后初始化Vue应用
|
372
|
+
document.addEventListener('DOMContentLoaded', () => {
|
373
|
+
console.log('🚀 预览页面开始初始化...')
|
374
|
+
|
375
|
+
// 简化的组件注册逻辑
|
376
|
+
if (window.XzxIconVue2) {
|
377
|
+
// 尝试不同的注册方式(按优先级)
|
378
|
+
if (window.XzxIconVue2.default && typeof window.XzxIconVue2.default.install === 'function') {
|
379
|
+
Vue.use(window.XzxIconVue2.default)
|
380
|
+
console.log('✅ 使用 default.install 注册组件')
|
381
|
+
} else if (typeof window.XzxIconVue2.install === 'function') {
|
382
|
+
Vue.use(window.XzxIconVue2)
|
383
|
+
console.log('✅ 使用 install 注册组件')
|
384
|
+
} else if (window.XzxIconVue2.XzxIcon) {
|
385
|
+
Vue.component('XzxIcon', window.XzxIconVue2.XzxIcon)
|
386
|
+
console.log('✅ 手动注册组件')
|
387
|
+
} else if (window.XzxIconVue2.default && window.XzxIconVue2.default.XzxIcon) {
|
388
|
+
Vue.component('XzxIcon', window.XzxIconVue2.default.XzxIcon)
|
389
|
+
console.log('✅ 通过 default 手动注册组件')
|
390
|
+
}
|
391
|
+
} else {
|
392
|
+
console.error('❌ XzxIconVue2 全局变量未找到')
|
393
|
+
}
|
394
|
+
|
395
|
+
// Vue 应用
|
396
|
+
vueApp = new Vue({
|
397
|
+
el: '.container',
|
398
|
+
data: {
|
399
|
+
allIcons: [],
|
400
|
+
filteredIcons: [],
|
401
|
+
searchQuery: '',
|
402
|
+
activeFilter: 'all',
|
403
|
+
loading: true,
|
404
|
+
copyCount: 0
|
405
|
+
},
|
406
|
+
async mounted() {
|
407
|
+
console.log('🎯 Vue应用已挂载')
|
408
|
+
await this.loadIcons()
|
409
|
+
this.setupEventListeners()
|
410
|
+
|
411
|
+
// 添加渲染检查
|
412
|
+
this.$nextTick(() => {
|
413
|
+
setTimeout(() => {
|
414
|
+
const iconElements = document.querySelectorAll('.icon-display xzx-icon')
|
415
|
+
const svgElements = document.querySelectorAll('.icon-display svg')
|
416
|
+
console.log(`🔍 图标元素: ${iconElements.length}, SVG元素: ${svgElements.length}`)
|
417
|
+
|
418
|
+
if (iconElements.length > 0 && svgElements.length === 0) {
|
419
|
+
console.error('❌ 图标组件未正确渲染为SVG')
|
420
|
+
} else if (svgElements.length > 0) {
|
421
|
+
console.log('✅ 图标渲染成功')
|
422
|
+
}
|
423
|
+
}, 200)
|
424
|
+
})
|
425
|
+
},
|
426
|
+
methods: {
|
427
|
+
async loadIcons() {
|
428
|
+
try {
|
429
|
+
const response = await fetch('/api/icons')
|
430
|
+
const data = await response.json()
|
431
|
+
this.allIcons = data.icons || []
|
432
|
+
this.filteredIcons = [...this.allIcons]
|
433
|
+
|
434
|
+
document.getElementById('total-count').textContent = this.allIcons.length
|
435
|
+
document.getElementById('filtered-count').textContent = this.filteredIcons.length
|
436
|
+
|
437
|
+
this.renderIcons()
|
438
|
+
this.loading = false
|
439
|
+
} catch (error) {
|
440
|
+
console.error('加载图标失败:', error)
|
441
|
+
this.showError()
|
442
|
+
}
|
443
|
+
},
|
444
|
+
|
445
|
+
setupEventListeners() {
|
446
|
+
// 搜索功能
|
447
|
+
const searchInput = document.getElementById('search-input')
|
448
|
+
searchInput.addEventListener('input', (e) => {
|
449
|
+
this.searchQuery = e.target.value.toLowerCase()
|
450
|
+
this.filterIcons()
|
451
|
+
})
|
452
|
+
|
453
|
+
// 筛选标签
|
454
|
+
document.querySelectorAll('.filter-tag').forEach(tag => {
|
455
|
+
tag.addEventListener('click', (e) => {
|
456
|
+
document.querySelectorAll('.filter-tag').forEach(t => t.classList.remove('active'))
|
457
|
+
e.target.classList.add('active')
|
458
|
+
this.activeFilter = e.target.dataset.filter
|
459
|
+
this.filterIcons()
|
460
|
+
})
|
461
|
+
})
|
462
|
+
|
463
|
+
// 键盘快捷键
|
464
|
+
document.addEventListener('keydown', (e) => {
|
465
|
+
if (e.key === '/' && !e.ctrlKey && !e.metaKey) {
|
466
|
+
e.preventDefault()
|
467
|
+
searchInput.focus()
|
468
|
+
}
|
469
|
+
if (e.key === 'Escape') {
|
470
|
+
searchInput.blur()
|
471
|
+
searchInput.value = ''
|
472
|
+
this.searchQuery = ''
|
473
|
+
this.filterIcons()
|
474
|
+
}
|
475
|
+
})
|
476
|
+
},
|
477
|
+
|
478
|
+
filterIcons() {
|
479
|
+
let filtered = [...this.allIcons]
|
480
|
+
|
481
|
+
// 文本搜索
|
482
|
+
if (this.searchQuery) {
|
483
|
+
filtered = filtered.filter(icon =>
|
484
|
+
icon.toLowerCase().includes(this.searchQuery)
|
485
|
+
)
|
486
|
+
}
|
487
|
+
|
488
|
+
// 分类筛选
|
489
|
+
if (this.activeFilter !== 'all') {
|
490
|
+
filtered = filtered.filter(icon => {
|
491
|
+
switch (this.activeFilter) {
|
492
|
+
case 'filled':
|
493
|
+
return icon.includes('filled')
|
494
|
+
case 'one':
|
495
|
+
return icon.includes('one')
|
496
|
+
case 'arrow':
|
497
|
+
return ['left', 'right', 'up', 'down', 'arrow'].some(word => icon.includes(word))
|
498
|
+
case 'file':
|
499
|
+
return icon.includes('file')
|
500
|
+
case 'edit':
|
501
|
+
return ['edit', 'write', 'pen', 'pencil'].some(word => icon.includes(word))
|
502
|
+
case 'close':
|
503
|
+
return ['close', 'delete', 'remove', 'minus'].some(word => icon.includes(word))
|
504
|
+
default:
|
505
|
+
return true
|
506
|
+
}
|
507
|
+
})
|
508
|
+
}
|
509
|
+
|
510
|
+
this.filteredIcons = filtered
|
511
|
+
document.getElementById('filtered-count').textContent = filtered.length
|
512
|
+
this.renderIcons()
|
513
|
+
},
|
514
|
+
|
515
|
+
renderIcons() {
|
516
|
+
const container = document.getElementById('icons-container')
|
517
|
+
|
518
|
+
if (this.filteredIcons.length === 0) {
|
519
|
+
container.innerHTML = `
|
520
|
+
<div class="no-results">
|
521
|
+
<div class="icon">🔍</div>
|
522
|
+
<h3>未找到匹配的图标</h3>
|
523
|
+
<p>尝试使用其他关键词搜索,或按 Esc 重置搜索</p>
|
524
|
+
</div>
|
525
|
+
`
|
526
|
+
return
|
527
|
+
}
|
528
|
+
|
529
|
+
const iconsHTML = this.filteredIcons.map(iconName => `
|
530
|
+
<div class="icon-item" data-icon="${iconName}" onclick="copyIconCode('${iconName}', this)">
|
531
|
+
<div class="icon-display">
|
532
|
+
<xzx-icon name="${iconName}" size="32"></xzx-icon>
|
533
|
+
</div>
|
534
|
+
<div class="icon-name">${iconName}</div>
|
535
|
+
<div class="copy-tooltip">点击复制名称</div>
|
536
|
+
</div>
|
537
|
+
`).join('')
|
538
|
+
|
539
|
+
container.innerHTML = `<div class="icons-grid">${iconsHTML}</div>`
|
540
|
+
|
541
|
+
// 等待DOM更新后重新编译Vue组件
|
542
|
+
this.$nextTick(() => {
|
543
|
+
// 获取新插入的图标元素并手动编译
|
544
|
+
const newIconElements = container.querySelectorAll('xzx-icon')
|
545
|
+
console.log(`🔄 正在编译 ${newIconElements.length} 个图标组件`)
|
546
|
+
|
547
|
+
// 使用Vue的编译方法重新编译新插入的元素
|
548
|
+
if (newIconElements.length > 0) {
|
549
|
+
newIconElements.forEach(el => {
|
550
|
+
if (!el.__vue__) {
|
551
|
+
// 手动创建Vue组件实例
|
552
|
+
new Vue({
|
553
|
+
el: el,
|
554
|
+
components: {
|
555
|
+
'xzx-icon': Vue.options.components.XzxIcon
|
556
|
+
}
|
557
|
+
})
|
558
|
+
}
|
559
|
+
})
|
560
|
+
}
|
561
|
+
})
|
562
|
+
},
|
563
|
+
|
564
|
+
showError() {
|
565
|
+
document.getElementById('icons-container').innerHTML = `
|
566
|
+
<div class="no-results">
|
567
|
+
<div class="icon">❌</div>
|
568
|
+
<h3>加载失败</h3>
|
569
|
+
<p>请检查网络连接或稍后重试</p>
|
570
|
+
</div>
|
571
|
+
`
|
572
|
+
this.loading = false
|
573
|
+
}
|
574
|
+
}
|
575
|
+
})
|
576
|
+
})
|
577
|
+
</script>
|
578
|
+
</body>
|
579
|
+
</html>
|
@@ -0,0 +1,145 @@
|
|
1
|
+
const express = require('express')
|
2
|
+
const path = require('path')
|
3
|
+
const open = require('open')
|
4
|
+
const fs = require('fs')
|
5
|
+
|
6
|
+
const app = express()
|
7
|
+
const PORT = 3005
|
8
|
+
|
9
|
+
// 静态文件服务
|
10
|
+
app.use(express.static(path.join(__dirname)))
|
11
|
+
app.use('/lib', express.static(path.join(__dirname, '../lib')))
|
12
|
+
app.use('/src', express.static(path.join(__dirname, '../src')))
|
13
|
+
|
14
|
+
// 主页面路由
|
15
|
+
app.get('/', (req, res) => {
|
16
|
+
res.sendFile(path.join(__dirname, 'index.html'))
|
17
|
+
})
|
18
|
+
|
19
|
+
// API: 获取所有图标列表
|
20
|
+
app.get('/api/icons', (req, res) => {
|
21
|
+
try {
|
22
|
+
// 读取内联图标文件内容
|
23
|
+
const iconFilePath = path.join(__dirname, '../src/inline-icons.js')
|
24
|
+
const iconFileContent = fs.readFileSync(iconFilePath, 'utf-8')
|
25
|
+
|
26
|
+
// 提取iconData对象
|
27
|
+
const iconDataMatch = iconFileContent.match(/export const iconData = (\{[\s\S]*?\n\})/)
|
28
|
+
if (!iconDataMatch) {
|
29
|
+
throw new Error('无法解析iconData')
|
30
|
+
}
|
31
|
+
|
32
|
+
// 使用eval来解析JSON对象 (注意:这只在服务器端安全)
|
33
|
+
const iconDataStr = iconDataMatch[1]
|
34
|
+
const iconData = eval('(' + iconDataStr + ')')
|
35
|
+
const icons = Object.keys(iconData)
|
36
|
+
|
37
|
+
res.json({ icons, count: icons.length })
|
38
|
+
console.log(`✅ 成功加载 ${icons.length} 个图标`)
|
39
|
+
} catch (error) {
|
40
|
+
console.error('获取图标列表失败:', error.message)
|
41
|
+
res.status(500).json({ error: '获取图标列表失败: ' + error.message })
|
42
|
+
}
|
43
|
+
})
|
44
|
+
|
45
|
+
app.get('/simple', (req, res) => {
|
46
|
+
const simplePage = `<!DOCTYPE html>
|
47
|
+
<html lang="zh-CN">
|
48
|
+
<head>
|
49
|
+
<meta charset="UTF-8">
|
50
|
+
<title>简化图标预览</title>
|
51
|
+
<style>
|
52
|
+
body { font-family: Arial, sans-serif; padding: 20px; }
|
53
|
+
.icons-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 15px; }
|
54
|
+
.icon-item { text-align: center; padding: 15px; border: 1px solid #ddd; border-radius: 4px; cursor: pointer; }
|
55
|
+
.icon-item:hover { background: #f5f5f5; }
|
56
|
+
.icon-display { margin-bottom: 8px; }
|
57
|
+
.icon-name { font-size: 12px; color: #666; }
|
58
|
+
</style>
|
59
|
+
</head>
|
60
|
+
<body>
|
61
|
+
<div id="app">
|
62
|
+
<h1>图标预览 ({{ filteredIcons.length }})</h1>
|
63
|
+
<input v-model="searchQuery" placeholder="搜索..." @input="filterIcons" style="width: 100%; padding: 8px; margin-bottom: 20px;">
|
64
|
+
|
65
|
+
<div class="icons-grid">
|
66
|
+
<div class="icon-item" v-for="iconName in filteredIcons" :key="iconName" @click="copy(iconName)">
|
67
|
+
<div class="icon-display">
|
68
|
+
<xzx-icon :name="iconName" size="32"></xzx-icon>
|
69
|
+
</div>
|
70
|
+
<div class="icon-name">{{ iconName }}</div>
|
71
|
+
</div>
|
72
|
+
</div>
|
73
|
+
</div>
|
74
|
+
|
75
|
+
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
|
76
|
+
<script src="/lib/index.umd.js"></script>
|
77
|
+
<script>
|
78
|
+
if (window.XzxIconVue2) {
|
79
|
+
if (window.XzxIconVue2.default) {
|
80
|
+
Vue.use(window.XzxIconVue2.default)
|
81
|
+
console.log('✅ 注册组件成功')
|
82
|
+
} else {
|
83
|
+
Vue.use(window.XzxIconVue2)
|
84
|
+
console.log('✅ 注册组件成功')
|
85
|
+
}
|
86
|
+
}
|
87
|
+
|
88
|
+
new Vue({
|
89
|
+
el: '#app',
|
90
|
+
data: {
|
91
|
+
allIcons: [],
|
92
|
+
filteredIcons: [],
|
93
|
+
searchQuery: ''
|
94
|
+
},
|
95
|
+
async mounted() {
|
96
|
+
const response = await fetch('/api/icons')
|
97
|
+
const data = await response.json()
|
98
|
+
this.allIcons = data.icons || []
|
99
|
+
this.filteredIcons = [...this.allIcons]
|
100
|
+
console.log('加载图标:', this.allIcons.length)
|
101
|
+
},
|
102
|
+
methods: {
|
103
|
+
filterIcons() {
|
104
|
+
this.filteredIcons = this.searchQuery
|
105
|
+
? this.allIcons.filter(icon => icon.includes(this.searchQuery.toLowerCase()))
|
106
|
+
: [...this.allIcons]
|
107
|
+
},
|
108
|
+
copy(name) {
|
109
|
+
navigator.clipboard.writeText(name)
|
110
|
+
console.log('复制:', name)
|
111
|
+
}
|
112
|
+
}
|
113
|
+
})
|
114
|
+
</script>
|
115
|
+
</body>
|
116
|
+
</html>`
|
117
|
+
res.send(simplePage)
|
118
|
+
})
|
119
|
+
|
120
|
+
// 启动服务器
|
121
|
+
app.listen(PORT, () => {
|
122
|
+
console.log(`
|
123
|
+
🎉 图标预览服务器已启动!
|
124
|
+
|
125
|
+
📍 本地地址: http://localhost:${PORT}
|
126
|
+
🌐 自动打开浏览器中...
|
127
|
+
|
128
|
+
按 Ctrl+C 停止服务器
|
129
|
+
`)
|
130
|
+
|
131
|
+
// 自动打开浏览器
|
132
|
+
open(`http://localhost:${PORT}`).catch(() => {
|
133
|
+
console.log('💡 请手动打开浏览器访问: http://localhost:' + PORT)
|
134
|
+
})
|
135
|
+
})
|
136
|
+
|
137
|
+
// 错误处理
|
138
|
+
app.on('error', (err) => {
|
139
|
+
if (err.code === 'EADDRINUSE') {
|
140
|
+
console.error(`❌ 端口 ${PORT} 已被占用,请先关闭其他服务或稍后重试`)
|
141
|
+
} else {
|
142
|
+
console.error('❌ 服务器启动失败:', err.message)
|
143
|
+
}
|
144
|
+
process.exit(1)
|
145
|
+
})
|