auto-gen-crud 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/API.md +130 -0
- package/README.md +234 -0
- package/dist/adapters/ant-design-vue.d.ts +8 -0
- package/dist/adapters/element-plus.d.ts +8 -0
- package/dist/adapters/index.d.ts +4 -0
- package/dist/adapters/index.js +7 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/naive.d.ts +8 -0
- package/dist/adapters/types.d.ts +162 -0
- package/dist/components/AutoCrud.vue.d.ts +398 -0
- package/dist/components/CopyText.vue.d.ts +6 -0
- package/dist/components/CrudModal.vue.d.ts +47 -0
- package/dist/components/DecodeText.vue.d.ts +8 -0
- package/dist/components/DynamicForm.vue.d.ts +63 -0
- package/dist/components/DynamicFormItem.vue.d.ts +20 -0
- package/dist/components/DynamicTable.vue.d.ts +30 -0
- package/dist/context.d.ts +36 -0
- package/dist/core/date-format-utils.d.ts +8 -0
- package/dist/core/form-runtime-cache.d.ts +27 -0
- package/dist/core/form-utils.d.ts +51 -0
- package/dist/core/object-utils.d.ts +43 -0
- package/dist/core/table-utils.d.ts +19 -0
- package/dist/core/types.d.ts +443 -0
- package/dist/generator/fields.d.ts +27 -0
- package/dist/generator/index.d.ts +14 -0
- package/dist/generator/index.js +740 -0
- package/dist/generator/index.js.map +1 -0
- package/dist/generator/openapi.d.ts +10 -0
- package/dist/generator/operations.d.ts +8 -0
- package/dist/generator/schema.d.ts +32 -0
- package/dist/generator/string-utils.d.ts +11 -0
- package/dist/generator/templates.d.ts +6 -0
- package/dist/generator/types.d.ts +185 -0
- package/dist/generator/writer.d.ts +8 -0
- package/dist/hooks/index.d.ts +3 -0
- package/dist/hooks/index.js +7 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/useModalForm.d.ts +11 -0
- package/dist/hooks/useSearchForm.d.ts +10 -0
- package/dist/hooks/useTable.d.ts +11 -0
- package/dist/index-Dc-solHn.js +299 -0
- package/dist/index-Dc-solHn.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +985 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin.d.ts +17 -0
- package/dist/style.css +1 -0
- package/dist/useTable-BwDSqD1q.js +214 -0
- package/dist/useTable-BwDSqD1q.js.map +1 -0
- package/dist/utils/clipboard.d.ts +16 -0
- package/dist/utils/index.d.ts +4 -0
- package/dist/utils/index.js +532 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/message.d.ts +9 -0
- package/dist/utils/request.d.ts +8 -0
- package/dist/utils/sm4-core.d.ts +12 -0
- package/dist/utils/sm4.d.ts +29 -0
- package/docs/AI_AGENT_GUIDE.md +210 -0
- package/mcp/auto-crud-mcp.mjs +502 -0
- package/mcp/auto-gen-crud-mcp.mjs +586 -0
- package/package.json +93 -0
- package/skills/auto-gen-crud-agent/SKILL.md +114 -0
|
@@ -0,0 +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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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { GenerateCrudPageOptions, OpenApiDocument } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* 读取 OpenAPI 文档。
|
|
4
|
+
*
|
|
5
|
+
* 支持三种来源:
|
|
6
|
+
* - URL:适合 Agent 插件直接传接口平台导出地址。
|
|
7
|
+
* - 本地文件路径:适合测试 fixture 和离线生成。
|
|
8
|
+
* - 已解析对象:适合上层插件提前缓存或改写 OpenAPI 后再传入。
|
|
9
|
+
*/
|
|
10
|
+
export declare function loadOpenApiDocument(options: Pick<GenerateCrudPageOptions, "openapi" | "cwd" | "maxRetries">): Promise<OpenApiDocument>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { CrudOperationGroup, GenerateCrudPageOptions, OpenApiDocument } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* 从 OpenAPI 文档中识别一个 CRUD 页面所需的接口组。
|
|
4
|
+
*
|
|
5
|
+
* 当前接口平台可能没有 operationId,也可能所有接口都挂在同一个 tag 下,
|
|
6
|
+
* 所以这里优先使用 tag 做模块边界,再用 path/summary 里的中英文关键词识别查询、新增、编辑、删除。
|
|
7
|
+
*/
|
|
8
|
+
export declare function buildOperationGroup(document: OpenApiDocument, options: GenerateCrudPageOptions): CrudOperationGroup;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { NormalizedOperation, OpenApiDocument, OpenApiParameter, OpenApiSchema, ResponsePathInfo } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* 解析 schema 引用。
|
|
4
|
+
*
|
|
5
|
+
* OpenAPI 里字段可能直接写在当前节点,也可能通过 $ref/allOf 组合复用。
|
|
6
|
+
* 生成器只需要拿到最终 properties/required,所以这里把常见引用形式先摊平。
|
|
7
|
+
*/
|
|
8
|
+
export declare function resolveSchema(document: OpenApiDocument, schema?: OpenApiSchema): OpenApiSchema | undefined;
|
|
9
|
+
/** 只取 query 参数;header/path/cookie 不参与表单字段生成。 */
|
|
10
|
+
export declare function getQueryParameters(operation: NormalizedOperation): OpenApiParameter[];
|
|
11
|
+
/** 读取 JSON requestBody schema,兼容 content-type 不是 application/json 但仍有第一个 content 的情况。 */
|
|
12
|
+
export declare function getRequestBodySchema(document: OpenApiDocument, operation?: NormalizedOperation): OpenApiSchema | undefined;
|
|
13
|
+
/** 读取成功响应 schema,优先级:200 -> 201 -> default -> 第一个响应。 */
|
|
14
|
+
export declare function getSuccessResponseSchema(document: OpenApiDocument, operation: NormalizedOperation): OpenApiSchema | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* 推断列表行数据结构。
|
|
17
|
+
*
|
|
18
|
+
* 当前 Apifox 文档的列表结构是 result.records.items,
|
|
19
|
+
* 但这里不写死路径,而是在响应 schema 中递归找到第一个数组字段并取 items。
|
|
20
|
+
*/
|
|
21
|
+
export declare function inferRecordSchema(document: OpenApiDocument, operation: NormalizedOperation): OpenApiSchema;
|
|
22
|
+
/**
|
|
23
|
+
* 推断列表数据和总数字段路径。
|
|
24
|
+
*
|
|
25
|
+
* 默认递归寻找数组字段作为列表路径,再在同级对象中寻找 total/totalCount/count;
|
|
26
|
+
* 如果文档没有描述清楚,则退回当前项目常见的 result.records/result.total。
|
|
27
|
+
*/
|
|
28
|
+
export declare function inferResponsePath(document: OpenApiDocument, operation: NormalizedOperation): ResponsePathInfo;
|
|
29
|
+
/** 安全读取 schema.properties,调用方不用反复判空。 */
|
|
30
|
+
export declare function getSchemaProperties(document: OpenApiDocument, schema?: OpenApiSchema): Record<string, OpenApiSchema>;
|
|
31
|
+
/** 安全读取 required 数组,缺失时按空数组处理。 */
|
|
32
|
+
export declare function getSchemaRequired(schema?: OpenApiSchema): string[];
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare function upperFirst(value: string): string;
|
|
2
|
+
export declare function lowerFirst(value: string): string;
|
|
3
|
+
export declare function toPascalCase(value: string): string;
|
|
4
|
+
export declare function toCamelCase(value: string): string;
|
|
5
|
+
export declare function toConstantName(value: string): string;
|
|
6
|
+
export declare function getLastTagSegment(tag: string): string;
|
|
7
|
+
export declare function stripKnownActionWords(value: string): string;
|
|
8
|
+
export declare function inferEntityNameFromPath(apiPath: string): string;
|
|
9
|
+
export declare function normalizeLabel(value: string | undefined, fallback: string): string;
|
|
10
|
+
export declare function quoteString(value: string): string;
|
|
11
|
+
export declare function indent(content: string, size?: number): string;
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { CrudFormComponent } from '../core/types';
|
|
2
|
+
/** OpenAPI 里本生成器关心的 HTTP 方法。 */
|
|
3
|
+
export type OpenApiHttpMethod = "get" | "post" | "put" | "patch" | "delete";
|
|
4
|
+
/** 生成 CRUD 页面时需要识别的业务接口类型。 */
|
|
5
|
+
export type CrudOperationKind = "list" | "create" | "update" | "delete";
|
|
6
|
+
export type OpenApiSchemaType = string | string[];
|
|
7
|
+
export interface OpenApiSchema {
|
|
8
|
+
$ref?: string;
|
|
9
|
+
type?: OpenApiSchemaType;
|
|
10
|
+
title?: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
enum?: unknown[];
|
|
13
|
+
format?: string;
|
|
14
|
+
required?: string[];
|
|
15
|
+
properties?: Record<string, OpenApiSchema>;
|
|
16
|
+
items?: OpenApiSchema;
|
|
17
|
+
oneOf?: OpenApiSchema[];
|
|
18
|
+
anyOf?: OpenApiSchema[];
|
|
19
|
+
allOf?: OpenApiSchema[];
|
|
20
|
+
"x-apifox-enum"?: Array<{
|
|
21
|
+
value: unknown;
|
|
22
|
+
name?: string;
|
|
23
|
+
description?: string;
|
|
24
|
+
}>;
|
|
25
|
+
"x-apifox-orders"?: string[];
|
|
26
|
+
[key: string]: unknown;
|
|
27
|
+
}
|
|
28
|
+
export interface OpenApiParameter {
|
|
29
|
+
name: string;
|
|
30
|
+
in: "query" | "path" | "header" | "cookie" | string;
|
|
31
|
+
description?: string;
|
|
32
|
+
required?: boolean;
|
|
33
|
+
schema?: OpenApiSchema;
|
|
34
|
+
}
|
|
35
|
+
export interface OpenApiOperation {
|
|
36
|
+
operationId?: string;
|
|
37
|
+
summary?: string;
|
|
38
|
+
description?: string;
|
|
39
|
+
tags?: string[];
|
|
40
|
+
parameters?: OpenApiParameter[];
|
|
41
|
+
requestBody?: {
|
|
42
|
+
content?: Record<string, {
|
|
43
|
+
schema?: OpenApiSchema;
|
|
44
|
+
}>;
|
|
45
|
+
};
|
|
46
|
+
responses?: Record<string, {
|
|
47
|
+
description?: string;
|
|
48
|
+
content?: Record<string, {
|
|
49
|
+
schema?: OpenApiSchema;
|
|
50
|
+
}>;
|
|
51
|
+
}>;
|
|
52
|
+
}
|
|
53
|
+
export interface OpenApiDocument {
|
|
54
|
+
openapi?: string;
|
|
55
|
+
info?: {
|
|
56
|
+
title?: string;
|
|
57
|
+
description?: string;
|
|
58
|
+
};
|
|
59
|
+
paths?: Record<string, Partial<Record<OpenApiHttpMethod, OpenApiOperation>> & {
|
|
60
|
+
parameters?: OpenApiParameter[];
|
|
61
|
+
}>;
|
|
62
|
+
components?: {
|
|
63
|
+
schemas?: Record<string, OpenApiSchema>;
|
|
64
|
+
};
|
|
65
|
+
[key: string]: unknown;
|
|
66
|
+
}
|
|
67
|
+
/** 生成器内部统一后的 operation,后续模块不再直接操作原始 pathItem。 */
|
|
68
|
+
export interface NormalizedOperation {
|
|
69
|
+
method: OpenApiHttpMethod;
|
|
70
|
+
path: string;
|
|
71
|
+
operation: OpenApiOperation;
|
|
72
|
+
operationId: string;
|
|
73
|
+
summary: string;
|
|
74
|
+
tags: string[];
|
|
75
|
+
parameters: OpenApiParameter[];
|
|
76
|
+
}
|
|
77
|
+
/** 一个页面所需的 CRUD 接口组合。list 是必需项,其它接口允许缺失。 */
|
|
78
|
+
export interface CrudOperationGroup {
|
|
79
|
+
tag: string;
|
|
80
|
+
pageTitle: string;
|
|
81
|
+
entityName: string;
|
|
82
|
+
list: NormalizedOperation;
|
|
83
|
+
create?: NormalizedOperation;
|
|
84
|
+
update?: NormalizedOperation;
|
|
85
|
+
delete?: NormalizedOperation;
|
|
86
|
+
}
|
|
87
|
+
export interface GeneratedEnumOption {
|
|
88
|
+
label: string;
|
|
89
|
+
value: unknown;
|
|
90
|
+
}
|
|
91
|
+
export interface GeneratedEnum {
|
|
92
|
+
optionsName: string;
|
|
93
|
+
textMapName: string;
|
|
94
|
+
sourceField: string;
|
|
95
|
+
options: GeneratedEnumOption[];
|
|
96
|
+
}
|
|
97
|
+
export interface GeneratedField {
|
|
98
|
+
name: string;
|
|
99
|
+
label: string;
|
|
100
|
+
tsType: string;
|
|
101
|
+
required: boolean;
|
|
102
|
+
component: CrudFormComponent;
|
|
103
|
+
description?: string;
|
|
104
|
+
enumName?: string;
|
|
105
|
+
source: "search" | "modal" | "table";
|
|
106
|
+
}
|
|
107
|
+
/** 列表响应读取路径,例如 result.records 和 result.total。 */
|
|
108
|
+
export interface ResponsePathInfo {
|
|
109
|
+
listPath: string;
|
|
110
|
+
totalPath: string;
|
|
111
|
+
}
|
|
112
|
+
/** requestAdapter.renderCall 收到的上下文,供调用方生成项目自己的请求代码。 */
|
|
113
|
+
export interface RequestRenderContext {
|
|
114
|
+
functionName: string;
|
|
115
|
+
method: OpenApiHttpMethod;
|
|
116
|
+
path: string;
|
|
117
|
+
payloadName: string;
|
|
118
|
+
responseType: string;
|
|
119
|
+
}
|
|
120
|
+
export interface RequestAdapterOptions {
|
|
121
|
+
/** 请求工具导入代码,默认使用示例项目里的 http 封装。 */
|
|
122
|
+
importCode?: string;
|
|
123
|
+
/** 自定义请求调用渲染,适配不同项目的 request/http 方法签名。 */
|
|
124
|
+
renderCall?: (ctx: RequestRenderContext) => string;
|
|
125
|
+
}
|
|
126
|
+
export interface GenerateCrudPageOptions {
|
|
127
|
+
/** OpenAPI 3.1 文档地址、文件路径,或已经解析好的文档对象。 */
|
|
128
|
+
openapi: string | OpenApiDocument;
|
|
129
|
+
/** 当前工作目录。相对 outDir、相对 openapi 文件路径都会基于它解析。 */
|
|
130
|
+
cwd?: string;
|
|
131
|
+
/** 可选覆盖 tag;不传时自动选择 operation 数量最多的 tag。 */
|
|
132
|
+
tag?: string;
|
|
133
|
+
/** 可选覆盖页面标题;不传时从 tag 最后一段自动推断。 */
|
|
134
|
+
pageTitle?: string;
|
|
135
|
+
/** 可选覆盖实体名;不传时优先从接口路径推断。 */
|
|
136
|
+
entityName?: string;
|
|
137
|
+
/** 可选输出目录;不传时生成到 src/views/<EntityName>Page。 */
|
|
138
|
+
outDir?: string;
|
|
139
|
+
/** 可选覆盖 CRUD operation,值可以是 operationId、path 或 METHOD path。 */
|
|
140
|
+
operationMap?: Partial<Record<CrudOperationKind, string>>;
|
|
141
|
+
/** 请求层适配配置,避免把某个项目的 request 写死到生成器里。 */
|
|
142
|
+
requestAdapter?: RequestAdapterOptions;
|
|
143
|
+
/** 是否额外生成 TypeOptions 下拉数组,默认 false;默认只生成 TypeTextMap,减少初始化页面噪音。 */
|
|
144
|
+
generateTypeOptions?: boolean;
|
|
145
|
+
/** 是否允许覆盖已存在文件,默认 false,防止误伤业务代码。 */
|
|
146
|
+
overwrite?: boolean;
|
|
147
|
+
/** 只返回生成内容,不写入文件。 */
|
|
148
|
+
dryRun?: boolean;
|
|
149
|
+
/** 网络读取失败时的最大重试次数,内部会强制限制不超过 5 次。 */
|
|
150
|
+
maxRetries?: number;
|
|
151
|
+
}
|
|
152
|
+
export interface GeneratedFile {
|
|
153
|
+
path: string;
|
|
154
|
+
content: string;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* 模板渲染的完整页面模型。
|
|
158
|
+
*
|
|
159
|
+
* 解析 OpenAPI 的复杂性到这里为止,模板只消费这个稳定模型,
|
|
160
|
+
* 这样后续增加 React/Vue2/Element UI 模板时可以复用前面的推断逻辑。
|
|
161
|
+
*/
|
|
162
|
+
export interface CrudPageModel {
|
|
163
|
+
tag: string;
|
|
164
|
+
pageTitle: string;
|
|
165
|
+
entityName: string;
|
|
166
|
+
entityVariable: string;
|
|
167
|
+
permissionSuffix: string;
|
|
168
|
+
outDir: string;
|
|
169
|
+
operations: CrudOperationGroup;
|
|
170
|
+
searchFields: GeneratedField[];
|
|
171
|
+
modalFields: GeneratedField[];
|
|
172
|
+
tableFields: GeneratedField[];
|
|
173
|
+
enums: GeneratedEnum[];
|
|
174
|
+
generateTypeOptions: boolean;
|
|
175
|
+
responsePath: ResponsePathInfo;
|
|
176
|
+
requestAdapter: Required<RequestAdapterOptions>;
|
|
177
|
+
}
|
|
178
|
+
export interface GenerateCrudPageResult {
|
|
179
|
+
tag: string;
|
|
180
|
+
pageTitle: string;
|
|
181
|
+
entityName: string;
|
|
182
|
+
outDir: string;
|
|
183
|
+
files: GeneratedFile[];
|
|
184
|
+
dryRun: boolean;
|
|
185
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { GeneratedFile } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* 写入生成文件。
|
|
4
|
+
*
|
|
5
|
+
* overwrite 默认关闭,避免 Agent 插件误覆盖开发者已经二开过的页面;
|
|
6
|
+
* 需要重新生成时由调用方显式传 overwrite: true。
|
|
7
|
+
*/
|
|
8
|
+
export declare function writeGeneratedFiles(outDir: string, files: GeneratedFile[], overwrite?: boolean): Promise<void>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { CrudModalFormConfig, CrudModalMode, UseModalFormOptions } from '../core/types';
|
|
2
|
+
/**
|
|
3
|
+
* 创建弹窗表单配置。
|
|
4
|
+
*
|
|
5
|
+
* 设计说明:
|
|
6
|
+
* - `add/edit/view` 三种模式是后台 CRUD 最常见的状态,本 hook 将它们统一管理。
|
|
7
|
+
* - `openAdd` 每次都会根据 columns 和初始 data 重建表单,避免旧实现中编辑数据残留到新增弹窗。
|
|
8
|
+
* - `openEdit/openView` 使用深拷贝,避免弹窗中修改嵌套对象时直接污染表格行数据。
|
|
9
|
+
*/
|
|
10
|
+
export declare function useModalForm<T extends Record<string, unknown> = Record<string, unknown>>(options: UseModalFormOptions<T>): CrudModalFormConfig<T>;
|
|
11
|
+
export type { CrudModalMode };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { CrudSearchFormConfig, UseSearchFormOptions } from '../core/types';
|
|
2
|
+
/**
|
|
3
|
+
* 创建搜索表单配置。
|
|
4
|
+
*
|
|
5
|
+
* 设计说明:
|
|
6
|
+
* - 搜索表单本质是“查询条件状态容器”,不应该直接关心表格组件或接口格式。
|
|
7
|
+
* - 这里会在初始化时保存一份快照,重置时恢复快照,而不是简单把字段清空。
|
|
8
|
+
* - 是否刷新列表由 `AutoCrud` 组合层和 `useTable` 协调,避免旧实现中搜索提交和表格重置重复请求。
|
|
9
|
+
*/
|
|
10
|
+
export declare function useSearchForm<T extends Record<string, unknown> = Record<string, unknown>>(options: UseSearchFormOptions<T>): CrudSearchFormConfig<T>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { CrudPagination, CrudTableConfig, UseTableOptions } from '../core/types';
|
|
2
|
+
/**
|
|
3
|
+
* 创建表格配置。
|
|
4
|
+
*
|
|
5
|
+
* 设计说明:
|
|
6
|
+
* - hook 只负责状态,不渲染按钮、确认框、图片等 UI。
|
|
7
|
+
* - `getDataList` 由业务传入,因为每个项目的接口格式、分页字段、返回结构都不一样。
|
|
8
|
+
* - 分页统一使用 `page/pageSize/total`,具体 UI 字段由 adapter 映射。
|
|
9
|
+
*/
|
|
10
|
+
export declare function useTable<T extends Record<string, unknown> = Record<string, unknown>>(options: UseTableOptions<T>): CrudTableConfig<T>;
|
|
11
|
+
export type { CrudPagination };
|