m8-mcp-server 1.0.3 → 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.
Files changed (84) hide show
  1. package/README.md +184 -14
  2. package/dist/generator/index.d.ts +5 -1
  3. package/dist/generator/index.d.ts.map +1 -1
  4. package/dist/generator/index.js +435 -150
  5. package/dist/generator/index.js.map +1 -1
  6. package/dist/generator/vue-template.d.ts +51 -3
  7. package/dist/generator/vue-template.d.ts.map +1 -1
  8. package/dist/generator/vue-template.js +928 -85
  9. package/dist/generator/vue-template.js.map +1 -1
  10. package/dist/tools/generate-code.d.ts +1 -1
  11. package/dist/tools/generate-code.d.ts.map +1 -1
  12. package/dist/tools/generate-code.js +72 -16
  13. package/dist/tools/generate-code.js.map +1 -1
  14. package/dist/types/index.d.ts +9 -1
  15. package/dist/types/index.d.ts.map +1 -1
  16. package/package.json +2 -1
  17. package/resources/components/actionsheet.json +199 -0
  18. package/resources/components/amap.json +66 -0
  19. package/resources/components/area.json +158 -0
  20. package/resources/components/badge.json +93 -0
  21. package/resources/components/button.json +260 -74
  22. package/resources/components/calendar.json +225 -0
  23. package/resources/components/cascader.json +115 -0
  24. package/resources/components/cell.json +85 -69
  25. package/resources/components/chart.json +55 -0
  26. package/resources/components/checkbox.json +158 -0
  27. package/resources/components/circle.json +138 -0
  28. package/resources/components/collapse.json +88 -0
  29. package/resources/components/countdown.json +105 -0
  30. package/resources/components/datepicker.json +216 -0
  31. package/resources/components/dialog.json +250 -0
  32. package/resources/components/divider.json +82 -0
  33. package/resources/components/dragsort.json +67 -0
  34. package/resources/components/dropdownmenu.json +129 -0
  35. package/resources/components/easycalendar.json +84 -0
  36. package/resources/components/empty.json +90 -0
  37. package/resources/components/field.json +423 -88
  38. package/resources/components/form.json +156 -0
  39. package/resources/components/grid.json +131 -0
  40. package/resources/components/header.json +147 -0
  41. package/resources/components/icon.json +104 -0
  42. package/resources/components/image.json +169 -0
  43. package/resources/components/imagepreview.json +150 -0
  44. package/resources/components/imagescale.json +245 -0
  45. package/resources/components/indexbar.json +83 -0
  46. package/resources/components/layout.json +74 -0
  47. package/resources/components/lazyload.json +46 -0
  48. package/resources/components/loading.json +103 -0
  49. package/resources/components/minirefresh.json +341 -0
  50. package/resources/components/noticebar.json +148 -0
  51. package/resources/components/notify.json +60 -0
  52. package/resources/components/numberkeyboard.json +216 -0
  53. package/resources/components/overlay.json +78 -0
  54. package/resources/components/pagination.json +137 -0
  55. package/resources/components/panel.json +87 -0
  56. package/resources/components/passwordinput.json +103 -0
  57. package/resources/components/picker.json +195 -0
  58. package/resources/components/popover.json +161 -0
  59. package/resources/components/popup.json +229 -0
  60. package/resources/components/progress.json +111 -0
  61. package/resources/components/qrcode.json +128 -0
  62. package/resources/components/radio.json +139 -0
  63. package/resources/components/rate.json +157 -0
  64. package/resources/components/rtc.json +35 -0
  65. package/resources/components/search.json +234 -0
  66. package/resources/components/selectperson.json +175 -0
  67. package/resources/components/sidebar.json +74 -0
  68. package/resources/components/skeleton.json +110 -0
  69. package/resources/components/slider.json +159 -0
  70. package/resources/components/stepper.json +241 -0
  71. package/resources/components/steps.json +108 -0
  72. package/resources/components/sticky.json +72 -0
  73. package/resources/components/swipe.json +146 -0
  74. package/resources/components/swipecell.json +118 -0
  75. package/resources/components/switch.json +123 -0
  76. package/resources/components/switchcell.json +123 -0
  77. package/resources/components/tab.json +257 -0
  78. package/resources/components/tabbar.json +137 -0
  79. package/resources/components/table.json +149 -0
  80. package/resources/components/tag.json +149 -0
  81. package/resources/components/toast.json +70 -0
  82. package/resources/components/treeselect.json +106 -0
  83. package/resources/components/uploader.json +283 -0
  84. package/resources/components/verifycode.json +94 -0
@@ -2,112 +2,623 @@
2
2
  * Vue 模板生成器
3
3
  * @作者 M8 Team
4
4
  * @创建时间 2024-12-29
5
- * @描述 生成 Vue2 和 Vue3 组件模板
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
+ }
60
+ /**
61
+ * 可用的 M8 UI 组件列表
62
+ * 从 resources/components 目录动态加载,确保与组件文档同步
63
+ */
64
+ const VALID_COMPONENTS = loadValidComponents();
65
+ /**
66
+ * 组件名称映射:常见错误名称 -> 正确名称
67
+ * 确保使用者输入的无效组件名称能被正确映射到 M8 组件库中的有效组件
68
+ */
69
+ const COMPONENT_NAME_MAP = {
70
+ // 输入框类组件 - 统一使用 em-field
71
+ 'em-input': 'em-field',
72
+ 'em-text-field': 'em-field',
73
+ 'em-textfield': 'em-field',
74
+ 'em-textarea': 'em-field',
75
+ 'em-text': 'em-field',
76
+ 'input': 'em-field',
77
+ 'text-field': 'em-field',
78
+ // 选择器类组件 - 统一使用 em-picker
79
+ 'em-select': 'em-picker',
80
+ 'em-dropdown': 'em-picker',
81
+ 'select': 'em-picker',
82
+ // 日期时间类组件 - 统一使用 em-datepicker
83
+ 'em-date': 'em-datepicker',
84
+ 'em-time': 'em-datepicker',
85
+ 'em-datetime': 'em-datepicker',
86
+ 'date-picker': 'em-datepicker',
87
+ // 导航类组件 - 统一使用 em-header
88
+ 'em-nav': 'em-header',
89
+ 'em-navbar': 'em-header',
90
+ 'em-navigation': 'em-header',
91
+ 'navbar': 'em-header',
92
+ // 列表类组件 - 统一使用 em-cell
93
+ 'em-list': 'em-cell',
94
+ 'em-item': 'em-cell',
95
+ 'em-list-item': 'em-cell',
96
+ 'list-item': 'em-cell',
97
+ // 按钮类组件
98
+ 'btn': 'em-button',
99
+ // Toast/提示类 - 这些应该用 API 而非组件,添加警告
100
+ 'em-message': '', // 应使用 ejs.ui.toast
101
+ 'em-alert': '', // 应使用 ejs.ui.alert
102
+ };
103
+ /**
104
+ * 验证并修正组件名称
105
+ * @param componentName 组件名称
106
+ * @returns 修正后的组件名称,如果无效则返回 null;如果应该使用 API 则返回 { useApi: true }
107
+ */
108
+ export function validateComponentName(componentName) {
109
+ // 标准化组件名称
110
+ let normalized = componentName.toLowerCase().trim();
111
+ // 首先检查是否在映射表中(不管有没有 em- 前缀)
112
+ if (COMPONENT_NAME_MAP[normalized] !== undefined) {
113
+ const mapped = COMPONENT_NAME_MAP[normalized];
114
+ // 如果映射到空字符串,表示应该使用 API 而非组件
115
+ if (mapped === '') {
116
+ return null;
117
+ }
118
+ normalized = mapped;
119
+ }
120
+ else {
121
+ // 如果没有 em- 前缀,添加它再检查
122
+ if (!normalized.startsWith('em-')) {
123
+ const withPrefix = `em-${normalized}`;
124
+ if (COMPONENT_NAME_MAP[withPrefix] !== undefined) {
125
+ const mapped = COMPONENT_NAME_MAP[withPrefix];
126
+ if (mapped === '') {
127
+ return null;
128
+ }
129
+ normalized = mapped;
130
+ }
131
+ else {
132
+ normalized = withPrefix;
133
+ }
134
+ }
135
+ }
136
+ // 验证组件是否存在
137
+ if (VALID_COMPONENTS.has(normalized)) {
138
+ return normalized;
139
+ }
140
+ return null;
141
+ }
142
+ /**
143
+ * 过滤并验证组件列表
144
+ * @param components 组件列表
145
+ * @returns 验证后的组件列表和警告信息
146
+ */
147
+ export function filterValidComponents(components) {
148
+ const valid = [];
149
+ const warnings = [];
150
+ // 应该使用 API 而非组件的映射
151
+ const API_SUGGESTIONS = {
152
+ 'em-message': 'ejs.ui.toast({ message: "提示内容" })',
153
+ 'em-alert': 'ejs.ui.alert({ title: "标题", message: "内容" })',
154
+ 'em-toast': 'ejs.ui.toast({ message: "提示内容" })',
155
+ 'em-loading': 'ejs.ui.showWaiting({ message: "加载中..." })',
156
+ 'em-confirm': 'ejs.ui.confirm({ title: "确认", message: "确认操作吗?" })',
157
+ 'message': 'ejs.ui.toast({ message: "提示内容" })',
158
+ 'alert': 'ejs.ui.alert({ title: "标题", message: "内容" })',
159
+ 'toast': 'ejs.ui.toast({ message: "提示内容" })',
160
+ 'loading': 'ejs.ui.showWaiting({ message: "加载中..." })',
161
+ };
162
+ for (const comp of components) {
163
+ const normalized = comp.toLowerCase().trim();
164
+ // 检查是否应该使用 API
165
+ if (API_SUGGESTIONS[normalized]) {
166
+ warnings.push(`"${comp}" 建议使用 API 方式调用:${API_SUGGESTIONS[normalized]}`);
167
+ continue;
168
+ }
169
+ const validated = validateComponentName(comp);
170
+ if (validated) {
171
+ if (!valid.includes(validated)) {
172
+ valid.push(validated);
173
+ }
174
+ // 如果名称被修正了,添加警告
175
+ const original = comp.toLowerCase().startsWith('em-') ? comp : `em-${comp}`;
176
+ if (original.toLowerCase() !== validated) {
177
+ warnings.push(`组件 "${comp}" 已自动修正为 "${validated}"`);
178
+ }
179
+ }
180
+ else {
181
+ warnings.push(`组件 "${comp}" 不存在于 M8 组件库中,已忽略`);
182
+ }
183
+ }
184
+ return { valid, warnings };
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
+ }
271
+ /**
272
+ * 根据需求智能推荐组件
273
+ * @param requirement 需求描述
274
+ * @returns 推荐的组件列表
275
+ */
276
+ export function suggestComponents(requirement) {
277
+ const components = [];
278
+ const req = requirement.toLowerCase();
279
+ const fields = extractFormFields(requirement);
280
+ // 登录/表单相关
281
+ if (req.includes('登录') || req.includes('login')) {
282
+ components.push('em-field', 'em-button');
283
+ }
284
+ if (req.includes('表单') || req.includes('form') || req.includes('提交')) {
285
+ components.push('em-form', 'em-field', 'em-button');
286
+ }
287
+ if (req.includes('输入') || req.includes('input')) {
288
+ components.push('em-field');
289
+ }
290
+ if (req.includes('按钮') || req.includes('button')) {
291
+ components.push('em-button');
292
+ }
293
+ // 列表相关
294
+ if (req.includes('列表') || req.includes('list')) {
295
+ components.push('em-cell', 'em-minirefresh');
296
+ }
297
+ // 选择器相关
298
+ if (req.includes('选择') || req.includes('picker') || req.includes('下拉')) {
299
+ components.push('em-picker');
300
+ }
301
+ if (req.includes('日期') || req.includes('date')) {
302
+ components.push('em-datepicker');
303
+ }
304
+ // 搜索相关
305
+ if (req.includes('搜索') || req.includes('search')) {
306
+ components.push('em-search');
307
+ }
308
+ // 上传相关 - 包含"附件"关键词
309
+ if (req.includes('上传') || req.includes('upload') || req.includes('附件') || req.includes('文件')) {
310
+ components.push('em-uploader');
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
+ }
325
+ // 去重
326
+ return [...new Set(components)];
327
+ }
8
328
  /**
9
329
  * 生成 Vue2 组件模板 (Options API)
330
+ * 严格遵循 M8 规范:样式外部引入、使用 ejs.ui API
10
331
  * @param options 模板选项
11
332
  * @returns Vue2 组件代码
12
333
  */
13
334
  export function generateVue2Template(options) {
14
- const { name, description, components = [], includeStyle = true, author } = options;
335
+ const { name, description, components = [], author, useMock = true, pageTitle = '页面标题' } = options;
15
336
  const headerOptions = {
16
337
  author,
17
338
  description
18
339
  };
19
340
  const header = generateVueFileHeader(headerOptions);
20
341
  const scriptHeader = generateFileHeader(headerOptions);
21
- // 生成组件导入语句
22
- const imports = components.length > 0
23
- ? components.map(c => {
24
- const componentName = c.startsWith('em-') ? c : `em-${c}`;
25
- return `// ${componentName} 组件已全局注册,无需导入`;
26
- }).join('\n')
27
- : '';
342
+ // 验证组件
343
+ const { valid: validComponents, warnings } = filterValidComponents(components);
344
+ // 如果没有指定组件,根据需求智能推荐
345
+ const finalComponents = validComponents.length > 0
346
+ ? validComponents
347
+ : suggestComponents(description);
348
+ // 提取表单字段信息
349
+ const formFields = extractFormFields(description);
350
+ // 生成模板内容
351
+ const templateContent = generateTemplateContent(finalComponents, name, description);
352
+ // 生成 CSS 类名 (使用下划线风格,符合 M8 规范)
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() : '';
28
361
  const template = `${header}
29
362
  <template>
30
- <div class="${name}">
31
- <!-- TODO: 添加组件内容 -->
32
- <div class="${name}__content">
33
- ${components.map(c => `<${c.startsWith('em-') ? c : `em-${c}`}></${c.startsWith('em-') ? c : `em-${c}`}>`).join('\n ')}
34
- </div>
363
+ <div class="${cssClassName}">
364
+ ${templateContent}
35
365
  </div>
36
366
  </template>
37
367
 
38
368
  <script>
39
369
  ${scriptHeader}
40
370
 
41
- ${imports}
42
-
43
371
  export default {
44
- name: '${name}',
45
-
46
- components: {
47
- // M8 UI 组件已全局注册
48
- },
49
-
50
- props: {
51
- // TODO: 定义组件属性
52
- },
372
+ name: '${toPascalCase(name)}',
53
373
 
54
374
  data() {
55
375
  return {
56
- // TODO: 定义组件数据
376
+ // 表单数据
377
+ formData: ${formDataInit},
378
+ // 加载状态
379
+ loading: false
57
380
  };
58
381
  },
59
382
 
60
- computed: {
61
- // TODO: 定义计算属性
62
- },
63
-
64
- watch: {
65
- // TODO: 定义侦听器
66
- },
67
-
68
383
  created() {
69
- // TODO: 组件创建时的逻辑
70
- },
71
-
72
- mounted() {
73
- // TODO: 组件挂载后的逻辑
384
+ this.initPage();
74
385
  },
75
386
 
76
387
  methods: {
77
- // TODO: 定义组件方法
388
+ /**
389
+ * 初始化页面
390
+ */
391
+ initPage() {
392
+ this.loadData();
393
+ },
394
+
395
+ /**
396
+ * 加载数据
397
+ */
398
+ async loadData() {
399
+ try {
400
+ this.loading = true;
401
+ ejs.ui.showWaiting({ message: '加载中...' });
402
+
403
+ const result = await Util.ajax({
404
+ url: Config.serverUrl + '${useMock ? '/rest/mock/' + name : '/api/' + name}',
405
+ type: 'POST',
406
+ data: {
407
+ params: JSON.stringify({})
408
+ }
409
+ });
410
+
411
+ if (result.status?.code === 1) {
412
+ // TODO: 处理数据
413
+ console.log('数据加载成功:', result.data);
414
+ } else {
415
+ ejs.ui.toast({ message: result.status?.text || '加载失败' });
416
+ }
417
+ } catch (error) {
418
+ console.error('数据加载失败:', error);
419
+ ejs.ui.toast({ message: '网络错误,请重试' });
420
+ } finally {
421
+ this.loading = false;
422
+ ejs.ui.closeWaiting();
423
+ }
424
+ },
425
+ ${validateMethod}
426
+ /**
427
+ * 提交表单
428
+ */
429
+ async handleSubmit() {
430
+ // 表单校验
431
+ if (!this.validateForm()) {
432
+ return;
433
+ }
434
+
435
+ try {
436
+ ejs.ui.showWaiting({ message: '提交中...' });
437
+
438
+ const result = await Util.ajax({
439
+ url: Config.serverUrl + '${useMock ? '/rest/mock/' + name + '/submit' : '/api/' + name + '/submit'}',
440
+ type: 'POST',
441
+ data: {
442
+ params: JSON.stringify(this.formData)
443
+ }
444
+ });
445
+
446
+ if (result.status?.code === 1) {
447
+ ejs.ui.toast({ message: '提交成功' });
448
+ // TODO: 处理成功逻辑
449
+ } else {
450
+ ejs.ui.toast({ message: result.status?.text || '提交失败' });
451
+ }
452
+ } catch (error) {
453
+ console.error('提交失败:', error);
454
+ ejs.ui.toast({ message: '网络错误,请重试' });
455
+ } finally {
456
+ ejs.ui.closeWaiting();
457
+ }
458
+ }${uploaderMethod}
78
459
  }
79
460
  };
80
461
  </script>
81
- ${includeStyle ? `
462
+
82
463
  <style lang="scss" scoped>
83
- .${name} {
84
- &__content {
85
- // TODO: 添加样式
86
- }
87
- }
464
+ @import './css/${name}.scss';
88
465
  </style>
89
- ` : ''}`;
466
+ `;
90
467
  return template;
91
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
+ }
92
586
  /**
93
587
  * 生成 Vue3 组件模板 (Composition API with script setup)
588
+ * 严格遵循 M8 规范:样式外部引入、使用 ejs.ui API
94
589
  * @param options 模板选项
95
590
  * @returns Vue3 组件代码
96
591
  */
97
592
  export function generateVue3Template(options) {
98
- const { name, description, components = [], includeStyle = true, author } = options;
593
+ const { name, description, components = [], author, useMock = true, pageTitle = '页面标题' } = options;
99
594
  const headerOptions = {
100
595
  author,
101
596
  description
102
597
  };
103
598
  const header = generateVueFileHeader(headerOptions);
599
+ // 验证组件
600
+ const { valid: validComponents } = filterValidComponents(components);
601
+ // 如果没有指定组件,根据需求智能推荐
602
+ const finalComponents = validComponents.length > 0
603
+ ? validComponents
604
+ : suggestComponents(description);
605
+ // 提取表单字段信息
606
+ const formFields = extractFormFields(description);
607
+ // 生成模板内容
608
+ const templateContent = generateTemplateContent(finalComponents, name, description);
609
+ // 生成 CSS 类名
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() : '';
104
618
  const template = `${header}
105
619
  <template>
106
- <div class="${name}">
107
- <!-- TODO: 添加组件内容 -->
108
- <div class="${name}__content">
109
- ${components.map(c => `<${c.startsWith('em-') ? c : `em-${c}`}></${c.startsWith('em-') ? c : `em-${c}`}>`).join('\n ')}
110
- </div>
620
+ <div class="${cssClassName}">
621
+ ${templateContent}
111
622
  </div>
112
623
  </template>
113
624
 
@@ -116,49 +627,381 @@ export function generateVue3Template(options) {
116
627
  * @描述 ${description}
117
628
  */
118
629
 
119
- import { ref, reactive, computed, onMounted } from 'vue';
120
-
121
- // ==================== Props ====================
122
- const props = defineProps({
123
- // TODO: 定义组件属性
124
- });
125
-
126
- // ==================== Emits ====================
127
- const emit = defineEmits([
128
- // TODO: 定义组件事件
129
- ]);
630
+ import { ref, reactive, onMounted } from 'vue';
130
631
 
131
632
  // ==================== 响应式数据 ====================
132
- const state = reactive({
133
- // TODO: 定义组件状态
134
- });
135
-
136
- // ==================== 计算属性 ====================
137
- // const computedValue = computed(() => {
138
- // // TODO: 计算逻辑
139
- // });
140
-
141
- // ==================== 方法 ====================
142
- // const handleClick = () => {
143
- // // TODO: 处理逻辑
144
- // };
633
+ const formData = reactive(${formDataInitVue3});
634
+ const loading = ref(false);
145
635
 
146
636
  // ==================== 生命周期 ====================
147
637
  onMounted(() => {
148
- // TODO: 组件挂载后的逻辑
638
+ initPage();
149
639
  });
640
+
641
+ // ==================== 方法 ====================
642
+
643
+ /**
644
+ * 初始化页面
645
+ */
646
+ const initPage = () => {
647
+ loadData();
648
+ };
649
+
650
+ /**
651
+ * 加载数据
652
+ */
653
+ const loadData = async () => {
654
+ try {
655
+ loading.value = true;
656
+ ejs.ui.showWaiting({ message: '加载中...' });
657
+
658
+ const result = await Util.ajax({
659
+ url: Config.serverUrl + '${useMock ? '/rest/mock/' + name : '/api/' + name}',
660
+ type: 'POST',
661
+ data: {
662
+ params: JSON.stringify({})
663
+ }
664
+ });
665
+
666
+ if (result.status?.code === 1) {
667
+ // TODO: 处理数据
668
+ console.log('数据加载成功:', result.data);
669
+ } else {
670
+ ejs.ui.toast({ message: result.status?.text || '加载失败' });
671
+ }
672
+ } catch (error) {
673
+ console.error('数据加载失败:', error);
674
+ ejs.ui.toast({ message: '网络错误,请重试' });
675
+ } finally {
676
+ loading.value = false;
677
+ ejs.ui.closeWaiting();
678
+ }
679
+ };
680
+ ${validateMethodVue3}
681
+ /**
682
+ * 提交表单
683
+ */
684
+ const handleSubmit = async () => {
685
+ // 表单校验
686
+ if (!validateForm()) {
687
+ return;
688
+ }
689
+
690
+ try {
691
+ ejs.ui.showWaiting({ message: '提交中...' });
692
+
693
+ const result = await Util.ajax({
694
+ url: Config.serverUrl + '${useMock ? '/rest/mock/' + name + '/submit' : '/api/' + name + '/submit'}',
695
+ type: 'POST',
696
+ data: {
697
+ params: JSON.stringify(formData)
698
+ }
699
+ });
700
+
701
+ if (result.status?.code === 1) {
702
+ ejs.ui.toast({ message: '提交成功' });
703
+ // TODO: 处理成功逻辑
704
+ } else {
705
+ ejs.ui.toast({ message: result.status?.text || '提交失败' });
706
+ }
707
+ } catch (error) {
708
+ console.error('提交失败:', error);
709
+ ejs.ui.toast({ message: '网络错误,请重试' });
710
+ } finally {
711
+ ejs.ui.closeWaiting();
712
+ }
713
+ };
714
+ ${uploaderMethodVue3}
150
715
  </script>
151
- ${includeStyle ? `
716
+
152
717
  <style lang="scss" scoped>
153
- .${name} {
154
- &__content {
155
- // TODO: 添加样式
156
- }
157
- }
718
+ @import './css/${name}.scss';
158
719
  </style>
159
- ` : ''}`;
720
+ `;
160
721
  return template;
161
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
+ }
839
+ /**
840
+ * 根据组件列表生成模板内容
841
+ * @param components 组件列表
842
+ * @param name 页面名称
843
+ * @param description 需求描述
844
+ * @returns 模板内容
845
+ */
846
+ function generateTemplateContent(components, name, description) {
847
+ const lines = [];
848
+ const cssClassName = name.replace(/-/g, '_');
849
+ const req = description.toLowerCase();
850
+ // 提取表单字段信息
851
+ const formFields = extractFormFields(description);
852
+ // 判断页面类型
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;
857
+ const isListPage = req.includes('列表') || req.includes('list');
858
+ const hasUploader = components.includes('em-uploader') || formFields.some(f => f.type === 'uploader');
859
+ if (isLoginPage && !hasCustomFields) {
860
+ // 纯登录页面模板(只有用户名和密码)
861
+ lines.push(` <!-- 登录表单 -->`);
862
+ lines.push(` <div class="${cssClassName}__form">`);
863
+ lines.push(` <em-field`);
864
+ lines.push(` v-model="formData.username"`);
865
+ lines.push(` label="用户名"`);
866
+ lines.push(` placeholder="请输入用户名"`);
867
+ lines.push(` clearable`);
868
+ lines.push(` required`);
869
+ lines.push(` />`);
870
+ lines.push(` <em-field`);
871
+ lines.push(` v-model="formData.password"`);
872
+ lines.push(` type="password"`);
873
+ lines.push(` label="密码"`);
874
+ lines.push(` placeholder="请输入密码"`);
875
+ lines.push(` clearable`);
876
+ lines.push(` required`);
877
+ lines.push(` />`);
878
+ lines.push(` <div class="${cssClassName}__actions">`);
879
+ lines.push(` <em-button type="primary" block @click="handleSubmit">登录</em-button>`);
880
+ lines.push(` </div>`);
881
+ lines.push(` </div>`);
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
+ }
927
+ else if (isFormPage) {
928
+ // 通用表单页面模板(没有明确字段)
929
+ lines.push(` <!-- 表单内容 -->`);
930
+ lines.push(` <em-form ref="formRef">`);
931
+ for (const comp of components) {
932
+ if (comp === 'em-field') {
933
+ lines.push(` <em-field`);
934
+ lines.push(` v-model="formData.field1"`);
935
+ lines.push(` label="字段名"`);
936
+ lines.push(` placeholder="请输入"`);
937
+ lines.push(` required`);
938
+ lines.push(` />`);
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
+ }
952
+ }
953
+ lines.push(` <div class="${cssClassName}__actions">`);
954
+ lines.push(` <em-button type="primary" block @click="handleSubmit">提交</em-button>`);
955
+ lines.push(` </div>`);
956
+ lines.push(` </em-form>`);
957
+ }
958
+ else if (isListPage) {
959
+ // 列表页面模板
960
+ lines.push(` <!-- 列表内容 -->`);
961
+ lines.push(` <em-minirefresh`);
962
+ lines.push(` @refresh="onRefresh"`);
963
+ lines.push(` @loadmore="onLoadMore"`);
964
+ lines.push(` >`);
965
+ lines.push(` <em-cell`);
966
+ lines.push(` v-for="item in list"`);
967
+ lines.push(` :key="item.id"`);
968
+ lines.push(` :title="item.title"`);
969
+ lines.push(` is-link`);
970
+ lines.push(` @click="handleItemClick(item)"`);
971
+ lines.push(` />`);
972
+ lines.push(` </em-minirefresh>`);
973
+ }
974
+ else {
975
+ // 默认页面模板
976
+ lines.push(` <!-- 页面内容 -->`);
977
+ lines.push(` <div class="${cssClassName}__content">`);
978
+ for (const comp of components) {
979
+ const compTag = comp.startsWith('em-') ? comp : `em-${comp}`;
980
+ if (comp === 'em-uploader') {
981
+ lines.push(` <em-uploader v-model="fileList" :after-read="afterRead" />`);
982
+ }
983
+ else {
984
+ lines.push(` <${compTag} />`);
985
+ }
986
+ }
987
+ if (components.length === 0) {
988
+ lines.push(` <!-- TODO: 添加页面内容 -->`);
989
+ }
990
+ lines.push(` </div>`);
991
+ }
992
+ return lines.join('\n');
993
+ }
994
+ /**
995
+ * 转换为 PascalCase
996
+ * @param str 输入字符串
997
+ * @returns PascalCase 字符串
998
+ */
999
+ function toPascalCase(str) {
1000
+ return str
1001
+ .split('-')
1002
+ .map(part => part.charAt(0).toUpperCase() + part.slice(1))
1003
+ .join('');
1004
+ }
162
1005
  /**
163
1006
  * 根据版本生成 Vue 模板
164
1007
  * @param options 模板选项