import { AMProp } from "./types";
import { AMModelManager } from "./modelmanager";
import { AmUtil } from "./util";
/**
* 代码生成器
*/
export class AMPageGenerator{
/**
* 基础存储路径
*/
private baseSavePath:string;
/**
* 基础元数据路径
*/
private baseMetaPath:string;
/**
* 构造器
* @param savePath 存储路径
* @param metaPath 元数据路径
*/
constructor(savePath:string,metaPath:string){
this.baseSavePath = savePath;
this.baseMetaPath = metaPath;
}
/**
* 产生代码
*/
public genCode(){
//页面代码
for(let mdl of AMModelManager.modelMap){
this.genOne(mdl[1]);
}
}
/**
* 生成一个
* @param cls model类
* @param path 元数据基础路径
*/
public genOne(cls:any){
if(!cls.manageable && !cls.editable){
return;
}
cls.labelWidth ||= 100;
cls.colsInRow ||= 2;
const fsMdl = require('fs');
const pathMdl = require('path');
const fileBase = cls.ref.toLowerCase();
const BASEDIR = pathMdl.resolve(this.baseSavePath,fileBase);
const METADIR = pathMdl.resolve(this.baseMetaPath,fileBase);
//查询标志
let hasQuery = false;
//目录不存在则创建目录
if(!fsMdl.existsSync(BASEDIR)){
fsMdl.mkdirSync(BASEDIR,{recursive:true});
}
//可编辑字段
let fields = [];
// form生成
let formPath = pathMdl.resolve(METADIR,'form.json');
if(fsMdl.existsSync(formPath)){
let config = JSON.parse(fsMdl.readFileSync(formPath));
//美化
let str = AmUtil.formatTs(this.genForm(cls,config,fields));
//写文件
fsMdl.writeFileSync(pathMdl.resolve(BASEDIR,fileBase+'form.js'), str);
let queryStr = this.genQuery(cls);
if(queryStr !== null){
hasQuery = true;
//查询
str = AmUtil.formatTs(queryStr);
//写文件
fsMdl.writeFileSync(pathMdl.resolve(BASEDIR,fileBase+'query.js'), str);
}
}
//表格
let str = AmUtil.formatTs(this.genGrid(cls,fields));
//写文件
fsMdl.writeFileSync(pathMdl.resolve(BASEDIR,fileBase+'grid.js'), str);
if(cls.manageable){
let str = AmUtil.formatTs(this.genMain(cls,hasQuery));
//写文件
fsMdl.writeFileSync(pathMdl.resolve(BASEDIR,fileBase+'main.js'), str);
}
}
/**
* 生成管理页面
* @param cls 模型类
* @param hasQuery 是否查询
*/
public genMain(cls:any,hasQuery:boolean){
let relFields:any = {};
for(let p of cls.props){
if(p.ref){
if(!p.editOption){
continue;
}
let option = p.editOption.option;
let idProp = AMModelManager.getIdProp(p.ref);
let showProp = this.getRefProp(p.ref);
//id属性和prop属性不能为空
if(!idProp){
continue;
}
let obj;
if(p.editOption.type === 'file' && showProp){
obj = {
id:idProp.name,
showName:showProp.name,
type:'file',
multi:option&&option.multi?true:false
}
}else if(p.editOption.type === 'new'){ //新建多对多关系
obj = {
id:idProp.name,
type:'array',
main:`M${p.ref.substring(3)}Grid`
}
}else{
obj= {
id:idProp.name,
type:(p.editOption.type === 'transfer' || option&&option.multi)?'array':''
}
}
relFields[p.name] = obj;
}
}
let clsName = cls.ref;
let queryStr = hasQuery?`
`:''
let template = `
${hasQuery?"":''}
${queryStr}
`;
let modules = ['M'+clsName+'Grid','M'+clsName+'Form'];
let importArr = [`import { M${clsName}Grid } from "./${(clsName+'Grid').toLowerCase()}.js"`,
`import { M${clsName}Form } from "./${(clsName+'Form').toLowerCase()}.js"`]
if(hasQuery){
modules.push('M'+clsName+'Query');
importArr.push(`import { M${clsName}Query } from "./${(clsName+'Query').toLowerCase()}.js"`)
}
return `
import { AMBaseMain } from "../__base/__basemain.js";
${importArr.join('\r\n')}
export class M${clsName}Main extends AMBaseMain{
modules = [${modules.join(',')}];
constructor(){
super('${clsName}','${this.getIdName(cls)}',${JSON.stringify(relFields)});
}
template(props){
return \`
${template}
\`
}
}
`
}
/**
* 生成form代码
* @param cls model类
* @param config 元数据对象
* @param fields 可编辑字段
* @param refFields 外键字段
*/
public genForm(cls:any,config:any,fields:string[]):string{
let clsName = cls.ref;
let cols = config.cols;
//field item字符串数组
let fieldArr:string[] = [];
//模型数据
let moduleData = {formData:{}};
//加入beforefirstRender的方法代码
let firstMethod = [];
//模块方法数组代码
let methodArr = [];
//import 模块数组
let importArr:string[] = [];
//引用模块数组
let moduleArr:string[] = [];
//数据项索引,数据项以m_index进行定义
let dataIndex = 1;
let index = 0;
config.items.forEach((item) => {
fields.push(item.field);
//列开头和结束
if(index % cols === 0){
if(index !== 0){
fieldArr.push('');
}
fieldArr.push(``);
}
//校验串
let vldS = '';
if(item.validator){
for(let v of item.validator){
if(v === 'require'){
vldS += " required";
}else{
vldS += ` validator='${v}'`;
}
}
}
let s = `
`;
let s1;
if(item.editOption){
switch(item.editOption.type){
case 'text':
s1= ``;
break;
case 'textarea':
//textarea 单独一行
if(index !== 0){
fieldArr.push('
');
fieldArr.push(``);
}
//后续增加结束符
index=cols-1;
s1= ``;
break;
case 'number':
s1= ``;
break;
case 'radio':
s1= `
`;
break;
case 'checkbox':
//checkbox 不显示label
const sY = item.editOption.yes?"yes-value='" + item.editOption.yes + "'":'';
const sN = item.editOption.no!==undefined?"no-value='" + item.editOption.no + "'":'';
s1= `${item.title}`;
break;
case 'switch':
s1= ``;
break;
case 'datetime':
s1= ``;
break;
case 'file':
s1 = this.genFileStr(item.editOption.option,item.field);
break;
case 'list':
s1 = this.genSelectStr('list',item.editOption.option,item.field,dataIndex++,moduleData,firstMethod);
break;
case 'select':
s1 = this.genSelectStr('select',item.editOption.option,item.field,dataIndex++,moduleData,firstMethod);
break;
case 'transfer': //listtransfer
s1 = this.genTransferStr(item.editOption.option,item.field,dataIndex++,moduleData,firstMethod);
break;
case 'new': //onetomany新建
//新占一行
fieldArr.push('
');
fieldArr.push(``);
index = cols-1;
const sa = this.genNew(cls,item.field);
if(sa){
moduleArr.push(sa[0])
s1 = sa[1];
importArr.push(sa[2]);
}
}
}else{
s1= ``;
}
s += s1;
if(config.hasUnit){
let unit = item.unit || '';
s += `
${unit}
`;
}
s +='\r\n';
fieldArr.push(s);
index++;
});
//最后一个行结束符
if(fieldArr.length>0){
fieldArr.push('
');
}
//编辑form宽度
const editWidth = cls.editWidth?"style='width:" + cls.editWidth + "px'":'';
let template = `
${fieldArr.join('')}
`;
//data串
let dataStr = `
data(){
return ${JSON.stringify(moduleData)}
}
`;
// 方法串
let beforeStr = firstMethod&&firstMethod.length>0?`
onMount(){
${firstMethod.join('')}
}
`:'';
template = AmUtil.formatHtml(template);
return `
import{Module,Nodom} from "../../js/nodomui.js";
${importArr.join('\r\n')}
export class M${clsName}Form extends Module{
modules=[${moduleArr.join(',')}]
template(){
return \`
${template}
\`
}
${dataStr}
${beforeStr}
${methodArr.join('\r\n')}
}`
}
/**
* 生成form代码
* @param cls model类
* @param fields 可编辑字段
* @param refFields 外键
*/
public genGrid(cls:any,fields:string[]):string{
let clsName = cls.ref;
let colArr = [];
let detailArr:string[] = [];
cls.props.forEach((item,index) => {
//不显示在表格中
let obj;
//showInGrid为true且isId为false
if(item.showInGrid && !item.isId){
obj = this.genOneCol(item,cls.labelWidth);
colArr = colArr.concat(obj.colArr);
}
if(item.showInDetail){
if(obj){
detailArr = detailArr.concat(obj.detailArr);
}else{
detailArr.push(`
{{${item.name}}}
`
)
}
}
});
//行展开
let rowExpand = detailArr.length===0?'':`
${detailArr.join('\r\n')}
`
//分页
let pageStr = '';
pageStr = `
`;
// }
let template = `
${colArr.join('')}
${rowExpand}
${pageStr}
`;
template = AmUtil.formatHtml(template);
//消除带"."的字段,直接用第一段作为字段
let fArr = fields.slice(0);
for(let i=0;i {
if(!item.showInQuery){
return;
}
hasQuery = true;
//列开头和结束
if(index % cols === 0){
if(index !== 0){
fieldArr.push('');
rowStart = false;
}
fieldArr.push(``);
rowStart = true;
}
let fn = 'formData.' + item.name;
let s = `
`;
let s1;
if(item.editOption){
switch(item.editOption.type){
case 'text':
s1= ``;
fieldTypes[item.name] = 'string';
break;
case 'number':
s1= ``;
fieldTypes[item.name] = 'number';
break;
case 'datetime':
s1= ``;
fieldTypes[item.name] = 'date';
break;
case 'list':
s1 = this.genSelectStr('list',item.editOption.option,item.name,dataIndex++,moduleData,firstMethod);
fieldTypes[item.name] = item.editOption.option.multi?'array':'number';
break;
case 'select':
s1 = this.genSelectStr('select',item.editOption.option,item.name,dataIndex++,moduleData,firstMethod);
fieldTypes[item.name] = item.editOption.option.multi?'array':'number';
break;
}
}else{
s1= ``;
fieldTypes[item.name] = 'string';
}
s += s1;
if(item.unit){
let unit = item.unit || '';
s += `${unit}`;
}
s +='\r\n';
fieldArr.push(s);
index++;
});
if(!hasQuery){
return null;
}
//行未结束
if(rowStart){
//最后一个行结束符
fieldArr.push('
');
}
let template = `
`;
//data串
let dataStr = `
data(){
return ${JSON.stringify(moduleData)}
}
`;
// 方法串
let beforeStr = `
constructor(){
super();
this.fields = ${JSON.stringify(fieldTypes)}
}
onMount(){
${firstMethod.join('')}
}
`;
template = AmUtil.formatHtml(template);
return `
import { Nodom} from "../../js/nodomui.js";
import { AMBaseQuery } from "../__base/__basequery.js";
export class M${cls.ref}Query extends AMBaseQuery {
template(){
return \`
${template}
\`
}
${dataStr}
${beforeStr}
${methodArr.join('\r\n')}
}`
}
/**
* 生成select 字符串
* @param type - select/list
* @param option - 编辑选项
* @param field - 字段
* @param dataIndex - 数据index
* @param data - 模块数据集
* @param firstMethod - 添加到beforeRender中的串
* @returns
*/
private genSelectStr(type:string,option:any,field:string,dataIndex:number,data:any,firstMethod:string[]):string|undefined{
let dataName = 'm_' + dataIndex;
//加入附加数据项
let multiStr;
//字段名
let fieldName;
if(option.multi){
multiStr = 'multiple';
fieldName = `formData.${field}`
}else{
multiStr = '';
fieldName = `formData.${field}.${option.valueProp}`
}
if(option.list){
data[dataName] = option.list;
return `
{{text}}
`;
}else if(option.refModel){
if(!option.valueProp || !option.displayProp){
throw `'${field}' select配置错误!`;
}
//构造数据请求,获取外键数据
let url = '/' + option.refModel.substring(3).toLowerCase() + '/query';
let fields = `'${option.valueProp},${Array.isArray(option.displayProp)?option.displayProp.join(','):option.displayProp}'`;
//添加到beforeRender
firstMethod.push(`
Nodom.request({
url:'${url}',
type:'json',
params:{fields:${fields}}
}).then(r=>{
this.model['${dataName}'] = r.rows;
});
`);
if(Array.isArray(option.displayProp)){
let s = '';
for(let p of option.displayProp){
let prop = AMModelManager.getProp(option.refModel,p);
let width = prop.width || 100;
s += `{{${p}}}\r\n`;
}
return `
${s}
`;
}else{
return `
{{${option.displayProp}}}
`;
}
}
}
/**
* 生成select 字符串
* @param option 编辑选项
* @param field 字段
* @param dataIndex 数据index
* @param data 模块数据集
* @returns
*/
private genTransferStr(option:any,field:string,dataIndex:number,data:any,firstMethod:string[]):string|undefined{
let dataName = 'm_' + dataIndex;
//字段名
const fieldName = `formData.${field}`
if(option.list){
data[dataName] = option.list;
return `
{{text}}
`;
}else if(option.refModel){
if(!option.valueProp || !option.displayProp){
throw `'${field}' select配置错误!`;
}
//构造数据请求,获取外键数据
let url = '/' + option.refModel.substring(3).toLowerCase() + '/query';
let fields = `'${option.valueProp},${Array.isArray(option.displayProp)?option.displayProp.join(','):option.displayProp}'`;
//添加到beforeRender
firstMethod.push(`
Nodom.request({
url:'${url}',
type:'json',
params:{fields:${fields}}
}).then(r=>{
this.model['${dataName}'] = r.rows;
});
`);
if(Array.isArray(option.displayProp)){
let s = '';
for(let p of option.displayProp){
let prop = AMModelManager.getProp(option.refModel,p);
let width = prop.width || 100;
s += `{{${p}}}\r\n`;
}
return `
${s}
`;
}else{
return `
{{${option.displayProp}}}
`;
}
}
}
/**
* 产生文件串
* @param option edit option
* @param field 属性名
* @param relFields 关联字段
* @returns
*/
private genFileStr(option:any,field:string){
//多选文件
let multiple = option&&option.multi?"multiple='true'":'';
//文件类型
let filetype = option?option.type : '';
return ``;
}
/**
* 创建新建(oneToMany)
* @param cls - 类
* @param propName - 属性名
* @returns 模块相关串数组
*/
private genNew(cls,propName):string[]|undefined{
const prop = AMModelManager.getProp(cls.name,propName);
if(!prop){
return;
}
let entity = (prop.ref).substring(3);
let fn = entity.toLowerCase();
return [`M${entity}Main`,``,`import {M${entity}Main} from '../${fn}/${fn+'main'}.js';`]
}
/**
* 获取主键名
* @param cls
*/
private getIdName(cls:any):string{
// 取主键名
let idProp = cls.props.find(item=>item.isId);
if(!idProp){
throw `'模型${cls.name}'找不到id字段`;
}
if(idProp.ref){
let cls1 = AMModelManager.getModel(idProp.ref);
return idProp.name + '.' + this.getIdName(cls1);
}
return idProp.name;
}
/**
* 产生一行
* @param item 属性
* @param colArr 列字符串数组
* @returns {lastWidth:number,colArr:[],detailArr:[]}
*/
private genOneCol(item:AMProp,labelWidth:number):any{
const me = this;
let lastWidth;
//显示详情数组{name:string,expr:string,title:string,unit:string}
let detailArr:string[] = [];
let colArr:string[] = []
addProp(item);
return {
lastWidth:lastWidth,
colArr:colArr,
detailArr:detailArr
};
/**
* 添加属性
* @param item
* @param pre 字段前缀
* @returns
*/
function addProp(item:AMProp,pre?:string){
pre = pre || '';
//不显示在grid和detial中的项,直接跳过
if (!item.showInGrid && !item.showInDetail) {
return;
}
if (item.type === 'ref') { //非主键外键
let clsName: string
if (typeof (item.ref) == "object") {
clsName = item.ref.name;
} else {
clsName = item.ref;
}
let ref = AMModelManager.getModel(clsName);
if (item.isId) { //外键为主键 onetoone
pre = pre + item.name + '.';
for (let item1 of ref.props) {
if (!item.showInGrid) {
continue;
}
addProp(item1,pre);
}
}else if(item.editOption && (item.editOption['option'] && item.editOption['option'].multi
|| item.editOption['type'] === 'transfer')){ //多对多引用
addCol(item,(pre||'')+item.name,item.showName);
}else{ //普通引用
let pa = ref.props.filter(item => item.showInRef);
const linkUrl=item.link && clsName === 'MdlAmFile'?pre+item.name+'.downPath':undefined;
if (pa.length > 0) {
for (let p of pa) {
if(item.nameInGrid){ //外键名加入grid head
addCol(p,pre+item.name+'.'+p.name,p.showName+'('+ item.showName +')',linkUrl);
}else{
addCol(p,pre+item.name+'.'+p.name,p.showName,linkUrl);
}
}
}
}
}else {
let name = (pre||'')+item.name;
const linkUrl=item.link?name:undefined;
addCol(item,name,item.showName,linkUrl);
}
}
function addCol(item,name,title,linkUrl?:string){
let itemStr = getItemStr(item,name);
const unit = getUnit(item);
const style=item.width?`width=${item.width}`:'';
if(item.showInGrid){
let str;
if(linkUrl){
str = `
{{${itemStr}}}
`;
}else{
str = `{{${itemStr}}}`;
}
colArr.push(`
${str}
`);
lastWidth = item.width;
}
if(item.showInDetail){
detailArr.push(`
{{${itemStr}}}
`)
}
}
/**
* 返回item string
* @param item prop
* @returns
*/
function getItemStr(item:AMProp,name:string):string{
const opt = item.editOption;
if(opt && opt['type'] === 'datetime'){
return `genDate(${name})`
}else if(opt && (opt['type'] === 'transfer' || opt['option']&&opt['option'].multi)){ //取取第一个showInRef
const showProp = me.getRefProp(item.ref);
return `genM2mText(${name},'${showProp.name}')`;
}
return name;
}
/**
* 获取单位(unit)
* @param item prop
* @returns 单位内容
*/
function getUnit(item) {
if (item.unit) {
item.unit = item.unit.trim();
if (item.unit !== '') {
return '(' + item.unit + ')';
}
}
return '';
}
}
/**
* 获取引用模型第一个显示属性
* @param cls 模型
* @returns 属性对象
*/
private getRefProp(cls:any):any{
if(typeof cls === 'string'){
cls = AMModelManager.getModel(cls);
}
return cls.props.find(item=>item.showInRef);
}
}