worsoft-frontend-codegen-local-mcp 0.1.60 → 0.1.62

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.
@@ -1,3 +1,4 @@
1
+ <!-- 功能名称:{{FEATURE_TITLE}} -->
1
2
  <template>
2
3
  <div class="layout-padding">
3
4
  <div class="layout-padding-auto layout-padding-view flex h-full flex-col">
@@ -1,3 +1,4 @@
1
+ <!-- 功能名称:{{FEATURE_TITLE}} -->
1
2
  <template>
2
3
  <div class="layout-padding">
3
4
  <div class="layout-padding-auto layout-padding-view flex h-full flex-col">
@@ -1,3 +1,4 @@
1
+ <!-- 功能名称:{{FEATURE_TITLE}} -->
1
2
  <template>
2
3
  <div class="layout-padding">
3
4
  <div class="layout-padding-auto layout-padding-view flex h-full flex-col">
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.60';
8
+ const SERVER_VERSION = '0.1.62';
9
9
  const PROTOCOL_VERSION = '2024-11-05';
10
10
  const TOOL_NAME = 'worsoft_codegen_local_generate_frontend';
11
11
  const STYLE_CATALOG_PATH = path.join(__dirname, 'assets', 'style-catalog.json');
@@ -178,6 +178,7 @@ const TOOL_SCHEMA = {
178
178
  show: { type: ['boolean', 'string'], description: 'Whether the field is shown on the page.' },
179
179
  listShow: { type: ['boolean', 'string'], description: 'Whether the field is shown on the list page.' },
180
180
  formShow: { type: ['boolean', 'string'], description: 'Whether the field is shown on the form page.' },
181
+ formOrder: { type: ['number', 'string'], description: 'Generation-only form field order from PRD detail/form table. This is not emitted to options.ts.' },
181
182
  smart: { type: ['boolean', 'string'], description: 'Whether the field participates in smart keyword search. This must come from PRD, not inferred from type.' },
182
183
  queryType: { type: ['string', 'number'], description: 'Structured query type for list filtering, for example 20 for date ranges or 30 for dictionary filters.' },
183
184
  dictType: { type: 'string', description: 'Dictionary type code from structured metadata.' },
@@ -220,6 +221,7 @@ const TOOL_SCHEMA = {
220
221
  show: { type: ['boolean', 'string'], description: 'Whether the field is shown on the page.' },
221
222
  listShow: { type: ['boolean', 'string'], description: 'Whether the field is shown on the list page.' },
222
223
  formShow: { type: ['boolean', 'string'], description: 'Whether the field is shown on the form page.' },
224
+ formOrder: { type: ['number', 'string'], description: 'Generation-only form field order from PRD detail/form table. This is not emitted to options.ts.' },
223
225
  smart: { type: ['boolean', 'string'], description: 'Whether the field participates in smart keyword search. This must come from PRD, not inferred from type.' },
224
226
  queryType: { type: ['string', 'number'], description: 'Structured query type for list filtering, for example 20 for date ranges or 30 for dictionary filters.' },
225
227
  dictType: { type: 'string', description: 'Dictionary type code from structured metadata.' },
@@ -278,6 +280,7 @@ const TOOL_SCHEMA = {
278
280
  show: { type: ['boolean', 'string'], description: 'Whether the field is shown on the page.' },
279
281
  listShow: { type: ['boolean', 'string'], description: 'Whether the field is shown on the list page.' },
280
282
  formShow: { type: ['boolean', 'string'], description: 'Whether the field is shown on the form page.' },
283
+ formOrder: { type: ['number', 'string'], description: 'Generation-only form field order from PRD detail/form table. This is not emitted to options.ts.' },
281
284
  smart: { type: ['boolean', 'string'], description: 'Whether the field participates in smart keyword search. This must come from PRD, not inferred from type.' },
282
285
  queryType: { type: ['string', 'number'], description: 'Structured query type for list filtering, for example 20 for date ranges or 30 for dictionary filters.' },
283
286
  dictType: { type: 'string', description: 'Dictionary type code from structured metadata.' },
@@ -1139,16 +1142,50 @@ function normalizeStructuredSqlType(value) {
1139
1142
  if (['LOCALDATETIME', 'DATETIME', 'TIMESTAMP'].includes(upper) || ['\u65e5\u671f\u65f6\u95f4', '\u65f6\u95f4\u6233'].includes(normalized)) return 'DATETIME';
1140
1143
  return upper;
1141
1144
  }
1142
- function normalizeStructuredLengthAndScale(lengthValue, scaleValue) {
1143
- const parsed = splitLength(lengthValue);
1144
- const normalizedScale = scaleValue === undefined || scaleValue === null || scaleValue === '' ? parsed.scale : String(scaleValue).trim();
1145
- return {
1146
- length: parsed.length,
1147
- scale: normalizedScale || '',
1148
- };
1149
- }
1150
-
1151
- function normalizeStructuredField(inputField, index, contextLabel) {
1145
+ function normalizeStructuredLengthAndScale(lengthValue, scaleValue) {
1146
+ const parsed = splitLength(lengthValue);
1147
+ const normalizedScale = scaleValue === undefined || scaleValue === null || scaleValue === '' ? parsed.scale : String(scaleValue).trim();
1148
+ return {
1149
+ length: parsed.length,
1150
+ scale: normalizedScale || '',
1151
+ };
1152
+ }
1153
+
1154
+ function parseOptionalOrder(value, label) {
1155
+ if (value === undefined || value === null || value === '') return undefined;
1156
+ const order = Number.parseInt(String(value), 10);
1157
+ if (Number.isNaN(order) || order < 0) {
1158
+ throw new Error(`${label} must be a non-negative integer when provided`);
1159
+ }
1160
+ return order;
1161
+ }
1162
+
1163
+ function sortFieldsForForm(fields) {
1164
+ const seenOrders = new Map();
1165
+ fields.forEach((field) => {
1166
+ if (field.formOrder === undefined) return;
1167
+ const existing = seenOrders.get(field.formOrder);
1168
+ if (existing) {
1169
+ throw new Error(`Duplicate formOrder ${field.formOrder} on fields ${existing} and ${field.fieldName}`);
1170
+ }
1171
+ seenOrders.set(field.formOrder, field.fieldName);
1172
+ });
1173
+ return fields
1174
+ .map((field, index) => ({ field, index }))
1175
+ .sort((left, right) => {
1176
+ const leftOrder = left.field.formOrder;
1177
+ const rightOrder = right.field.formOrder;
1178
+ if (leftOrder !== undefined && rightOrder !== undefined && leftOrder !== rightOrder) {
1179
+ return leftOrder - rightOrder;
1180
+ }
1181
+ if (leftOrder !== undefined && rightOrder === undefined) return -1;
1182
+ if (leftOrder === undefined && rightOrder !== undefined) return 1;
1183
+ return left.index - right.index;
1184
+ })
1185
+ .map((item) => item.field);
1186
+ }
1187
+
1188
+ function normalizeStructuredField(inputField, index, contextLabel) {
1152
1189
  if (!inputField || typeof inputField !== 'object') {
1153
1190
  throw new Error(contextLabel + '[' + index + '] must be an object');
1154
1191
  }
@@ -1177,6 +1214,7 @@ function normalizeStructuredField(inputField, index, contextLabel) {
1177
1214
  const show = parseBooleanLike(inputField.show, true);
1178
1215
  const listShow = parseBooleanLike(inputField.listShow, show);
1179
1216
  const formShow = parseBooleanLike(inputField.formShow, show);
1217
+ const formOrder = parseOptionalOrder(inputField.formOrder, contextLabel + '[' + index + '].formOrder');
1180
1218
 
1181
1219
  return {
1182
1220
  fieldName,
@@ -1195,6 +1233,7 @@ function normalizeStructuredField(inputField, index, contextLabel) {
1195
1233
  show,
1196
1234
  listShow,
1197
1235
  formShow,
1236
+ formOrder,
1198
1237
  smart: parseBooleanLike(inputField.smart, false),
1199
1238
  queryType: Number.isNaN(explicitQueryType)
1200
1239
  ? undefined
@@ -1519,7 +1558,7 @@ function buildMultiLevelModule(moduleInput, levelIndex) {
1519
1558
  ? ensureFieldExists(fields, moduleInput.primaryKey, moduleInput.tableName, 'Primary key')
1520
1559
  : findPrimaryKeyFromStructuredFields(fields);
1521
1560
  const optionFields = fields.filter((field) => field.fieldName !== normalizedPk.fieldName && !field.isAudit);
1522
- const visibleFields = optionFields.filter((field) => field.formShow !== false);
1561
+ const visibleFields = sortFieldsForForm(optionFields.filter((field) => field.formShow !== false));
1523
1562
  const listFields = optionFields.filter((field) => field.listShow !== false);
1524
1563
  const statusField = detectStatusField(fields, moduleInput.statusField);
1525
1564
  const apiPath = moduleInput.apiPath || toCamelCase(moduleInput.tableName);
@@ -1642,7 +1681,7 @@ function buildChildModels(safeArgs, mainFields, mainPk) {
1642
1681
  const childOptionFields = childFields.filter(
1643
1682
  (field) => field.fieldName !== childPk.fieldName && !field.isAudit && field.fieldName !== relation.childField
1644
1683
  );
1645
- const childVisibleFields = childOptionFields.filter((field) => field.formShow !== false);
1684
+ const childVisibleFields = sortFieldsForForm(childOptionFields.filter((field) => field.formShow !== false));
1646
1685
  const payloadField = relation.payloadField;
1647
1686
  const listName = payloadField;
1648
1687
 
@@ -1675,7 +1714,7 @@ function buildModel(safeArgs) {
1675
1714
  fields: safeArgs.fields,
1676
1715
  });
1677
1716
  const optionFields = fields.filter((field) => field.fieldName !== pkField.fieldName && !field.isAudit);
1678
- const visibleFields = optionFields.filter((field) => field.formShow !== false);
1717
+ const visibleFields = sortFieldsForForm(optionFields.filter((field) => field.formShow !== false));
1679
1718
  const listFields = optionFields.filter((field) => field.listShow !== false);
1680
1719
  const gridFields = listFields.slice(0, 8);
1681
1720
  const statusField = detectStatusField(fields, safeArgs.statusField);
@@ -2416,7 +2455,7 @@ function renderMultiLevelFormVue(model, moduleModel) {
2416
2455
  ` tenantId: Session.getTenant(),`,
2417
2456
  ].join('\n');
2418
2457
  const rules = renderFormRulesV2(moduleModel.visibleFields);
2419
- return `<template>
2458
+ return `<template>
2420
2459
  <el-dialog v-model="visible" :title="form.${moduleModel.pk.attrName} ? t('common.editBtn') : t('common.addBtn')" :close-on-click-modal="false" draggable>
2421
2460
  <el-form ref="dataFormRef" :model="form" :rules="dataRules" label-width="100px" v-loading="loading">
2422
2461
  <el-row :gutter="24">
@@ -2677,10 +2716,10 @@ function renderMultiLevelIndexVue(model) {
2677
2716
  )
2678
2717
  .join('\n');
2679
2718
 
2680
- return `<template>
2681
- <div class="layout-padding">
2682
- <!-- \u529f\u80fd\u540d\u79f0\uFF1A${model.featureTitle.replace(/--/g, '')} -->
2683
- <div class="multi-level-dict-layout">
2719
+ return `<!-- 功能名称:${sanitizeHtmlComment(model.featureTitle)} -->
2720
+ <template>
2721
+ <div class="layout-padding">
2722
+ <div class="multi-level-dict-layout">
2684
2723
  <div class="multi-level-left">
2685
2724
  ${renderMultiLevelSchemaListSlot('level1Config', 'activeLevel1Key', 'activeLevel1Module')}
2686
2725
  </div>
@@ -3068,6 +3107,10 @@ function sanitizeComment(value) {
3068
3107
  return String(value || '').replace(/\*\//g, '* /').replace(/\r?\n/g, ' ').trim();
3069
3108
  }
3070
3109
 
3110
+ function sanitizeHtmlComment(value) {
3111
+ return String(value || '').replace(/--/g, '').replace(/\r?\n/g, ' ').trim();
3112
+ }
3113
+
3071
3114
  function renderExtraApiFunctions(model) {
3072
3115
  if (!Array.isArray(model.extraApis) || !model.extraApis.length) return '';
3073
3116
  return model.extraApis
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "worsoft-frontend-codegen-local-mcp",
3
- "version": "0.1.60",
3
+ "version": "0.1.62",
4
4
  "description": "Worsoft frontend local-template code generation MCP server.",
5
5
  "license": "UNLICENSED",
6
6
  "author": "worsoft <sw@worsoft.vip>",