537 lines
14 KiB
TypeScript
537 lines
14 KiB
TypeScript
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<unknown>): Array<unknown> {
|
|
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<T> {
|
|
/**
|
|
* 当前页面
|
|
*/
|
|
current: number;
|
|
/**
|
|
* 总页数
|
|
*/
|
|
pages?: number;
|
|
/**
|
|
* 数据记录
|
|
*/
|
|
records?: Array<T>;
|
|
/**
|
|
* 分页大小
|
|
*/
|
|
size: number;
|
|
/**
|
|
* 数据总条数
|
|
*/
|
|
total?: number;
|
|
}
|
|
/**
|
|
* Nutz 分页对象
|
|
*/
|
|
export interface Pagination<T> {
|
|
/**
|
|
* 数据记录
|
|
*/
|
|
dataList: Array<T>;
|
|
/**
|
|
* 是否首页
|
|
*/
|
|
first?: boolean;
|
|
/**
|
|
* 是否尾页
|
|
*/
|
|
last?: boolean;
|
|
/**
|
|
* 偏移量
|
|
*/
|
|
offset?: number;
|
|
/**
|
|
* 总页数
|
|
*/
|
|
pageCount: number;
|
|
/**
|
|
* 当前页码
|
|
*/
|
|
pageNumber: number;
|
|
/**
|
|
* 页面大小
|
|
*/
|
|
pageSize: number;
|
|
/**
|
|
* sql参数
|
|
*/
|
|
paras: Record<string, unknown>;
|
|
/**
|
|
* 数据记录总数
|
|
*/
|
|
recordCount?: number;
|
|
}
|
|
/**
|
|
* VXE 数据结构
|
|
*/
|
|
export interface VXETableSaveDTO<T> {
|
|
/**
|
|
* 新增记录数据
|
|
*/
|
|
insertRecords: Array<T>;
|
|
/**
|
|
* 删除记录数据
|
|
*/
|
|
removeRecords: Array<T>;
|
|
/**
|
|
* 更新记录数据
|
|
*/
|
|
updateRecords: Array<T>;
|
|
}
|
|
/**
|
|
* 枚举码本
|
|
*/
|
|
export interface Codebook {
|
|
/** 码本编码 */
|
|
code: string;
|
|
|
|
/** 码本名称 */
|
|
description: string;
|
|
|
|
/** 码本值 */
|
|
name: string;
|
|
}
|
|
|
|
/**
|
|
* 全局错误
|
|
*/
|
|
export interface GlobalError {
|
|
/** 错误码 */
|
|
code?: number;
|
|
|
|
/** 错误信息 */
|
|
message?: string;
|
|
}
|
|
/**
|
|
* NutMap
|
|
*/
|
|
type NutMap<Key extends string, Value = unknown> = {
|
|
[key in Key]: Value;
|
|
};
|
|
// 导入其他模块
|
|
${dsNames
|
|
.map((name) => {
|
|
return `/// <reference path="./${name}/api.d.ts" />`
|
|
})
|
|
.join('\n')}
|
|
`
|
|
}
|
|
/**
|
|
* 生成单个数据源文件结构
|
|
* @param generator 生成器
|
|
* @param usingMultipleOrigins 是否多源
|
|
* @returns
|
|
*/
|
|
getOriginFileStructures(
|
|
generator: CodeGenerator,
|
|
usingMultipleOrigins = false,
|
|
): {
|
|
[x: string]: unknown
|
|
mods: Record<string, unknown>
|
|
'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<void>{
|
|
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}
|
|
`
|
|
}
|
|
}
|