m8-mcp-server 1.0.4 → 1.0.5
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 +184 -14
- package/dist/generator/vue-template.d.ts +21 -0
- package/dist/generator/vue-template.d.ts.map +1 -1
- package/dist/generator/vue-template.js +497 -33
- package/dist/generator/vue-template.js.map +1 -1
- package/package.json +2 -1
- package/resources/components/actionsheet.json +199 -0
- package/resources/components/amap.json +66 -0
- package/resources/components/area.json +158 -0
- package/resources/components/badge.json +93 -0
- package/resources/components/button.json +260 -74
- package/resources/components/calendar.json +225 -0
- package/resources/components/cascader.json +115 -0
- package/resources/components/cell.json +85 -69
- package/resources/components/chart.json +55 -0
- package/resources/components/checkbox.json +158 -0
- package/resources/components/circle.json +138 -0
- package/resources/components/collapse.json +88 -0
- package/resources/components/countdown.json +105 -0
- package/resources/components/datepicker.json +216 -0
- package/resources/components/dialog.json +250 -0
- package/resources/components/divider.json +82 -0
- package/resources/components/dragsort.json +67 -0
- package/resources/components/dropdownmenu.json +129 -0
- package/resources/components/easycalendar.json +84 -0
- package/resources/components/empty.json +90 -0
- package/resources/components/field.json +423 -88
- package/resources/components/form.json +156 -0
- package/resources/components/grid.json +131 -0
- package/resources/components/header.json +147 -0
- package/resources/components/icon.json +104 -0
- package/resources/components/image.json +169 -0
- package/resources/components/imagepreview.json +150 -0
- package/resources/components/imagescale.json +245 -0
- package/resources/components/indexbar.json +83 -0
- package/resources/components/layout.json +74 -0
- package/resources/components/lazyload.json +46 -0
- package/resources/components/loading.json +103 -0
- package/resources/components/minirefresh.json +341 -0
- package/resources/components/noticebar.json +148 -0
- package/resources/components/notify.json +60 -0
- package/resources/components/numberkeyboard.json +216 -0
- package/resources/components/overlay.json +78 -0
- package/resources/components/pagination.json +137 -0
- package/resources/components/panel.json +87 -0
- package/resources/components/passwordinput.json +103 -0
- package/resources/components/picker.json +195 -0
- package/resources/components/popover.json +161 -0
- package/resources/components/popup.json +229 -0
- package/resources/components/progress.json +111 -0
- package/resources/components/qrcode.json +128 -0
- package/resources/components/radio.json +139 -0
- package/resources/components/rate.json +157 -0
- package/resources/components/rtc.json +35 -0
- package/resources/components/search.json +234 -0
- package/resources/components/selectperson.json +175 -0
- package/resources/components/sidebar.json +74 -0
- package/resources/components/skeleton.json +110 -0
- package/resources/components/slider.json +159 -0
- package/resources/components/stepper.json +241 -0
- package/resources/components/steps.json +108 -0
- package/resources/components/sticky.json +72 -0
- package/resources/components/swipe.json +146 -0
- package/resources/components/swipecell.json +118 -0
- package/resources/components/switch.json +123 -0
- package/resources/components/switchcell.json +123 -0
- package/resources/components/tab.json +257 -0
- package/resources/components/tabbar.json +137 -0
- package/resources/components/table.json +149 -0
- package/resources/components/tag.json +149 -0
- package/resources/components/toast.json +70 -0
- package/resources/components/treeselect.json +106 -0
- package/resources/components/uploader.json +283 -0
- package/resources/components/verifycode.json +94 -0
|
@@ -5,28 +5,63 @@
|
|
|
5
5
|
* @描述 生成 Vue2 和 Vue3 组件模板,严格遵循 M8 规范
|
|
6
6
|
*/
|
|
7
7
|
import { generateVueFileHeader, generateFileHeader } from './header.js';
|
|
8
|
+
import { readdirSync, existsSync } from 'fs';
|
|
9
|
+
import { join, dirname } from 'path';
|
|
10
|
+
import { fileURLToPath } from 'url';
|
|
11
|
+
// ESM 兼容的 __dirname
|
|
12
|
+
const __vueTemplateFilename = fileURLToPath(import.meta.url);
|
|
13
|
+
const __vueTemplateDir = dirname(__vueTemplateFilename);
|
|
14
|
+
/**
|
|
15
|
+
* 从 resources/components 目录动态加载可用组件列表
|
|
16
|
+
*/
|
|
17
|
+
function loadValidComponents() {
|
|
18
|
+
const componentsDir = join(__vueTemplateDir, '../resources/components');
|
|
19
|
+
const components = new Set();
|
|
20
|
+
// 默认组件列表(作为备用)
|
|
21
|
+
const fallbackComponents = [
|
|
22
|
+
'em-actionsheet', 'em-amap', 'em-button', 'em-cell', 'em-checkbox',
|
|
23
|
+
'em-circle', 'em-datepicker', 'em-field', 'em-form', 'em-header',
|
|
24
|
+
'em-icon', 'em-loading', 'em-noticebar', 'em-numberkeyboard',
|
|
25
|
+
'em-pagination', 'em-panel', 'em-passwordinput', 'em-picker',
|
|
26
|
+
'em-popup', 'em-progress', 'em-radio', 'em-rate', 'em-search',
|
|
27
|
+
'em-slider', 'em-stepper', 'em-swipecell', 'em-switch',
|
|
28
|
+
'em-switchcell', 'em-tag', 'em-treeselect', 'em-uploader',
|
|
29
|
+
'em-verifycode', 'em-minirefresh', 'em-layout', 'em-image',
|
|
30
|
+
'em-toast', 'em-calendar', 'em-area', 'em-tab', 'em-dialog',
|
|
31
|
+
'em-dropdownmenu', 'em-notify', 'em-overlay', 'em-collapse',
|
|
32
|
+
'em-grid', 'em-countdown', 'em-divider', 'em-empty',
|
|
33
|
+
'em-imagepreview', 'em-lazyload', 'em-skeleton', 'em-steps',
|
|
34
|
+
'em-sticky', 'em-indexbar', 'em-sidebar', 'em-tabbar', 'em-badge',
|
|
35
|
+
'em-popover', 'em-cascader', 'em-selectperson', 'em-swipe',
|
|
36
|
+
'em-easycalendar', 'em-qrcode', 'em-imagescale', 'em-dragsort',
|
|
37
|
+
'em-chart', 'em-rtc', 'em-table'
|
|
38
|
+
];
|
|
39
|
+
try {
|
|
40
|
+
if (existsSync(componentsDir)) {
|
|
41
|
+
const files = readdirSync(componentsDir).filter(f => f.endsWith('.json') && f !== 'index.json');
|
|
42
|
+
for (const file of files) {
|
|
43
|
+
// 从文件名提取组件 ID,如 button.json -> em-button
|
|
44
|
+
const id = file.replace('.json', '');
|
|
45
|
+
components.add(`em-${id}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
// 静默处理错误
|
|
51
|
+
}
|
|
52
|
+
// 如果动态加载失败,使用备用列表
|
|
53
|
+
if (components.size === 0) {
|
|
54
|
+
for (const comp of fallbackComponents) {
|
|
55
|
+
components.add(comp);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return components;
|
|
59
|
+
}
|
|
8
60
|
/**
|
|
9
61
|
* 可用的 M8 UI 组件列表
|
|
10
|
-
*
|
|
62
|
+
* 从 resources/components 目录动态加载,确保与组件文档同步
|
|
11
63
|
*/
|
|
12
|
-
const VALID_COMPONENTS =
|
|
13
|
-
'em-actionsheet', 'em-amap', 'em-button', 'em-cell', 'em-checkbox',
|
|
14
|
-
'em-circle', 'em-datepicker', 'em-field', 'em-form', 'em-header',
|
|
15
|
-
'em-icon', 'em-loading', 'em-noticebar', 'em-numberkeyboard',
|
|
16
|
-
'em-pagination', 'em-panel', 'em-passwordinput', 'em-picker',
|
|
17
|
-
'em-popup', 'em-progress', 'em-radio', 'em-rate', 'em-search',
|
|
18
|
-
'em-slider', 'em-stepper', 'em-swipecell', 'em-switch',
|
|
19
|
-
'em-switchcell', 'em-tag', 'em-treeselect', 'em-uploader',
|
|
20
|
-
'em-verifycode', 'em-minirefresh', 'em-layout', 'em-image',
|
|
21
|
-
'em-toast', 'em-calendar', 'em-area', 'em-tab', 'em-dialog',
|
|
22
|
-
'em-dropdownmenu', 'em-notify', 'em-overlay', 'em-collapse',
|
|
23
|
-
'em-grid', 'em-countdown', 'em-divider', 'em-empty',
|
|
24
|
-
'em-imagepreview', 'em-lazyload', 'em-skeleton', 'em-steps',
|
|
25
|
-
'em-sticky', 'em-indexbar', 'em-sidebar', 'em-tabbar', 'em-badge',
|
|
26
|
-
'em-popover', 'em-cascader', 'em-selectperson', 'em-swipe',
|
|
27
|
-
'em-easycalendar', 'em-qrcode', 'em-imagescale', 'em-dragsort',
|
|
28
|
-
'em-chart', 'em-rtc', 'em-table'
|
|
29
|
-
]);
|
|
64
|
+
const VALID_COMPONENTS = loadValidComponents();
|
|
30
65
|
/**
|
|
31
66
|
* 组件名称映射:常见错误名称 -> 正确名称
|
|
32
67
|
* 确保使用者输入的无效组件名称能被正确映射到 M8 组件库中的有效组件
|
|
@@ -148,6 +183,91 @@ export function filterValidComponents(components) {
|
|
|
148
183
|
}
|
|
149
184
|
return { valid, warnings };
|
|
150
185
|
}
|
|
186
|
+
/**
|
|
187
|
+
* 从需求描述中提取表单字段信息
|
|
188
|
+
* @param requirement 需求描述
|
|
189
|
+
* @returns 表单字段列表
|
|
190
|
+
*/
|
|
191
|
+
export function extractFormFields(requirement) {
|
|
192
|
+
const fields = [];
|
|
193
|
+
const req = requirement.toLowerCase();
|
|
194
|
+
// 手机号相关
|
|
195
|
+
if (req.includes('手机号') || req.includes('手机') || req.includes('mobile')) {
|
|
196
|
+
fields.push({
|
|
197
|
+
name: '手机号',
|
|
198
|
+
propertyName: 'mobile',
|
|
199
|
+
type: 'tel',
|
|
200
|
+
validation: 'mobile',
|
|
201
|
+
validationMethod: 'Util.string.isMobile'
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
// 身份证相关
|
|
205
|
+
if (req.includes('身份证') || req.includes('idcard') || req.includes('证件号')) {
|
|
206
|
+
fields.push({
|
|
207
|
+
name: '身份证号',
|
|
208
|
+
propertyName: 'idCard',
|
|
209
|
+
type: 'text',
|
|
210
|
+
validation: 'idcard',
|
|
211
|
+
validationMethod: 'Util.string.isIdCard'
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
// 邮箱相关
|
|
215
|
+
if (req.includes('邮箱') || req.includes('email')) {
|
|
216
|
+
fields.push({
|
|
217
|
+
name: '邮箱',
|
|
218
|
+
propertyName: 'email',
|
|
219
|
+
type: 'text',
|
|
220
|
+
validation: 'email',
|
|
221
|
+
validationMethod: 'Util.string.isEmail'
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
// 电话相关(固定电话)
|
|
225
|
+
if (req.includes('固定电话') || req.includes('固话') || req.includes('座机')) {
|
|
226
|
+
fields.push({
|
|
227
|
+
name: '固定电话',
|
|
228
|
+
propertyName: 'tel',
|
|
229
|
+
type: 'tel',
|
|
230
|
+
validation: 'tel',
|
|
231
|
+
validationMethod: 'Util.string.isTel'
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
// 姓名相关
|
|
235
|
+
if (req.includes('姓名') || req.includes('名字')) {
|
|
236
|
+
fields.push({
|
|
237
|
+
name: '姓名',
|
|
238
|
+
propertyName: 'name',
|
|
239
|
+
type: 'text',
|
|
240
|
+
validation: 'required'
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
// 用户名相关
|
|
244
|
+
if (req.includes('用户名') || req.includes('username')) {
|
|
245
|
+
fields.push({
|
|
246
|
+
name: '用户名',
|
|
247
|
+
propertyName: 'username',
|
|
248
|
+
type: 'text',
|
|
249
|
+
validation: 'required'
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
// 密码相关
|
|
253
|
+
if (req.includes('密码') || req.includes('password')) {
|
|
254
|
+
fields.push({
|
|
255
|
+
name: '密码',
|
|
256
|
+
propertyName: 'password',
|
|
257
|
+
type: 'password',
|
|
258
|
+
validation: 'required'
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
// 附件/文件上传相关
|
|
262
|
+
if (req.includes('附件') || req.includes('上传') || req.includes('文件上传') || req.includes('upload')) {
|
|
263
|
+
fields.push({
|
|
264
|
+
name: '附件',
|
|
265
|
+
propertyName: 'attachments',
|
|
266
|
+
type: 'uploader'
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
return fields;
|
|
270
|
+
}
|
|
151
271
|
/**
|
|
152
272
|
* 根据需求智能推荐组件
|
|
153
273
|
* @param requirement 需求描述
|
|
@@ -156,11 +276,12 @@ export function filterValidComponents(components) {
|
|
|
156
276
|
export function suggestComponents(requirement) {
|
|
157
277
|
const components = [];
|
|
158
278
|
const req = requirement.toLowerCase();
|
|
279
|
+
const fields = extractFormFields(requirement);
|
|
159
280
|
// 登录/表单相关
|
|
160
281
|
if (req.includes('登录') || req.includes('login')) {
|
|
161
282
|
components.push('em-field', 'em-button');
|
|
162
283
|
}
|
|
163
|
-
if (req.includes('表单') || req.includes('form')) {
|
|
284
|
+
if (req.includes('表单') || req.includes('form') || req.includes('提交')) {
|
|
164
285
|
components.push('em-form', 'em-field', 'em-button');
|
|
165
286
|
}
|
|
166
287
|
if (req.includes('输入') || req.includes('input')) {
|
|
@@ -184,10 +305,23 @@ export function suggestComponents(requirement) {
|
|
|
184
305
|
if (req.includes('搜索') || req.includes('search')) {
|
|
185
306
|
components.push('em-search');
|
|
186
307
|
}
|
|
187
|
-
// 上传相关
|
|
188
|
-
if (req.includes('上传') || req.includes('upload') || req.includes('
|
|
308
|
+
// 上传相关 - 包含"附件"关键词
|
|
309
|
+
if (req.includes('上传') || req.includes('upload') || req.includes('附件') || req.includes('文件')) {
|
|
189
310
|
components.push('em-uploader');
|
|
190
311
|
}
|
|
312
|
+
// 根据提取的字段自动添加组件
|
|
313
|
+
for (const field of fields) {
|
|
314
|
+
if (field.type === 'uploader') {
|
|
315
|
+
components.push('em-uploader');
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
components.push('em-field');
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
// 如果有表单字段,自动添加表单和按钮
|
|
322
|
+
if (fields.length > 0) {
|
|
323
|
+
components.push('em-form', 'em-button');
|
|
324
|
+
}
|
|
191
325
|
// 去重
|
|
192
326
|
return [...new Set(components)];
|
|
193
327
|
}
|
|
@@ -211,10 +345,19 @@ export function generateVue2Template(options) {
|
|
|
211
345
|
const finalComponents = validComponents.length > 0
|
|
212
346
|
? validComponents
|
|
213
347
|
: suggestComponents(description);
|
|
348
|
+
// 提取表单字段信息
|
|
349
|
+
const formFields = extractFormFields(description);
|
|
214
350
|
// 生成模板内容
|
|
215
351
|
const templateContent = generateTemplateContent(finalComponents, name, description);
|
|
216
352
|
// 生成 CSS 类名 (使用下划线风格,符合 M8 规范)
|
|
217
353
|
const cssClassName = name.replace(/-/g, '_');
|
|
354
|
+
// 生成动态 formData 初始化
|
|
355
|
+
const formDataInit = generateFormDataInit(formFields);
|
|
356
|
+
// 生成校验方法
|
|
357
|
+
const validateMethod = generateValidateMethod(formFields);
|
|
358
|
+
// 检查是否需要 uploader 处理
|
|
359
|
+
const hasUploader = finalComponents.includes('em-uploader') || formFields.some(f => f.type === 'uploader');
|
|
360
|
+
const uploaderMethod = hasUploader ? generateUploaderMethod() : '';
|
|
218
361
|
const template = `${header}
|
|
219
362
|
<template>
|
|
220
363
|
<div class="${cssClassName}">
|
|
@@ -231,7 +374,7 @@ export default {
|
|
|
231
374
|
data() {
|
|
232
375
|
return {
|
|
233
376
|
// 表单数据
|
|
234
|
-
formData: {},
|
|
377
|
+
formData: ${formDataInit},
|
|
235
378
|
// 加载状态
|
|
236
379
|
loading: false
|
|
237
380
|
};
|
|
@@ -279,11 +422,16 @@ export default {
|
|
|
279
422
|
ejs.ui.closeWaiting();
|
|
280
423
|
}
|
|
281
424
|
},
|
|
282
|
-
|
|
425
|
+
${validateMethod}
|
|
283
426
|
/**
|
|
284
427
|
* 提交表单
|
|
285
428
|
*/
|
|
286
429
|
async handleSubmit() {
|
|
430
|
+
// 表单校验
|
|
431
|
+
if (!this.validateForm()) {
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
|
|
287
435
|
try {
|
|
288
436
|
ejs.ui.showWaiting({ message: '提交中...' });
|
|
289
437
|
|
|
@@ -307,7 +455,7 @@ export default {
|
|
|
307
455
|
} finally {
|
|
308
456
|
ejs.ui.closeWaiting();
|
|
309
457
|
}
|
|
310
|
-
}
|
|
458
|
+
}${uploaderMethod}
|
|
311
459
|
}
|
|
312
460
|
};
|
|
313
461
|
</script>
|
|
@@ -318,6 +466,123 @@ export default {
|
|
|
318
466
|
`;
|
|
319
467
|
return template;
|
|
320
468
|
}
|
|
469
|
+
/**
|
|
470
|
+
* 生成 formData 初始化代码
|
|
471
|
+
* @param fields 表单字段列表
|
|
472
|
+
* @returns formData 初始化对象字符串
|
|
473
|
+
*/
|
|
474
|
+
function generateFormDataInit(fields) {
|
|
475
|
+
if (fields.length === 0) {
|
|
476
|
+
return '{}';
|
|
477
|
+
}
|
|
478
|
+
const props = [];
|
|
479
|
+
for (const field of fields) {
|
|
480
|
+
if (field.type === 'uploader') {
|
|
481
|
+
props.push(`${field.propertyName}: []`);
|
|
482
|
+
}
|
|
483
|
+
else {
|
|
484
|
+
props.push(`${field.propertyName}: ''`);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
return `{\n ${props.join(',\n ')}\n }`;
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* 生成表单校验方法
|
|
491
|
+
* @param fields 表单字段列表
|
|
492
|
+
* @returns 校验方法代码
|
|
493
|
+
*/
|
|
494
|
+
function generateValidateMethod(fields) {
|
|
495
|
+
if (fields.length === 0) {
|
|
496
|
+
return `
|
|
497
|
+
/**
|
|
498
|
+
* 表单校验
|
|
499
|
+
*/
|
|
500
|
+
validateForm() {
|
|
501
|
+
return true;
|
|
502
|
+
},
|
|
503
|
+
`;
|
|
504
|
+
}
|
|
505
|
+
const validations = [];
|
|
506
|
+
for (const field of fields) {
|
|
507
|
+
if (field.validationMethod) {
|
|
508
|
+
// 使用 Util.string 校验方法
|
|
509
|
+
validations.push(` // 校验${field.name}
|
|
510
|
+
if (!this.formData.${field.propertyName}) {
|
|
511
|
+
ejs.ui.toast({ message: '请输入${field.name}' });
|
|
512
|
+
return false;
|
|
513
|
+
}
|
|
514
|
+
if (!${field.validationMethod}(this.formData.${field.propertyName})) {
|
|
515
|
+
ejs.ui.toast({ message: '${field.name}格式不正确' });
|
|
516
|
+
return false;
|
|
517
|
+
}`);
|
|
518
|
+
}
|
|
519
|
+
else if (field.validation === 'required' && field.type !== 'uploader') {
|
|
520
|
+
// 仅必填校验
|
|
521
|
+
validations.push(` // 校验${field.name}
|
|
522
|
+
if (!this.formData.${field.propertyName}) {
|
|
523
|
+
ejs.ui.toast({ message: '请输入${field.name}' });
|
|
524
|
+
return false;
|
|
525
|
+
}`);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
return `
|
|
529
|
+
/**
|
|
530
|
+
* 表单校验
|
|
531
|
+
* 使用 Util.string 提供的校验方法,符合 M8 规范
|
|
532
|
+
*/
|
|
533
|
+
validateForm() {
|
|
534
|
+
${validations.join('\n\n')}
|
|
535
|
+
|
|
536
|
+
return true;
|
|
537
|
+
},
|
|
538
|
+
`;
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* 生成 uploader 处理方法
|
|
542
|
+
* @returns uploader 相关方法代码
|
|
543
|
+
*/
|
|
544
|
+
function generateUploaderMethod() {
|
|
545
|
+
return `,
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* 文件上传后的回调
|
|
549
|
+
* @param file 上传的文件信息
|
|
550
|
+
* @param detail 额外信息
|
|
551
|
+
*/
|
|
552
|
+
afterRead(file, detail) {
|
|
553
|
+
// 设置上传中状态
|
|
554
|
+
file.status = 'uploading';
|
|
555
|
+
file.message = '上传中...';
|
|
556
|
+
|
|
557
|
+
// TODO: 调用上传接口
|
|
558
|
+
// 示例:使用 Util.ajax 上传文件
|
|
559
|
+
// Util.ajax({
|
|
560
|
+
// url: Config.serverUrl + '/rest/api/upload',
|
|
561
|
+
// type: 'POST',
|
|
562
|
+
// data: {
|
|
563
|
+
// file: file.file
|
|
564
|
+
// }
|
|
565
|
+
// }).then(res => {
|
|
566
|
+
// if (res.status?.code === 1) {
|
|
567
|
+
// file.status = 'done';
|
|
568
|
+
// file.url = res.data.url;
|
|
569
|
+
// } else {
|
|
570
|
+
// file.status = 'failed';
|
|
571
|
+
// file.message = '上传失败';
|
|
572
|
+
// }
|
|
573
|
+
// }).catch(() => {
|
|
574
|
+
// file.status = 'failed';
|
|
575
|
+
// file.message = '上传失败';
|
|
576
|
+
// });
|
|
577
|
+
|
|
578
|
+
// 模拟上传成功
|
|
579
|
+
setTimeout(() => {
|
|
580
|
+
file.status = 'done';
|
|
581
|
+
file.message = '';
|
|
582
|
+
console.log('文件上传完成:', file);
|
|
583
|
+
}, 1000);
|
|
584
|
+
}`;
|
|
585
|
+
}
|
|
321
586
|
/**
|
|
322
587
|
* 生成 Vue3 组件模板 (Composition API with script setup)
|
|
323
588
|
* 严格遵循 M8 规范:样式外部引入、使用 ejs.ui API
|
|
@@ -337,10 +602,19 @@ export function generateVue3Template(options) {
|
|
|
337
602
|
const finalComponents = validComponents.length > 0
|
|
338
603
|
? validComponents
|
|
339
604
|
: suggestComponents(description);
|
|
605
|
+
// 提取表单字段信息
|
|
606
|
+
const formFields = extractFormFields(description);
|
|
340
607
|
// 生成模板内容
|
|
341
608
|
const templateContent = generateTemplateContent(finalComponents, name, description);
|
|
342
609
|
// 生成 CSS 类名
|
|
343
610
|
const cssClassName = name.replace(/-/g, '_');
|
|
611
|
+
// 生成动态 formData 初始化
|
|
612
|
+
const formDataInitVue3 = generateFormDataInitVue3(formFields);
|
|
613
|
+
// 生成校验方法
|
|
614
|
+
const validateMethodVue3 = generateValidateMethodVue3(formFields);
|
|
615
|
+
// 检查是否需要 uploader 处理
|
|
616
|
+
const hasUploader = finalComponents.includes('em-uploader') || formFields.some(f => f.type === 'uploader');
|
|
617
|
+
const uploaderMethodVue3 = hasUploader ? generateUploaderMethodVue3() : '';
|
|
344
618
|
const template = `${header}
|
|
345
619
|
<template>
|
|
346
620
|
<div class="${cssClassName}">
|
|
@@ -356,7 +630,7 @@ ${templateContent}
|
|
|
356
630
|
import { ref, reactive, onMounted } from 'vue';
|
|
357
631
|
|
|
358
632
|
// ==================== 响应式数据 ====================
|
|
359
|
-
const formData = reactive({});
|
|
633
|
+
const formData = reactive(${formDataInitVue3});
|
|
360
634
|
const loading = ref(false);
|
|
361
635
|
|
|
362
636
|
// ==================== 生命周期 ====================
|
|
@@ -403,11 +677,16 @@ const loadData = async () => {
|
|
|
403
677
|
ejs.ui.closeWaiting();
|
|
404
678
|
}
|
|
405
679
|
};
|
|
406
|
-
|
|
680
|
+
${validateMethodVue3}
|
|
407
681
|
/**
|
|
408
682
|
* 提交表单
|
|
409
683
|
*/
|
|
410
684
|
const handleSubmit = async () => {
|
|
685
|
+
// 表单校验
|
|
686
|
+
if (!validateForm()) {
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
|
|
411
690
|
try {
|
|
412
691
|
ejs.ui.showWaiting({ message: '提交中...' });
|
|
413
692
|
|
|
@@ -432,6 +711,7 @@ const handleSubmit = async () => {
|
|
|
432
711
|
ejs.ui.closeWaiting();
|
|
433
712
|
}
|
|
434
713
|
};
|
|
714
|
+
${uploaderMethodVue3}
|
|
435
715
|
</script>
|
|
436
716
|
|
|
437
717
|
<style lang="scss" scoped>
|
|
@@ -440,6 +720,122 @@ const handleSubmit = async () => {
|
|
|
440
720
|
`;
|
|
441
721
|
return template;
|
|
442
722
|
}
|
|
723
|
+
/**
|
|
724
|
+
* 生成 Vue3 formData 初始化代码
|
|
725
|
+
* @param fields 表单字段列表
|
|
726
|
+
* @returns formData 初始化对象字符串
|
|
727
|
+
*/
|
|
728
|
+
function generateFormDataInitVue3(fields) {
|
|
729
|
+
if (fields.length === 0) {
|
|
730
|
+
return '{}';
|
|
731
|
+
}
|
|
732
|
+
const props = [];
|
|
733
|
+
for (const field of fields) {
|
|
734
|
+
if (field.type === 'uploader') {
|
|
735
|
+
props.push(`${field.propertyName}: []`);
|
|
736
|
+
}
|
|
737
|
+
else {
|
|
738
|
+
props.push(`${field.propertyName}: ''`);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
return `{\n ${props.join(',\n ')}\n}`;
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* 生成 Vue3 表单校验方法
|
|
745
|
+
* @param fields 表单字段列表
|
|
746
|
+
* @returns 校验方法代码
|
|
747
|
+
*/
|
|
748
|
+
function generateValidateMethodVue3(fields) {
|
|
749
|
+
if (fields.length === 0) {
|
|
750
|
+
return `
|
|
751
|
+
/**
|
|
752
|
+
* 表单校验
|
|
753
|
+
*/
|
|
754
|
+
const validateForm = () => {
|
|
755
|
+
return true;
|
|
756
|
+
};
|
|
757
|
+
`;
|
|
758
|
+
}
|
|
759
|
+
const validations = [];
|
|
760
|
+
for (const field of fields) {
|
|
761
|
+
if (field.validationMethod) {
|
|
762
|
+
// 使用 Util.string 校验方法
|
|
763
|
+
validations.push(` // 校验${field.name}
|
|
764
|
+
if (!formData.${field.propertyName}) {
|
|
765
|
+
ejs.ui.toast({ message: '请输入${field.name}' });
|
|
766
|
+
return false;
|
|
767
|
+
}
|
|
768
|
+
if (!${field.validationMethod}(formData.${field.propertyName})) {
|
|
769
|
+
ejs.ui.toast({ message: '${field.name}格式不正确' });
|
|
770
|
+
return false;
|
|
771
|
+
}`);
|
|
772
|
+
}
|
|
773
|
+
else if (field.validation === 'required' && field.type !== 'uploader') {
|
|
774
|
+
// 仅必填校验
|
|
775
|
+
validations.push(` // 校验${field.name}
|
|
776
|
+
if (!formData.${field.propertyName}) {
|
|
777
|
+
ejs.ui.toast({ message: '请输入${field.name}' });
|
|
778
|
+
return false;
|
|
779
|
+
}`);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
return `
|
|
783
|
+
/**
|
|
784
|
+
* 表单校验
|
|
785
|
+
* 使用 Util.string 提供的校验方法,符合 M8 规范
|
|
786
|
+
*/
|
|
787
|
+
const validateForm = () => {
|
|
788
|
+
${validations.join('\n\n')}
|
|
789
|
+
|
|
790
|
+
return true;
|
|
791
|
+
};
|
|
792
|
+
`;
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* 生成 Vue3 uploader 处理方法
|
|
796
|
+
* @returns uploader 相关方法代码
|
|
797
|
+
*/
|
|
798
|
+
function generateUploaderMethodVue3() {
|
|
799
|
+
return `
|
|
800
|
+
/**
|
|
801
|
+
* 文件上传后的回调
|
|
802
|
+
* @param file 上传的文件信息
|
|
803
|
+
* @param detail 额外信息
|
|
804
|
+
*/
|
|
805
|
+
const afterRead = (file, detail) => {
|
|
806
|
+
// 设置上传中状态
|
|
807
|
+
file.status = 'uploading';
|
|
808
|
+
file.message = '上传中...';
|
|
809
|
+
|
|
810
|
+
// TODO: 调用上传接口
|
|
811
|
+
// 示例:使用 Util.ajax 上传文件
|
|
812
|
+
// Util.ajax({
|
|
813
|
+
// url: Config.serverUrl + '/rest/api/upload',
|
|
814
|
+
// type: 'POST',
|
|
815
|
+
// data: {
|
|
816
|
+
// file: file.file
|
|
817
|
+
// }
|
|
818
|
+
// }).then(res => {
|
|
819
|
+
// if (res.status?.code === 1) {
|
|
820
|
+
// file.status = 'done';
|
|
821
|
+
// file.url = res.data.url;
|
|
822
|
+
// } else {
|
|
823
|
+
// file.status = 'failed';
|
|
824
|
+
// file.message = '上传失败';
|
|
825
|
+
// }
|
|
826
|
+
// }).catch(() => {
|
|
827
|
+
// file.status = 'failed';
|
|
828
|
+
// file.message = '上传失败';
|
|
829
|
+
// });
|
|
830
|
+
|
|
831
|
+
// 模拟上传成功
|
|
832
|
+
setTimeout(() => {
|
|
833
|
+
file.status = 'done';
|
|
834
|
+
file.message = '';
|
|
835
|
+
console.log('文件上传完成:', file);
|
|
836
|
+
}, 1000);
|
|
837
|
+
};`;
|
|
838
|
+
}
|
|
443
839
|
/**
|
|
444
840
|
* 根据组件列表生成模板内容
|
|
445
841
|
* @param components 组件列表
|
|
@@ -451,12 +847,17 @@ function generateTemplateContent(components, name, description) {
|
|
|
451
847
|
const lines = [];
|
|
452
848
|
const cssClassName = name.replace(/-/g, '_');
|
|
453
849
|
const req = description.toLowerCase();
|
|
850
|
+
// 提取表单字段信息
|
|
851
|
+
const formFields = extractFormFields(description);
|
|
454
852
|
// 判断页面类型
|
|
455
|
-
|
|
456
|
-
const
|
|
853
|
+
// 注意:如果有除用户名/密码之外的明确字段,应该使用动态表单模板
|
|
854
|
+
const hasCustomFields = formFields.some(f => !['username', 'password'].includes(f.propertyName));
|
|
855
|
+
const isLoginPage = (req.includes('登录') || req.includes('login')) && !hasCustomFields;
|
|
856
|
+
const isFormPage = req.includes('表单') || req.includes('form') || req.includes('提交') || formFields.length > 0;
|
|
457
857
|
const isListPage = req.includes('列表') || req.includes('list');
|
|
458
|
-
|
|
459
|
-
|
|
858
|
+
const hasUploader = components.includes('em-uploader') || formFields.some(f => f.type === 'uploader');
|
|
859
|
+
if (isLoginPage && !hasCustomFields) {
|
|
860
|
+
// 纯登录页面模板(只有用户名和密码)
|
|
460
861
|
lines.push(` <!-- 登录表单 -->`);
|
|
461
862
|
lines.push(` <div class="${cssClassName}__form">`);
|
|
462
863
|
lines.push(` <em-field`);
|
|
@@ -464,6 +865,7 @@ function generateTemplateContent(components, name, description) {
|
|
|
464
865
|
lines.push(` label="用户名"`);
|
|
465
866
|
lines.push(` placeholder="请输入用户名"`);
|
|
466
867
|
lines.push(` clearable`);
|
|
868
|
+
lines.push(` required`);
|
|
467
869
|
lines.push(` />`);
|
|
468
870
|
lines.push(` <em-field`);
|
|
469
871
|
lines.push(` v-model="formData.password"`);
|
|
@@ -471,14 +873,59 @@ function generateTemplateContent(components, name, description) {
|
|
|
471
873
|
lines.push(` label="密码"`);
|
|
472
874
|
lines.push(` placeholder="请输入密码"`);
|
|
473
875
|
lines.push(` clearable`);
|
|
876
|
+
lines.push(` required`);
|
|
474
877
|
lines.push(` />`);
|
|
475
878
|
lines.push(` <div class="${cssClassName}__actions">`);
|
|
476
879
|
lines.push(` <em-button type="primary" block @click="handleSubmit">登录</em-button>`);
|
|
477
880
|
lines.push(` </div>`);
|
|
478
881
|
lines.push(` </div>`);
|
|
479
882
|
}
|
|
883
|
+
else if (isFormPage && formFields.length > 0) {
|
|
884
|
+
// 有明确字段的表单页面模板
|
|
885
|
+
lines.push(` <!-- 表单内容 -->`);
|
|
886
|
+
lines.push(` <em-form ref="formRef">`);
|
|
887
|
+
// 根据提取的字段生成表单项
|
|
888
|
+
for (const field of formFields) {
|
|
889
|
+
if (field.type === 'uploader') {
|
|
890
|
+
// 文件上传组件
|
|
891
|
+
lines.push(` <!-- ${field.name}上传 -->`);
|
|
892
|
+
lines.push(` <div class="${cssClassName}__uploader">`);
|
|
893
|
+
lines.push(` <div class="${cssClassName}__uploader-label">${field.name}</div>`);
|
|
894
|
+
lines.push(` <em-uploader`);
|
|
895
|
+
lines.push(` v-model="formData.${field.propertyName}"`);
|
|
896
|
+
lines.push(` :after-read="afterRead"`);
|
|
897
|
+
lines.push(` :max-count="9"`);
|
|
898
|
+
lines.push(` multiple`);
|
|
899
|
+
lines.push(` />`);
|
|
900
|
+
lines.push(` </div>`);
|
|
901
|
+
}
|
|
902
|
+
else {
|
|
903
|
+
// 普通输入字段
|
|
904
|
+
lines.push(` <em-field`);
|
|
905
|
+
lines.push(` v-model="formData.${field.propertyName}"`);
|
|
906
|
+
if (field.type === 'tel') {
|
|
907
|
+
lines.push(` type="tel"`);
|
|
908
|
+
}
|
|
909
|
+
else if (field.type === 'password') {
|
|
910
|
+
lines.push(` type="password"`);
|
|
911
|
+
}
|
|
912
|
+
lines.push(` label="${field.name}"`);
|
|
913
|
+
lines.push(` placeholder="请输入${field.name}"`);
|
|
914
|
+
lines.push(` clearable`);
|
|
915
|
+
if (field.validation === 'required' || field.validationMethod) {
|
|
916
|
+
lines.push(` required`);
|
|
917
|
+
}
|
|
918
|
+
lines.push(` />`);
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
lines.push(` <div class="${cssClassName}__actions">`);
|
|
922
|
+
const buttonText = req.includes('登录') ? '登录' : '提交';
|
|
923
|
+
lines.push(` <em-button type="primary" block @click="handleSubmit">${buttonText}</em-button>`);
|
|
924
|
+
lines.push(` </div>`);
|
|
925
|
+
lines.push(` </em-form>`);
|
|
926
|
+
}
|
|
480
927
|
else if (isFormPage) {
|
|
481
|
-
//
|
|
928
|
+
// 通用表单页面模板(没有明确字段)
|
|
482
929
|
lines.push(` <!-- 表单内容 -->`);
|
|
483
930
|
lines.push(` <em-form ref="formRef">`);
|
|
484
931
|
for (const comp of components) {
|
|
@@ -490,6 +937,18 @@ function generateTemplateContent(components, name, description) {
|
|
|
490
937
|
lines.push(` required`);
|
|
491
938
|
lines.push(` />`);
|
|
492
939
|
}
|
|
940
|
+
else if (comp === 'em-uploader') {
|
|
941
|
+
lines.push(` <!-- 附件上传 -->`);
|
|
942
|
+
lines.push(` <div class="${cssClassName}__uploader">`);
|
|
943
|
+
lines.push(` <div class="${cssClassName}__uploader-label">附件</div>`);
|
|
944
|
+
lines.push(` <em-uploader`);
|
|
945
|
+
lines.push(` v-model="formData.attachments"`);
|
|
946
|
+
lines.push(` :after-read="afterRead"`);
|
|
947
|
+
lines.push(` :max-count="9"`);
|
|
948
|
+
lines.push(` multiple`);
|
|
949
|
+
lines.push(` />`);
|
|
950
|
+
lines.push(` </div>`);
|
|
951
|
+
}
|
|
493
952
|
}
|
|
494
953
|
lines.push(` <div class="${cssClassName}__actions">`);
|
|
495
954
|
lines.push(` <em-button type="primary" block @click="handleSubmit">提交</em-button>`);
|
|
@@ -518,7 +977,12 @@ function generateTemplateContent(components, name, description) {
|
|
|
518
977
|
lines.push(` <div class="${cssClassName}__content">`);
|
|
519
978
|
for (const comp of components) {
|
|
520
979
|
const compTag = comp.startsWith('em-') ? comp : `em-${comp}`;
|
|
521
|
-
|
|
980
|
+
if (comp === 'em-uploader') {
|
|
981
|
+
lines.push(` <em-uploader v-model="fileList" :after-read="afterRead" />`);
|
|
982
|
+
}
|
|
983
|
+
else {
|
|
984
|
+
lines.push(` <${compTag} />`);
|
|
985
|
+
}
|
|
522
986
|
}
|
|
523
987
|
if (components.length === 0) {
|
|
524
988
|
lines.push(` <!-- TODO: 添加页面内容 -->`);
|