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 = `
${fieldArr.join('')}
`; //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); } }