create-vite-vue 1.6.0 → 1.7.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.
- package/README.md +28 -0
- package/bin/index.js +44 -412
- package/lib/features.js +13 -0
- package/lib/mainFile.js +48 -0
- package/lib/package.js +45 -0
- package/lib/prompts.js +80 -0
- package/lib/router.js +21 -0
- package/lib/template.js +38 -0
- package/lib/utils.js +38 -0
- package/lib/viteConfig.js +24 -0
- package/package.json +1 -1
- package/template/axios-ts/src/utils/requestCache.ts +7 -0
- package/template/base-js/README.md +28 -0
- package/template/base-js/jsconfig.json +1 -0
- package/template/base-ts/README.md +29 -1
- package/template/base-ts/tsconfig.json +1 -0
package/README.md
CHANGED
|
@@ -232,6 +232,34 @@ console.log(route.params.id)
|
|
|
232
232
|
console.log(route.params.name)
|
|
233
233
|
```
|
|
234
234
|
|
|
235
|
+
### ✅ meta.json 自动注入路由
|
|
236
|
+
|
|
237
|
+
在每个页面目录下可以新增一个 `meta.json` 文件:
|
|
238
|
+
|
|
239
|
+
```text
|
|
240
|
+
src/views/home/
|
|
241
|
+
├─ index.vue
|
|
242
|
+
└─ meta.json
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
#### 示例:
|
|
246
|
+
|
|
247
|
+
```json
|
|
248
|
+
{
|
|
249
|
+
"name": "首页",
|
|
250
|
+
"orderMenu": 1
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
👉 脚手架会自动读取该文件,并注入到对应路由的 `meta` 属性中:
|
|
255
|
+
|
|
256
|
+
```js
|
|
257
|
+
route.meta = {
|
|
258
|
+
name: "首页",
|
|
259
|
+
orderMenu: 1
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
235
263
|
---
|
|
236
264
|
|
|
237
265
|
### 4️⃣ 页面内容与样式
|
package/bin/index.js
CHANGED
|
@@ -1,440 +1,72 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { execSync } from 'child_process'
|
|
1
|
+
#!/usr/bin/env node
|
|
3
2
|
import fs from 'fs'
|
|
4
3
|
import path from 'path'
|
|
5
|
-
import prompts from 'prompts'
|
|
6
4
|
import { fileURLToPath } from 'url'
|
|
7
5
|
|
|
8
|
-
//
|
|
6
|
+
// === 导入模块 ===
|
|
7
|
+
import { parseExtraPlugins, parseFeatures } from '../lib/features.js'
|
|
8
|
+
import { generateMainFile } from '../lib/mainFile.js'
|
|
9
|
+
import { generatePackageJson } from '../lib/package.js'
|
|
10
|
+
import { askAutoRoute, askRunDev, chooseFeatures, chooseLanguage, getProjectName } from '../lib/prompts.js'
|
|
11
|
+
import { configureRouter } from '../lib/router.js'
|
|
12
|
+
import { appendTailwind, copyBaseTemplate, copyOptionalTemplates, updateIndexHtml } from '../lib/template.js'
|
|
13
|
+
import { checkNodeVersion, detectPackageManager, runCmd } from '../lib/utils.js'
|
|
14
|
+
import { configureVite } from '../lib/viteConfig.js'
|
|
15
|
+
|
|
16
|
+
// ===================== 常量 =====================
|
|
9
17
|
const requiredVersion = '22.19.0'
|
|
10
|
-
|
|
11
|
-
function compareVersion (v1, v2) {
|
|
12
|
-
const a = v1.split('.').map(Number)
|
|
13
|
-
const b = v2.split('.').map(Number)
|
|
14
|
-
|
|
15
|
-
for(let i = 0; i < Math.max(a.length, b.length); i++) {
|
|
16
|
-
const n1 = a[i] || 0
|
|
17
|
-
const n2 = b[i] || 0
|
|
18
|
-
if(n1 > n2) return 1
|
|
19
|
-
if(n1 < n2) return -1
|
|
20
|
-
}
|
|
21
|
-
return 0
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const currentVersion = process.version.replace('v', '')
|
|
25
|
-
|
|
26
|
-
if(compareVersion(currentVersion, requiredVersion) < 0) {
|
|
27
|
-
console.error(`❌ Node.js 版本过低`)
|
|
28
|
-
console.error(`当前版本: ${currentVersion}`)
|
|
29
|
-
console.error(`最低要求: ${requiredVersion}`)
|
|
30
|
-
console.error(`请升级 Node.js 后再运行`)
|
|
31
|
-
process.exit(1)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
18
|
const __filename = fileURLToPath(import.meta.url)
|
|
35
19
|
const __dirname = path.dirname(__filename)
|
|
36
|
-
|
|
37
20
|
const pkgManager = detectPackageManager()
|
|
38
21
|
|
|
39
22
|
const pkgCommands = {
|
|
40
|
-
npm: {
|
|
41
|
-
|
|
42
|
-
dev: 'npm run dev'
|
|
43
|
-
},
|
|
44
|
-
pnpm: {
|
|
45
|
-
install: 'pnpm install',
|
|
46
|
-
dev: 'pnpm dev'
|
|
47
|
-
},
|
|
48
|
-
bun: {
|
|
49
|
-
install: 'bun install',
|
|
50
|
-
dev: 'bun run dev'
|
|
51
|
-
}
|
|
23
|
+
npm: { install: 'npm install', dev: 'npm run dev' },
|
|
24
|
+
pnpm: { install: 'pnpm install', dev: 'pnpm dev' }
|
|
52
25
|
}
|
|
53
26
|
|
|
27
|
+
// ===================== 主流程 =====================
|
|
54
28
|
; (async () => {
|
|
55
|
-
|
|
56
|
-
let projectName
|
|
57
|
-
while(true) {
|
|
58
|
-
const res = await prompts({
|
|
59
|
-
type: 'text',
|
|
60
|
-
name: 'projectName',
|
|
61
|
-
message: '📦 项目名称',
|
|
62
|
-
validate: v => v ? true : '项目名不能为空'
|
|
63
|
-
})
|
|
64
|
-
projectName = res.projectName
|
|
65
|
-
if(!projectName) process.exit(1)
|
|
66
|
-
|
|
67
|
-
const targetDir = path.resolve(process.cwd(), projectName)
|
|
68
|
-
if(fs.existsSync(targetDir)) {
|
|
69
|
-
console.log('❌ 目录已存在,请重新输入')
|
|
70
|
-
continue
|
|
71
|
-
}
|
|
72
|
-
break
|
|
73
|
-
}
|
|
29
|
+
checkNodeVersion(requiredVersion)
|
|
74
30
|
|
|
31
|
+
const projectName = await getProjectName(fs, path)
|
|
75
32
|
const targetDir = path.resolve(process.cwd(), projectName)
|
|
76
33
|
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
message: '请选择项目语言',
|
|
82
|
-
choices: [
|
|
83
|
-
{ title: 'JavaScript', value: 'js' },
|
|
84
|
-
{ title: 'TypeScript', value: 'ts' }
|
|
85
|
-
]
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
// 2️⃣ 功能选择(多选)
|
|
89
|
-
const { featureList } = await prompts({
|
|
90
|
-
type: 'multiselect',
|
|
91
|
-
name: 'featureList',
|
|
92
|
-
message: '请选择基础功能(↑↓选择,空格确认,回车完成)',
|
|
93
|
-
instructions: false,
|
|
94
|
-
choices: [
|
|
95
|
-
{ title: 'Vue Router', value: 'router' },
|
|
96
|
-
{ title: 'Pinia(含持久化)', value: 'pinia' },
|
|
97
|
-
{ title: 'Axios', value: 'axios' },
|
|
98
|
-
{ title: 'Element Plus(PC UI)', value: 'element' },
|
|
99
|
-
{ title: 'Vant(Mobile UI)', value: 'vant' },
|
|
100
|
-
{ title: 'VueUse(实用 Composition API)', value: 'vueuse' },
|
|
101
|
-
{ title: 'Lodash(工具库)', value: 'lodash' },
|
|
102
|
-
{ title: 'Day.js(日期处理)', value: 'dayjs' },
|
|
103
|
-
{ title: 'Tailwind CSS(原子化 CSS)', value: 'tailwind' },
|
|
104
|
-
{ title: 'mitt(事件总线)', value: 'mitt' },
|
|
105
|
-
{ title: 'HTTPS(mkcert)', value: 'https' }
|
|
106
|
-
]
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
// 转换成原来的结构(保证后面代码基本不用动)
|
|
110
|
-
const features = {
|
|
111
|
-
router: featureList?.includes('router') || false,
|
|
112
|
-
pinia: featureList?.includes('pinia') || false,
|
|
113
|
-
axios: featureList?.includes('axios') || false,
|
|
114
|
-
ui: featureList?.filter(v => ['element', 'vant'].includes(v)) || []
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
const extraPlugins = featureList?.filter(v =>
|
|
118
|
-
['vueuse', 'lodash', 'dayjs', 'tailwind', 'mitt', 'https'].includes(v)
|
|
119
|
-
) || []
|
|
120
|
-
|
|
121
|
-
// 询问是否开启自动路由
|
|
122
|
-
let autoRoute = false
|
|
123
|
-
if(features.router) {
|
|
124
|
-
const { enableAutoRoute } = await prompts({
|
|
125
|
-
type: 'toggle',
|
|
126
|
-
name: 'enableAutoRoute',
|
|
127
|
-
message: '是否开启自动配置路由(vite-plugin-pages)?',
|
|
128
|
-
initial: false,
|
|
129
|
-
active: '是',
|
|
130
|
-
inactive: '否'
|
|
131
|
-
})
|
|
132
|
-
autoRoute = enableAutoRoute
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const enableHttps = featureList?.includes('https') || false
|
|
136
|
-
|
|
137
|
-
// 3️⃣ 是否立即运行 dev
|
|
138
|
-
const { runDev } = await prompts({
|
|
139
|
-
type: 'select',
|
|
140
|
-
name: 'runDev',
|
|
141
|
-
message: `是否立即运行 ${pkgCommands[pkgManager].dev}?`,
|
|
142
|
-
choices: [{ title: 'Yes', value: true }, { title: 'No', value: false }]
|
|
143
|
-
})
|
|
144
|
-
|
|
145
|
-
// 4️⃣ 拷贝 base 模板
|
|
146
|
-
const baseTemplate = language === 'ts' ? 'base-ts' : 'base-js'
|
|
147
|
-
fs.cpSync(
|
|
148
|
-
path.resolve(__dirname, `../template/${baseTemplate}`),
|
|
149
|
-
targetDir,
|
|
150
|
-
{ recursive: true }
|
|
151
|
-
)
|
|
152
|
-
|
|
153
|
-
// 替换 index.html 的 title
|
|
154
|
-
const indexPath = path.join(targetDir, 'index.html')
|
|
155
|
-
if(fs.existsSync(indexPath)) {
|
|
156
|
-
const indexContent = fs.readFileSync(indexPath, 'utf-8')
|
|
157
|
-
fs.writeFileSync(
|
|
158
|
-
indexPath,
|
|
159
|
-
indexContent.replace(/<title>.*<\/title>/, `<title>${projectName}</title>`)
|
|
160
|
-
)
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// 追加 Tailwind CSS 导入
|
|
164
|
-
if(extraPlugins.includes('tailwind')) {
|
|
165
|
-
const stylePath = path.join(targetDir, 'src/style.css')
|
|
166
|
-
const original = fs.readFileSync(stylePath, 'utf-8')
|
|
167
|
-
if(!original.startsWith('@import "tailwindcss";')) {
|
|
168
|
-
fs.writeFileSync(stylePath, `@import "tailwindcss";\n${original}`)
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// 5️⃣ 拷贝可选模板(基础功能)
|
|
173
|
-
const copy = name => {
|
|
174
|
-
fs.cpSync(path.resolve(__dirname, `../template/${name}`), targetDir, { recursive: true })
|
|
175
|
-
}
|
|
176
|
-
features.router && copy(language === 'ts' ? 'router-ts' : 'router-js')
|
|
177
|
-
features.pinia && copy(language === 'ts' ? 'pinia-ts' : 'pinia-js')
|
|
178
|
-
features.axios && copy(language === 'ts' ? 'axios-ts' : 'axios-js')
|
|
179
|
-
|
|
180
|
-
// 拷贝增强插件模板
|
|
181
|
-
for(const plugin of extraPlugins) {
|
|
182
|
-
const templateName = `${plugin}-${language === 'ts' ? 'ts' : 'js'}`
|
|
183
|
-
const templatePath = path.resolve(__dirname, `../template/${templateName}`)
|
|
184
|
-
if(fs.existsSync(templatePath)) {
|
|
185
|
-
fs.cpSync(templatePath, targetDir, { recursive: true })
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// 6️⃣ 生成 main.js / main.ts
|
|
190
|
-
const mainFile = language === 'ts' ? 'main.ts' : 'main.js'
|
|
191
|
-
const mainTplPath = path.join(targetDir, `src/${mainFile}.tpl`)
|
|
192
|
-
let main = fs.readFileSync(mainTplPath, 'utf-8')
|
|
34
|
+
const language = await chooseLanguage()
|
|
35
|
+
const featureList = await chooseFeatures()
|
|
36
|
+
const features = parseFeatures(featureList)
|
|
37
|
+
const extraPlugins = parseExtraPlugins(featureList)
|
|
193
38
|
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
? "import { createPinia } from 'pinia'\nimport persistedstate from 'pinia-plugin-persistedstate'"
|
|
198
|
-
: '',
|
|
199
|
-
'/* __ELEMENT_IMPORT__ */': features.ui.includes('element')
|
|
200
|
-
? `import ElementPlus from 'element-plus'
|
|
201
|
-
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
|
202
|
-
import 'element-plus/dist/index.css'
|
|
203
|
-
import * as ElementPlusIconsVue from '@element-plus/icons-vue'`
|
|
204
|
-
: '',
|
|
205
|
-
'/* __VANT_IMPORT__ */': features.ui.includes('vant')
|
|
206
|
-
? `import Vant from 'vant'
|
|
207
|
-
import 'vant/lib/index.css'`
|
|
208
|
-
: '',
|
|
209
|
-
'/* __ROUTER_USE__ */': features.router ? 'app.use(router)' : '',
|
|
210
|
-
'/* __PINIA_USE__ */': features.pinia
|
|
211
|
-
? 'app.use(createPinia().use(persistedstate))'
|
|
212
|
-
: '',
|
|
213
|
-
'/* __ELEMENT_USE__ */': features.ui.includes('element')
|
|
214
|
-
? `app.use(ElementPlus, { locale: zhCn })
|
|
215
|
-
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
|
216
|
-
app.component(key, component)
|
|
217
|
-
}`
|
|
218
|
-
: '',
|
|
219
|
-
'/* __VANT_USE__ */': features.ui.includes('vant')
|
|
220
|
-
? 'app.use(Vant)'
|
|
221
|
-
: ''
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
function escapeRegExp (str) {
|
|
225
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
for(const [placeholder, content] of Object.entries(replacements)) {
|
|
229
|
-
if(content) {
|
|
230
|
-
main = main.replace(placeholder, content)
|
|
231
|
-
} else {
|
|
232
|
-
const re = new RegExp(`^\\s*${escapeRegExp(placeholder)}\\s*$\\n?`, 'gm')
|
|
233
|
-
main = main.replace(re, '')
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
main = main.replace(/(\s*)const app = createApp\(App\)/, '\n\n$1const app = createApp(App)')
|
|
238
|
-
main = main.replace(/\n{3,}/g, '\n\n')
|
|
239
|
-
|
|
240
|
-
fs.writeFileSync(path.join(targetDir, `src/${mainFile}`), main)
|
|
241
|
-
fs.unlinkSync(mainTplPath)
|
|
242
|
-
|
|
243
|
-
// 7️⃣ 生成 package.json
|
|
244
|
-
const pkgTpl = path.join(targetDir, 'package.json.tpl')
|
|
245
|
-
if(fs.existsSync(pkgTpl)) {
|
|
246
|
-
let pkg = fs.readFileSync(pkgTpl, 'utf-8')
|
|
247
|
-
|
|
248
|
-
const optionalDeps = {}
|
|
249
|
-
if(features.router) optionalDeps['vue-router'] = '^5.0.3'
|
|
250
|
-
if(features.pinia) {
|
|
251
|
-
optionalDeps['pinia'] = '^3.0.4'
|
|
252
|
-
optionalDeps['pinia-plugin-persistedstate'] = '^4.7.1'
|
|
253
|
-
}
|
|
254
|
-
if(features.axios) optionalDeps['axios'] = '^1.13.6'
|
|
255
|
-
if(features.ui.includes('element')) {
|
|
256
|
-
optionalDeps['element-plus'] = '^2.13.5'
|
|
257
|
-
optionalDeps['@element-plus/icons-vue'] = '^2.3.2'
|
|
258
|
-
}
|
|
259
|
-
if(features.ui.includes('vant')) {
|
|
260
|
-
optionalDeps['vant'] = '^4.9.22'
|
|
261
|
-
}
|
|
262
|
-
// 增强插件依赖
|
|
263
|
-
if(extraPlugins.includes('vueuse')) optionalDeps['@vueuse/core'] = '^14.2.1'
|
|
264
|
-
if(extraPlugins.includes('dayjs')) optionalDeps['dayjs'] = '^1.11.20'
|
|
265
|
-
if(extraPlugins.includes('lodash')) optionalDeps['lodash'] = '^4.17.23'
|
|
266
|
-
if(extraPlugins.includes('tailwind')) {
|
|
267
|
-
optionalDeps['tailwindcss'] = '^4.2.2'
|
|
268
|
-
optionalDeps['@tailwindcss/postcss'] = '^4.2.2'
|
|
269
|
-
optionalDeps['postcss'] = '^8.5.8'
|
|
270
|
-
}
|
|
271
|
-
if(extraPlugins.includes('mitt')) optionalDeps['mitt'] = '^3.0.1'
|
|
272
|
-
if(enableHttps) optionalDeps['vite-plugin-mkcert'] = '^1.17.10'
|
|
273
|
-
if(autoRoute) optionalDeps['vite-plugin-pages'] = '^0.33.3'
|
|
274
|
-
|
|
275
|
-
let depsStr = ''
|
|
276
|
-
const keys = Object.keys(optionalDeps)
|
|
277
|
-
if(keys.length > 0) {
|
|
278
|
-
depsStr = ',\n' + keys.map(k => ` "${k}": "${optionalDeps[k]}"`).join(',\n')
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
pkg = pkg
|
|
282
|
-
.replace('__PROJECT_NAME__', projectName)
|
|
283
|
-
.replace('__OPTIONAL_DEP__', depsStr)
|
|
284
|
-
|
|
285
|
-
fs.writeFileSync(path.join(targetDir, 'package.json'), pkg)
|
|
286
|
-
fs.unlinkSync(pkgTpl)
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// 8️⃣ 配置 vite.config.js / vite.config.ts(自动路由 + HTTPS)
|
|
290
|
-
const viteConfigPath = path.join(
|
|
291
|
-
targetDir,
|
|
292
|
-
`vite.config.${language === 'ts' ? 'ts' : 'js'}`
|
|
293
|
-
)
|
|
39
|
+
const autoRoute = await askAutoRoute(features.router)
|
|
40
|
+
const enableHttps = featureList.includes('https') || false
|
|
41
|
+
const runDev = await askRunDev(pkgCommands[pkgManager].dev)
|
|
294
42
|
|
|
295
|
-
|
|
296
|
-
|
|
43
|
+
// 模板文件处理
|
|
44
|
+
copyBaseTemplate(language, targetDir, __dirname)
|
|
45
|
+
updateIndexHtml(projectName, targetDir)
|
|
46
|
+
appendTailwind(extraPlugins, targetDir)
|
|
47
|
+
copyOptionalTemplates(features, extraPlugins, language, targetDir, __dirname)
|
|
297
48
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
if(!viteConfig.includes("vite-plugin-mkcert")) {
|
|
301
|
-
viteConfig = viteConfig.replace(
|
|
302
|
-
/(import .*?from .*?\n)/,
|
|
303
|
-
`$1import mkcert from 'vite-plugin-mkcert'\n`
|
|
304
|
-
)
|
|
305
|
-
}
|
|
49
|
+
// 生成 main 文件
|
|
50
|
+
await generateMainFile(features, extraPlugins, language, targetDir)
|
|
306
51
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
/plugins:\s*\[/,
|
|
310
|
-
`plugins: [
|
|
311
|
-
mkcert(),`
|
|
312
|
-
)
|
|
313
|
-
}
|
|
314
|
-
}
|
|
52
|
+
// package.json
|
|
53
|
+
generatePackageJson(projectName, features, extraPlugins, autoRoute, enableHttps, language, targetDir, pkgManager)
|
|
315
54
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
if(!viteConfig.includes("import fs from 'fs'")) {
|
|
319
|
-
viteConfig = `import fs from 'fs'\n${viteConfig}`
|
|
320
|
-
}
|
|
321
|
-
if(!viteConfig.includes("import Pages from 'vite-plugin-pages'")) {
|
|
322
|
-
viteConfig = viteConfig.replace(
|
|
323
|
-
/(import .*?from .*?\n)/,
|
|
324
|
-
`$1import Pages from 'vite-plugin-pages'\n`
|
|
325
|
-
)
|
|
326
|
-
}
|
|
327
|
-
if(!viteConfig.includes("Pages({")) {
|
|
328
|
-
viteConfig = viteConfig.replace(
|
|
329
|
-
/plugins:\s*\[/,
|
|
330
|
-
`plugins: [
|
|
331
|
-
Pages({
|
|
332
|
-
dirs: 'src/views',
|
|
333
|
-
extensions: ['vue'],
|
|
334
|
-
exclude: ['**/_*.vue'],
|
|
335
|
-
async extendRoute(route) {
|
|
336
|
-
const componentPath = path.resolve(process.cwd(), route.component.slice(1))
|
|
337
|
-
const dirPath = path.dirname(componentPath)
|
|
338
|
-
const metaFile = path.resolve(dirPath, 'meta.json')
|
|
339
|
-
if(fs.existsSync(metaFile)) {
|
|
340
|
-
try {
|
|
341
|
-
const metaContent = fs.readFileSync(metaFile, 'utf-8')
|
|
342
|
-
const meta = JSON.parse(metaContent)
|
|
343
|
-
route.meta = { ...(route.meta || {}), ...meta }
|
|
344
|
-
} catch(err) {
|
|
345
|
-
console.warn(\`加载 \${metaFile} 失败:\`, err)
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
return { ...route }
|
|
349
|
-
}
|
|
350
|
-
}),`
|
|
351
|
-
)
|
|
352
|
-
}
|
|
353
|
-
}
|
|
55
|
+
// 配置 vite
|
|
56
|
+
configureVite(language, autoRoute, enableHttps, targetDir)
|
|
354
57
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
// 9️⃣ 替换 router/index.js
|
|
358
|
-
if(features.router) {
|
|
359
|
-
const routerIndexPath = path.join(targetDir, `src/router/index.${language === 'ts' ? 'ts' : 'js'}`)
|
|
360
|
-
if(autoRoute) {
|
|
361
|
-
fs.writeFileSync(
|
|
362
|
-
routerIndexPath,
|
|
363
|
-
`import { createRouter, createWebHistory } from 'vue-router'
|
|
364
|
-
import routes from '~pages'
|
|
365
|
-
|
|
366
|
-
routes.unshift({
|
|
367
|
-
path: '/',
|
|
368
|
-
redirect: '/home'
|
|
369
|
-
})
|
|
370
|
-
|
|
371
|
-
export default createRouter({
|
|
372
|
-
history: createWebHistory(),
|
|
373
|
-
routes
|
|
374
|
-
})
|
|
375
|
-
`
|
|
376
|
-
)
|
|
377
|
-
} else {
|
|
378
|
-
fs.writeFileSync(
|
|
379
|
-
routerIndexPath,
|
|
380
|
-
`import { createRouter, createWebHistory } from 'vue-router'
|
|
381
|
-
|
|
382
|
-
const routes = [
|
|
383
|
-
{
|
|
384
|
-
path: '/',
|
|
385
|
-
component: () => import('@/views/home/index.vue')
|
|
386
|
-
}
|
|
387
|
-
]
|
|
388
|
-
|
|
389
|
-
export default createRouter({
|
|
390
|
-
history: createWebHistory(),
|
|
391
|
-
routes
|
|
392
|
-
})
|
|
393
|
-
`
|
|
394
|
-
)
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
// 1️⃣0️⃣ 安装依赖
|
|
399
|
-
console.log('📦 安装依赖中...')
|
|
58
|
+
// 配置 router
|
|
59
|
+
configureRouter(features.router, autoRoute, language, targetDir)
|
|
400
60
|
|
|
401
|
-
|
|
61
|
+
// 安装依赖
|
|
62
|
+
runCmd(pkgCommands[pkgManager].install, targetDir)
|
|
402
63
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
installCmd = 'npm install'
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
execSync(installCmd, { cwd: targetDir, stdio: 'inherit' })
|
|
410
|
-
|
|
411
|
-
// 1️⃣1️⃣ 运行 dev
|
|
412
|
-
if(runDev) {
|
|
413
|
-
console.log('🚀 启动开发服务器...')
|
|
414
|
-
if(enableHttps) {
|
|
415
|
-
console.log('🔐 首次启用 HTTPS 会自动生成证书,请稍等...')
|
|
416
|
-
}
|
|
417
|
-
execSync(pkgCommands[pkgManager].dev, {
|
|
418
|
-
cwd: targetDir,
|
|
419
|
-
stdio: 'inherit'
|
|
420
|
-
})
|
|
421
|
-
} else {
|
|
64
|
+
// 启动 dev 或提示完成
|
|
65
|
+
if(runDev) runCmd(pkgCommands[pkgManager].dev, targetDir)
|
|
66
|
+
else {
|
|
422
67
|
console.log(`\n✅ 项目创建完成`)
|
|
423
68
|
console.log(`👉 cd ${projectName}`)
|
|
424
69
|
console.log(`👉 ${pkgCommands[pkgManager].dev}`)
|
|
425
|
-
if(enableHttps)
|
|
426
|
-
console.log('🔐 首次启用 HTTPS 会自动生成证书,请稍等...\n')
|
|
427
|
-
}
|
|
70
|
+
if(enableHttps) console.log('🔐 首次启用 HTTPS 会自动生成证书,请稍等...')
|
|
428
71
|
}
|
|
429
|
-
})()
|
|
430
|
-
|
|
431
|
-
function detectPackageManager () {
|
|
432
|
-
const userAgent = process.env.npm_config_user_agent || ''
|
|
433
|
-
|
|
434
|
-
if(userAgent.startsWith('pnpm')) return 'pnpm'
|
|
435
|
-
if(userAgent.startsWith('npm')) return 'npm'
|
|
436
|
-
|
|
437
|
-
if(fs.existsSync('pnpm-lock.yaml')) return 'pnpm'
|
|
438
|
-
|
|
439
|
-
return 'npm'
|
|
440
|
-
}
|
|
72
|
+
})()
|
package/lib/features.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// lib/features.js
|
|
2
|
+
export function parseFeatures (featureList) {
|
|
3
|
+
return {
|
|
4
|
+
router: featureList.includes('router'),
|
|
5
|
+
pinia: featureList.includes('pinia'),
|
|
6
|
+
axios: featureList.includes('axios'),
|
|
7
|
+
ui: featureList.filter(v => ['element', 'vant'].includes(v))
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function parseExtraPlugins (featureList) {
|
|
12
|
+
return featureList.filter(v => ['vueuse', 'lodash', 'dayjs', 'tailwind', 'mitt', 'https'].includes(v))
|
|
13
|
+
}
|
package/lib/mainFile.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// lib/mainFile.js
|
|
2
|
+
import fs from 'fs'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
|
|
5
|
+
export async function generateMainFile (features, extraPlugins, language, targetDir) {
|
|
6
|
+
const mainFile = language === 'ts' ? 'main.ts' : 'main.js'
|
|
7
|
+
const mainTplPath = path.join(targetDir, `src/${mainFile}.tpl`)
|
|
8
|
+
if(!fs.existsSync(mainTplPath)) return
|
|
9
|
+
let main = fs.readFileSync(mainTplPath, 'utf-8')
|
|
10
|
+
|
|
11
|
+
const replacements = {
|
|
12
|
+
'/* __ROUTER_IMPORT__ */': features.router ? "import router from './router'" : '',
|
|
13
|
+
'/* __PINIA_IMPORT__ */': features.pinia ? "import { createPinia } from 'pinia'\nimport persistedstate from 'pinia-plugin-persistedstate'" : '',
|
|
14
|
+
'/* __ELEMENT_IMPORT__ */': features.ui.includes('element')
|
|
15
|
+
? `import ElementPlus from 'element-plus'
|
|
16
|
+
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
|
17
|
+
import 'element-plus/dist/index.css'
|
|
18
|
+
import * as ElementPlusIconsVue from '@element-plus/icons-vue'`
|
|
19
|
+
: '',
|
|
20
|
+
'/* __VANT_IMPORT__ */': features.ui.includes('vant')
|
|
21
|
+
? `import Vant from 'vant'
|
|
22
|
+
import 'vant/lib/index.css'`
|
|
23
|
+
: '',
|
|
24
|
+
'/* __ROUTER_USE__ */': features.router ? 'app.use(router)' : '',
|
|
25
|
+
'/* __PINIA_USE__ */': features.pinia ? 'app.use(createPinia().use(persistedstate))' : '',
|
|
26
|
+
'/* __ELEMENT_USE__ */': features.ui.includes('element')
|
|
27
|
+
? `app.use(ElementPlus, { locale: zhCn })
|
|
28
|
+
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
|
29
|
+
app.component(key, component)
|
|
30
|
+
}` : '',
|
|
31
|
+
'/* __VANT_USE__ */': features.ui.includes('vant') ? 'app.use(Vant)' : ''
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function escapeRegExp (str) {
|
|
35
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
for(const [placeholder, content] of Object.entries(replacements)) {
|
|
39
|
+
if(content) main = main.replace(placeholder, content)
|
|
40
|
+
else main = main.replace(new RegExp(`^\\s*${escapeRegExp(placeholder)}\\s*$\\n?`, 'gm'), '')
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
main = main.replace(/(\s*)const app = createApp\(App\)/, '\n\n$1const app = createApp(App)')
|
|
44
|
+
main = main.replace(/\n{3,}/g, '\n\n')
|
|
45
|
+
|
|
46
|
+
fs.writeFileSync(path.join(targetDir, `src/${mainFile}`), main)
|
|
47
|
+
fs.unlinkSync(mainTplPath)
|
|
48
|
+
}
|
package/lib/package.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// lib/package.js
|
|
2
|
+
import fs from 'fs'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
|
|
5
|
+
export function generatePackageJson (projectName, features, extraPlugins, autoRoute, enableHttps, language, targetDir, pkgManager) {
|
|
6
|
+
const pkgTpl = path.join(targetDir, 'package.json.tpl')
|
|
7
|
+
if(!fs.existsSync(pkgTpl)) return
|
|
8
|
+
let pkg = fs.readFileSync(pkgTpl, 'utf-8')
|
|
9
|
+
|
|
10
|
+
const optionalDeps = {}
|
|
11
|
+
if(features.router) optionalDeps['vue-router'] = '^5.0.3'
|
|
12
|
+
if(features.pinia) {
|
|
13
|
+
optionalDeps['pinia'] = '^3.0.4'
|
|
14
|
+
optionalDeps['pinia-plugin-persistedstate'] = '^4.7.1'
|
|
15
|
+
}
|
|
16
|
+
if(features.axios) optionalDeps['axios'] = '^1.13.6'
|
|
17
|
+
if(features.ui.includes('element')) {
|
|
18
|
+
optionalDeps['element-plus'] = '^2.13.5'
|
|
19
|
+
optionalDeps['@element-plus/icons-vue'] = '^2.3.2'
|
|
20
|
+
}
|
|
21
|
+
if(features.ui.includes('vant')) optionalDeps['vant'] = '^4.9.22'
|
|
22
|
+
if(extraPlugins.includes('vueuse')) optionalDeps['@vueuse/core'] = '^14.2.1'
|
|
23
|
+
if(extraPlugins.includes('dayjs')) optionalDeps['dayjs'] = '^1.11.20'
|
|
24
|
+
if(extraPlugins.includes('lodash')) optionalDeps['lodash'] = '^4.17.23'
|
|
25
|
+
if(extraPlugins.includes('tailwind')) {
|
|
26
|
+
optionalDeps['tailwindcss'] = '^4.2.2'
|
|
27
|
+
optionalDeps['@tailwindcss/postcss'] = '^4.2.2'
|
|
28
|
+
optionalDeps['postcss'] = '^8.5.8'
|
|
29
|
+
}
|
|
30
|
+
if(extraPlugins.includes('mitt')) optionalDeps['mitt'] = '^3.0.1'
|
|
31
|
+
if(enableHttps) optionalDeps['vite-plugin-mkcert'] = '^1.17.10'
|
|
32
|
+
if(autoRoute) optionalDeps['vite-plugin-pages'] = '^0.33.3'
|
|
33
|
+
|
|
34
|
+
const keys = Object.keys(optionalDeps)
|
|
35
|
+
const depsStr = keys.length ? ',\n' + keys.map(k => ` "${k}": "${optionalDeps[k]}"`).join(',\n') : ''
|
|
36
|
+
pkg = pkg.replace('__PROJECT_NAME__', projectName).replace('__OPTIONAL_DEP__', depsStr)
|
|
37
|
+
|
|
38
|
+
const pkgObj = JSON.parse(pkg)
|
|
39
|
+
if(pkgManager === 'pnpm' && features.ui.includes('vant')) {
|
|
40
|
+
pkgObj.pnpm = { overrides: { "@vant/use": "^1.0.0", "@vant/popperjs": "^1.0.0" } }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
fs.writeFileSync(path.join(targetDir, 'package.json'), JSON.stringify(pkgObj, null, 2))
|
|
44
|
+
fs.unlinkSync(pkgTpl)
|
|
45
|
+
}
|
package/lib/prompts.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// lib/prompts.js
|
|
2
|
+
import prompts from 'prompts'
|
|
3
|
+
|
|
4
|
+
export async function getProjectName (fs, path) {
|
|
5
|
+
while(true) {
|
|
6
|
+
const res = await prompts({
|
|
7
|
+
type: 'text',
|
|
8
|
+
name: 'projectName',
|
|
9
|
+
message: '📦 项目名称',
|
|
10
|
+
validate: v => v ? true : '项目名不能为空'
|
|
11
|
+
})
|
|
12
|
+
const name = res.projectName
|
|
13
|
+
if(!name) process.exit(1)
|
|
14
|
+
const targetDir = path.resolve(process.cwd(), name)
|
|
15
|
+
if(fs.existsSync(targetDir)) {
|
|
16
|
+
console.log('❌ 目录已存在,请重新输入')
|
|
17
|
+
continue
|
|
18
|
+
}
|
|
19
|
+
return name
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function chooseLanguage () {
|
|
24
|
+
const { language } = await prompts({
|
|
25
|
+
type: 'select',
|
|
26
|
+
name: 'language',
|
|
27
|
+
message: '请选择项目语言',
|
|
28
|
+
choices: [
|
|
29
|
+
{ title: 'JavaScript', value: 'js' },
|
|
30
|
+
{ title: 'TypeScript', value: 'ts' }
|
|
31
|
+
]
|
|
32
|
+
})
|
|
33
|
+
return language
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function chooseFeatures () {
|
|
37
|
+
const { featureList } = await prompts({
|
|
38
|
+
type: 'multiselect',
|
|
39
|
+
name: 'featureList',
|
|
40
|
+
message: '请选择基础功能(↑↓选择,空格确认,回车完成)',
|
|
41
|
+
instructions: false,
|
|
42
|
+
choices: [
|
|
43
|
+
{ title: 'Vue Router', value: 'router' },
|
|
44
|
+
{ title: 'Pinia(含持久化)', value: 'pinia' },
|
|
45
|
+
{ title: 'Axios', value: 'axios' },
|
|
46
|
+
{ title: 'Element Plus(PC UI)', value: 'element' },
|
|
47
|
+
{ title: 'Vant(Mobile UI)', value: 'vant' },
|
|
48
|
+
{ title: 'VueUse(实用 Composition API)', value: 'vueuse' },
|
|
49
|
+
{ title: 'Lodash(工具库)', value: 'lodash' },
|
|
50
|
+
{ title: 'Day.js(日期处理)', value: 'dayjs' },
|
|
51
|
+
{ title: 'Tailwind CSS(原子化 CSS)', value: 'tailwind' },
|
|
52
|
+
{ title: 'mitt(事件总线)', value: 'mitt' },
|
|
53
|
+
{ title: 'HTTPS(mkcert)', value: 'https' }
|
|
54
|
+
]
|
|
55
|
+
})
|
|
56
|
+
return featureList || []
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function askAutoRoute (routerEnabled) {
|
|
60
|
+
if(!routerEnabled) return false
|
|
61
|
+
const { enableAutoRoute } = await prompts({
|
|
62
|
+
type: 'toggle',
|
|
63
|
+
name: 'enableAutoRoute',
|
|
64
|
+
message: '是否开启自动配置路由(vite-plugin-pages)?',
|
|
65
|
+
initial: false,
|
|
66
|
+
active: '是',
|
|
67
|
+
inactive: '否'
|
|
68
|
+
})
|
|
69
|
+
return enableAutoRoute
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function askRunDev (devCommand) {
|
|
73
|
+
const { runDev } = await prompts({
|
|
74
|
+
type: 'select',
|
|
75
|
+
name: 'runDev',
|
|
76
|
+
message: `是否立即运行 ${devCommand}?`,
|
|
77
|
+
choices: [{ title: 'Yes', value: true }, { title: 'No', value: false }]
|
|
78
|
+
})
|
|
79
|
+
return runDev
|
|
80
|
+
}
|
package/lib/router.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// lib/router.js
|
|
2
|
+
import fs from 'fs'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
|
|
5
|
+
export function configureRouter (routerEnabled, autoRoute, language, targetDir) {
|
|
6
|
+
if(!routerEnabled) return
|
|
7
|
+
const routerIndexPath = path.join(targetDir, `src/router/index.${language === 'ts' ? 'ts' : 'js'}`)
|
|
8
|
+
const content = autoRoute
|
|
9
|
+
? `import { createRouter, createWebHistory } from 'vue-router'
|
|
10
|
+
import routes from '~pages'
|
|
11
|
+
|
|
12
|
+
routes.unshift({ path: '/', redirect: '/home' })
|
|
13
|
+
|
|
14
|
+
export default createRouter({ history: createWebHistory(), routes })`
|
|
15
|
+
: `import { createRouter, createWebHistory } from 'vue-router'
|
|
16
|
+
|
|
17
|
+
const routes = [ { path: '/', component: () => import('@/views/home/index.vue') } ]
|
|
18
|
+
|
|
19
|
+
export default createRouter({ history: createWebHistory(), routes })`
|
|
20
|
+
fs.writeFileSync(routerIndexPath, content)
|
|
21
|
+
}
|
package/lib/template.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// lib/template.js
|
|
2
|
+
import fs from 'fs'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
|
|
5
|
+
export function copyBaseTemplate (language, targetDir, __dirname) {
|
|
6
|
+
const baseTemplate = language === 'ts' ? 'base-ts' : 'base-js'
|
|
7
|
+
fs.cpSync(path.resolve(__dirname, `../template/${baseTemplate}`), targetDir, { recursive: true })
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function updateIndexHtml (projectName, targetDir) {
|
|
11
|
+
const indexPath = path.join(targetDir, 'index.html')
|
|
12
|
+
if(!fs.existsSync(indexPath)) return
|
|
13
|
+
const indexContent = fs.readFileSync(indexPath, 'utf-8')
|
|
14
|
+
fs.writeFileSync(indexPath, indexContent.replace(/<title>.*<\/title>/, `<title>${projectName}</title>`))
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function appendTailwind (extraPlugins, targetDir) {
|
|
18
|
+
if(!extraPlugins.includes('tailwind')) return
|
|
19
|
+
const stylePath = path.join(targetDir, 'src/style.css')
|
|
20
|
+
const original = fs.readFileSync(stylePath, 'utf-8')
|
|
21
|
+
if(!original.startsWith('@import "tailwindcss";')) {
|
|
22
|
+
fs.writeFileSync(stylePath, `@import "tailwindcss";\n${original}`)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function copyOptionalTemplates (features, extraPlugins, language, targetDir, __dirname) {
|
|
27
|
+
const copy = name => {
|
|
28
|
+
fs.cpSync(path.resolve(__dirname, `../template/${name}`), targetDir, { recursive: true })
|
|
29
|
+
}
|
|
30
|
+
features.router && copy(language === 'ts' ? 'router-ts' : 'router-js')
|
|
31
|
+
features.pinia && copy(language === 'ts' ? 'pinia-ts' : 'pinia-js')
|
|
32
|
+
features.axios && copy(language === 'ts' ? 'axios-ts' : 'axios-js')
|
|
33
|
+
for(const plugin of extraPlugins) {
|
|
34
|
+
const templateName = `${plugin}-${language === 'ts' ? 'ts' : 'js'}`
|
|
35
|
+
const templatePath = path.resolve(__dirname, `../template/${templateName}`)
|
|
36
|
+
if(fs.existsSync(templatePath)) copy(templateName)
|
|
37
|
+
}
|
|
38
|
+
}
|
package/lib/utils.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// lib/utils.js
|
|
2
|
+
import { execSync } from 'child_process'
|
|
3
|
+
import fs from 'fs'
|
|
4
|
+
|
|
5
|
+
export function checkNodeVersion (requiredVersion) {
|
|
6
|
+
const currentVersion = process.version.replace('v', '')
|
|
7
|
+
if(compareVersion(currentVersion, requiredVersion) < 0) {
|
|
8
|
+
console.error(`❌ Node.js 版本过低`)
|
|
9
|
+
console.error(`当前版本: ${currentVersion}`)
|
|
10
|
+
console.error(`最低要求: ${requiredVersion}`)
|
|
11
|
+
console.error(`请升级 Node.js 后再运行`)
|
|
12
|
+
process.exit(1)
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function compareVersion (v1, v2) {
|
|
17
|
+
const a = v1.split('.').map(Number)
|
|
18
|
+
const b = v2.split('.').map(Number)
|
|
19
|
+
for(let i = 0; i < Math.max(a.length, b.length); i++) {
|
|
20
|
+
const n1 = a[i] || 0
|
|
21
|
+
const n2 = b[i] || 0
|
|
22
|
+
if(n1 > n2) return 1
|
|
23
|
+
if(n1 < n2) return -1
|
|
24
|
+
}
|
|
25
|
+
return 0
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function detectPackageManager () {
|
|
29
|
+
const userAgent = process.env.npm_config_user_agent || ''
|
|
30
|
+
if(userAgent.startsWith('pnpm')) return 'pnpm'
|
|
31
|
+
if(userAgent.startsWith('npm')) return 'npm'
|
|
32
|
+
if(fs.existsSync('pnpm-lock.yaml')) return 'pnpm'
|
|
33
|
+
return 'npm'
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function runCmd (cmd, cwd = process.cwd()) {
|
|
37
|
+
execSync(cmd, { cwd, stdio: 'inherit' })
|
|
38
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// lib/viteConfig.js
|
|
2
|
+
import fs from 'fs'
|
|
3
|
+
import path from 'path'
|
|
4
|
+
|
|
5
|
+
export function configureVite (language, autoRoute, enableHttps, targetDir) {
|
|
6
|
+
const viteConfigPath = path.join(targetDir, `vite.config.${language === 'ts' ? 'ts' : 'js'}`)
|
|
7
|
+
if(!fs.existsSync(viteConfigPath)) return
|
|
8
|
+
let viteConfig = fs.readFileSync(viteConfigPath, 'utf-8')
|
|
9
|
+
|
|
10
|
+
// mkcert
|
|
11
|
+
if(enableHttps && !viteConfig.includes("vite-plugin-mkcert")) {
|
|
12
|
+
viteConfig = viteConfig.replace(/(import .*?from .*?\n)/, `$1import mkcert from 'vite-plugin-mkcert'\n`)
|
|
13
|
+
viteConfig = viteConfig.replace(/plugins:\s*\[/, `plugins: [\n mkcert(),`)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// 自动路由
|
|
17
|
+
if(autoRoute) {
|
|
18
|
+
if(!viteConfig.includes("import fs from 'fs'")) viteConfig = `import fs from 'fs'\n${viteConfig}`
|
|
19
|
+
if(!viteConfig.includes("import Pages from 'vite-plugin-pages'")) viteConfig = viteConfig.replace(/(import .*?from .*?\n)/, `$1import Pages from 'vite-plugin-pages'\n`)
|
|
20
|
+
if(!viteConfig.includes("Pages({")) viteConfig = viteConfig.replace(/plugins:\s*\[/, `plugins: [\n Pages({\n dirs: 'src/views',\n extensions: ['vue'],\n exclude: ['**/_*.vue'],\n async extendRoute(route) {\n const componentPath = path.resolve(process.cwd(), route.component.slice(1))\n const dirPath = path.dirname(componentPath)\n const metaFile = path.resolve(dirPath, 'meta.json')\n if(fs.existsSync(metaFile)) {\n try {\n const metaContent = fs.readFileSync(metaFile, 'utf-8')\n const meta = JSON.parse(metaContent)\n route.meta = { ...(route.meta || {}), ...meta }\n } catch(err) {\n console.warn(\`加载 \${metaFile} 失败:\`, err)\n }\n }\n return { ...route }\n }\n }),`)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
fs.writeFileSync(viteConfigPath, viteConfig)
|
|
24
|
+
}
|
package/package.json
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { InternalAxiosRequestConfig } from "axios"
|
|
2
2
|
|
|
3
|
+
// 扩展 InternalAxiosRequestConfig 类型,添加自定义属性 __requestKey
|
|
4
|
+
declare module "axios" {
|
|
5
|
+
interface InternalAxiosRequestConfig {
|
|
6
|
+
__requestKey?: string
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
3
10
|
const pendingRequests = new Map()
|
|
4
11
|
|
|
5
12
|
/**
|
|
@@ -232,6 +232,34 @@ console.log(route.params.id)
|
|
|
232
232
|
console.log(route.params.name)
|
|
233
233
|
```
|
|
234
234
|
|
|
235
|
+
### ✅ meta.json 自动注入路由
|
|
236
|
+
|
|
237
|
+
在每个页面目录下可以新增一个 `meta.json` 文件:
|
|
238
|
+
|
|
239
|
+
```text
|
|
240
|
+
src/views/home/
|
|
241
|
+
├─ index.vue
|
|
242
|
+
└─ meta.json
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
#### 示例:
|
|
246
|
+
|
|
247
|
+
```json
|
|
248
|
+
{
|
|
249
|
+
"name": "首页",
|
|
250
|
+
"orderMenu": 1
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
👉 脚手架会自动读取该文件,并注入到对应路由的 `meta` 属性中:
|
|
255
|
+
|
|
256
|
+
```js
|
|
257
|
+
route.meta = {
|
|
258
|
+
name: "首页",
|
|
259
|
+
orderMenu: 1
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
235
263
|
---
|
|
236
264
|
|
|
237
265
|
### 4️⃣ 页面内容与样式
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
🧰 VueUse · Lodash · Day.js
|
|
38
38
|
🎨 Tailwind CSS
|
|
39
39
|
🔔 Mitt
|
|
40
|
-
🛡️ mkcert (本地 HTTPS)
|
|
40
|
+
🛡️ mkcert (本地 HTTPS)
|
|
41
41
|
|
|
42
42
|
---
|
|
43
43
|
|
|
@@ -232,6 +232,34 @@ console.log(route.params.id)
|
|
|
232
232
|
console.log(route.params.name)
|
|
233
233
|
```
|
|
234
234
|
|
|
235
|
+
### ✅ meta.json 自动注入路由
|
|
236
|
+
|
|
237
|
+
在每个页面目录下可以新增一个 `meta.json` 文件:
|
|
238
|
+
|
|
239
|
+
```text
|
|
240
|
+
src/views/home/
|
|
241
|
+
├─ index.vue
|
|
242
|
+
└─ meta.json
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
#### 示例:
|
|
246
|
+
|
|
247
|
+
```json
|
|
248
|
+
{
|
|
249
|
+
"name": "首页",
|
|
250
|
+
"orderMenu": 1
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
👉 脚手架会自动读取该文件,并注入到对应路由的 `meta` 属性中:
|
|
255
|
+
|
|
256
|
+
```js
|
|
257
|
+
route.meta = {
|
|
258
|
+
name: "首页",
|
|
259
|
+
orderMenu: 1
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
235
263
|
---
|
|
236
264
|
|
|
237
265
|
### 4️⃣ 页面内容与样式
|