auto-gen-crud 0.1.0 → 0.1.1

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.
@@ -364,9 +364,9 @@ function Oe(e) {
364
364
  { path: "api/index.ts", content: Re(e) },
365
365
  { path: "api/types.ts", content: je(e) },
366
366
  { path: "config/enums.ts", content: qe(e) },
367
- { path: "config/searchForm.ts", content: Ie(e) },
368
- { path: "config/table.ts", content: Le(e) },
369
- { path: "config/modalForm.ts", content: De(e) }
367
+ { path: "config/searchForm.tsx", content: Ie(e) },
368
+ { path: "config/table.tsx", content: Le(e) },
369
+ { path: "config/modalForm.tsx", content: De(e) }
370
370
  ];
371
371
  }
372
372
  function Ee(e) {
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../src/generator/schema.ts","../../src/generator/string-utils.ts","../../src/generator/fields.ts","../../src/generator/openapi.ts","../../src/generator/operations.ts","../../src/generator/templates.ts","../../src/generator/writer.ts","../../src/generator/index.ts"],"sourcesContent":["import type { NormalizedOperation, OpenApiDocument, OpenApiParameter, OpenApiSchema, ResponsePathInfo } from \"./types\";\n\n/**\n * 解析 schema 引用。\n *\n * OpenAPI 里字段可能直接写在当前节点,也可能通过 $ref/allOf 组合复用。\n * 生成器只需要拿到最终 properties/required,所以这里把常见引用形式先摊平。\n */\nexport function resolveSchema(document: OpenApiDocument, schema?: OpenApiSchema): OpenApiSchema | undefined {\n if (!schema) return undefined;\n\n if (schema.$ref) {\n const name = schema.$ref.split(\"/\").pop();\n return name ? resolveSchema(document, document.components?.schemas?.[name]) : schema;\n }\n\n if (schema.allOf?.length) {\n return mergeObjectSchemas(document, schema.allOf);\n }\n\n return schema;\n}\n\n/** 只取 query 参数;header/path/cookie 不参与表单字段生成。 */\nexport function getQueryParameters(operation: NormalizedOperation): OpenApiParameter[] {\n return operation.parameters.filter(parameter => parameter.in === \"query\");\n}\n\n/** 读取 JSON requestBody schema,兼容 content-type 不是 application/json 但仍有第一个 content 的情况。 */\nexport function getRequestBodySchema(document: OpenApiDocument, operation?: NormalizedOperation): OpenApiSchema | undefined {\n if (!operation) return undefined;\n const content = operation.operation.requestBody?.content || {};\n const schema = content[\"application/json\"]?.schema || Object.values(content)[0]?.schema;\n return resolveSchema(document, schema);\n}\n\n/** 读取成功响应 schema,优先级:200 -> 201 -> default -> 第一个响应。 */\nexport function getSuccessResponseSchema(document: OpenApiDocument, operation: NormalizedOperation): OpenApiSchema | undefined {\n const responses = operation.operation.responses || {};\n const response = responses[\"200\"] || responses[\"201\"] || responses.default || Object.values(responses)[0];\n const content = response?.content || {};\n const schema = content[\"application/json\"]?.schema || Object.values(content)[0]?.schema;\n return resolveSchema(document, schema);\n}\n\n/**\n * 推断列表行数据结构。\n *\n * 当前 Apifox 文档的列表结构是 result.records.items,\n * 但这里不写死路径,而是在响应 schema 中递归找到第一个数组字段并取 items。\n */\nexport function inferRecordSchema(document: OpenApiDocument, operation: NormalizedOperation): OpenApiSchema {\n const responseSchema = getSuccessResponseSchema(document, operation);\n const recordSchema = findArrayItemSchema(document, responseSchema);\n return resolveSchema(document, recordSchema) || { type: \"object\", properties: {} };\n}\n\n/**\n * 推断列表数据和总数字段路径。\n *\n * 默认递归寻找数组字段作为列表路径,再在同级对象中寻找 total/totalCount/count;\n * 如果文档没有描述清楚,则退回当前项目常见的 result.records/result.total。\n */\nexport function inferResponsePath(document: OpenApiDocument, operation: NormalizedOperation): ResponsePathInfo {\n const responseSchema = getSuccessResponseSchema(document, operation);\n const found = findArrayPath(document, responseSchema);\n\n if (found) {\n return {\n listPath: found.path,\n totalPath: findTotalPath(responseSchema, found.parentPath) || \"total\",\n };\n }\n\n return { listPath: \"result.records\", totalPath: \"result.total\" };\n}\n\n/** 安全读取 schema.properties,调用方不用反复判空。 */\nexport function getSchemaProperties(document: OpenApiDocument, schema?: OpenApiSchema): Record<string, OpenApiSchema> {\n const resolved = resolveSchema(document, schema);\n return resolved?.properties || {};\n}\n\n/** 安全读取 required 数组,缺失时按空数组处理。 */\nexport function getSchemaRequired(schema?: OpenApiSchema): string[] {\n return Array.isArray(schema?.required) ? schema.required : [];\n}\n\n/** 合并 allOf 对象 schema,保留所有 properties 和 required。 */\nfunction mergeObjectSchemas(document: OpenApiDocument, schemas: OpenApiSchema[]): OpenApiSchema {\n return schemas.reduce<OpenApiSchema>(\n (result, item) => {\n const resolved = resolveSchema(document, item);\n return {\n ...result,\n ...resolved,\n properties: {\n ...(result.properties || {}),\n ...(resolved?.properties || {}),\n },\n required: [...new Set([...(result.required || []), ...(resolved?.required || [])])],\n };\n },\n { type: \"object\", properties: {} },\n );\n}\n\n/** 在响应 schema 中递归寻找第一个数组字段的 item schema。 */\nfunction findArrayItemSchema(document: OpenApiDocument, schema?: OpenApiSchema): OpenApiSchema | undefined {\n const resolved = resolveSchema(document, schema);\n if (!resolved) return undefined;\n\n if (isArraySchema(resolved)) return resolveSchema(document, resolved.items);\n\n const properties = resolved.properties || {};\n for (const key of getOrderedKeys(resolved, properties)) {\n const child = resolveSchema(document, properties[key]);\n const found = findArrayItemSchema(document, child);\n if (found) return found;\n }\n\n return undefined;\n}\n\n/** 在响应 schema 中递归寻找第一个数组字段路径,例如 result.records。 */\nfunction findArrayPath(document: OpenApiDocument, schema?: OpenApiSchema, prefix = \"\"): { path: string; parentPath: string } | undefined {\n const resolved = resolveSchema(document, schema);\n if (!resolved) return undefined;\n\n if (isArraySchema(resolved)) {\n return { path: prefix, parentPath: prefix.split(\".\").slice(0, -1).join(\".\") };\n }\n\n const properties = resolved.properties || {};\n for (const key of getOrderedKeys(resolved, properties)) {\n const childPath = prefix ? `${prefix}.${key}` : key;\n const child = resolveSchema(document, properties[key]);\n const found = findArrayPath(document, child, childPath);\n if (found) return found;\n }\n\n return undefined;\n}\n\n/** 在列表字段同级优先寻找 total,其次在根对象寻找 total。 */\nfunction findTotalPath(schema: OpenApiSchema | undefined, parentPath: string): string | undefined {\n if (!schema) return undefined;\n\n const parentSchema = getSchemaByPath(schema, parentPath);\n const properties = parentSchema?.properties || {};\n const totalKey = [\"total\", \"totalCount\", \"count\"].find(key => key in properties);\n if (totalKey) return parentPath ? `${parentPath}.${totalKey}` : totalKey;\n\n const rootProperties = schema.properties || {};\n const rootTotalKey = [\"total\", \"totalCount\", \"count\"].find(key => key in rootProperties);\n return rootTotalKey;\n}\n\n/** 按点路径在 schema 树里取节点,用于根据 result.records 找到 result 对象。 */\nfunction getSchemaByPath(schema: OpenApiSchema, path: string): OpenApiSchema | undefined {\n if (!path) return schema;\n\n return path.split(\".\").reduce<OpenApiSchema | undefined>((current, key) => {\n if (!current) return undefined;\n return current.properties?.[key];\n }, schema);\n}\n\nfunction isArraySchema(schema: OpenApiSchema): boolean {\n return schema.type === \"array\" || (Array.isArray(schema.type) && schema.type.includes(\"array\"));\n}\n\n/** Apifox 会提供 x-apifox-orders,这里用它保持生成列顺序和接口文档一致。 */\nfunction getOrderedKeys(schema: OpenApiSchema, properties: Record<string, OpenApiSchema>): string[] {\n const ordered = schema[\"x-apifox-orders\"] || [];\n return [...ordered.filter(key => key in properties), ...Object.keys(properties).filter(key => !ordered.includes(key))];\n}\n","const COMMON_PATH_WORDS = new Set([\n \"web\",\n \"api\",\n \"admin\",\n \"manage\",\n \"manager\",\n \"query\",\n \"list\",\n \"page\",\n \"search\",\n \"add\",\n \"create\",\n \"save\",\n \"edit\",\n \"update\",\n \"modify\",\n \"delete\",\n \"remove\",\n]);\n\nexport function upperFirst(value: string): string {\n if (!value) return value;\n return `${value[0].toUpperCase()}${value.slice(1)}`;\n}\n\nexport function lowerFirst(value: string): string {\n if (!value) return value;\n return `${value[0].toLowerCase()}${value.slice(1)}`;\n}\n\nexport function toPascalCase(value: string): string {\n const words = value\n .replace(/\\.json$/i, \"\")\n .replace(/([a-z0-9])([A-Z])/g, \"$1 $2\")\n .split(/[^a-zA-Z0-9\\u4e00-\\u9fa5]+/)\n .filter(Boolean);\n\n const asciiWords = words.filter(word => /^[a-zA-Z0-9]+$/.test(word));\n if (asciiWords.length === 0) return \"Generated\";\n\n return asciiWords.map(word => upperFirst(word.toLowerCase())).join(\"\");\n}\n\nexport function toCamelCase(value: string): string {\n return lowerFirst(toPascalCase(value));\n}\n\nexport function toConstantName(value: string): string {\n return value\n .replace(/([a-z0-9])([A-Z])/g, \"$1_$2\")\n .replace(/[^a-zA-Z0-9]+/g, \"_\")\n .replace(/^_+|_+$/g, \"\")\n .toUpperCase();\n}\n\nexport function getLastTagSegment(tag: string): string {\n return tag.split(/[\\\\\\/]/).filter(Boolean).pop() || tag;\n}\n\nexport function stripKnownActionWords(value: string): string {\n let result = value;\n let previous = \"\";\n\n while (result && result !== previous) {\n previous = result;\n result = result\n // 兼容 pageQueryDictType、queryDictTypeList 这类多动作词驼峰名称,避免实体名混入 Query/Page。\n .replace(/^(query|get|list|page|search|create|add|save|edit|update|modify|delete|remove)(?=[A-Z_/\\-.]|$)/i, \"\")\n .replace(/(list|page|query|search|create|add|save|edit|update|modify|delete|remove)$/i, \"\");\n }\n\n return result;\n}\n\nexport function inferEntityNameFromPath(apiPath: string): string {\n const segments = apiPath\n .split(\"/\")\n .map(segment => segment.replace(/\\.json$/i, \"\"))\n .flatMap(segment => segment.split(/[^a-zA-Z0-9]+/))\n .filter(Boolean)\n .filter(segment => !COMMON_PATH_WORDS.has(segment.toLowerCase()));\n\n const meaningful = segments.length > 0 ? segments[segments.length - 1] : \"\";\n return toPascalCase(stripKnownActionWords(meaningful) || meaningful || \"Generated\");\n}\n\nexport function normalizeLabel(value: string | undefined, fallback: string): string {\n const raw = (value || fallback).trim();\n return raw.replace(/[((].*?[))]/g, \"\").trim() || fallback;\n}\n\nexport function quoteString(value: string): string {\n return JSON.stringify(value);\n}\n\nexport function indent(content: string, size = 2): string {\n const prefix = \" \".repeat(size);\n return content\n .split(\"\\n\")\n .map(line => (line ? `${prefix}${line}` : line))\n .join(\"\\n\");\n}\n","import type { GeneratedEnum, GeneratedField, NormalizedOperation, OpenApiDocument, OpenApiParameter, OpenApiSchema } from \"./types\";\nimport { getQueryParameters, getRequestBodySchema, getSchemaProperties, getSchemaRequired, inferRecordSchema, resolveSchema } from \"./schema\";\nimport { normalizeLabel, toCamelCase } from \"./string-utils\";\n\nconst PAGE_FIELD_NAMES = new Set([\"currentPage\", \"page\", \"pageNum\", \"pageNo\"]);\nconst PAGE_SIZE_FIELD_NAMES = new Set([\"pageSize\", \"size\", \"limit\"]);\nconst INTERNAL_FIELD_NAMES = new Set([\"traceLogId\", \"operator\", \"createdBy\"]);\nconst ID_FIELD_NAMES = new Set([\"id\", \"ids\"]);\n\n/**\n * 生成搜索表单字段。\n *\n * 搜索字段来自列表接口的 query 参数;分页字段和公共业务字段会被过滤掉,\n * 因为分页由 table.pagination 管,traceLogId/operator 这类公共参数应由请求封装统一补齐。\n */\nexport function buildSearchFields(document: OpenApiDocument, operation: NormalizedOperation, enums: Map<string, GeneratedEnum>): GeneratedField[] {\n return getQueryParameters(operation)\n .filter(parameter => !isPaginationField(parameter.name))\n .filter(parameter => !INTERNAL_FIELD_NAMES.has(parameter.name))\n .map(parameter => fieldFromParameter(document, parameter, \"search\", enums));\n}\n\n/**\n * 生成弹窗表单字段。\n *\n * 新增和编辑接口都可能提供字段,这里合并两边字段,并保留 required 信息;\n * id 默认不进入表单,因为新增时通常没有 id,编辑时 id 会随 row 数据一起提交。\n */\nexport function buildModalFields(\n document: OpenApiDocument,\n operations: Array<NormalizedOperation | undefined>,\n enums: Map<string, GeneratedEnum>,\n): GeneratedField[] {\n const fieldMap = new Map<string, GeneratedField>();\n\n operations.filter(Boolean).forEach(operation => {\n const typedOperation = operation as NormalizedOperation;\n getQueryParameters(typedOperation)\n .filter(parameter => !isPaginationField(parameter.name))\n .filter(parameter => !INTERNAL_FIELD_NAMES.has(parameter.name))\n .filter(parameter => !ID_FIELD_NAMES.has(parameter.name))\n .forEach(parameter => {\n const current = fieldFromParameter(document, parameter, \"modal\", enums);\n const existed = fieldMap.get(current.name);\n fieldMap.set(current.name, existed ? { ...existed, required: existed.required || current.required } : current);\n });\n\n const bodySchema = getRequestBodySchema(document, typedOperation);\n appendSchemaFields(document, bodySchema, getSchemaRequired(bodySchema), \"modal\", enums, fieldMap, true);\n });\n\n return [...fieldMap.values()];\n}\n\n/** 表格列字段来自列表接口响应里的 records/items schema。 */\nexport function buildTableFields(document: OpenApiDocument, operation: NormalizedOperation, enums: Map<string, GeneratedEnum>): GeneratedField[] {\n const recordSchema = inferRecordSchema(document, operation);\n const fieldMap = new Map<string, GeneratedField>();\n appendSchemaFields(document, recordSchema, getSchemaRequired(recordSchema), \"table\", enums, fieldMap, false);\n return [...fieldMap.values()];\n}\n\n/**\n * 收集所有接口里出现过的枚举。\n *\n * Apifox 会把枚举中文名放在 x-apifox-enum 中,生成器优先使用它;\n * 如果没有 x-apifox-enum,则退回 OpenAPI 标准 enum 值。\n */\nexport function collectEnumsFromOperations(document: OpenApiDocument, operations: NormalizedOperation[]): Map<string, GeneratedEnum> {\n const enums = new Map<string, GeneratedEnum>();\n\n operations.forEach(operation => {\n getQueryParameters(operation).forEach(parameter => {\n const schema = resolveSchema(document, parameter.schema);\n collectEnum(parameter.name, schema, enums);\n });\n\n const bodySchema = getRequestBodySchema(document, operation);\n Object.entries(getSchemaProperties(document, bodySchema)).forEach(([name, schema]) => collectEnum(name, schema, enums));\n });\n\n return enums;\n}\n\nexport function isPaginationField(name: string): boolean {\n return PAGE_FIELD_NAMES.has(name) || PAGE_SIZE_FIELD_NAMES.has(name);\n}\n\nexport function getPageParamName(operation: NormalizedOperation): string {\n return getQueryParameters(operation).find(parameter => PAGE_FIELD_NAMES.has(parameter.name))?.name || \"currentPage\";\n}\n\nexport function getPageSizeParamName(operation: NormalizedOperation): string {\n return getQueryParameters(operation).find(parameter => PAGE_SIZE_FIELD_NAMES.has(parameter.name))?.name || \"pageSize\";\n}\n\nfunction appendSchemaFields(\n document: OpenApiDocument,\n schema: OpenApiSchema | undefined,\n requiredFields: string[],\n source: GeneratedField[\"source\"],\n enums: Map<string, GeneratedEnum>,\n fieldMap: Map<string, GeneratedField>,\n skipId: boolean,\n): void {\n Object.entries(getSchemaProperties(document, schema)).forEach(([name, property]) => {\n if (skipId && ID_FIELD_NAMES.has(name)) return;\n const resolved = resolveSchema(document, property);\n collectEnum(name, resolved, enums);\n fieldMap.set(name, fieldFromSchema(name, resolved, requiredFields.includes(name), source, enums));\n });\n}\n\n/** 将 OpenAPI parameter 转成表单/表格都能使用的中立字段模型。 */\nfunction fieldFromParameter(\n document: OpenApiDocument,\n parameter: OpenApiParameter,\n source: GeneratedField[\"source\"],\n enums: Map<string, GeneratedEnum>,\n): GeneratedField {\n const schema = resolveSchema(document, parameter.schema);\n collectEnum(parameter.name, schema, enums);\n\n return {\n name: parameter.name,\n label: normalizeLabel(parameter.description || schema?.title || schema?.description, parameter.name),\n tsType: schemaToTsType(schema),\n required: Boolean(parameter.required),\n // 同名字段可能只有新增接口声明了枚举,列表接口没声明;只要统一枚举表里有,就按 select 生成。\n component: inferComponent(parameter.name, schema, parameter.description, enums.has(parameter.name)),\n description: parameter.description || schema?.description,\n enumName: enums.get(parameter.name)?.textMapName,\n source,\n };\n}\n\n/** 将 response/requestBody schema property 转成中立字段模型。 */\nfunction fieldFromSchema(\n name: string,\n schema: OpenApiSchema | undefined,\n required: boolean,\n source: GeneratedField[\"source\"],\n enums: Map<string, GeneratedEnum>,\n): GeneratedField {\n return {\n name,\n label: normalizeLabel(schema?.title || schema?.description, name),\n tsType: schemaToTsType(schema),\n required,\n component: inferComponent(name, schema, schema?.description || schema?.title, enums.has(name)),\n description: schema?.description,\n enumName: enums.get(name)?.textMapName,\n source,\n };\n}\n\n/** 从 OpenAPI enum 或 Apifox 扩展枚举生成 CrudOption 数据源。 */\nfunction collectEnum(name: string, schema: OpenApiSchema | undefined, enums: Map<string, GeneratedEnum>): void {\n if (!schema || enums.has(name)) return;\n\n const apifoxEnum = schema[\"x-apifox-enum\"];\n const values = apifoxEnum?.length\n ? apifoxEnum.map(item => ({ label: item.name || String(item.value), value: item.value }))\n : schema.enum?.map(value => ({ label: String(value), value }));\n\n if (!values || values.length === 0) return;\n\n const baseName = toCamelCase(name);\n enums.set(name, {\n optionsName: `${baseName}Options`,\n textMapName: `${baseName}TextMap`,\n sourceField: name,\n options: values,\n });\n}\n\n/**\n * 根据字段名、描述和 schema 类型推断表单控件。\n *\n * 这只是初始化脚手架的默认值,不追求替代业务判断;\n * 生成后的 config/searchForm.ts 和 config/modalForm.ts 都是给业务继续改的。\n */\nfunction inferComponent(name: string, schema: OpenApiSchema | undefined, description?: string, hasCollectedEnum = false): GeneratedField[\"component\"] {\n if (hasCollectedEnum || schema?.enum?.length || schema?.[\"x-apifox-enum\"]?.length) return \"select\";\n\n const lowerName = name.toLowerCase();\n const text = `${name} ${description || \"\"}`;\n\n if (/remark|desc|description|备注|描述|说明/.test(text)) return \"textarea\";\n if (/time|date|createdAt|updatedAt|开始时间|结束时间|日期/.test(text)) return \"datePicker\";\n if (isNumberSchema(schema)) return \"inputNumber\";\n if (isBooleanSchema(schema)) return \"switch\";\n if (lowerName.includes(\"status\") || lowerName.includes(\"type\")) return \"select\";\n\n return \"input\";\n}\n\n/** 把 OpenAPI 基础类型转换成生成页里的 TypeScript 字段类型。 */\nfunction schemaToTsType(schema: OpenApiSchema | undefined): string {\n if (!schema) return \"unknown\";\n const type = Array.isArray(schema.type) ? schema.type.filter(item => item !== \"null\")[0] : schema.type;\n\n if (type === \"integer\" || type === \"number\") return \"number\";\n if (type === \"boolean\") return \"boolean\";\n if (type === \"array\") return \"unknown[]\";\n if (type === \"object\") return \"Record<string, unknown>\";\n return \"string\";\n}\n\nfunction isNumberSchema(schema: OpenApiSchema | undefined): boolean {\n const type = Array.isArray(schema?.type) ? schema?.type : [schema?.type];\n return type.includes(\"number\") || type.includes(\"integer\");\n}\n\nfunction isBooleanSchema(schema: OpenApiSchema | undefined): boolean {\n const type = Array.isArray(schema?.type) ? schema?.type : [schema?.type];\n return type.includes(\"boolean\");\n}\n","import { readFile } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport type { GenerateCrudPageOptions, OpenApiDocument } from \"./types\";\n\nconst MAX_RETRY_LIMIT = 5;\n\n/**\n * 读取 OpenAPI 文档。\n *\n * 支持三种来源:\n * - URL:适合 Agent 插件直接传接口平台导出地址。\n * - 本地文件路径:适合测试 fixture 和离线生成。\n * - 已解析对象:适合上层插件提前缓存或改写 OpenAPI 后再传入。\n */\nexport async function loadOpenApiDocument(options: Pick<GenerateCrudPageOptions, \"openapi\" | \"cwd\" | \"maxRetries\">): Promise<OpenApiDocument> {\n if (typeof options.openapi !== \"string\") return options.openapi;\n\n const content = isHttpUrl(options.openapi)\n ? await readRemoteOpenApi(options.openapi, options.maxRetries)\n : await readFile(resolve(options.cwd || \".\", options.openapi), \"utf8\");\n\n return parseOpenApiContent(content);\n}\n\n/** 首版只解析 JSON,避免为了 YAML 引入额外依赖,也贴合当前 Apifox 导出地址。 */\nfunction parseOpenApiContent(content: string): OpenApiDocument {\n const trimmed = content.trim();\n\n if (trimmed.startsWith(\"{\")) {\n return JSON.parse(trimmed) as OpenApiDocument;\n }\n\n throw new Error(\"当前生成器首版只解析 JSON OpenAPI 文档,请先在接口平台导出 JSON。\");\n}\n\nfunction isHttpUrl(value: string): boolean {\n return /^https?:\\/\\//i.test(value);\n}\n\nasync function readRemoteOpenApi(url: string, maxRetries = 2): Promise<string> {\n // 严格限制重试次数,避免接口平台限流时被生成器连续请求打到 429。\n const retryCount = Math.min(Math.max(maxRetries, 0), MAX_RETRY_LIMIT);\n let lastError: unknown;\n\n for (let attempt = 0; attempt <= retryCount; attempt += 1) {\n try {\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(`下载 OpenAPI 文档失败:${response.status} ${response.statusText}`);\n }\n return response.text();\n } catch (error) {\n lastError = error;\n if (attempt >= retryCount) break;\n }\n }\n\n const message = lastError instanceof Error ? lastError.message : String(lastError);\n throw new Error(`读取 OpenAPI 文档失败,已按限制停止重试:${message}`);\n}\n","import type {\n CrudOperationGroup,\n CrudOperationKind,\n GenerateCrudPageOptions,\n NormalizedOperation,\n OpenApiDocument,\n OpenApiHttpMethod,\n} from \"./types\";\nimport { getLastTagSegment, inferEntityNameFromPath, stripKnownActionWords, toPascalCase } from \"./string-utils\";\n\nconst HTTP_METHODS: OpenApiHttpMethod[] = [\"get\", \"post\", \"put\", \"patch\", \"delete\"];\n\n/**\n * 从 OpenAPI 文档中识别一个 CRUD 页面所需的接口组。\n *\n * 当前接口平台可能没有 operationId,也可能所有接口都挂在同一个 tag 下,\n * 所以这里优先使用 tag 做模块边界,再用 path/summary 里的中英文关键词识别查询、新增、编辑、删除。\n */\nexport function buildOperationGroup(document: OpenApiDocument, options: GenerateCrudPageOptions): CrudOperationGroup {\n const operations = collectOperations(document);\n if (operations.length === 0) {\n throw new Error(\"OpenAPI 文档中没有可用于生成 CRUD 页面的接口。\");\n }\n\n // 不传 tag 时默认选择接口数量最多的 tag,符合“一个接口文档生成一个页面”的首版目标。\n const tag = options.tag || inferPrimaryTag(operations);\n const selected = operations.filter(operation => operation.tags.includes(tag));\n const scopedOperations = selected.length > 0 ? selected : operations;\n const pageTitle = options.pageTitle || inferPageTitle(tag, document);\n const operationMap = options.operationMap || {};\n // 列表接口是 CRUD 页面的最小必需项,识别不到时先退回当前模块第一个接口,避免完全不可用。\n const list = findOperation(\"list\", scopedOperations, operationMap) || scopedOperations[0];\n\n if (!list) {\n throw new Error(\"无法识别列表接口,请通过 operationMap.list 显式指定。\");\n }\n\n // 实体名优先从接口路径推断,例如 /web/dict/queryDictTypeList.json -> DictType。\n const entityName = toPascalCase(\n options.entityName ||\n inferEntityNameFromPath(list.path) ||\n stripKnownActionWords(list.operationId) ||\n pageTitle ||\n \"Generated\",\n );\n\n return {\n tag,\n pageTitle,\n entityName,\n list,\n create: findOperation(\"create\", scopedOperations, operationMap),\n update: findOperation(\"update\", scopedOperations, operationMap),\n delete: findOperation(\"delete\", scopedOperations, operationMap),\n };\n}\n\n/** 将 paths 里的各种 method 展平成统一 operation 列表,方便后续打分和过滤。 */\nfunction collectOperations(document: OpenApiDocument): NormalizedOperation[] {\n const operations: NormalizedOperation[] = [];\n\n Object.entries(document.paths || {}).forEach(([apiPath, pathItem]) => {\n HTTP_METHODS.forEach(method => {\n const operation = pathItem?.[method];\n if (!operation) return;\n\n const parameters = [...(pathItem.parameters || []), ...(operation.parameters || [])];\n // 很多 Apifox 导出的文档没有 operationId,这里生成稳定兜底值,后续 operationMap 也能引用。\n const operationId = operation.operationId || `${method}_${apiPath.replace(/[^a-zA-Z0-9]+/g, \"_\")}`;\n\n operations.push({\n method,\n path: apiPath,\n operation,\n operationId,\n summary: operation.summary || operation.description || operationId,\n tags: operation.tags || [],\n parameters,\n });\n });\n });\n\n return operations;\n}\n\n/** 多 tag 文档默认选 operation 数量最多的 tag;如果完全没有 tag,则落到“默认模块”。 */\nfunction inferPrimaryTag(operations: NormalizedOperation[]): string {\n const tagCount = new Map<string, number>();\n\n operations.forEach(operation => {\n operation.tags.forEach(tag => {\n tagCount.set(tag, (tagCount.get(tag) || 0) + 1);\n });\n });\n\n const [tag] = [...tagCount.entries()].sort((left, right) => right[1] - left[1])[0] || [];\n return tag || \"默认模块\";\n}\n\n/** 页面标题用 tag 最后一段,例:开发中心/字典管理 -> 字典管理。 */\nfunction inferPageTitle(tag: string, document: OpenApiDocument): string {\n if (tag && tag !== \"默认模块\") return getLastTagSegment(tag);\n return document.info?.title || \"生成页面\";\n}\n\n/**\n * 查找指定 CRUD 类型的接口。\n *\n * operationMap 的优先级最高,用来解决接口命名很特殊时的误判;\n * 未传 operationMap 时再走关键词打分。\n */\nfunction findOperation(\n kind: CrudOperationKind,\n operations: NormalizedOperation[],\n operationMap: Partial<Record<CrudOperationKind, string>>,\n): NormalizedOperation | undefined {\n const mapped = operationMap[kind];\n if (mapped) return operations.find(operation => isSameOperation(operation, mapped));\n\n return operations\n .map(operation => ({ operation, score: scoreOperation(kind, operation) }))\n .filter(item => item.score > 0)\n .sort((left, right) => right.score - left.score)[0]?.operation;\n}\n\n/** operationMap 支持三种写法:operationId、path、METHOD path。 */\nfunction isSameOperation(operation: NormalizedOperation, value: string): boolean {\n const normalized = value.trim().toLowerCase();\n return (\n operation.operationId.toLowerCase() === normalized ||\n operation.path.toLowerCase() === normalized ||\n `${operation.method} ${operation.path}`.toLowerCase() === normalized\n );\n}\n\n/** 对 path、summary、operationId 做关键词打分,兼容中英文接口命名。 */\nfunction scoreOperation(kind: CrudOperationKind, operation: NormalizedOperation): number {\n const source = `${operation.operationId} ${operation.path} ${operation.summary}`.toLowerCase();\n const chineseSource = `${operation.summary} ${operation.path}`;\n\n if (kind === \"list\") {\n return scoreByKeywords(source, chineseSource, [\"query\", \"list\", \"page\", \"search\"], [\"查询\", \"列表\", \"分页\"]);\n }\n\n if (kind === \"create\") {\n return scoreByKeywords(source, chineseSource, [\"add\", \"create\", \"save\"], [\"新增\", \"添加\", \"创建\"]);\n }\n\n if (kind === \"update\") {\n return scoreByKeywords(source, chineseSource, [\"edit\", \"update\", \"modify\"], [\"编辑\", \"修改\", \"更新\"]);\n }\n\n return scoreByKeywords(source, chineseSource, [\"delete\", \"remove\", \"del\"], [\"删除\", \"移除\"]);\n}\n\nfunction scoreByKeywords(source: string, chineseSource: string, englishWords: string[], chineseWords: string[]): number {\n let score = 0;\n\n englishWords.forEach(word => {\n if (source.includes(word)) score += 10;\n });\n\n chineseWords.forEach(word => {\n if (chineseSource.includes(word)) score += 10;\n });\n\n return score;\n}\n","import type { CrudOperationKind, CrudPageModel, GeneratedEnum, GeneratedField, NormalizedOperation, RequestRenderContext } from \"./types\";\nimport { getPageParamName, getPageSizeParamName } from \"./fields\";\nimport { indent, quoteString } from \"./string-utils\";\n\n/** 渲染最终页面目录的七个文件,目录结构对齐 examples/vue3-consumer/src/views/CrudDemoPage。 */\nexport function renderFiles(model: CrudPageModel): Array<{ path: string; content: string }> {\n return [\n { path: \"index.vue\", content: renderIndexVue(model) },\n { path: \"api/index.ts\", content: renderApiIndex(model) },\n { path: \"api/types.ts\", content: renderApiTypes(model) },\n { path: \"config/enums.ts\", content: renderEnums(model) },\n { path: \"config/searchForm.ts\", content: renderSearchForm(model) },\n { path: \"config/table.ts\", content: renderTable(model) },\n { path: \"config/modalForm.ts\", content: renderModalForm(model) },\n ];\n}\n\n/**\n * 渲染页面入口 index.vue。\n *\n * 这个文件只做页面编排:创建 pageContext、actions、searchForm、modalForm、table,\n * 让生成后的业务代码保持直观,使用者不需要先理解一个黑盒 CRUD 配置对象。\n */\nfunction renderIndexVue(model: CrudPageModel): string {\n const createCall = model.operations.create ? `await create${model.entityName}(data);` : \"console.warn(\\\"当前 OpenAPI 文档未提供新增接口,请在这里补充新增逻辑。\\\", data);\";\n const updateCall = model.operations.update ? `await update${model.entityName}(data);` : \"console.warn(\\\"当前 OpenAPI 文档未提供编辑接口,请在这里补充编辑逻辑。\\\", data);\";\n const apiImports = [\n `query${model.entityName}List`,\n model.operations.create ? `create${model.entityName}` : \"\",\n model.operations.update ? `update${model.entityName}` : \"\",\n ].filter(Boolean);\n const pageParamName = getPageParamName(model.operations.list);\n const pageSizeParamName = getPageSizeParamName(model.operations.list);\n\n return `<template>\n <section class=\"demo-route-panel\">\n <AutoCrud\n title=${quoteString(model.pageTitle)}\n :search-form-config=\"searchForm\"\n :table-config=\"table\"\n :modal-form-config=\"modalForm\"\n />\n </section>\n</template>\n\n<script setup lang=\"ts\">\nimport { reactive } from \"vue\";\nimport { AutoCrud } from \"auto-gen-crud\";\nimport { ${apiImports.join(\", \")} } from \"./api\";\nimport { getModalConfig } from \"./config/modalForm\";\nimport { getSearchConfig } from \"./config/searchForm\";\nimport { getTableConfig } from \"./config/table\";\nimport type { ActionsType, PageContextType, RowType } from \"./api/types\";\n\n// 页面行为统一放在 actions 中,配置文件只消费动作,不反向引用页面变量。\nconst actions: ActionsType = {\n onSubmit,\n onEdit: row => {\n modalForm.openEdit(row);\n },\n onView: row => {\n modalForm.openView(row);\n },\n getDataList,\n};\n\n// 页面上下文默认保持为空,业务二开时再按权限、联动状态或页面缓存逐步补充。\nconst pageContext = reactive<PageContextType>({});\n\nconst searchForm = getSearchConfig();\nconst modalForm = getModalConfig(pageContext, actions);\nconst table = getTableConfig(pageContext, actions);\n\nasync function onSubmit(data: RowType) {\n // 新增和编辑共用弹窗表单,通过 mode 区分最终调用哪个接口。\n if (modalForm.mode === \"edit\") {\n ${updateCall}\n } else {\n ${createCall}\n }\n\n modalForm.close();\n await table.load();\n}\n\nasync function getDataList() {\n const { page, pageSize } = table.pagination;\n\n // 搜索条件整体透传,避免生成器为每个查询字段重复展开,业务二开时也更容易增删字段。\n const result = await query${model.entityName}List({\n ${pageParamName}: page,\n ${pageSizeParamName}: pageSize,\n ...searchForm.data,\n });\n\n table.data = result.${model.responsePath.listPath};\n table.pagination.total = result.${model.responsePath.totalPath};\n}\n</script>\n`;\n}\n\n/** 渲染 api/index.ts,所有请求函数都由 OpenAPI operation 生成。 */\nfunction renderApiIndex(model: CrudPageModel): string {\n const typeImports = [\n \"ListResponse\",\n \"SearchParamsType\",\n model.operations.create || model.operations.update ? \"SaveParams\" : \"\",\n model.operations.delete ? \"DeleteParams\" : \"\",\n ].filter(Boolean);\n const blocks = [\n renderOperationFunction(model, \"list\", model.operations.list),\n model.operations.create ? renderOperationFunction(model, \"create\", model.operations.create) : \"\",\n model.operations.update ? renderOperationFunction(model, \"update\", model.operations.update) : \"\",\n model.operations.delete ? renderOperationFunction(model, \"delete\", model.operations.delete) : \"\",\n ].filter(Boolean);\n\n return `${model.requestAdapter.importCode}\nimport type { ${typeImports.join(\", \")} } from \"./types\";\n\n${blocks.join(\"\\n\\n\")}\n`;\n}\n\n/** 根据 CRUD 类型生成稳定函数名和参数名,例如 queryDictTypeList/createDictType。 */\nfunction renderOperationFunction(model: CrudPageModel, kind: CrudOperationKind, operation: NormalizedOperation): string {\n const functionName = getFunctionName(model.entityName, kind);\n const payloadName = kind === \"list\" ? \"params\" : kind === \"delete\" ? \"params\" : \"data\";\n const payloadType = kind === \"list\" ? \"SearchParamsType\" : kind === \"delete\" ? \"DeleteParams\" : \"SaveParams\";\n const responseType = kind === \"list\" ? \"ListResponse\" : \"unknown\";\n const ctx: RequestRenderContext = {\n functionName,\n method: operation.method,\n path: operation.path,\n payloadName,\n responseType,\n };\n\n return `export function ${functionName}(${payloadName}: ${payloadType}) {\n ${renderIndentedRequestCall(model, ctx)}\n}`;\n}\n\n/** 将项目级 requestAdapter 的返回值嵌入函数体,避免生成器写死某个项目的 request 签名。 */\nfunction renderIndentedRequestCall(model: CrudPageModel, ctx: RequestRenderContext): string {\n return model.requestAdapter.renderCall(ctx).trim();\n}\n\n/** 渲染 api/types.ts,所有类型都放在页面目录内部,避免生成相对路径硬编码。 */\nfunction renderApiTypes(model: CrudPageModel): string {\n return `${renderInterfaceComment(`${model.pageTitle}表格与表单共用数据结构。字段保持可选,方便新增、编辑表单复用同一份页面数据。`)}\nexport interface RowType {\n${indent(renderRowFields(model.tableFields), 2)}\n [key: string]: unknown;\n}\n\n${renderInterfaceComment(\"新增、修改接口提交体。生成器默认复用 RowType,业务可按真实接口继续收窄字段。\")}\nexport interface SaveParams extends RowType {}\n\n${renderInterfaceComment(\"删除接口参数。默认保留 id,若后端批量删除可在这里扩展 ids。\")}\nexport interface DeleteParams {\n id: number | string;\n [key: string]: unknown;\n}\n\n${renderInterfaceComment(\"分页接口原始响应结构,字段路径按当前 OpenAPI 响应自动生成。\")}\nexport interface ListResponse {\n${indent(renderResponseType(model), 2)}\n [key: string]: unknown;\n}\n\n${renderInterfaceComment(\"搜索表单数据,字段名与查询接口保持一致。\")}\nexport type SearchParamsType = Record<string, unknown> & {\n${indent(renderSearchTypeFields(model), 2)}\n};\n\n${renderInterfaceComment(\"页面操作函数集合。由页面统一组装后传给表格和弹窗配置,避免配置文件之间互相引用。\")}\nexport interface ActionsType {\n onSubmit: (data: RowType) => void | Promise<void>;\n onEdit: (row: RowType) => void;\n onView: (row: RowType) => void;\n getDataList: () => void | Promise<void>;\n}\n\n${renderInterfaceComment(\"页面级上下文默认不预设字段,业务二开时再补充权限、枚举、联动状态或页面缓存。\")}\nexport type PageContextType = Record<string, unknown>;\n`;\n}\n\n/** 渲染 config/enums.ts,枚举字段来自 OpenAPI enum 或 x-apifox-enum。 */\nfunction renderEnums(model: CrudPageModel): string {\n const { enums } = model;\n\n if (enums.length === 0) {\n return `// 当前 OpenAPI 文档没有枚举字段,预留空模块便于后续业务补充下拉选项。\nexport {};\n`;\n }\n\n const importCode = model.generateTypeOptions ? 'import type { CrudOption } from \"auto-gen-crud\";\\n\\n' : \"\";\n\n return `${importCode}${enums.map(enumItem => renderEnum(enumItem, model.generateTypeOptions)).join(\"\\n\\n\")}\n`;\n}\n\n/** 渲染搜索表单配置,字段只来自列表接口 query 参数。 */\nfunction renderSearchForm(model: CrudPageModel): string {\n return `import { useSearchForm } from \"auto-gen-crud\";\n${renderEnumImportForFields(model.searchFields)}import type { SearchParamsType } from \"../api/types\";\n\n// 搜索表单配置只描述字段,不直接关心列表接口和表格状态。\nexport function getSearchConfig() {\n return useSearchForm<SearchParamsType>({\n columns: [\n${indent(model.searchFields.map(renderFormField).join(\",\\n\"), 6)}\n ],\n });\n}\n`;\n}\n\n/** 渲染表格配置,字段来自列表响应 records/items。 */\nfunction renderTable(model: CrudPageModel): string {\n return `import { useTable } from \"auto-gen-crud\";\nimport type { ActionsType, PageContextType, RowType } from \"../api/types\";\n\n// 表格配置只负责列展示和操作入口,真实请求由页面层 actions.getDataList 处理。\nexport function getTableConfig(pageContext: PageContextType, actions: ActionsType) {\n return useTable<RowType>({\n columns: [\n${indent(model.tableFields.map(renderTableColumn).join(\",\\n\"), 6)},\n {\n title: \"操作\",\n width: \"200px\",\n fixed: \"right\",\n actions: [\n {\n key: \"edit\",\n label: \"编辑\",\n type: \"primary\",\n show: () => Boolean(pageContext.canEdit${model.permissionSuffix} ?? true),\n onClick: actions.onEdit,\n },\n {\n key: \"view\",\n label: \"查看\",\n type: \"link\",\n show: () => Boolean(pageContext.canView${model.permissionSuffix} ?? true),\n onClick: actions.onView,\n },\n ],\n },\n ],\n data: [],\n getDataList: actions.getDataList,\n });\n}\n`;\n}\n\n/** 渲染弹窗表单配置,字段来自新增/编辑接口参数或 requestBody。 */\nfunction renderModalForm(model: CrudPageModel): string {\n return `import { useModalForm } from \"auto-gen-crud\";\n${renderEnumImportForFields(model.modalFields)}import type { ActionsType, PageContextType, RowType } from \"../api/types\";\n\n// 弹窗表单配置只描述新增、编辑、查看共用字段,提交动作交给页面层处理。\nexport function getModalConfig(pageContext: PageContextType, actions: ActionsType) {\n // 保留 pageContext 参数,便于后续按权限、状态或枚举动态控制字段。\n void pageContext;\n\n return useModalForm<RowType>({\n columns: [\n${indent(model.modalFields.map(renderFormField).join(\",\\n\"), 6)}\n ],\n onSubmit: actions.onSubmit,\n });\n}\n`;\n}\n\nfunction renderNamedImport(names: string[]): string {\n return `{ ${names.join(\", \")} }`;\n}\n\n/** RowType 字段默认可选,避免新增表单被列表响应里的只读字段卡住。 */\nfunction renderRowFields(fields: GeneratedField[]): string {\n if (fields.length === 0) return \"id?: number | string;\\n\";\n\n return fields.map(field => {\n return `/** ${field.label} */\\n${field.name}?: ${field.tsType};`;\n }).join(\"\\n\");\n}\n\n/** 搜索类型保留分页字段,方便 queryList 入参完整描述接口参数。 */\nfunction renderSearchTypeFields(model: CrudPageModel): string {\n const pageParamName = getPageParamName(model.operations.list);\n const pageSizeParamName = getPageSizeParamName(model.operations.list);\n const fields = [\n `${pageParamName}?: number;`,\n `${pageSizeParamName}?: number;`,\n ...model.searchFields.map(field => `${field.name}?: ${field.tsType};`),\n ];\n\n return fields.join(\"\\n\");\n}\n\n/** 当前先为常见分页结构生成精确类型,其它结构保留路径注释和 Record 兜底。 */\nfunction renderResponseType(model: CrudPageModel): string {\n if (model.responsePath.listPath === \"result.records\" && model.responsePath.totalPath === \"result.total\") {\n return `result: {\n records: RowType[];\n total: number;\n [key: string]: unknown;\n};`;\n }\n\n return `/** 列表字段路径:${model.responsePath.listPath},总数字段路径:${model.responsePath.totalPath}。 */\nresult: Record<string, unknown>;\ndata?: Record<string, unknown>;`;\n}\n\n/** 表单字段配置只输出中立字段,具体 UI 组件由 auto-gen-crud adapter 负责。 */\nfunction renderFormField(field: GeneratedField): string {\n const props = [\n `label: ${quoteString(field.label)}`,\n `prop: ${quoteString(field.name)}`,\n `component: ${quoteString(field.component)}`,\n ];\n\n if (field.required) props.push(\"required: true\");\n if (field.enumName) props.push(`options: ${field.enumName}`);\n\n return `{ ${props.join(\", \")} }`;\n}\n\n/** 表格列配置保持最小可读,日期字段自动补 dateTime 默认展示格式。 */\nfunction renderTableColumn(field: GeneratedField): string {\n const props = [\n `title: ${quoteString(field.label)}`,\n `field: ${quoteString(field.name)}`,\n ];\n\n if (/time|date|createdAt|updatedAt/i.test(field.name)) props.push(`dateTime: \"YYYY-MM-DD HH:mm\"`);\n return `{ ${props.join(\", \")} }`;\n}\n\n/** 将 OpenAPI 枚举渲染成 textMap;TypeOptions 只有开启配置后才额外输出。 */\nfunction renderEnum(item: GeneratedEnum, generateTypeOptions: boolean): string {\n const options = item.options\n .map(option => ` { label: ${quoteString(option.label)}, value: ${JSON.stringify(option.value)} },`)\n .join(\"\\n\");\n\n const textMap = item.options\n .map(option => ` ${JSON.stringify(option.value)}: ${quoteString(option.label)},`)\n .join(\"\\n\");\n\n const optionsBlock = generateTypeOptions\n ? `export const ${item.optionsName}: CrudOption[] = [\n${options}\n];\n\n`\n : \"\";\n\n return `${optionsBlock}export const ${item.textMapName}: Record<string, string> = {\n${textMap}\n};`;\n}\n\n/** 只导入当前配置文件实际用到的枚举,降低生成代码的 lint 噪音。 */\nfunction renderEnumImportForFields(fields: GeneratedField[]): string {\n const names = [...new Set(fields.map(field => field.enumName).filter(Boolean))] as string[];\n if (names.length === 0) return \"\";\n return `import ${renderNamedImport(names)} from \"./enums\";\\n`;\n}\n\nfunction getFunctionName(entityName: string, kind: CrudOperationKind): string {\n if (kind === \"list\") return `query${entityName}List`;\n if (kind === \"create\") return `create${entityName}`;\n if (kind === \"update\") return `update${entityName}`;\n return `delete${entityName}`;\n}\n\nfunction renderInterfaceComment(text: string): string {\n return `/** ${text} */`;\n}\n","import { access, mkdir, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport type { GeneratedFile } from \"./types\";\n\n/**\n * 写入生成文件。\n *\n * overwrite 默认关闭,避免 Agent 插件误覆盖开发者已经二开过的页面;\n * 需要重新生成时由调用方显式传 overwrite: true。\n */\nexport async function writeGeneratedFiles(outDir: string, files: GeneratedFile[], overwrite = false): Promise<void> {\n for (const file of files) {\n await writeGeneratedFile(join(outDir, file.path), file.content, overwrite);\n }\n}\n\n/** 单文件写入会自动创建父目录,并统一补结尾换行。 */\nasync function writeGeneratedFile(filePath: string, content: string, overwrite: boolean): Promise<void> {\n if (!overwrite && await exists(filePath)) {\n throw new Error(`生成文件已存在:${filePath}。如需覆盖请传入 overwrite: true。`);\n }\n\n await mkdir(dirname(filePath), { recursive: true });\n await writeFile(filePath, ensureTrailingNewline(content), \"utf8\");\n}\n\nasync function exists(filePath: string): Promise<boolean> {\n try {\n await access(filePath);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction ensureTrailingNewline(content: string): string {\n return content.endsWith(\"\\n\") ? content : `${content}\\n`;\n}\n","import { isAbsolute, join, resolve } from \"node:path\";\nimport { buildModalFields, buildSearchFields, buildTableFields, collectEnumsFromOperations } from \"./fields\";\nimport { loadOpenApiDocument } from \"./openapi\";\nimport { buildOperationGroup } from \"./operations\";\nimport { inferResponsePath } from \"./schema\";\nimport { renderFiles } from \"./templates\";\nimport { toCamelCase } from \"./string-utils\";\nimport { writeGeneratedFiles } from \"./writer\";\nimport type {\n CrudPageModel,\n GenerateCrudPageOptions,\n GenerateCrudPageResult,\n GeneratedFile,\n RequestAdapterOptions,\n RequestRenderContext,\n} from \"./types\";\n\nexport type {\n CrudOperationKind,\n GenerateCrudPageOptions,\n GenerateCrudPageResult,\n GeneratedFile,\n OpenApiDocument,\n RequestAdapterOptions,\n RequestRenderContext,\n} from \"./types\";\n\n/**\n * 根据 OpenAPI 3.1 文档生成一个可二次开发的 CRUD 页面目录。\n *\n * 使用方式:\n * - 最简单:`generateCrudPage(openapiUrl)`,生成器自动识别 tag、标题、实体名和输出目录。\n * - 进阶:`generateCrudPage({ openapi, outDir, operationMap })`,当接口文档不够规范时手动覆盖关键推断。\n *\n * 这里是生成器总编排层,只负责串起“读取文档 -> 推断模型 -> 渲染文件 -> 写入磁盘”四步,\n * 具体推断规则分散在 operations/fields/schema/templates 中,避免入口函数变成难维护的大杂烩。\n */\nexport async function generateCrudPage(openapi: string): Promise<GenerateCrudPageResult>;\nexport async function generateCrudPage(options: GenerateCrudPageOptions): Promise<GenerateCrudPageResult>;\nexport async function generateCrudPage(input: string | GenerateCrudPageOptions): Promise<GenerateCrudPageResult> {\n const options = normalizeOptions(input);\n const document = await loadOpenApiDocument(options);\n const operationGroup = buildOperationGroup(document, options);\n const outDir = resolveOutDir(options.cwd || \".\", options.outDir, operationGroup.entityName);\n const operations = [operationGroup.list, operationGroup.create, operationGroup.update, operationGroup.delete].filter(isDefined);\n const enumMap = collectEnumsFromOperations(document, operations);\n const searchFields = buildSearchFields(document, operationGroup.list, enumMap);\n const modalFields = buildModalFields(document, [operationGroup.create, operationGroup.update], enumMap);\n const tableFields = buildTableFields(document, operationGroup.list, enumMap);\n // CrudPageModel 是模板层唯一输入,模板不再直接读 OpenAPI,后续想换模板时不会牵动解析逻辑。\n const model: CrudPageModel = {\n tag: operationGroup.tag,\n pageTitle: operationGroup.pageTitle,\n entityName: operationGroup.entityName,\n entityVariable: toCamelCase(operationGroup.entityName),\n permissionSuffix: operationGroup.entityName,\n outDir,\n operations: operationGroup,\n searchFields,\n // 如果文档只有查询接口,也生成一个能跑起来的弹窗表单,业务后续再按真实新增/编辑接口调整。\n modalFields: modalFields.length > 0 ? modalFields : searchFields,\n tableFields,\n enums: [...enumMap.values()],\n generateTypeOptions: Boolean(options.generateTypeOptions),\n responsePath: inferResponsePath(document, operationGroup.list),\n requestAdapter: normalizeRequestAdapter(options.requestAdapter),\n };\n\n const files = renderFiles(model).map<GeneratedFile>(file => ({\n path: file.path,\n content: file.content,\n }));\n\n if (!options.dryRun) {\n await writeGeneratedFiles(outDir, files, options.overwrite);\n }\n\n return {\n tag: model.tag,\n pageTitle: model.pageTitle,\n entityName: model.entityName,\n outDir,\n files,\n dryRun: Boolean(options.dryRun),\n };\n}\n\nfunction normalizeOptions(input: string | GenerateCrudPageOptions): GenerateCrudPageOptions {\n if (typeof input === \"string\") {\n return { openapi: input };\n }\n\n return input;\n}\n\nfunction isDefined<T>(value: T | undefined): value is T {\n return value !== undefined;\n}\n\n/** 默认输出到当前项目 src/views/<EntityName>Page,方便“只传接口文档地址”直接生成页面。 */\nfunction resolveOutDir(cwd: string, outDir: string | undefined, entityName: string): string {\n const target = outDir || join(\"src\", \"views\", `${entityName}Page`);\n return isAbsolute(target) ? target : resolve(cwd, target);\n}\n\n/**\n * 请求层适配器。\n *\n * 不同项目的请求工具签名可能完全不同,所以生成器只内置一个示例项目默认值,\n * 真正接入其它项目时可以通过 requestAdapter.importCode/renderCall 覆盖。\n */\nfunction normalizeRequestAdapter(adapter?: RequestAdapterOptions): Required<RequestAdapterOptions> {\n return {\n importCode: adapter?.importCode || 'import { http } from \"@/utils/http\";',\n renderCall: adapter?.renderCall || defaultRenderCall,\n };\n}\n\n/** 默认适配 examples/vue3-consumer/src/utils/http.ts 的 http.get/post/put/delete 写法。 */\nfunction defaultRenderCall(ctx: RequestRenderContext): string {\n const method = ctx.method.toLowerCase();\n\n if (method === \"get\" || method === \"delete\") {\n return `return http.${method}<${ctx.responseType}>(${JSON.stringify(ctx.path)}, { query: ${ctx.payloadName} });`;\n }\n\n return `return http.${method}<${ctx.responseType}, typeof ${ctx.payloadName}>(${JSON.stringify(ctx.path)}, ${ctx.payloadName});`;\n}\n"],"names":["resolveSchema","document","schema","name","mergeObjectSchemas","getQueryParameters","operation","parameter","getRequestBodySchema","content","getSuccessResponseSchema","responses","inferRecordSchema","responseSchema","recordSchema","findArrayItemSchema","inferResponsePath","found","findArrayPath","findTotalPath","getSchemaProperties","getSchemaRequired","schemas","result","item","resolved","isArraySchema","properties","key","getOrderedKeys","child","prefix","childPath","parentPath","getSchemaByPath","totalKey","rootProperties","path","current","ordered","COMMON_PATH_WORDS","upperFirst","value","lowerFirst","toPascalCase","asciiWords","word","toCamelCase","getLastTagSegment","tag","stripKnownActionWords","previous","inferEntityNameFromPath","apiPath","segments","segment","meaningful","normalizeLabel","fallback","quoteString","indent","size","line","PAGE_FIELD_NAMES","PAGE_SIZE_FIELD_NAMES","INTERNAL_FIELD_NAMES","ID_FIELD_NAMES","buildSearchFields","enums","isPaginationField","fieldFromParameter","buildModalFields","operations","fieldMap","typedOperation","existed","bodySchema","appendSchemaFields","buildTableFields","collectEnumsFromOperations","collectEnum","getPageParamName","getPageSizeParamName","requiredFields","source","skipId","property","fieldFromSchema","schemaToTsType","inferComponent","required","apifoxEnum","values","baseName","description","hasCollectedEnum","lowerName","text","isNumberSchema","isBooleanSchema","type","MAX_RETRY_LIMIT","loadOpenApiDocument","options","isHttpUrl","readRemoteOpenApi","readFile","resolve","parseOpenApiContent","trimmed","url","maxRetries","retryCount","lastError","attempt","response","error","message","HTTP_METHODS","buildOperationGroup","collectOperations","inferPrimaryTag","selected","scopedOperations","pageTitle","inferPageTitle","operationMap","list","findOperation","entityName","pathItem","method","parameters","operationId","tagCount","left","right","kind","mapped","isSameOperation","scoreOperation","normalized","chineseSource","scoreByKeywords","englishWords","chineseWords","score","renderFiles","model","renderIndexVue","renderApiIndex","renderApiTypes","renderEnums","renderSearchForm","renderTable","renderModalForm","createCall","updateCall","apiImports","pageParamName","pageSizeParamName","typeImports","blocks","renderOperationFunction","functionName","getFunctionName","payloadName","payloadType","responseType","ctx","renderIndentedRequestCall","renderInterfaceComment","renderRowFields","renderResponseType","renderSearchTypeFields","enumItem","renderEnum","renderEnumImportForFields","renderFormField","renderTableColumn","renderNamedImport","names","fields","field","props","generateTypeOptions","option","textMap","writeGeneratedFiles","outDir","files","overwrite","file","writeGeneratedFile","join","filePath","exists","mkdir","dirname","writeFile","ensureTrailingNewline","access","generateCrudPage","input","normalizeOptions","operationGroup","resolveOutDir","isDefined","enumMap","searchFields","modalFields","tableFields","normalizeRequestAdapter","cwd","target","isAbsolute","adapter","defaultRenderCall"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../../src/generator/schema.ts","../../src/generator/string-utils.ts","../../src/generator/fields.ts","../../src/generator/openapi.ts","../../src/generator/operations.ts","../../src/generator/templates.ts","../../src/generator/writer.ts","../../src/generator/index.ts"],"sourcesContent":["import type { NormalizedOperation, OpenApiDocument, OpenApiParameter, OpenApiSchema, ResponsePathInfo } from \"./types\";\n\n/**\n * 解析 schema 引用。\n *\n * OpenAPI 里字段可能直接写在当前节点,也可能通过 $ref/allOf 组合复用。\n * 生成器只需要拿到最终 properties/required,所以这里把常见引用形式先摊平。\n */\nexport function resolveSchema(document: OpenApiDocument, schema?: OpenApiSchema): OpenApiSchema | undefined {\n if (!schema) return undefined;\n\n if (schema.$ref) {\n const name = schema.$ref.split(\"/\").pop();\n return name ? resolveSchema(document, document.components?.schemas?.[name]) : schema;\n }\n\n if (schema.allOf?.length) {\n return mergeObjectSchemas(document, schema.allOf);\n }\n\n return schema;\n}\n\n/** 只取 query 参数;header/path/cookie 不参与表单字段生成。 */\nexport function getQueryParameters(operation: NormalizedOperation): OpenApiParameter[] {\n return operation.parameters.filter(parameter => parameter.in === \"query\");\n}\n\n/** 读取 JSON requestBody schema,兼容 content-type 不是 application/json 但仍有第一个 content 的情况。 */\nexport function getRequestBodySchema(document: OpenApiDocument, operation?: NormalizedOperation): OpenApiSchema | undefined {\n if (!operation) return undefined;\n const content = operation.operation.requestBody?.content || {};\n const schema = content[\"application/json\"]?.schema || Object.values(content)[0]?.schema;\n return resolveSchema(document, schema);\n}\n\n/** 读取成功响应 schema,优先级:200 -> 201 -> default -> 第一个响应。 */\nexport function getSuccessResponseSchema(document: OpenApiDocument, operation: NormalizedOperation): OpenApiSchema | undefined {\n const responses = operation.operation.responses || {};\n const response = responses[\"200\"] || responses[\"201\"] || responses.default || Object.values(responses)[0];\n const content = response?.content || {};\n const schema = content[\"application/json\"]?.schema || Object.values(content)[0]?.schema;\n return resolveSchema(document, schema);\n}\n\n/**\n * 推断列表行数据结构。\n *\n * 当前 Apifox 文档的列表结构是 result.records.items,\n * 但这里不写死路径,而是在响应 schema 中递归找到第一个数组字段并取 items。\n */\nexport function inferRecordSchema(document: OpenApiDocument, operation: NormalizedOperation): OpenApiSchema {\n const responseSchema = getSuccessResponseSchema(document, operation);\n const recordSchema = findArrayItemSchema(document, responseSchema);\n return resolveSchema(document, recordSchema) || { type: \"object\", properties: {} };\n}\n\n/**\n * 推断列表数据和总数字段路径。\n *\n * 默认递归寻找数组字段作为列表路径,再在同级对象中寻找 total/totalCount/count;\n * 如果文档没有描述清楚,则退回当前项目常见的 result.records/result.total。\n */\nexport function inferResponsePath(document: OpenApiDocument, operation: NormalizedOperation): ResponsePathInfo {\n const responseSchema = getSuccessResponseSchema(document, operation);\n const found = findArrayPath(document, responseSchema);\n\n if (found) {\n return {\n listPath: found.path,\n totalPath: findTotalPath(responseSchema, found.parentPath) || \"total\",\n };\n }\n\n return { listPath: \"result.records\", totalPath: \"result.total\" };\n}\n\n/** 安全读取 schema.properties,调用方不用反复判空。 */\nexport function getSchemaProperties(document: OpenApiDocument, schema?: OpenApiSchema): Record<string, OpenApiSchema> {\n const resolved = resolveSchema(document, schema);\n return resolved?.properties || {};\n}\n\n/** 安全读取 required 数组,缺失时按空数组处理。 */\nexport function getSchemaRequired(schema?: OpenApiSchema): string[] {\n return Array.isArray(schema?.required) ? schema.required : [];\n}\n\n/** 合并 allOf 对象 schema,保留所有 properties 和 required。 */\nfunction mergeObjectSchemas(document: OpenApiDocument, schemas: OpenApiSchema[]): OpenApiSchema {\n return schemas.reduce<OpenApiSchema>(\n (result, item) => {\n const resolved = resolveSchema(document, item);\n return {\n ...result,\n ...resolved,\n properties: {\n ...(result.properties || {}),\n ...(resolved?.properties || {}),\n },\n required: [...new Set([...(result.required || []), ...(resolved?.required || [])])],\n };\n },\n { type: \"object\", properties: {} },\n );\n}\n\n/** 在响应 schema 中递归寻找第一个数组字段的 item schema。 */\nfunction findArrayItemSchema(document: OpenApiDocument, schema?: OpenApiSchema): OpenApiSchema | undefined {\n const resolved = resolveSchema(document, schema);\n if (!resolved) return undefined;\n\n if (isArraySchema(resolved)) return resolveSchema(document, resolved.items);\n\n const properties = resolved.properties || {};\n for (const key of getOrderedKeys(resolved, properties)) {\n const child = resolveSchema(document, properties[key]);\n const found = findArrayItemSchema(document, child);\n if (found) return found;\n }\n\n return undefined;\n}\n\n/** 在响应 schema 中递归寻找第一个数组字段路径,例如 result.records。 */\nfunction findArrayPath(document: OpenApiDocument, schema?: OpenApiSchema, prefix = \"\"): { path: string; parentPath: string } | undefined {\n const resolved = resolveSchema(document, schema);\n if (!resolved) return undefined;\n\n if (isArraySchema(resolved)) {\n return { path: prefix, parentPath: prefix.split(\".\").slice(0, -1).join(\".\") };\n }\n\n const properties = resolved.properties || {};\n for (const key of getOrderedKeys(resolved, properties)) {\n const childPath = prefix ? `${prefix}.${key}` : key;\n const child = resolveSchema(document, properties[key]);\n const found = findArrayPath(document, child, childPath);\n if (found) return found;\n }\n\n return undefined;\n}\n\n/** 在列表字段同级优先寻找 total,其次在根对象寻找 total。 */\nfunction findTotalPath(schema: OpenApiSchema | undefined, parentPath: string): string | undefined {\n if (!schema) return undefined;\n\n const parentSchema = getSchemaByPath(schema, parentPath);\n const properties = parentSchema?.properties || {};\n const totalKey = [\"total\", \"totalCount\", \"count\"].find(key => key in properties);\n if (totalKey) return parentPath ? `${parentPath}.${totalKey}` : totalKey;\n\n const rootProperties = schema.properties || {};\n const rootTotalKey = [\"total\", \"totalCount\", \"count\"].find(key => key in rootProperties);\n return rootTotalKey;\n}\n\n/** 按点路径在 schema 树里取节点,用于根据 result.records 找到 result 对象。 */\nfunction getSchemaByPath(schema: OpenApiSchema, path: string): OpenApiSchema | undefined {\n if (!path) return schema;\n\n return path.split(\".\").reduce<OpenApiSchema | undefined>((current, key) => {\n if (!current) return undefined;\n return current.properties?.[key];\n }, schema);\n}\n\nfunction isArraySchema(schema: OpenApiSchema): boolean {\n return schema.type === \"array\" || (Array.isArray(schema.type) && schema.type.includes(\"array\"));\n}\n\n/** Apifox 会提供 x-apifox-orders,这里用它保持生成列顺序和接口文档一致。 */\nfunction getOrderedKeys(schema: OpenApiSchema, properties: Record<string, OpenApiSchema>): string[] {\n const ordered = schema[\"x-apifox-orders\"] || [];\n return [...ordered.filter(key => key in properties), ...Object.keys(properties).filter(key => !ordered.includes(key))];\n}\n","const COMMON_PATH_WORDS = new Set([\n \"web\",\n \"api\",\n \"admin\",\n \"manage\",\n \"manager\",\n \"query\",\n \"list\",\n \"page\",\n \"search\",\n \"add\",\n \"create\",\n \"save\",\n \"edit\",\n \"update\",\n \"modify\",\n \"delete\",\n \"remove\",\n]);\n\nexport function upperFirst(value: string): string {\n if (!value) return value;\n return `${value[0].toUpperCase()}${value.slice(1)}`;\n}\n\nexport function lowerFirst(value: string): string {\n if (!value) return value;\n return `${value[0].toLowerCase()}${value.slice(1)}`;\n}\n\nexport function toPascalCase(value: string): string {\n const words = value\n .replace(/\\.json$/i, \"\")\n .replace(/([a-z0-9])([A-Z])/g, \"$1 $2\")\n .split(/[^a-zA-Z0-9\\u4e00-\\u9fa5]+/)\n .filter(Boolean);\n\n const asciiWords = words.filter(word => /^[a-zA-Z0-9]+$/.test(word));\n if (asciiWords.length === 0) return \"Generated\";\n\n return asciiWords.map(word => upperFirst(word.toLowerCase())).join(\"\");\n}\n\nexport function toCamelCase(value: string): string {\n return lowerFirst(toPascalCase(value));\n}\n\nexport function toConstantName(value: string): string {\n return value\n .replace(/([a-z0-9])([A-Z])/g, \"$1_$2\")\n .replace(/[^a-zA-Z0-9]+/g, \"_\")\n .replace(/^_+|_+$/g, \"\")\n .toUpperCase();\n}\n\nexport function getLastTagSegment(tag: string): string {\n return tag.split(/[\\\\\\/]/).filter(Boolean).pop() || tag;\n}\n\nexport function stripKnownActionWords(value: string): string {\n let result = value;\n let previous = \"\";\n\n while (result && result !== previous) {\n previous = result;\n result = result\n // 兼容 pageQueryDictType、queryDictTypeList 这类多动作词驼峰名称,避免实体名混入 Query/Page。\n .replace(/^(query|get|list|page|search|create|add|save|edit|update|modify|delete|remove)(?=[A-Z_/\\-.]|$)/i, \"\")\n .replace(/(list|page|query|search|create|add|save|edit|update|modify|delete|remove)$/i, \"\");\n }\n\n return result;\n}\n\nexport function inferEntityNameFromPath(apiPath: string): string {\n const segments = apiPath\n .split(\"/\")\n .map(segment => segment.replace(/\\.json$/i, \"\"))\n .flatMap(segment => segment.split(/[^a-zA-Z0-9]+/))\n .filter(Boolean)\n .filter(segment => !COMMON_PATH_WORDS.has(segment.toLowerCase()));\n\n const meaningful = segments.length > 0 ? segments[segments.length - 1] : \"\";\n return toPascalCase(stripKnownActionWords(meaningful) || meaningful || \"Generated\");\n}\n\nexport function normalizeLabel(value: string | undefined, fallback: string): string {\n const raw = (value || fallback).trim();\n return raw.replace(/[((].*?[))]/g, \"\").trim() || fallback;\n}\n\nexport function quoteString(value: string): string {\n return JSON.stringify(value);\n}\n\nexport function indent(content: string, size = 2): string {\n const prefix = \" \".repeat(size);\n return content\n .split(\"\\n\")\n .map(line => (line ? `${prefix}${line}` : line))\n .join(\"\\n\");\n}\n","import type { GeneratedEnum, GeneratedField, NormalizedOperation, OpenApiDocument, OpenApiParameter, OpenApiSchema } from \"./types\";\nimport { getQueryParameters, getRequestBodySchema, getSchemaProperties, getSchemaRequired, inferRecordSchema, resolveSchema } from \"./schema\";\nimport { normalizeLabel, toCamelCase } from \"./string-utils\";\n\nconst PAGE_FIELD_NAMES = new Set([\"currentPage\", \"page\", \"pageNum\", \"pageNo\"]);\nconst PAGE_SIZE_FIELD_NAMES = new Set([\"pageSize\", \"size\", \"limit\"]);\nconst INTERNAL_FIELD_NAMES = new Set([\"traceLogId\", \"operator\", \"createdBy\"]);\nconst ID_FIELD_NAMES = new Set([\"id\", \"ids\"]);\n\n/**\n * 生成搜索表单字段。\n *\n * 搜索字段来自列表接口的 query 参数;分页字段和公共业务字段会被过滤掉,\n * 因为分页由 table.pagination 管,traceLogId/operator 这类公共参数应由请求封装统一补齐。\n */\nexport function buildSearchFields(document: OpenApiDocument, operation: NormalizedOperation, enums: Map<string, GeneratedEnum>): GeneratedField[] {\n return getQueryParameters(operation)\n .filter(parameter => !isPaginationField(parameter.name))\n .filter(parameter => !INTERNAL_FIELD_NAMES.has(parameter.name))\n .map(parameter => fieldFromParameter(document, parameter, \"search\", enums));\n}\n\n/**\n * 生成弹窗表单字段。\n *\n * 新增和编辑接口都可能提供字段,这里合并两边字段,并保留 required 信息;\n * id 默认不进入表单,因为新增时通常没有 id,编辑时 id 会随 row 数据一起提交。\n */\nexport function buildModalFields(\n document: OpenApiDocument,\n operations: Array<NormalizedOperation | undefined>,\n enums: Map<string, GeneratedEnum>,\n): GeneratedField[] {\n const fieldMap = new Map<string, GeneratedField>();\n\n operations.filter(Boolean).forEach(operation => {\n const typedOperation = operation as NormalizedOperation;\n getQueryParameters(typedOperation)\n .filter(parameter => !isPaginationField(parameter.name))\n .filter(parameter => !INTERNAL_FIELD_NAMES.has(parameter.name))\n .filter(parameter => !ID_FIELD_NAMES.has(parameter.name))\n .forEach(parameter => {\n const current = fieldFromParameter(document, parameter, \"modal\", enums);\n const existed = fieldMap.get(current.name);\n fieldMap.set(current.name, existed ? { ...existed, required: existed.required || current.required } : current);\n });\n\n const bodySchema = getRequestBodySchema(document, typedOperation);\n appendSchemaFields(document, bodySchema, getSchemaRequired(bodySchema), \"modal\", enums, fieldMap, true);\n });\n\n return [...fieldMap.values()];\n}\n\n/** 表格列字段来自列表接口响应里的 records/items schema。 */\nexport function buildTableFields(document: OpenApiDocument, operation: NormalizedOperation, enums: Map<string, GeneratedEnum>): GeneratedField[] {\n const recordSchema = inferRecordSchema(document, operation);\n const fieldMap = new Map<string, GeneratedField>();\n appendSchemaFields(document, recordSchema, getSchemaRequired(recordSchema), \"table\", enums, fieldMap, false);\n return [...fieldMap.values()];\n}\n\n/**\n * 收集所有接口里出现过的枚举。\n *\n * Apifox 会把枚举中文名放在 x-apifox-enum 中,生成器优先使用它;\n * 如果没有 x-apifox-enum,则退回 OpenAPI 标准 enum 值。\n */\nexport function collectEnumsFromOperations(document: OpenApiDocument, operations: NormalizedOperation[]): Map<string, GeneratedEnum> {\n const enums = new Map<string, GeneratedEnum>();\n\n operations.forEach(operation => {\n getQueryParameters(operation).forEach(parameter => {\n const schema = resolveSchema(document, parameter.schema);\n collectEnum(parameter.name, schema, enums);\n });\n\n const bodySchema = getRequestBodySchema(document, operation);\n Object.entries(getSchemaProperties(document, bodySchema)).forEach(([name, schema]) => collectEnum(name, schema, enums));\n });\n\n return enums;\n}\n\nexport function isPaginationField(name: string): boolean {\n return PAGE_FIELD_NAMES.has(name) || PAGE_SIZE_FIELD_NAMES.has(name);\n}\n\nexport function getPageParamName(operation: NormalizedOperation): string {\n return getQueryParameters(operation).find(parameter => PAGE_FIELD_NAMES.has(parameter.name))?.name || \"currentPage\";\n}\n\nexport function getPageSizeParamName(operation: NormalizedOperation): string {\n return getQueryParameters(operation).find(parameter => PAGE_SIZE_FIELD_NAMES.has(parameter.name))?.name || \"pageSize\";\n}\n\nfunction appendSchemaFields(\n document: OpenApiDocument,\n schema: OpenApiSchema | undefined,\n requiredFields: string[],\n source: GeneratedField[\"source\"],\n enums: Map<string, GeneratedEnum>,\n fieldMap: Map<string, GeneratedField>,\n skipId: boolean,\n): void {\n Object.entries(getSchemaProperties(document, schema)).forEach(([name, property]) => {\n if (skipId && ID_FIELD_NAMES.has(name)) return;\n const resolved = resolveSchema(document, property);\n collectEnum(name, resolved, enums);\n fieldMap.set(name, fieldFromSchema(name, resolved, requiredFields.includes(name), source, enums));\n });\n}\n\n/** 将 OpenAPI parameter 转成表单/表格都能使用的中立字段模型。 */\nfunction fieldFromParameter(\n document: OpenApiDocument,\n parameter: OpenApiParameter,\n source: GeneratedField[\"source\"],\n enums: Map<string, GeneratedEnum>,\n): GeneratedField {\n const schema = resolveSchema(document, parameter.schema);\n collectEnum(parameter.name, schema, enums);\n\n return {\n name: parameter.name,\n label: normalizeLabel(parameter.description || schema?.title || schema?.description, parameter.name),\n tsType: schemaToTsType(schema),\n required: Boolean(parameter.required),\n // 同名字段可能只有新增接口声明了枚举,列表接口没声明;只要统一枚举表里有,就按 select 生成。\n component: inferComponent(parameter.name, schema, parameter.description, enums.has(parameter.name)),\n description: parameter.description || schema?.description,\n enumName: enums.get(parameter.name)?.textMapName,\n source,\n };\n}\n\n/** 将 response/requestBody schema property 转成中立字段模型。 */\nfunction fieldFromSchema(\n name: string,\n schema: OpenApiSchema | undefined,\n required: boolean,\n source: GeneratedField[\"source\"],\n enums: Map<string, GeneratedEnum>,\n): GeneratedField {\n return {\n name,\n label: normalizeLabel(schema?.title || schema?.description, name),\n tsType: schemaToTsType(schema),\n required,\n component: inferComponent(name, schema, schema?.description || schema?.title, enums.has(name)),\n description: schema?.description,\n enumName: enums.get(name)?.textMapName,\n source,\n };\n}\n\n/** 从 OpenAPI enum 或 Apifox 扩展枚举生成 CrudOption 数据源。 */\nfunction collectEnum(name: string, schema: OpenApiSchema | undefined, enums: Map<string, GeneratedEnum>): void {\n if (!schema || enums.has(name)) return;\n\n const apifoxEnum = schema[\"x-apifox-enum\"];\n const values = apifoxEnum?.length\n ? apifoxEnum.map(item => ({ label: item.name || String(item.value), value: item.value }))\n : schema.enum?.map(value => ({ label: String(value), value }));\n\n if (!values || values.length === 0) return;\n\n const baseName = toCamelCase(name);\n enums.set(name, {\n optionsName: `${baseName}Options`,\n textMapName: `${baseName}TextMap`,\n sourceField: name,\n options: values,\n });\n}\n\n/**\n * 根据字段名、描述和 schema 类型推断表单控件。\n *\n * 这只是初始化脚手架的默认值,不追求替代业务判断;\n * 生成后的 config/searchForm.tsx 和 config/modalForm.tsx 都是给业务继续改的。\n */\nfunction inferComponent(name: string, schema: OpenApiSchema | undefined, description?: string, hasCollectedEnum = false): GeneratedField[\"component\"] {\n if (hasCollectedEnum || schema?.enum?.length || schema?.[\"x-apifox-enum\"]?.length) return \"select\";\n\n const lowerName = name.toLowerCase();\n const text = `${name} ${description || \"\"}`;\n\n if (/remark|desc|description|备注|描述|说明/.test(text)) return \"textarea\";\n if (/time|date|createdAt|updatedAt|开始时间|结束时间|日期/.test(text)) return \"datePicker\";\n if (isNumberSchema(schema)) return \"inputNumber\";\n if (isBooleanSchema(schema)) return \"switch\";\n if (lowerName.includes(\"status\") || lowerName.includes(\"type\")) return \"select\";\n\n return \"input\";\n}\n\n/** 把 OpenAPI 基础类型转换成生成页里的 TypeScript 字段类型。 */\nfunction schemaToTsType(schema: OpenApiSchema | undefined): string {\n if (!schema) return \"unknown\";\n const type = Array.isArray(schema.type) ? schema.type.filter(item => item !== \"null\")[0] : schema.type;\n\n if (type === \"integer\" || type === \"number\") return \"number\";\n if (type === \"boolean\") return \"boolean\";\n if (type === \"array\") return \"unknown[]\";\n if (type === \"object\") return \"Record<string, unknown>\";\n return \"string\";\n}\n\nfunction isNumberSchema(schema: OpenApiSchema | undefined): boolean {\n const type = Array.isArray(schema?.type) ? schema?.type : [schema?.type];\n return type.includes(\"number\") || type.includes(\"integer\");\n}\n\nfunction isBooleanSchema(schema: OpenApiSchema | undefined): boolean {\n const type = Array.isArray(schema?.type) ? schema?.type : [schema?.type];\n return type.includes(\"boolean\");\n}\n","import { readFile } from \"node:fs/promises\";\nimport { resolve } from \"node:path\";\nimport type { GenerateCrudPageOptions, OpenApiDocument } from \"./types\";\n\nconst MAX_RETRY_LIMIT = 5;\n\n/**\n * 读取 OpenAPI 文档。\n *\n * 支持三种来源:\n * - URL:适合 Agent 插件直接传接口平台导出地址。\n * - 本地文件路径:适合测试 fixture 和离线生成。\n * - 已解析对象:适合上层插件提前缓存或改写 OpenAPI 后再传入。\n */\nexport async function loadOpenApiDocument(options: Pick<GenerateCrudPageOptions, \"openapi\" | \"cwd\" | \"maxRetries\">): Promise<OpenApiDocument> {\n if (typeof options.openapi !== \"string\") return options.openapi;\n\n const content = isHttpUrl(options.openapi)\n ? await readRemoteOpenApi(options.openapi, options.maxRetries)\n : await readFile(resolve(options.cwd || \".\", options.openapi), \"utf8\");\n\n return parseOpenApiContent(content);\n}\n\n/** 首版只解析 JSON,避免为了 YAML 引入额外依赖,也贴合当前 Apifox 导出地址。 */\nfunction parseOpenApiContent(content: string): OpenApiDocument {\n const trimmed = content.trim();\n\n if (trimmed.startsWith(\"{\")) {\n return JSON.parse(trimmed) as OpenApiDocument;\n }\n\n throw new Error(\"当前生成器首版只解析 JSON OpenAPI 文档,请先在接口平台导出 JSON。\");\n}\n\nfunction isHttpUrl(value: string): boolean {\n return /^https?:\\/\\//i.test(value);\n}\n\nasync function readRemoteOpenApi(url: string, maxRetries = 2): Promise<string> {\n // 严格限制重试次数,避免接口平台限流时被生成器连续请求打到 429。\n const retryCount = Math.min(Math.max(maxRetries, 0), MAX_RETRY_LIMIT);\n let lastError: unknown;\n\n for (let attempt = 0; attempt <= retryCount; attempt += 1) {\n try {\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(`下载 OpenAPI 文档失败:${response.status} ${response.statusText}`);\n }\n return response.text();\n } catch (error) {\n lastError = error;\n if (attempt >= retryCount) break;\n }\n }\n\n const message = lastError instanceof Error ? lastError.message : String(lastError);\n throw new Error(`读取 OpenAPI 文档失败,已按限制停止重试:${message}`);\n}\n","import type {\n CrudOperationGroup,\n CrudOperationKind,\n GenerateCrudPageOptions,\n NormalizedOperation,\n OpenApiDocument,\n OpenApiHttpMethod,\n} from \"./types\";\nimport { getLastTagSegment, inferEntityNameFromPath, stripKnownActionWords, toPascalCase } from \"./string-utils\";\n\nconst HTTP_METHODS: OpenApiHttpMethod[] = [\"get\", \"post\", \"put\", \"patch\", \"delete\"];\n\n/**\n * 从 OpenAPI 文档中识别一个 CRUD 页面所需的接口组。\n *\n * 当前接口平台可能没有 operationId,也可能所有接口都挂在同一个 tag 下,\n * 所以这里优先使用 tag 做模块边界,再用 path/summary 里的中英文关键词识别查询、新增、编辑、删除。\n */\nexport function buildOperationGroup(document: OpenApiDocument, options: GenerateCrudPageOptions): CrudOperationGroup {\n const operations = collectOperations(document);\n if (operations.length === 0) {\n throw new Error(\"OpenAPI 文档中没有可用于生成 CRUD 页面的接口。\");\n }\n\n // 不传 tag 时默认选择接口数量最多的 tag,符合“一个接口文档生成一个页面”的首版目标。\n const tag = options.tag || inferPrimaryTag(operations);\n const selected = operations.filter(operation => operation.tags.includes(tag));\n const scopedOperations = selected.length > 0 ? selected : operations;\n const pageTitle = options.pageTitle || inferPageTitle(tag, document);\n const operationMap = options.operationMap || {};\n // 列表接口是 CRUD 页面的最小必需项,识别不到时先退回当前模块第一个接口,避免完全不可用。\n const list = findOperation(\"list\", scopedOperations, operationMap) || scopedOperations[0];\n\n if (!list) {\n throw new Error(\"无法识别列表接口,请通过 operationMap.list 显式指定。\");\n }\n\n // 实体名优先从接口路径推断,例如 /web/dict/queryDictTypeList.json -> DictType。\n const entityName = toPascalCase(\n options.entityName ||\n inferEntityNameFromPath(list.path) ||\n stripKnownActionWords(list.operationId) ||\n pageTitle ||\n \"Generated\",\n );\n\n return {\n tag,\n pageTitle,\n entityName,\n list,\n create: findOperation(\"create\", scopedOperations, operationMap),\n update: findOperation(\"update\", scopedOperations, operationMap),\n delete: findOperation(\"delete\", scopedOperations, operationMap),\n };\n}\n\n/** 将 paths 里的各种 method 展平成统一 operation 列表,方便后续打分和过滤。 */\nfunction collectOperations(document: OpenApiDocument): NormalizedOperation[] {\n const operations: NormalizedOperation[] = [];\n\n Object.entries(document.paths || {}).forEach(([apiPath, pathItem]) => {\n HTTP_METHODS.forEach(method => {\n const operation = pathItem?.[method];\n if (!operation) return;\n\n const parameters = [...(pathItem.parameters || []), ...(operation.parameters || [])];\n // 很多 Apifox 导出的文档没有 operationId,这里生成稳定兜底值,后续 operationMap 也能引用。\n const operationId = operation.operationId || `${method}_${apiPath.replace(/[^a-zA-Z0-9]+/g, \"_\")}`;\n\n operations.push({\n method,\n path: apiPath,\n operation,\n operationId,\n summary: operation.summary || operation.description || operationId,\n tags: operation.tags || [],\n parameters,\n });\n });\n });\n\n return operations;\n}\n\n/** 多 tag 文档默认选 operation 数量最多的 tag;如果完全没有 tag,则落到“默认模块”。 */\nfunction inferPrimaryTag(operations: NormalizedOperation[]): string {\n const tagCount = new Map<string, number>();\n\n operations.forEach(operation => {\n operation.tags.forEach(tag => {\n tagCount.set(tag, (tagCount.get(tag) || 0) + 1);\n });\n });\n\n const [tag] = [...tagCount.entries()].sort((left, right) => right[1] - left[1])[0] || [];\n return tag || \"默认模块\";\n}\n\n/** 页面标题用 tag 最后一段,例:开发中心/字典管理 -> 字典管理。 */\nfunction inferPageTitle(tag: string, document: OpenApiDocument): string {\n if (tag && tag !== \"默认模块\") return getLastTagSegment(tag);\n return document.info?.title || \"生成页面\";\n}\n\n/**\n * 查找指定 CRUD 类型的接口。\n *\n * operationMap 的优先级最高,用来解决接口命名很特殊时的误判;\n * 未传 operationMap 时再走关键词打分。\n */\nfunction findOperation(\n kind: CrudOperationKind,\n operations: NormalizedOperation[],\n operationMap: Partial<Record<CrudOperationKind, string>>,\n): NormalizedOperation | undefined {\n const mapped = operationMap[kind];\n if (mapped) return operations.find(operation => isSameOperation(operation, mapped));\n\n return operations\n .map(operation => ({ operation, score: scoreOperation(kind, operation) }))\n .filter(item => item.score > 0)\n .sort((left, right) => right.score - left.score)[0]?.operation;\n}\n\n/** operationMap 支持三种写法:operationId、path、METHOD path。 */\nfunction isSameOperation(operation: NormalizedOperation, value: string): boolean {\n const normalized = value.trim().toLowerCase();\n return (\n operation.operationId.toLowerCase() === normalized ||\n operation.path.toLowerCase() === normalized ||\n `${operation.method} ${operation.path}`.toLowerCase() === normalized\n );\n}\n\n/** 对 path、summary、operationId 做关键词打分,兼容中英文接口命名。 */\nfunction scoreOperation(kind: CrudOperationKind, operation: NormalizedOperation): number {\n const source = `${operation.operationId} ${operation.path} ${operation.summary}`.toLowerCase();\n const chineseSource = `${operation.summary} ${operation.path}`;\n\n if (kind === \"list\") {\n return scoreByKeywords(source, chineseSource, [\"query\", \"list\", \"page\", \"search\"], [\"查询\", \"列表\", \"分页\"]);\n }\n\n if (kind === \"create\") {\n return scoreByKeywords(source, chineseSource, [\"add\", \"create\", \"save\"], [\"新增\", \"添加\", \"创建\"]);\n }\n\n if (kind === \"update\") {\n return scoreByKeywords(source, chineseSource, [\"edit\", \"update\", \"modify\"], [\"编辑\", \"修改\", \"更新\"]);\n }\n\n return scoreByKeywords(source, chineseSource, [\"delete\", \"remove\", \"del\"], [\"删除\", \"移除\"]);\n}\n\nfunction scoreByKeywords(source: string, chineseSource: string, englishWords: string[], chineseWords: string[]): number {\n let score = 0;\n\n englishWords.forEach(word => {\n if (source.includes(word)) score += 10;\n });\n\n chineseWords.forEach(word => {\n if (chineseSource.includes(word)) score += 10;\n });\n\n return score;\n}\n","import type { CrudOperationKind, CrudPageModel, GeneratedEnum, GeneratedField, NormalizedOperation, RequestRenderContext } from \"./types\";\nimport { getPageParamName, getPageSizeParamName } from \"./fields\";\nimport { indent, quoteString } from \"./string-utils\";\n\n/** 渲染最终页面目录的七个文件,目录结构对齐 examples/vue3-consumer/src/views/CrudDemoPage。 */\nexport function renderFiles(model: CrudPageModel): Array<{ path: string; content: string }> {\n return [\n { path: \"index.vue\", content: renderIndexVue(model) },\n { path: \"api/index.ts\", content: renderApiIndex(model) },\n { path: \"api/types.ts\", content: renderApiTypes(model) },\n { path: \"config/enums.ts\", content: renderEnums(model) },\n { path: \"config/searchForm.tsx\", content: renderSearchForm(model) },\n { path: \"config/table.tsx\", content: renderTable(model) },\n { path: \"config/modalForm.tsx\", content: renderModalForm(model) },\n ];\n}\n\n/**\n * 渲染页面入口 index.vue。\n *\n * 这个文件只做页面编排:创建 pageContext、actions、searchForm、modalForm、table,\n * 让生成后的业务代码保持直观,使用者不需要先理解一个黑盒 CRUD 配置对象。\n */\nfunction renderIndexVue(model: CrudPageModel): string {\n const createCall = model.operations.create ? `await create${model.entityName}(data);` : \"console.warn(\\\"当前 OpenAPI 文档未提供新增接口,请在这里补充新增逻辑。\\\", data);\";\n const updateCall = model.operations.update ? `await update${model.entityName}(data);` : \"console.warn(\\\"当前 OpenAPI 文档未提供编辑接口,请在这里补充编辑逻辑。\\\", data);\";\n const apiImports = [\n `query${model.entityName}List`,\n model.operations.create ? `create${model.entityName}` : \"\",\n model.operations.update ? `update${model.entityName}` : \"\",\n ].filter(Boolean);\n const pageParamName = getPageParamName(model.operations.list);\n const pageSizeParamName = getPageSizeParamName(model.operations.list);\n\n return `<template>\n <section class=\"demo-route-panel\">\n <AutoCrud\n title=${quoteString(model.pageTitle)}\n :search-form-config=\"searchForm\"\n :table-config=\"table\"\n :modal-form-config=\"modalForm\"\n />\n </section>\n</template>\n\n<script setup lang=\"ts\">\nimport { reactive } from \"vue\";\nimport { AutoCrud } from \"auto-gen-crud\";\nimport { ${apiImports.join(\", \")} } from \"./api\";\nimport { getModalConfig } from \"./config/modalForm\";\nimport { getSearchConfig } from \"./config/searchForm\";\nimport { getTableConfig } from \"./config/table\";\nimport type { ActionsType, PageContextType, RowType } from \"./api/types\";\n\n// 页面行为统一放在 actions 中,配置文件只消费动作,不反向引用页面变量。\nconst actions: ActionsType = {\n onSubmit,\n onEdit: row => {\n modalForm.openEdit(row);\n },\n onView: row => {\n modalForm.openView(row);\n },\n getDataList,\n};\n\n// 页面上下文默认保持为空,业务二开时再按权限、联动状态或页面缓存逐步补充。\nconst pageContext = reactive<PageContextType>({});\n\nconst searchForm = getSearchConfig();\nconst modalForm = getModalConfig(pageContext, actions);\nconst table = getTableConfig(pageContext, actions);\n\nasync function onSubmit(data: RowType) {\n // 新增和编辑共用弹窗表单,通过 mode 区分最终调用哪个接口。\n if (modalForm.mode === \"edit\") {\n ${updateCall}\n } else {\n ${createCall}\n }\n\n modalForm.close();\n await table.load();\n}\n\nasync function getDataList() {\n const { page, pageSize } = table.pagination;\n\n // 搜索条件整体透传,避免生成器为每个查询字段重复展开,业务二开时也更容易增删字段。\n const result = await query${model.entityName}List({\n ${pageParamName}: page,\n ${pageSizeParamName}: pageSize,\n ...searchForm.data,\n });\n\n table.data = result.${model.responsePath.listPath};\n table.pagination.total = result.${model.responsePath.totalPath};\n}\n</script>\n`;\n}\n\n/** 渲染 api/index.ts,所有请求函数都由 OpenAPI operation 生成。 */\nfunction renderApiIndex(model: CrudPageModel): string {\n const typeImports = [\n \"ListResponse\",\n \"SearchParamsType\",\n model.operations.create || model.operations.update ? \"SaveParams\" : \"\",\n model.operations.delete ? \"DeleteParams\" : \"\",\n ].filter(Boolean);\n const blocks = [\n renderOperationFunction(model, \"list\", model.operations.list),\n model.operations.create ? renderOperationFunction(model, \"create\", model.operations.create) : \"\",\n model.operations.update ? renderOperationFunction(model, \"update\", model.operations.update) : \"\",\n model.operations.delete ? renderOperationFunction(model, \"delete\", model.operations.delete) : \"\",\n ].filter(Boolean);\n\n return `${model.requestAdapter.importCode}\nimport type { ${typeImports.join(\", \")} } from \"./types\";\n\n${blocks.join(\"\\n\\n\")}\n`;\n}\n\n/** 根据 CRUD 类型生成稳定函数名和参数名,例如 queryDictTypeList/createDictType。 */\nfunction renderOperationFunction(model: CrudPageModel, kind: CrudOperationKind, operation: NormalizedOperation): string {\n const functionName = getFunctionName(model.entityName, kind);\n const payloadName = kind === \"list\" ? \"params\" : kind === \"delete\" ? \"params\" : \"data\";\n const payloadType = kind === \"list\" ? \"SearchParamsType\" : kind === \"delete\" ? \"DeleteParams\" : \"SaveParams\";\n const responseType = kind === \"list\" ? \"ListResponse\" : \"unknown\";\n const ctx: RequestRenderContext = {\n functionName,\n method: operation.method,\n path: operation.path,\n payloadName,\n responseType,\n };\n\n return `export function ${functionName}(${payloadName}: ${payloadType}) {\n ${renderIndentedRequestCall(model, ctx)}\n}`;\n}\n\n/** 将项目级 requestAdapter 的返回值嵌入函数体,避免生成器写死某个项目的 request 签名。 */\nfunction renderIndentedRequestCall(model: CrudPageModel, ctx: RequestRenderContext): string {\n return model.requestAdapter.renderCall(ctx).trim();\n}\n\n/** 渲染 api/types.ts,所有类型都放在页面目录内部,避免生成相对路径硬编码。 */\nfunction renderApiTypes(model: CrudPageModel): string {\n return `${renderInterfaceComment(`${model.pageTitle}表格与表单共用数据结构。字段保持可选,方便新增、编辑表单复用同一份页面数据。`)}\nexport interface RowType {\n${indent(renderRowFields(model.tableFields), 2)}\n [key: string]: unknown;\n}\n\n${renderInterfaceComment(\"新增、修改接口提交体。生成器默认复用 RowType,业务可按真实接口继续收窄字段。\")}\nexport interface SaveParams extends RowType {}\n\n${renderInterfaceComment(\"删除接口参数。默认保留 id,若后端批量删除可在这里扩展 ids。\")}\nexport interface DeleteParams {\n id: number | string;\n [key: string]: unknown;\n}\n\n${renderInterfaceComment(\"分页接口原始响应结构,字段路径按当前 OpenAPI 响应自动生成。\")}\nexport interface ListResponse {\n${indent(renderResponseType(model), 2)}\n [key: string]: unknown;\n}\n\n${renderInterfaceComment(\"搜索表单数据,字段名与查询接口保持一致。\")}\nexport type SearchParamsType = Record<string, unknown> & {\n${indent(renderSearchTypeFields(model), 2)}\n};\n\n${renderInterfaceComment(\"页面操作函数集合。由页面统一组装后传给表格和弹窗配置,避免配置文件之间互相引用。\")}\nexport interface ActionsType {\n onSubmit: (data: RowType) => void | Promise<void>;\n onEdit: (row: RowType) => void;\n onView: (row: RowType) => void;\n getDataList: () => void | Promise<void>;\n}\n\n${renderInterfaceComment(\"页面级上下文默认不预设字段,业务二开时再补充权限、枚举、联动状态或页面缓存。\")}\nexport type PageContextType = Record<string, unknown>;\n`;\n}\n\n/** 渲染 config/enums.ts,枚举字段来自 OpenAPI enum 或 x-apifox-enum。 */\nfunction renderEnums(model: CrudPageModel): string {\n const { enums } = model;\n\n if (enums.length === 0) {\n return `// 当前 OpenAPI 文档没有枚举字段,预留空模块便于后续业务补充下拉选项。\nexport {};\n`;\n }\n\n const importCode = model.generateTypeOptions ? 'import type { CrudOption } from \"auto-gen-crud\";\\n\\n' : \"\";\n\n return `${importCode}${enums.map(enumItem => renderEnum(enumItem, model.generateTypeOptions)).join(\"\\n\\n\")}\n`;\n}\n\n/** 渲染搜索表单配置,字段只来自列表接口 query 参数。 */\nfunction renderSearchForm(model: CrudPageModel): string {\n return `import { useSearchForm } from \"auto-gen-crud\";\n${renderEnumImportForFields(model.searchFields)}import type { SearchParamsType } from \"../api/types\";\n\n// 搜索表单配置只描述字段,不直接关心列表接口和表格状态。\nexport function getSearchConfig() {\n return useSearchForm<SearchParamsType>({\n columns: [\n${indent(model.searchFields.map(renderFormField).join(\",\\n\"), 6)}\n ],\n });\n}\n`;\n}\n\n/** 渲染表格配置,字段来自列表响应 records/items。 */\nfunction renderTable(model: CrudPageModel): string {\n return `import { useTable } from \"auto-gen-crud\";\nimport type { ActionsType, PageContextType, RowType } from \"../api/types\";\n\n// 表格配置只负责列展示和操作入口,真实请求由页面层 actions.getDataList 处理。\nexport function getTableConfig(pageContext: PageContextType, actions: ActionsType) {\n return useTable<RowType>({\n columns: [\n${indent(model.tableFields.map(renderTableColumn).join(\",\\n\"), 6)},\n {\n title: \"操作\",\n width: \"200px\",\n fixed: \"right\",\n actions: [\n {\n key: \"edit\",\n label: \"编辑\",\n type: \"primary\",\n show: () => Boolean(pageContext.canEdit${model.permissionSuffix} ?? true),\n onClick: actions.onEdit,\n },\n {\n key: \"view\",\n label: \"查看\",\n type: \"link\",\n show: () => Boolean(pageContext.canView${model.permissionSuffix} ?? true),\n onClick: actions.onView,\n },\n ],\n },\n ],\n data: [],\n getDataList: actions.getDataList,\n });\n}\n`;\n}\n\n/** 渲染弹窗表单配置,字段来自新增/编辑接口参数或 requestBody。 */\nfunction renderModalForm(model: CrudPageModel): string {\n return `import { useModalForm } from \"auto-gen-crud\";\n${renderEnumImportForFields(model.modalFields)}import type { ActionsType, PageContextType, RowType } from \"../api/types\";\n\n// 弹窗表单配置只描述新增、编辑、查看共用字段,提交动作交给页面层处理。\nexport function getModalConfig(pageContext: PageContextType, actions: ActionsType) {\n // 保留 pageContext 参数,便于后续按权限、状态或枚举动态控制字段。\n void pageContext;\n\n return useModalForm<RowType>({\n columns: [\n${indent(model.modalFields.map(renderFormField).join(\",\\n\"), 6)}\n ],\n onSubmit: actions.onSubmit,\n });\n}\n`;\n}\n\nfunction renderNamedImport(names: string[]): string {\n return `{ ${names.join(\", \")} }`;\n}\n\n/** RowType 字段默认可选,避免新增表单被列表响应里的只读字段卡住。 */\nfunction renderRowFields(fields: GeneratedField[]): string {\n if (fields.length === 0) return \"id?: number | string;\\n\";\n\n return fields.map(field => {\n return `/** ${field.label} */\\n${field.name}?: ${field.tsType};`;\n }).join(\"\\n\");\n}\n\n/** 搜索类型保留分页字段,方便 queryList 入参完整描述接口参数。 */\nfunction renderSearchTypeFields(model: CrudPageModel): string {\n const pageParamName = getPageParamName(model.operations.list);\n const pageSizeParamName = getPageSizeParamName(model.operations.list);\n const fields = [\n `${pageParamName}?: number;`,\n `${pageSizeParamName}?: number;`,\n ...model.searchFields.map(field => `${field.name}?: ${field.tsType};`),\n ];\n\n return fields.join(\"\\n\");\n}\n\n/** 当前先为常见分页结构生成精确类型,其它结构保留路径注释和 Record 兜底。 */\nfunction renderResponseType(model: CrudPageModel): string {\n if (model.responsePath.listPath === \"result.records\" && model.responsePath.totalPath === \"result.total\") {\n return `result: {\n records: RowType[];\n total: number;\n [key: string]: unknown;\n};`;\n }\n\n return `/** 列表字段路径:${model.responsePath.listPath},总数字段路径:${model.responsePath.totalPath}。 */\nresult: Record<string, unknown>;\ndata?: Record<string, unknown>;`;\n}\n\n/** 表单字段配置只输出中立字段,具体 UI 组件由 auto-gen-crud adapter 负责。 */\nfunction renderFormField(field: GeneratedField): string {\n const props = [\n `label: ${quoteString(field.label)}`,\n `prop: ${quoteString(field.name)}`,\n `component: ${quoteString(field.component)}`,\n ];\n\n if (field.required) props.push(\"required: true\");\n if (field.enumName) props.push(`options: ${field.enumName}`);\n\n return `{ ${props.join(\", \")} }`;\n}\n\n/** 表格列配置保持最小可读,日期字段自动补 dateTime 默认展示格式。 */\nfunction renderTableColumn(field: GeneratedField): string {\n const props = [\n `title: ${quoteString(field.label)}`,\n `field: ${quoteString(field.name)}`,\n ];\n\n if (/time|date|createdAt|updatedAt/i.test(field.name)) props.push(`dateTime: \"YYYY-MM-DD HH:mm\"`);\n return `{ ${props.join(\", \")} }`;\n}\n\n/** 将 OpenAPI 枚举渲染成 textMap;TypeOptions 只有开启配置后才额外输出。 */\nfunction renderEnum(item: GeneratedEnum, generateTypeOptions: boolean): string {\n const options = item.options\n .map(option => ` { label: ${quoteString(option.label)}, value: ${JSON.stringify(option.value)} },`)\n .join(\"\\n\");\n\n const textMap = item.options\n .map(option => ` ${JSON.stringify(option.value)}: ${quoteString(option.label)},`)\n .join(\"\\n\");\n\n const optionsBlock = generateTypeOptions\n ? `export const ${item.optionsName}: CrudOption[] = [\n${options}\n];\n\n`\n : \"\";\n\n return `${optionsBlock}export const ${item.textMapName}: Record<string, string> = {\n${textMap}\n};`;\n}\n\n/** 只导入当前配置文件实际用到的枚举,降低生成代码的 lint 噪音。 */\nfunction renderEnumImportForFields(fields: GeneratedField[]): string {\n const names = [...new Set(fields.map(field => field.enumName).filter(Boolean))] as string[];\n if (names.length === 0) return \"\";\n return `import ${renderNamedImport(names)} from \"./enums\";\\n`;\n}\n\nfunction getFunctionName(entityName: string, kind: CrudOperationKind): string {\n if (kind === \"list\") return `query${entityName}List`;\n if (kind === \"create\") return `create${entityName}`;\n if (kind === \"update\") return `update${entityName}`;\n return `delete${entityName}`;\n}\n\nfunction renderInterfaceComment(text: string): string {\n return `/** ${text} */`;\n}\n","import { access, mkdir, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport type { GeneratedFile } from \"./types\";\n\n/**\n * 写入生成文件。\n *\n * overwrite 默认关闭,避免 Agent 插件误覆盖开发者已经二开过的页面;\n * 需要重新生成时由调用方显式传 overwrite: true。\n */\nexport async function writeGeneratedFiles(outDir: string, files: GeneratedFile[], overwrite = false): Promise<void> {\n for (const file of files) {\n await writeGeneratedFile(join(outDir, file.path), file.content, overwrite);\n }\n}\n\n/** 单文件写入会自动创建父目录,并统一补结尾换行。 */\nasync function writeGeneratedFile(filePath: string, content: string, overwrite: boolean): Promise<void> {\n if (!overwrite && await exists(filePath)) {\n throw new Error(`生成文件已存在:${filePath}。如需覆盖请传入 overwrite: true。`);\n }\n\n await mkdir(dirname(filePath), { recursive: true });\n await writeFile(filePath, ensureTrailingNewline(content), \"utf8\");\n}\n\nasync function exists(filePath: string): Promise<boolean> {\n try {\n await access(filePath);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction ensureTrailingNewline(content: string): string {\n return content.endsWith(\"\\n\") ? content : `${content}\\n`;\n}\n","import { isAbsolute, join, resolve } from \"node:path\";\nimport { buildModalFields, buildSearchFields, buildTableFields, collectEnumsFromOperations } from \"./fields\";\nimport { loadOpenApiDocument } from \"./openapi\";\nimport { buildOperationGroup } from \"./operations\";\nimport { inferResponsePath } from \"./schema\";\nimport { renderFiles } from \"./templates\";\nimport { toCamelCase } from \"./string-utils\";\nimport { writeGeneratedFiles } from \"./writer\";\nimport type {\n CrudPageModel,\n GenerateCrudPageOptions,\n GenerateCrudPageResult,\n GeneratedFile,\n RequestAdapterOptions,\n RequestRenderContext,\n} from \"./types\";\n\nexport type {\n CrudOperationKind,\n GenerateCrudPageOptions,\n GenerateCrudPageResult,\n GeneratedFile,\n OpenApiDocument,\n RequestAdapterOptions,\n RequestRenderContext,\n} from \"./types\";\n\n/**\n * 根据 OpenAPI 3.1 文档生成一个可二次开发的 CRUD 页面目录。\n *\n * 使用方式:\n * - 最简单:`generateCrudPage(openapiUrl)`,生成器自动识别 tag、标题、实体名和输出目录。\n * - 进阶:`generateCrudPage({ openapi, outDir, operationMap })`,当接口文档不够规范时手动覆盖关键推断。\n *\n * 这里是生成器总编排层,只负责串起“读取文档 -> 推断模型 -> 渲染文件 -> 写入磁盘”四步,\n * 具体推断规则分散在 operations/fields/schema/templates 中,避免入口函数变成难维护的大杂烩。\n */\nexport async function generateCrudPage(openapi: string): Promise<GenerateCrudPageResult>;\nexport async function generateCrudPage(options: GenerateCrudPageOptions): Promise<GenerateCrudPageResult>;\nexport async function generateCrudPage(input: string | GenerateCrudPageOptions): Promise<GenerateCrudPageResult> {\n const options = normalizeOptions(input);\n const document = await loadOpenApiDocument(options);\n const operationGroup = buildOperationGroup(document, options);\n const outDir = resolveOutDir(options.cwd || \".\", options.outDir, operationGroup.entityName);\n const operations = [operationGroup.list, operationGroup.create, operationGroup.update, operationGroup.delete].filter(isDefined);\n const enumMap = collectEnumsFromOperations(document, operations);\n const searchFields = buildSearchFields(document, operationGroup.list, enumMap);\n const modalFields = buildModalFields(document, [operationGroup.create, operationGroup.update], enumMap);\n const tableFields = buildTableFields(document, operationGroup.list, enumMap);\n // CrudPageModel 是模板层唯一输入,模板不再直接读 OpenAPI,后续想换模板时不会牵动解析逻辑。\n const model: CrudPageModel = {\n tag: operationGroup.tag,\n pageTitle: operationGroup.pageTitle,\n entityName: operationGroup.entityName,\n entityVariable: toCamelCase(operationGroup.entityName),\n permissionSuffix: operationGroup.entityName,\n outDir,\n operations: operationGroup,\n searchFields,\n // 如果文档只有查询接口,也生成一个能跑起来的弹窗表单,业务后续再按真实新增/编辑接口调整。\n modalFields: modalFields.length > 0 ? modalFields : searchFields,\n tableFields,\n enums: [...enumMap.values()],\n generateTypeOptions: Boolean(options.generateTypeOptions),\n responsePath: inferResponsePath(document, operationGroup.list),\n requestAdapter: normalizeRequestAdapter(options.requestAdapter),\n };\n\n const files = renderFiles(model).map<GeneratedFile>(file => ({\n path: file.path,\n content: file.content,\n }));\n\n if (!options.dryRun) {\n await writeGeneratedFiles(outDir, files, options.overwrite);\n }\n\n return {\n tag: model.tag,\n pageTitle: model.pageTitle,\n entityName: model.entityName,\n outDir,\n files,\n dryRun: Boolean(options.dryRun),\n };\n}\n\nfunction normalizeOptions(input: string | GenerateCrudPageOptions): GenerateCrudPageOptions {\n if (typeof input === \"string\") {\n return { openapi: input };\n }\n\n return input;\n}\n\nfunction isDefined<T>(value: T | undefined): value is T {\n return value !== undefined;\n}\n\n/** 默认输出到当前项目 src/views/<EntityName>Page,方便“只传接口文档地址”直接生成页面。 */\nfunction resolveOutDir(cwd: string, outDir: string | undefined, entityName: string): string {\n const target = outDir || join(\"src\", \"views\", `${entityName}Page`);\n return isAbsolute(target) ? target : resolve(cwd, target);\n}\n\n/**\n * 请求层适配器。\n *\n * 不同项目的请求工具签名可能完全不同,所以生成器只内置一个示例项目默认值,\n * 真正接入其它项目时可以通过 requestAdapter.importCode/renderCall 覆盖。\n */\nfunction normalizeRequestAdapter(adapter?: RequestAdapterOptions): Required<RequestAdapterOptions> {\n return {\n importCode: adapter?.importCode || 'import { http } from \"@/utils/http\";',\n renderCall: adapter?.renderCall || defaultRenderCall,\n };\n}\n\n/** 默认适配 examples/vue3-consumer/src/utils/http.ts 的 http.get/post/put/delete 写法。 */\nfunction defaultRenderCall(ctx: RequestRenderContext): string {\n const method = ctx.method.toLowerCase();\n\n if (method === \"get\" || method === \"delete\") {\n return `return http.${method}<${ctx.responseType}>(${JSON.stringify(ctx.path)}, { query: ${ctx.payloadName} });`;\n }\n\n return `return http.${method}<${ctx.responseType}, typeof ${ctx.payloadName}>(${JSON.stringify(ctx.path)}, ${ctx.payloadName});`;\n}\n"],"names":["resolveSchema","document","schema","name","mergeObjectSchemas","getQueryParameters","operation","parameter","getRequestBodySchema","content","getSuccessResponseSchema","responses","inferRecordSchema","responseSchema","recordSchema","findArrayItemSchema","inferResponsePath","found","findArrayPath","findTotalPath","getSchemaProperties","getSchemaRequired","schemas","result","item","resolved","isArraySchema","properties","key","getOrderedKeys","child","prefix","childPath","parentPath","getSchemaByPath","totalKey","rootProperties","path","current","ordered","COMMON_PATH_WORDS","upperFirst","value","lowerFirst","toPascalCase","asciiWords","word","toCamelCase","getLastTagSegment","tag","stripKnownActionWords","previous","inferEntityNameFromPath","apiPath","segments","segment","meaningful","normalizeLabel","fallback","quoteString","indent","size","line","PAGE_FIELD_NAMES","PAGE_SIZE_FIELD_NAMES","INTERNAL_FIELD_NAMES","ID_FIELD_NAMES","buildSearchFields","enums","isPaginationField","fieldFromParameter","buildModalFields","operations","fieldMap","typedOperation","existed","bodySchema","appendSchemaFields","buildTableFields","collectEnumsFromOperations","collectEnum","getPageParamName","getPageSizeParamName","requiredFields","source","skipId","property","fieldFromSchema","schemaToTsType","inferComponent","required","apifoxEnum","values","baseName","description","hasCollectedEnum","lowerName","text","isNumberSchema","isBooleanSchema","type","MAX_RETRY_LIMIT","loadOpenApiDocument","options","isHttpUrl","readRemoteOpenApi","readFile","resolve","parseOpenApiContent","trimmed","url","maxRetries","retryCount","lastError","attempt","response","error","message","HTTP_METHODS","buildOperationGroup","collectOperations","inferPrimaryTag","selected","scopedOperations","pageTitle","inferPageTitle","operationMap","list","findOperation","entityName","pathItem","method","parameters","operationId","tagCount","left","right","kind","mapped","isSameOperation","scoreOperation","normalized","chineseSource","scoreByKeywords","englishWords","chineseWords","score","renderFiles","model","renderIndexVue","renderApiIndex","renderApiTypes","renderEnums","renderSearchForm","renderTable","renderModalForm","createCall","updateCall","apiImports","pageParamName","pageSizeParamName","typeImports","blocks","renderOperationFunction","functionName","getFunctionName","payloadName","payloadType","responseType","ctx","renderIndentedRequestCall","renderInterfaceComment","renderRowFields","renderResponseType","renderSearchTypeFields","enumItem","renderEnum","renderEnumImportForFields","renderFormField","renderTableColumn","renderNamedImport","names","fields","field","props","generateTypeOptions","option","textMap","writeGeneratedFiles","outDir","files","overwrite","file","writeGeneratedFile","join","filePath","exists","mkdir","dirname","writeFile","ensureTrailingNewline","access","generateCrudPage","input","normalizeOptions","operationGroup","resolveOutDir","isDefined","enumMap","searchFields","modalFields","tableFields","normalizeRequestAdapter","cwd","target","isAbsolute","adapter","defaultRenderCall"],"mappings":"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"}
@@ -17,9 +17,9 @@
17
17
  - 不要把 CRUD 封成一个更大的黑盒页面组件;优先生成清晰的页面目录。
18
18
  - 页面目录默认拆成 `index.vue`、`api/`、`config/` 三层。
19
19
  - `index.vue` 只负责页面编排、请求调用和跨模块动作。
20
- - `config/searchForm.ts` 只描述搜索字段。
21
- - `config/table.ts` 只描述表格列和操作入口。
22
- - `config/modalForm.ts` 只描述新增、编辑、查看弹窗字段。
20
+ - `config/searchForm.tsx` 只描述搜索字段。
21
+ - `config/table.tsx` 只描述表格列和操作入口。
22
+ - `config/modalForm.tsx` 只描述新增、编辑、查看弹窗字段。
23
23
  - `api/types.ts` 放当前页面的类型,不要硬编码跨项目相对路径。
24
24
  - `pageContext` 默认保持空对象,只在业务需要权限、联动、缓存时补字段。
25
25
  - `getDataList` 推荐直接展开 `...searchForm.data`,不要逐个生成查询字段。
@@ -79,9 +79,9 @@ src/views/ExamplePage/
79
79
  │ └── types.ts
80
80
  └── config/
81
81
  ├── enums.ts
82
- ├── searchForm.ts
83
- ├── table.ts
84
- └── modalForm.ts
82
+ ├── searchForm.tsx
83
+ ├── table.tsx
84
+ └── modalForm.tsx
85
85
  ```
86
86
 
87
87
  `index.vue` 的核心查询逻辑应保持这种形态:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auto-gen-crud",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "基于配置驱动的 Vue 3 CRUD 组件包,初始化时可选择 Element Plus、Naive UI 或 Ant Design Vue。",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -31,9 +31,9 @@ src/views/ExamplePage/
31
31
  │ └── types.ts
32
32
  └── config/
33
33
  ├── enums.ts
34
- ├── searchForm.ts
35
- ├── table.ts
36
- └── modalForm.ts
34
+ ├── searchForm.tsx
35
+ ├── table.tsx
36
+ └── modalForm.tsx
37
37
  ```
38
38
 
39
39
  职责边界:
@@ -41,9 +41,9 @@ src/views/ExamplePage/
41
41
  - `index.vue`:页面编排、请求流程、跨模块动作、`pageContext`。
42
42
  - `api/index.ts`:当前页面 API 函数。
43
43
  - `api/types.ts`:当前页面局部类型。
44
- - `config/searchForm.ts`:只描述搜索字段。
45
- - `config/table.ts`:只描述表格列和行操作。
46
- - `config/modalForm.ts`:只描述新增、编辑、查看弹窗字段。
44
+ - `config/searchForm.tsx`:只描述搜索字段。
45
+ - `config/table.tsx`:只描述表格列和行操作。
46
+ - `config/modalForm.tsx`:只描述新增、编辑、查看弹窗字段。
47
47
  - `config/enums.ts`:枚举映射和可选的选项数组。
48
48
 
49
49
  ## 生成器规则