worsoft-frontend-codegen-local-mcp 0.1.21 → 0.1.23

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 (2) hide show
  1. package/mcp_server.js +102 -62
  2. package/package.json +1 -1
package/mcp_server.js CHANGED
@@ -5,7 +5,7 @@ const fs = require('fs');
5
5
  const path = require('path');
6
6
 
7
7
  const SERVER_NAME = 'worsoft-codegen-local';
8
- const SERVER_VERSION = '0.1.21';
8
+ const SERVER_VERSION = '0.1.23';
9
9
  const PROTOCOL_VERSION = '2024-11-05';
10
10
  const TOOL_NAME = 'worsoft_codegen_local_generate_frontend';
11
11
  const TEMPLATE_LIBRARY_ROOT = path.resolve(__dirname, '..', 'template');
@@ -234,12 +234,33 @@ function toPascalCase(value) {
234
234
  return camel.charAt(0).toUpperCase() + camel.slice(1);
235
235
  }
236
236
 
237
- function normalizeModuleName(moduleName) {
238
- if (!moduleName) {
239
- return 'admin/test';
240
- }
241
- return moduleName.replace(/\\/g, '/').replace(/^\/+|\/+$/g, '');
242
- }
237
+ function normalizeModuleName(moduleName) {
238
+ if (!moduleName) {
239
+ return 'admin/test';
240
+ }
241
+ return moduleName.replace(/\\/g, '/').replace(/^\/+|\/+$/g, '');
242
+ }
243
+
244
+ function normalizeModulePathForFeature(moduleName, functionName, apiPath) {
245
+ const normalized = normalizeModuleName(moduleName);
246
+ const segments = normalized.split('/').filter(Boolean);
247
+ if (segments.length <= 1) {
248
+ return normalized;
249
+ }
250
+
251
+ const duplicateNames = new Set(
252
+ [functionName, apiPath]
253
+ .filter(Boolean)
254
+ .map((item) => String(item).trim())
255
+ .filter(Boolean)
256
+ );
257
+
258
+ while (segments.length > 1 && duplicateNames.has(segments[segments.length - 1])) {
259
+ segments.pop();
260
+ }
261
+
262
+ return segments.join('/');
263
+ }
243
264
 
244
265
  function toConstantCase(value) {
245
266
  return String(value || '')
@@ -917,14 +938,16 @@ function buildModel(safeArgs) {
917
938
  const childDictTypes = children.flatMap((child) => child.visibleFields.map((field) => field.dictType).filter(Boolean));
918
939
  const dictTypes = [...new Set([...visibleFields.map((field) => field.dictType).filter(Boolean), ...childDictTypes])];
919
940
 
941
+ const functionName = toCamelCase(safeArgs.tableName);
942
+ const apiPath = safeArgs.apiPath || functionName;
920
943
  return {
921
944
  featureTitle: safeArgs.featureTitle || safeArgs.tableComment || safeArgs.tableName,
922
945
  tableName: safeArgs.tableName,
923
946
  tableComment: safeArgs.tableComment || safeArgs.featureTitle || safeArgs.tableName,
924
- apiPath: safeArgs.apiPath || toCamelCase(safeArgs.tableName),
947
+ apiPath,
925
948
  className: toPascalCase(safeArgs.tableName),
926
- functionName: toCamelCase(safeArgs.tableName),
927
- moduleName: normalizeModuleName(safeArgs.moduleName),
949
+ functionName,
950
+ moduleName: normalizeModulePathForFeature(safeArgs.moduleName, functionName, apiPath),
928
951
  pk: pkField,
929
952
  fields,
930
953
  visibleFields,
@@ -1226,21 +1249,32 @@ function renderTextareaMaxlengthAttrsV2(field) {
1226
1249
  if (!field.length) return '';
1227
1250
  return ` :maxlength="${field.length}" show-word-limit`;
1228
1251
  }
1229
-
1252
+
1253
+ function renderDisabledAttrV2(field) {
1254
+ return field.readonly ? ' disabled' : '';
1255
+ }
1256
+
1257
+ function renderFieldCommentV2(field, indent = ' ') {
1258
+ const label = stripDictAnnotation(field.comment || field.attrName).replace(/-->/g, '').trim() || field.attrName;
1259
+ return `${indent}<!-- 字段:${label} -->`;
1260
+ }
1261
+
1230
1262
  function renderFormFieldV2(field) {
1231
1263
  const prop = field.attrName;
1232
1264
  const labelExpr = `getMasterFieldLabel('${prop}')`;
1233
1265
  const dictExpr = `getMasterFieldMeta('${prop}')?.dictType`;
1266
+ const disabledAttr = renderDisabledAttrV2(field);
1234
1267
 
1235
1268
  if (field.formType === 'select') {
1236
1269
  return [
1270
+ renderFieldCommentV2(field),
1237
1271
  ` <el-col :span="12" class="mb20">`,
1238
1272
  ` <el-form-item :label="${labelExpr}" prop="${prop}">`,
1239
- ` <el-select v-model="form.${prop}" :placeholder="selectPlaceholder(${labelExpr})" style="width: 100%">`,
1273
+ ` <el-select v-model="form.${prop}" :placeholder="selectPlaceholder(${labelExpr})" style="width: 100%"${disabledAttr}>`,
1240
1274
  ` <el-option v-for="item in getDictOptions(${dictExpr})" :key="item.value" :label="item.label" :value="Number(item.value)" />`,
1241
- ' </el-select>',
1242
- ' </el-form-item>',
1243
- ' </el-col>',
1275
+ ' </el-select>',
1276
+ ' </el-form-item>',
1277
+ ' </el-col>',
1244
1278
  ].join('\n');
1245
1279
  }
1246
1280
 
@@ -1248,46 +1282,50 @@ function renderFormFieldV2(field) {
1248
1282
  const max = field.comment.includes('%') || /\u6BD4\u4F8B/.test(field.comment) ? ' :max="100"' : '';
1249
1283
  const precision = field.sqlType === 'DECIMAL' && field.scale ? ` :precision="${field.scale}" :step="0.01"` : '';
1250
1284
  return [
1285
+ renderFieldCommentV2(field),
1251
1286
  ` <el-col :span="12" class="mb20">`,
1252
1287
  ` <el-form-item :label="${labelExpr}" prop="${prop}">`,
1253
- ` <el-input-number v-model="form.${prop}" :min="0"${max}${precision} :placeholder="inputPlaceholder(${labelExpr})" style="width: 100%" />`,
1288
+ ` <el-input-number v-model="form.${prop}" :min="0"${max}${precision} :placeholder="inputPlaceholder(${labelExpr})" style="width: 100%"${disabledAttr} />`,
1254
1289
  ' </el-form-item>',
1255
- ' </el-col>',
1256
- ].join('\n');
1257
- }
1258
-
1290
+ ' </el-col>',
1291
+ ].join('\n');
1292
+ }
1293
+
1259
1294
  if (field.formType === 'datetime' || field.formType === 'date') {
1260
1295
  const pickerType = field.formType === 'datetime' ? 'datetime' : 'date';
1261
1296
  const formatName = field.formType === 'datetime' ? 'dateTimeStr' : 'dateStr';
1262
1297
  return [
1298
+ renderFieldCommentV2(field),
1263
1299
  ` <el-col :span="12" class="mb20">`,
1264
1300
  ` <el-form-item :label="${labelExpr}" prop="${prop}">`,
1265
- ` <el-date-picker type="${pickerType}" :placeholder="inputPlaceholder(${labelExpr})" v-model="form.${prop}" :value-format="${formatName}" style="width: 100%"></el-date-picker>`,
1301
+ ` <el-date-picker type="${pickerType}" :placeholder="inputPlaceholder(${labelExpr})" v-model="form.${prop}" :value-format="${formatName}" style="width: 100%"${disabledAttr}></el-date-picker>`,
1266
1302
  ' </el-form-item>',
1267
- ' </el-col>',
1268
- ].join('\n');
1269
- }
1270
-
1303
+ ' </el-col>',
1304
+ ].join('\n');
1305
+ }
1306
+
1271
1307
  if (field.formType === 'textarea') {
1272
1308
  const textareaAttrs = renderTextareaMaxlengthAttrsV2(field);
1273
1309
  return [
1310
+ renderFieldCommentV2(field),
1274
1311
  ` <el-col :span="24" class="mb20">`,
1275
1312
  ` <el-form-item :label="${labelExpr}" prop="${prop}">`,
1276
- ` <el-input type="textarea" v-model="form.${prop}" :placeholder="inputPlaceholder(${labelExpr})"${textareaAttrs} />`,
1313
+ ` <el-input type="textarea" v-model="form.${prop}" :placeholder="inputPlaceholder(${labelExpr})"${textareaAttrs}${disabledAttr} />`,
1277
1314
  ' </el-form-item>',
1278
- ' </el-col>',
1279
- ].join('\n');
1280
- }
1281
-
1315
+ ' </el-col>',
1316
+ ].join('\n');
1317
+ }
1318
+
1282
1319
  const maxlengthAttr = renderInputMaxlengthAttr(field);
1283
1320
  return [
1321
+ renderFieldCommentV2(field),
1284
1322
  ` <el-col :span="12" class="mb20">`,
1285
1323
  ` <el-form-item :label="${labelExpr}" prop="${prop}">`,
1286
- ` <el-input v-model="form.${prop}" :placeholder="inputPlaceholder(${labelExpr})"${maxlengthAttr} />`,
1324
+ ` <el-input v-model="form.${prop}" :placeholder="inputPlaceholder(${labelExpr})"${maxlengthAttr}${disabledAttr} />`,
1287
1325
  ' </el-form-item>',
1288
- ' </el-col>',
1289
- ].join('\n');
1290
- }
1326
+ ' </el-col>',
1327
+ ].join('\n');
1328
+ }
1291
1329
 
1292
1330
  function renderTableColumnV2(field, dictRegistryRefs) {
1293
1331
  const label = stripDictAnnotation(field.comment).replace(/'/g, "\\'");
@@ -1305,34 +1343,36 @@ function renderTableColumnV2(field, dictRegistryRefs) {
1305
1343
  return ` { ${parts.join(', ')} },`;
1306
1344
  }
1307
1345
 
1308
- function renderChildTableColumnV2(field, childListName) {
1309
- const rules = field.notNull ? ` :rules="[{ required: true, trigger: 'blur' }]"` : '';
1310
- const labelExpr = `getChildFieldLabel('${childListName}', '${field.attrName}')`;
1311
- const dictExpr = `getChildFieldMeta('${childListName}', '${field.attrName}')?.dictType`;
1312
-
1313
- let control = ` <el-input v-model="row.${field.attrName}" :placeholder="inputPlaceholder(${labelExpr})"${renderTextMaxlengthAttrV2(field)} />`;
1314
- if (field.formType === 'select' && field.dictType) {
1315
- control = [
1316
- ` <el-select v-model="row.${field.attrName}" :placeholder="selectPlaceholder(${labelExpr})" style="width: 100%">`,
1317
- ` <el-option v-for="item in getDictOptions(${dictExpr})" :key="item.value" :label="item.label" :value="Number(item.value)" />`,
1318
- ' </el-select>',
1319
- ].join('\n');
1320
- } else if (field.formType === 'number') {
1321
- const max = field.comment.includes('%') || /\u6BD4\u4F8B/.test(field.comment) ? ' :max="100"' : '';
1322
- const precision = field.sqlType === 'DECIMAL' && field.scale ? ` :precision="${field.scale}" :step="0.01"` : '';
1323
- control = ` <el-input-number v-model="row.${field.attrName}" :min="0"${max}${precision} style="width: 100%" />`;
1324
- } else if (field.formType === 'datetime' || field.formType === 'date') {
1325
- const pickerType = field.formType === 'datetime' ? 'datetime' : 'date';
1326
- const formatName = field.formType === 'datetime' ? 'dateTimeStr' : 'dateStr';
1327
- control = ` <el-date-picker type="${pickerType}" v-model="row.${field.attrName}" :value-format="${formatName}" :placeholder="inputPlaceholder(${labelExpr})" style="width: 100%"></el-date-picker>`;
1328
- } else if (field.formType === 'textarea') {
1329
- control = ` <el-input type="textarea" v-model="row.${field.attrName}" :placeholder="inputPlaceholder(${labelExpr})"${renderTextareaMaxlengthAttrsV2(field)} />`;
1330
- }
1331
-
1332
- return [
1333
- ` <el-table-column :label="${labelExpr}" prop="${field.attrName}">`,
1334
- ' <template #default="{ row, $index }">',
1335
- ` <el-form-item :prop="\`${childListName}.\${$index}.${field.attrName}\`"${rules}>`,
1346
+ function renderChildTableColumnV2(field, childListName) {
1347
+ const rules = field.notNull ? ` :rules="[{ required: true, trigger: 'blur' }]"` : '';
1348
+ const labelExpr = `getChildFieldLabel('${childListName}', '${field.attrName}')`;
1349
+ const dictExpr = `getChildFieldMeta('${childListName}', '${field.attrName}')?.dictType`;
1350
+ const disabledAttr = renderDisabledAttrV2(field);
1351
+
1352
+ let control = ` <el-input v-model="row.${field.attrName}" :placeholder="inputPlaceholder(${labelExpr})"${renderTextMaxlengthAttrV2(field)}${disabledAttr} />`;
1353
+ if (field.formType === 'select' && field.dictType) {
1354
+ control = [
1355
+ ` <el-select v-model="row.${field.attrName}" :placeholder="selectPlaceholder(${labelExpr})" style="width: 100%"${disabledAttr}>`,
1356
+ ` <el-option v-for="item in getDictOptions(${dictExpr})" :key="item.value" :label="item.label" :value="Number(item.value)" />`,
1357
+ ' </el-select>',
1358
+ ].join('\n');
1359
+ } else if (field.formType === 'number') {
1360
+ const max = field.comment.includes('%') || /\u6BD4\u4F8B/.test(field.comment) ? ' :max="100"' : '';
1361
+ const precision = field.sqlType === 'DECIMAL' && field.scale ? ` :precision="${field.scale}" :step="0.01"` : '';
1362
+ control = ` <el-input-number v-model="row.${field.attrName}" :min="0"${max}${precision} style="width: 100%"${disabledAttr} />`;
1363
+ } else if (field.formType === 'datetime' || field.formType === 'date') {
1364
+ const pickerType = field.formType === 'datetime' ? 'datetime' : 'date';
1365
+ const formatName = field.formType === 'datetime' ? 'dateTimeStr' : 'dateStr';
1366
+ control = ` <el-date-picker type="${pickerType}" v-model="row.${field.attrName}" :value-format="${formatName}" :placeholder="inputPlaceholder(${labelExpr})" style="width: 100%"${disabledAttr}></el-date-picker>`;
1367
+ } else if (field.formType === 'textarea') {
1368
+ control = ` <el-input type="textarea" v-model="row.${field.attrName}" :placeholder="inputPlaceholder(${labelExpr})"${renderTextareaMaxlengthAttrsV2(field)}${disabledAttr} />`;
1369
+ }
1370
+
1371
+ return [
1372
+ renderFieldCommentV2(field, ' '),
1373
+ ` <el-table-column :label="${labelExpr}" prop="${field.attrName}">`,
1374
+ ' <template #default="{ row, $index }">',
1375
+ ` <el-form-item :prop="\`${childListName}.\${$index}.${field.attrName}\`"${rules}>`,
1336
1376
  control,
1337
1377
  ' </el-form-item>',
1338
1378
  ' </template>',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "worsoft-frontend-codegen-local-mcp",
3
- "version": "0.1.21",
3
+ "version": "0.1.23",
4
4
  "description": "Worsoft frontend local-template code generation MCP server.",
5
5
  "license": "UNLICENSED",
6
6
  "author": "worsoft <sw@worsoft.vip>",