import * as Pont from 'pont-engine' import { BaseClass, CodeGenerator, Interface } from 'pont-engine' /** * 首字母大写 * @param str 源字符串 * @returns 首字母大写字符串 */ export function upperFirst(str: string): string { return str.charAt(0).toUpperCase() + str.slice(1) } /** * 数组去重 * @param arr 源数组 * @returns 去重数组 */ export function distinct(arr: Array): Array { return Array.from(new Set([...arr])) } export const dsUrlMapping = {} export function getUrl(ds: string): string { return dsUrlMapping[ds] || '' } export function fixInterfaceName(name: string) { return name.replace(/_\d?/, '') } /** * 提取路径参数 * @param url url * @returns */ export function extractPathParameters(url: string): string[] { const reg = /\{(.+?)\}/ const reg_g = /\{(.+?)\}/g const result = url.match(reg_g) if (result == null) { return [] } else { const list = result.reduce((pre: string[], item) => { const r = item.match(reg) if (r == null) { return pre } else { pre.push(r[1]) return pre } }, []) return list } } /** * 求差集 * @param arr1 * @param arr2 * @returns */ export function subSet(arr1: string[], arr2: string[]) { const set1 = new Set(arr1) const set2 = new Set(arr2) const subset: string[] = [] for (const item of set1) { if (!set2.has(item)) { subset.push(item) } } return subset } export class FileStructures extends Pont.FileStructures { /** * 多源的文件结构 */ getMultipleOriginsFileStructures(): { 'index.ts': unknown 'api-lock.json': string 'api.d.ts': unknown } { const files = {} this.generators.forEach((generator) => { const dsName = generator.dataSource.name const dsFiles = this.getOriginFileStructures(generator, true) files[dsName] = dsFiles }) return { ...files, 'index.ts': this.getDataSourcesTs.bind(this), 'api.d.ts': this.getDataSourcesDeclarationTs.bind(this), 'api-lock.json': this.getLockContent.bind(this), } } /** * api 插件 index.ts 生成 * @returns */ getDataSourcesTs(): string { const dsNames = this.generators.map((ge) => ge.dataSource.name) const apis = dsNames .map((name) => { return `${name}Api` }) .join(',') return ` import type { App } from 'vue'; ${dsNames .map((name) => { return `import ${name}Api from './${name}/mods'; ` }) .join('\n')} export const api = { ${apis}, install: (app: App) => { app.config.globalProperties.$api = api; }, }; export default api; ` } /** * 生成全局类型定义 * @returns */ getDataSourcesDeclarationTs(): string { const dsNames = this.generators.map((ge) => ge.dataSource.name) return ` /** * mybatis-plus 分页对象 */ export interface IPage { /** * 当前页面 */ current: number; /** * 总页数 */ pages?: number; /** * 数据记录 */ records?: Array; /** * 分页大小 */ size: number; /** * 数据总条数 */ total?: number; } /** * Nutz 分页对象 */ export interface Pagination { /** * 数据记录 */ dataList: Array; /** * 是否首页 */ first?: boolean; /** * 是否尾页 */ last?: boolean; /** * 偏移量 */ offset?: number; /** * 总页数 */ pageCount: number; /** * 当前页码 */ pageNumber: number; /** * 页面大小 */ pageSize: number; /** * sql参数 */ paras: Record; /** * 数据记录总数 */ recordCount?: number; } /** * VXE 数据结构 */ export interface VXETableSaveDTO { /** * 新增记录数据 */ insertRecords: Array; /** * 删除记录数据 */ removeRecords: Array; /** * 更新记录数据 */ updateRecords: Array; } /** * 枚举码本 */ export interface Codebook { /** 码本编码 */ code: string; /** 码本名称 */ description: string; /** 码本值 */ name: string; } /** * 全局错误 */ export interface GlobalError { /** 错误码 */ code?: number; /** 错误信息 */ message?: string; } /** * NutMap */ type NutMap = { [key in Key]: Value; }; // 导入其他模块 ${dsNames .map((name) => { return `/// ` }) .join('\n')} ` } /** * 生成单个数据源文件结构 * @param generator 生成器 * @param usingMultipleOrigins 是否多源 * @returns */ getOriginFileStructures( generator: CodeGenerator, usingMultipleOrigins = false, ): { [x: string]: unknown mods: Record 'api.d.ts': unknown } { const mods = {} const dataSource = generator.dataSource dataSource.mods.forEach((mod) => { const currMod = {} //每一个接口一个文件 mod.interfaces.forEach((inter) => { currMod[fixInterfaceName(inter.name) + '.ts'] = generator.getInterfaceContent.bind(generator, inter) }) //接口汇总导出文件和接口声明 currMod['index.ts'] = generator.getModIndex.bind(generator, mod) //每个模块生成一份 mods[mod.name] = currMod //每个模块的汇总导出文件和声明文件 mods['index.ts'] = generator.getModsIndex.bind(generator) }) const result = { mods: mods, //tags 'api.d.ts': generator.getDeclaration.bind(generator), // 模块类型声明 } if (!usingMultipleOrigins) { result['api-lock.json'] = this.getLockContent.bind(this) } return result } } export default class MyGenerator extends CodeGenerator { /** * 生成 namespace 类型定义 * @returns */ getDeclaration(): string { return ` ${this.getCommonDeclaration()} ${this.getBaseClassesInDeclaration()} ` } getBaseClassesInDeclaration(): string { const content = `declare namespace ${this.dataSource.name || 'defs'} { ${this.dataSource.baseClasses .filter((item) => { return ( item.name !== 'Result' && !item.name.startsWith('Result') && item.name !== 'Pagination' && !item.name.startsWith('Pagination') && item.name !== 'IPage' && !item.name.startsWith('IPage') && item.name !== 'VXETable' && !item.name.startsWith('VXETable') && item.name !== 'NutMap' && !item.name.startsWith('NutMap') && item.name !== 'Codebook' && !item.name.startsWith('Codebook') && item.name !== 'GlobalError' && !item.name.startsWith('GlobalError') ) }) .map( (base) => ` ${this.getBaseClassInDeclaration(base)} `, ) .join('\n')} } ` return content.replace(/defs./g, '') } getBaseClassInDeclaration(base: BaseClass): string { if (base.templateArgs && base.templateArgs.length) { return ` /** * ${base.description || base.name} */ export interface ${base.name}<${base.templateArgs.map((_, index) => `T${index} = any`).join(', ')}> { ${base.properties.map((prop) => prop.toPropertyCode(Pont.Surrounding.typeScript, true)).join('\n')} } ` } return ` /** * ${base.description || base.name} */ export interface ${base.name} { ${base.properties.map((prop) => prop.toPropertyCode(Pont.Surrounding.typeScript, true)).join('\n')} } ` } getParamsTypeDec(inter: Interface): string { if (!inter.parameters.filter((p) => p.in === 'query').length) { return '' } return `export interface Params { ${inter.parameters .filter((param) => param.in === 'query') .sort((a, b) => { const x = a.required ? 1 : 0 const y = b.required ? 1 : 0 return y - x }) .map( (param) => `/** ${param.description || param.name} */ ${param.name}${param.required ? '' : '?'}:${param.dataType .generateCode(param.getDsName()) .replace(/defs./g, '')},`, ) .join('\n')} }` } getParamsDec(inter: Interface): string { const hasQueryParams: boolean = inter.parameters.filter((p) => p.in === 'query').length > 0 let extPathParameters = extractPathParameters(inter.path) extPathParameters = subSet( extPathParameters, inter.parameters.filter((p) => p.in === 'path').map((p) => p.name), ) return ` ${inter.parameters .filter((p) => p.in === 'path') .map( (p) => `/** ${p.description || p.name} */ ${p.name}:${p.dataType.generateCode(p.getDsName()).replace(/defs./g, '')},`, ) .join('\n')} ${extPathParameters.map((p) => `${p} : string,`).join('\n')} ${inter.parameters .filter((p) => p.in === 'body') .map((p) => { let temp = p.dataType.generateCode(p.getDsName()).replace(/defs./g, '') temp = temp.indexOf('VXETableSaveDTO') > 0 ? `VXETableSaveDTO<${temp.replace('VXETableSaveDTO', '')}>` : temp return `/** ${p.description || '请求体'} */ ${p.name}:${temp},` }) .join('\n')} ${hasQueryParams ? 'params: Params,' : ''}` } genDataType(dsName: string, inter: Interface) { const typeName = inter.response.typeName if (!typeName || typeName === 'ResultVoid' || typeName === 'Void') { return 'void' } if (typeName === 'string' || typeName === 'number' || typeName === 'boolean') { return typeName } const dataType = typeName.replace('Result', '').replace('IPage', '').replace('Pagination', '').replace('List', '') const originType = `${dsName}.${dataType}` let genDataType = typeName.replace(dataType, originType).replace('Result', '') if (typeName.indexOf('IPage') >= 0) { genDataType = genDataType.replace('IPage', 'IPage<') + '>' } if (typeName.indexOf('Pagination') >= 0) { genDataType = genDataType.replace('Pagination', 'Pagination<') + '>' } if (typeName.indexOf('VXETableSaveDTO') >= 0) { genDataType = genDataType.replace('VXETableSaveDTO', 'VXETableSaveDTO<') + '>' } if (typeName.indexOf('List') >= 0) { genDataType = genDataType.replace('List', 'Array<') + '>' } if (typeName.indexOf('Array') >= 0) { genDataType = inter.response.generateCode(dsName).replace('defs.', '') if (genDataType.indexOf('Codebook') >= 0) { genDataType = genDataType.replace(dsName + '.', '') } } return genDataType } getInterfaceContent(inter: Interface) { const bodyParmas = inter.getBodyParamsCode() const bodyParam = inter.parameters.find((p) => p.in === 'body') const bodyParmaName = bodyParam ? bodyParam.name : '' const hasQueryParams = inter.parameters.filter((p) => p.in === 'query').length > 0 const hasResult = inter.response.typeName.indexOf('Result') >= 0 const hasIPage = inter.response.typeName.indexOf('IPage') >= 0 const hasPagination = inter.response.typeName.indexOf('Pagination') >= 0 const hasCodebook = inter.response.typeArgs[0] && inter.response.typeArgs[0].typeName.indexOf('Codebook') >= 0 const hasVxe = !!inter.parameters.find((p) => p.dataType.typeName.indexOf('VXETableSaveDTO') >= 0) const imports = [ hasResult ? 'Result' : null, hasIPage ? 'IPage' : null, hasPagination ? 'Pagination' : null, hasVxe ? 'VXETableSaveDTO' : null, hasCodebook ? 'Codebook' : null, ].filter((item) => item !== null) //url: \`/${getUrl(inter.getDsName())}${inter.path.replace(/{/g, '${')}\`, const url = getUrl(inter.getDsName()) ? `/${getUrl(inter.getDsName())}` : '' return ` /** * @desc ${inter.description} */ import {defaultSuccess, defaultError, http} from '@/plugins/axios'; import type { AxiosResponse } from 'axios'; ${imports.length ? `import type { ${imports.join(',')} } from '@/api/api';` : ''}; ${hasQueryParams ? `${this.getParamsTypeDec(inter)}` : ''} export default async function( ${this.getParamsDec(inter)} success: (data: ${this.genDataType(inter.getDsName(), inter)}) => void = defaultSuccess, fail: (error: { code: string; error?: string }) => void = defaultError ) :Promise{ return http({ method: '${inter.method}', url: \`${url}${inter.path.replace(/{/g, '${')}\`, ${bodyParmas ? `data: ${bodyParmaName},` : ''} ${hasQueryParams ? `params,` : ''} }).then((data: AxiosResponse<${this.genDataType(inter.getDsName(), inter)}, unknown>) => { success(data.data); }) .catch((error: { code: string; error?: string }) => fail(error)); } ` } getModIndex(mod: Pont.Mod): string { // const name: string = upperFirst(mod.name); // const hasResult = mod.interfaces.find(inter => inter.response.typeName.indexOf('Result') >= 0); // const hasIPage = mod.interfaces.find(inter => inter.response.typeName.indexOf('IPage') >= 0); // const hasPagination = mod.interfaces.find(inter => inter.response.typeName.indexOf('Pagination') >= 0); // const hasVxe = mod.interfaces.find(inter => // inter.parameters.find(p => p.dataType.typeName.indexOf('VXETableSaveDTO') >= 0), // ); // const hasCodebook = mod.interfaces.find( // inter => inter.response.typeArgs[0] && inter.response.typeArgs[0].typeName.indexOf('Codebook') >= 0, // ); // const defTypes = this.getAllRefTypeNames(inter); // const imports = [ // hasResult ? 'Result' : null, // hasIPage ? 'IPage' : null, // hasPagination ? 'Pagination' : null, // hasVxe ? 'VXETableSaveDTO' : null, // hasCodebook ? 'Codebook' : null, // ].filter(item => item !== null); return ` /** * @description ${mod.description} * */ ${mod.interfaces .map((inter) => { return `import ${fixInterfaceName(inter.name)} from './${fixInterfaceName(inter.name)}';` }) .join('\n')} export default { ${mod.interfaces.map((inter) => fixInterfaceName(inter.name)).join(', \n')} } ` } getModsIndex(): string { const exportseg = `export default { ${this.dataSource.mods.map((mod) => mod.name).join(', \n')} };` return ` ${this.dataSource.mods .map((mod) => { return ` import ${mod.name} from './${mod.name}'; ` }) .join('\n')} ${exportseg} ` } }