快速开发之代码生成器(asp.net mvc4 + easyui + knockoutjs)

1、前言

做为一个码农这么多年,一直在想怎么提升咱们的编码效率,关于如何提升编码效率,我本身的几点体会javascript

一、清晰的项目结构,要编写代码的地方集中
二、实现相同功能的代码量少而且清晰易懂
三、重复或有规律的代码应该自动生成css

在这里我就讨论下代码生成的问题。html

2、关于代码生成器

刚毕业时我也很是迷信代码生成器,喜欢在网上找一些代码生成器及相关的源码,喜欢在和网友讨论哪款生成器最好用,但实际上不多真正用这些东西来开发项目,缘由很简单:
一、生成出来的代码不是咱们要的代码
二、生成后的代码再修修改改,其实尚未个人ctrl+c和ctrl+v速度快。
三、生成的基本上是实体类及sql拼接代码,如今直接用linq或一些好用的orm多方便,谁还用SQLHelper加sql文拼接?
四、b/s项目中没有一个生成器能很好的能生成UI层代码及前端交互的js代码,即便能生成也是简单的页面。前端

因此,我劝你们不要迷信代码生成器了。它的确能够提升咱们的效率,可是并非网上你找一个生成器就行的。代码生成器它只是一个模板引擎而已,最重要的不是代码生成器自己,而是对一类功能或一类页面的代码规范,对本身代码的提炼,提炼出一个通用的模板。java

好比咱们常见的查询页面,录入页面等,咱们只要提炼一个标准的查询页面的代码,包括前台html,前台js,后台控制器,后台数据服务。而后把它写成模板,再利用模板引擎就能够生成咱们须要的代码了。node

代码生成器自己就是模板引擎,因此我以为最好的代码生成器不是网上流传的那些能够生成三层架构代码的软件,而是微软的razor引擎,很是简洁易懂,并且作过asp.net mvc项目的朋友应该也很熟悉。我我的以为这是用来作代码生成最好的引擎。jquery

3、页面模板

咱们仍是会想要快速开发,好比我选择了一些设定以后,就能够直接生成我想要的代码包括html及js,拷贝到项目中就能够直接运行,运行后就看到我想要的页面,基本功能都有。固然这里所说的快速开发是创建在我对页面功能的提炼模板之上的。实际上我提炼了三种模板:
一、查询页面
这个模板能够解决大部分的查询报表业务功能
image

二、编辑页面
这个编辑模板能够解决基本上全部的录入功能,由于包括了主表,及多个从表(1:N或1:1)录入,并且能够一次性在同一事务中保存。而且定义了不少触发先后事件用于写一些业务处理,而且作到差别更新。
image

三、查询编辑页面,能够查询也能够直接在grid中编辑,这个页面用于作一些简单单据或一些基础数据页面。imageweb

4、代码生成原理

把以上页面代码作成razor模板,razor模板 + 设定选项 ==razor引擎==> 页面代码ajax

怎么利用razor引擎,其实有如下几种方法:
一、直接利用mvc的view输出功能,如下为关键代码sql

var stringWriter = new StringWriter();
var viewContext = new ViewContext(controllerContext, view, viewData, TempData, stringWriter);
view.Render(viewContext, stringWriter);
var result = stringWriter.ToString();

用这种方法的优势在于不须要引入第三方类库,直接调用mvc视图的Render方法生成,并且效率很高,缺点是controllerContext及view对象的构建获取很是复杂。这种方法适用于有洁辟的码农们,我属于这一种。

二、利用第三方类库RazorEngine输出,如下为关键代码

var template = "Hello @Model.Name! Welcome to Razor!";
var viewData = new { Name = "World" });
var result = Razor.Parse(template, viewData);

这代码很清爽,一目了然,只不过要引入RazorEngine类库,并且效率不如前者。

5、代码生成页面的源码

咱们模板准备好了,引擎准备好了,那么还须要一个数据输入viewData,咱们作用户界面的目的也就是为了更好的定义这个viewData。 
UI展示主要是用了easyui 及jquery插件smartwizard
前端交互主要是采用了knockoutjs
table表格的行拖拉是采用jquery插件tableDnD
后台用webapi来处理请求,代码有点长:

Index.cshtml

@{
    ViewBag.Title = "代码生成";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

@section head{
    <link href="~/Content/js/jquery-plugins/smartwizard/smart_wizard.css" rel="stylesheet" />
    <style type="text/css">
        div#navigation{float: left;width: 180px;}
        div#wrapper{float: right;width: 100%;margin-left: -185px;}
        div#wizard{margin-left: 185px;}
        ul.anchor{margin:0 0 10px 0 !important;}
        ul li{margin-left:-16px;}
        .grid .z-txt{margin:0 -3px;width:90%;} 
        .grid input{width:90%;}
        .grid input[type=checkbox]{cursor:default;}
        .grid select{width:80%;padding:0 !important;height:22px;}
        .grid select + a{margin:5px;}
        .tDnD_whileDrag{background-color: #FBEC88 !important;}
    </style>
}

@section scripts{
    <script src="~/Content/js/jquery-plugins/smartwizard/jquery.smartWizard.js"></script>
    <script src="~/Content/js/jquery-extend/jquery.tablednd.js"></script>
    @Scripts.Render("~/Resource/Sys/Generator.js")
    <script type="text/javascript">
        $(function () {
            ko.applyBindings(new viewModel());
        });
    </script>
}

<div id="container">
    <div id="navigation">
        <div class="panel-header" style="width: 168px; border-width: 0; background: #FAFAFA;">
            代码类别 
            <input type="text" class="z-txt" data-bind="easyuiCombobox:codetype" />
            <div style="margin:1px;"></div>
            数据库名 
            <input type="text" class="z-txt" data-bind="easyuiCombobox:database" />

            <div style="margin:5px;"></div>
             <div  data-bind="autoheight:60"  style="width: 172px; border-width: 0;margin:0;padding:0; background: #FAFAFA; overflow:auto;">
                <ul data-bind="easyuiTree:tabletree"></ul>
            </div>
        </div>
    </div>
    <div id="wrapper">
        <div id="wizard" class="swMain" style="width:100%"></div>
    </div>
</div>

<script id="template-searchEdit" type="text/html">
    <ul>
        <li><a href="#step-1">
            <label class="stepNumber">1</label>
            <span class="stepDesc">设置条件部<br />
                <small>定义查询条件</small>
            </span>
        </a></li>
        <li><a href="#step-2">
            <label class="stepNumber">2</label>
            <span class="stepDesc">设置数据列<br />
                <small>定义查询显示的数据字段</small>
            </span>
        </a></li>
        <li><a href="#step-3">
            <label class="stepNumber">3</label>
            <span class="stepDesc">其它设置<br />
                <small>修改其它代码生成设置</small>
            </span>
        </a></li>
    </ul>

    <div id="step-1" class="step">
        <h2 class="StepTitle">第一步 请勾选要查询的字段</h2>
        <div>  
            <div style="width:200px;float:left;overflow:auto;" data-bind="autoheight:172">
                    <ul data-bind="easyuiTree:searchEdit.columntree"></ul>
            </div>  
            <div style="float:left;overflow:auto" data-bind="autoheight:172,autowidth:405">
                <table class="grid">
                    <thead>
                        <tr>
                            <th style="width:50px">字段</th>
                            <th style="width:120px">显示名称</th>
                            <th style="width:120px">控件类型</th>
                            @*<th >参数</th>*@
                            <th style="width:80px">查询逻辑</th>
                        </tr>
                    </thead>
                    <tbody data-bind="foreach:form.conditions">
                        <tr data-bind="attr:{id:$index}">
                            <td data-bind="text:field" style="text-align:left"></td>
                            <td><input type="text" class="z-txt" data-bind="value:title"/></td>
                            <td><select class="z-txt"  data-bind="options:$root.data.input,value:type"></select></td>
                            @*<td><input type="text" class="z-txt" data-bind="value:options"/></td>*@
                            <td><select class="z-txt"  data-bind="options:$root.data.compare,value:cp"></select></td>
                        </tr>

                    </tbody>
                </table>
            </div>  
        </div>
    </div>
    <div id="step-2" class="step">
        <h2 class="StepTitle">第二步 请勾选要显示的数据字段</h2>
        <div style="width:200px;float:left;overflow:auto;" data-bind="autoheight:172">
            <ul data-bind="easyuiTree:searchEdit.columntree2"></ul>
        </div>  
        <div style="float:left;overflow:auto" data-bind="autoheight:172,autowidth:405">
            <table class="grid">
                <thead>
                    <tr>
                        <th style="width:50px">字段</th>
                        <th style="width:100px">题头</th>
                        <th style="width:30px">隐藏</th>
                        <th style="width:30px">排序</th>
                        <th style="width:50px">对齐</th>
                        <th style="width:40px">宽度</th>
                        <th style="width:50px">格式化</th>
                        <th style="width:50px">编辑器</th>
                    </tr>
                </thead>
                <tbody data-bind="foreach:form.columns">
                    <tr data-bind="attr:{id:$index}">
                        <td data-bind="text:field" style="text-align:left"></td>
                        <td><input type="text" class="z-txt" data-bind="value:title" /></td>
                        <td><input type="checkbox" data-bind="checked:hidden"/></td>
                        <td><input type="checkbox" data-bind="checked:sortable"/></td>
                        <td><select class="z-txt"  data-bind="options:$root.data.align,value:align" ></select></td>
                        <td><input type="text" class="z-txt" data-bind="value:width" /></td>
                        <td><select class="z-txt"  data-bind="options:$root.data.formatter,optionsText:'text',optionsValue:'value',value:formatter" ></select></td>
                        <td><select class="z-txt"  data-bind="options:$root.data.editor,optionsText:'text',optionsValue:'value',value:editor" ></select></td>
                    </tr>
                </tbody>
            </table>
        </div>  
    </div>

     <div id="step-3" class="step">
        <h2 class="StepTitle">第三步 其它设置</h2>

        <div class="container_12">
            <div class="grid_1 lbl">业务区域</div>
            <div class="grid_2 val"><input type="text" class="z-txt" data-bind="value:form.area"/></div>

            <div class="clear"></div>

            <div class="grid_1 lbl">控制器名称</div>
            <div class="grid_2 val"><input type="text" class="z-txt" data-bind="value:form.controller"/></div>

            <div class="clear"></div>

            <div class="grid_1 lbl">生成路径</div>
            <div class="grid_2 val"><input type="text" class="z-txt" data-bind="value:form.path"/></div>
        </div>
    </div>
</script>

<script id="template-search" type="text/html">
    <ul>
        <li><a href="#step-1">
            <label class="stepNumber">1</label>
            <span class="stepDesc">设置条件部<br />
                <small>定义查询条件</small>
            </span>
        </a></li>
        <li><a href="#step-2">
            <label class="stepNumber">2</label>
            <span class="stepDesc">设置数据列<br />
                <small>定义查询显示的数据字段</small>
            </span>
        </a></li>
        <li><a href="#step-3">
            <label class="stepNumber">3</label>
            <span class="stepDesc">其它设置<br />
                <small>修改其它代码生成设置</small>
            </span>
        </a></li>
    </ul>

    <div id="step-1" class="step">
        <h2 class="StepTitle">第一步 请勾选要查询的字段</h2>
        <div>  
            <div style="width:200px;float:left;overflow:auto;" data-bind="autoheight:172">
                    <ul data-bind="easyuiTree:searchEdit.columntree"></ul>
            </div>  
            <div style="float:left;overflow:auto" data-bind="autoheight:172,autowidth:405">
                <table class="grid">
                    <thead>
                        <tr>
                            <th style="width:50px">字段</th>
                            <th style="width:120px">显示名称</th>
                            <th style="width:120px">控件类型</th>
                            @*<th >参数</th>*@
                            <th style="width:80px">查询逻辑</th>
                        </tr>
                    </thead>
                    <tbody data-bind="foreach:form.conditions">
                        <tr data-bind="attr:{id:$index}">
                            <td data-bind="text:field" style="text-align:left"></td>
                            <td><input type="text" class="z-txt" data-bind="value:title"/></td>
                            <td><select class="z-txt"  data-bind="options:$root.data.input,value:type"></select></td>
                            @*<td><input type="text" class="z-txt" data-bind="value:options"/></td>*@
                            <td><select class="z-txt"  data-bind="options:$root.data.compare,value:cp"></select></td>
                        </tr>

                    </tbody>
                </table>
            </div>  
        </div>
    </div>
    <div id="step-2" class="step">
        <h2 class="StepTitle">第二步 请勾选要显示的数据字段</h2>
        <div style="width:200px;float:left;overflow:auto;" data-bind="autoheight:172">
            <ul data-bind="easyuiTree:searchEdit.columntree2"></ul>
        </div>  
        <div style="float:left;overflow:auto" data-bind="autoheight:172,autowidth:405">
            <table class="grid">
                <thead>
                    <tr>
                        <th style="width:50px">字段</th>
                        <th style="width:100px">题头</th>
                        <th style="width:30px">隐藏</th>
                        <th style="width:30px">排序</th>
                        <th style="width:50px">对齐</th>
                        <th style="width:40px">宽度</th>
                        <th style="width:50px">格式化</th>
                    </tr>
                </thead>
                <tbody data-bind="foreach:form.columns">
                    <tr data-bind="attr:{id:$index}">
                        <td data-bind="text:field" style="text-align:left"></td>
                        <td><input type="text" class="z-txt" data-bind="value:title" /></td>
                        <td><input type="checkbox" data-bind="checked:hidden"/></td>
                        <td><input type="checkbox" data-bind="checked:sortable"/></td>
                        <td><select class="z-txt"  data-bind="options:$root.data.align,value:align" ></select></td>
                        <td><input type="text" class="z-txt" data-bind="value:width" /></td>
                        <td><select class="z-txt"  data-bind="options:$root.data.formatter,optionsText:'text',optionsValue:'value',value:formatter" ></select></td>
                    </tr>
                </tbody>
            </table>
        </div>  
    </div>

     <div id="step-3" class="step">
        <h2 class="StepTitle">第三步 其它设置</h2>

        <div class="container_12">
            <div class="grid_1 lbl">业务区域</div>
            <div class="grid_2 val"><input type="text" class="z-txt" data-bind="value:form.area"/></div>

            <div class="clear"></div>

            <div class="grid_1 lbl">控制器名称</div>
            <div class="grid_2 val"><input type="text" class="z-txt" data-bind="value:form.controller"/></div>

            <div class="clear"></div>

            <div class="grid_1 lbl">生成路径</div>
            <div class="grid_2 val"><input type="text" class="z-txt" data-bind="value:form.path"/></div>
        </div>
    </div>
</script>

<script id="template-edit" type="text/html">
    <ul>
        <li><a href="#step-1">
            <label class="stepNumber">1</label>
            <span class="stepDesc">设置主表编辑区<br />
                <small>定义主表编辑字段</small>
            </span>
        </a></li>
        <li><a href="#step-2">
            <label class="stepNumber">2</label>
            <span class="stepDesc">设置明细数据页签<br />
                <small>定义明细表及页签</small>
            </span>
        </a></li>
        <li><a href="#step-3">
            <label class="stepNumber">3</label>
            <span class="stepDesc">其它设置<br />
                <small>修改其它代码生成设置</small>
            </span>
        </a></li>
    </ul>

    <div id="step-1" class="step">
        <h2 class="StepTitle">第一步 请勾选要编辑的字段</h2>
        <div>  
            <div style="width:200px;float:left;overflow:auto;" data-bind="autoheight:172">
                    <ul data-bind="easyuiTree:searchEdit.columntree"></ul>
            </div>  
            <div style="float:left;overflow:auto" data-bind="autoheight:172,autowidth:405">
                <table class="grid">
                    <thead>
                        <tr>
                            <th style="width:20%">字段</th>
                            <th style="width:40%">标签名称</th>
                            <th style="width:30%">控件类型</th>
                            <th style="width:10%">只读</th>
                        </tr>
                    </thead>
                    <tbody data-bind="foreach:form.conditions">
                        <tr data-bind="attr:{id:$index}">
                            <td data-bind="text:field" style="text-align:left"></td>
                            <td><input type="text" class="z-txt" data-bind="value:title"/></td>
                            <td><select class="z-txt"  data-bind="options:$root.data.input,value:type" style="width:60%"></select><a href="#">高级</a></td>
                            <td><input type="checkbox" data-bind="checked:readonly"/></td>
                        </tr>
                    </tbody>
                </table>
            </div>  
        </div>
    </div>
    <div id="step-2" class="step">
        <h2 class="StepTitle">第二步 请设置页面中的tab页签</h2>
         
        <div style="float:left;overflow:auto;width:150px;" data-bind="autoheight:172">
            <a href="#" class="buttonNext" style="float:left;margin:5px 3px 5px 0" data-bind="click:edit.addTab">添加Tab页签</a>
            <table class="grid">
                <thead>
                    <tr>
                        <th style="width:30%">#</th>
                        <th style="width:70%">名称</th>
                    </tr>
                </thead>
                <tbody data-bind="foreach:form.tabs">
                    <tr data-bind="attr:{id:$index}">
                        <td><a href="#" data-bind="click:$parent.edit.removeTab">删除</a></td>
                        <td><input type="text" class="z-txt" data-bind="value:title,click:$parent.edit.clickTab" style="width:90%"/></td>
                    </tr>
                </tbody>
            </table>
        </div>  

         <div id="edit-tab-setting" style="float:left;overflow:auto;" data-bind="autoheight:172,autowidth:355,visible:edit.selectedTitle()!=null">
 
        </div> 
    </div>

     <div id="step-3" class="step">
        <h2 class="StepTitle">第三步 其它设置</h2>

        <div class="container_12">
            <div class="grid_1 lbl">业务区域</div>
            <div class="grid_2 val"><input type="text" class="z-txt" data-bind="value:form.area"/></div>

            <div class="clear"></div>

            <div class="grid_1 lbl">控制器名称</div>
            <div class="grid_2 val"><input type="text" class="z-txt" data-bind="value:form.controller"/></div>

            <div class="clear"></div>

            <div class="grid_1 lbl">生成路径</div>
            <div class="grid_2 val"><input type="text" class="z-txt" data-bind="value:form.path"/></div>
        </div>
    </div>
</script>

<script type="text/html" id="template-edit-tab-setting">
     <div style="padding:8px;clear:both">
        <span>页签类型 </span>
        <select class="z-txt" style="padding:0;height:22px;" data-bind="value:edit.selectedTab.type">
            <option value="empty">empty</option>
            <option value="grid">grid</option>
            <option value="form">form</option>
        </select> 

        <span data-bind="visible:edit.selectedTab.type()!='empty'"> 数据表 </span>
        <select class="z-txt" style="padding:0;height:22px;" data-bind="options:data.table,optionsText:'text',optionsValue:'id',value:edit.selectedTab.subtable,visible:edit.selectedTab.type()!='empty'"></select>

        <span data-bind="visible:edit.selectedTab.type()!='empty'">与主表的关联</span>
        <select class="z-txt" style="padding:0;height:22px;" data-bind="options:data.tablekey,value:edit.selectedTab.relationship,visible:edit.selectedTab.type()!='empty'"></select>
    </div>

    <div style="width:180px;float:left;overflow:auto;margin-right:-18px;" data-bind="autoheight:212,visible:edit.selectedTab.type()!='empty'">
        <ul data-bind="easyuiTree:edit.columntree2"></ul>
    </div> 
    
    <div style="float:right;overflow:auto;" data-bind="autoheight:210,autowidth:535,visible:edit.selectedTab.type()!='empty'">
        <table class="grid">
            <thead>
                <tr>
                    <th style="width:50px">字段</th>
                    <th style="width:100px">题头</th>
                    <th style="width:30px" data-bind="visible:edit.selectedTab.type()=='grid'">隐藏</th>
                    <th style="width:30px" data-bind="visible:edit.selectedTab.type()=='grid'">排序</th>
                    <th style="width:50px" data-bind="visible:edit.selectedTab.type()=='grid'">对齐</th>
                    <th style="width:40px" data-bind="visible:edit.selectedTab.type()=='grid'">宽度</th>
                    <th style="width:50px" data-bind="visible:edit.selectedTab.type()=='grid'">格式化</th>
                    <th style="width:50px" data-bind="visible:edit.selectedTab.type()=='grid'">编辑器</th>
                    <th style="width:50px" data-bind="visible:edit.selectedTab.type()=='form'">控件类型</th>
                    <th style="width:10px" data-bind="visible:edit.selectedTab.type()=='form'">只读</th>
                </tr>
            </thead>
            <tbody data-bind="foreach:edit.selectedTab.columns">
                <tr data-bind="attr:{id:$index}">
                    <td data-bind="text:field" style="text-align:left"></td>
                    <td><input type="text" class="z-txt" data-bind="value:title" /></td>
                    <td data-bind="visible:$parent.edit.selectedTab.type()=='grid'"><input type="checkbox" data-bind="checked:hidden"/></td>
                    <td data-bind="visible:$parent.edit.selectedTab.type()=='grid'"><input type="checkbox" data-bind="checked:sortable"/></td>
                    <td data-bind="visible:$parent.edit.selectedTab.type()=='grid'"><select class="z-txt"  data-bind="options:$root.data.align,value:align" ></select></td>
                    <td data-bind="visible:$parent.edit.selectedTab.type()=='grid'"><input type="text" class="z-txt" data-bind="value:width" /></td>
                    <td data-bind="visible:$parent.edit.selectedTab.type()=='grid'"><select class="z-txt"  data-bind="options:$root.data.formatter,optionsText:'text',optionsValue:'value',value:formatter" ></select></td>
                    <td data-bind="visible:$parent.edit.selectedTab.type()=='grid'"><select class="z-txt"  data-bind="options:$root.data.editor,optionsText:'text',optionsValue:'value',value:editor" ></select></td>
                    <td data-bind="visible:$parent.edit.selectedTab.type()=='form'"><select class="z-txt"  data-bind="options:$root.data.input,value:type"></select></td>
                    <td data-bind="visible:$parent.edit.selectedTab.type()=='form'"><input type="checkbox" data-bind="checked:readonly"/></td>
                </tr>
            </tbody>
        </table>
    </div>
</script>

Generator.js

/**
* 模块名:mms viewModel
* 程序名: Generator.js
* Copyright(c) 2013 liuhuisheng [ liuhuisheng.xm@gmail.com ] 
**/

var viewModel = function () {
    var self = this;

    this.form = {
        type: '',
        database:ko.observable(),
        table: ko.observable(),
        controller: ko.observable(),
        area:ko.observable(),
        conditions: ko.observableArray(),
        columns: ko.observableArray(),
        tabs: ko.observableArray(),
        path: ko.observable("~/Generator/")
    };
 
    this.resetForm = function () {
        self.form.conditions([]);
        self.form.columns([]);
        self.form.tabs([]);
    };

    this.data = {
        codetype: [{ text: 'search', value: 'search' }, { text: 'edit', value: 'edit' }, { text: 'searchEdit', value: 'searchEdit' }],
        database: ko.observableArray(), 
        table: ko.observableArray(),
        column:ko.observableArray(),
        tablekey: ko.observableArray(),
        input: ['text', 'autocomplete', 'combobox', 'lookup','datebox','daterange'],
        compare: ['equal', 'like', 'startwith', 'endwith', 'greater', 'less', 'daterange'],
        align:['left','center','right'],
        formatter: [{text:'',value:''},{ text: '日期', value: 'com.formatDate' }, { text: '时间', value: 'com.formatTime' }, { text: '金额', value: 'com.formatMoney' }, { text: '是否', value: 'com.formatCheckbox' }],
        editor: [{text:'',value:''},{ text: '文本', value: 'text'}, { text: '整数', value: "{type: 'numberbox',options:{min: 0}}" }, { text: '两位小数', value: "{type: 'numberbox',options:{min: 0, precision: 2}}" }, { text: '下拉框', value: "{type:'combobox',options:{}}" }, { text: '弹出框', value: "{type:'lookup',options:{}}" }, { text: '日期', value: 'datebox' }]
    };

    this.initDatabase = function () {
        com.ajax({
            type: 'GET',
            async:false,
            url: '/api/sys/generator/GetConnectionStrings',
            success: function (d) {
                self.data.database(d);
            }
        });
    };

    this.initDatabase();

    this.getTableUrl = function () {
        return '/api/sys/generator/GetTables?database=' + self.form.database();
    };
    this.getColumnUrl = function (table) {
        return '/api/sys/generator/GetColumns?database=' + self.form.database() + "&table=" + table;
    }

    this.codetype = {
        showblank: true,
        width: 110,
        data: self.data.codetype,
        onSelect: function (node) {
            self.form.type = node.value;
            self.initWizard();
        }
    };

    this.database = {
        showblank: true,
        width: 110,
        data: self.data.database,
        onSelect: function (node) {
            self.form.database(node.value)
            self.form.area((node.value.split('.')[1] || node.value).replace(/(^|\s+)\w/g, function (s) { return s.toUpperCase(); }));
        }
    };

    this.tabletree = {
        method: 'GET',
        url: ko.computed(self.getTableUrl),
        loadFilter: function (d) {
            var data = utils.filterProperties(d.rows || d, ['TableName as id', 'TableName as text']);
            self.data.table(data);
            return data;
        },
        onSelect: function (node) {
            self.form.table(node.id);
            self.edit.init();
            self.resetWizard();
            self.form.controller((node.id.split('_')[1] || node.id).replace(/(^|\s+)\w/g, function (s) { return s.toUpperCase(); }));
        }
    };

    this.generator = function () {
        com.ajax({
            type:'POST',
            url: '/api/sys/generator',
            data: ko.toJSON(self.form),
            success: function (d) {
                com.message('success', "代码已生成!");
            }
        });
    };

    this.searchEdit = {};
    this.searchEdit.columntree = {
        method: 'GET',
        url: ko.computed(function () {
            return self.getColumnUrl(self.form.table());
        }),
        checkbox: true,
        loadFilter: function (d) {
            return utils.filterProperties(d.rows || d, ['ColumnName as id', 'ColumnName as text']);
        },
        onSelect: function (node) {
            var handle = node.checked ? 'uncheck' : 'check';
            $(this).tree(handle, node.target);
        },
        onCheck: function (node, checked) {
            if (checked)
                self.form.conditions.push({ field: node.id, title: node.id, type: 'text', options: '', cp: 'equal',readonly:false });
            else
                self.form.conditions.remove(function (item) { return item.field == node.id });
        },
        onLoadSuccess: self.resetForm
    };

    this.searchEdit.columntree2 = {
        method: 'GET',
        url: ko.computed(function () {
            return self.getColumnUrl(self.form.table());
        }),
        checkbox: true,
        loadFilter: function (d) {
            return utils.filterProperties(d.rows || d, ['ColumnName as id', 'ColumnName as text']);
        },
        onSelect: function (node) {
            var handle = node.checked ? 'uncheck' : 'check';
            $(this).tree(handle, node.target);
        },
        onCheck: function (node, checked) {
            var arr = self.form.columns;
            
            if (checked) {
                var item = $.grep(arr(), function (row) {return row.field == node.id;})[0];
                item || arr.push({ field: node.id, title: node.id, hidden: false, sortable: true, align: 'left', width: 80, formatter: '', editor: 'text' });
            } else
                arr.remove(function (item) { return item.field == node.id });
        }
    };

    this.edit = {};
    this.edit.selectedTab = {
        title: ko.observable(),
        type: ko.observable(),
        subtable: ko.observable(),
        relationship: ko.observable(),
        columns: ko.observableArray(),
        primaryKeys:ko.observableArray()
    };
     
    this.edit.columntree2 = {
        method: 'GET',
        url:ko.observable(),
        checkbox: true,
        loadFilter: function (d) {
            self.data.column(d);
            var list = utils.filterProperties(d.rows || d, ['ColumnName as id', 'ColumnName as text']);
            self.edit.setDefaultForm();
            self.edit.resetTableKey();
            var checkedList = [];
            for (var i in self.edit.selectedTab.columns())
                checkedList.push(self.edit.selectedTab.columns()[i].field);
            for (var i in list)
                if ($.inArray(list[i].id, checkedList) > -1) list[i].checked = true;
            
            return list
        },
        onSelect: function (node) {
            var handle = node.checked ? 'uncheck' : 'check';
            $(this).tree(handle, node.target);
        },
        onCheck: function (node, checked) {
            var arr = self.edit.selectedTab.columns;

            if (checked) {
                var item = $.grep(arr(), function (row) { return row.field == node.id; })[0];
                item || arr.push({ field: node.id, title: node.id, hidden: false, sortable: true, align: 'left', width: 80, formatter: '', editor: 'text', type: '', readonly: true });
            } else
                arr.remove(function (item) { return item.field == node.id });
        }
    }
    this.edit.init = function () {
        self.edit.selectedTitle(null);
        self.edit.selectedTab = null;
        $('#edit-tab-setting').empty();
    };
    this.edit.addTab = function () {
        var title = 'tab' + (self.form.tabs().length + 1);
        var newTab = {
            title: ko.observable(title),
            type: ko.observable('empty'),
            subtable: ko.observable(self.form.table()),
            relationship: ko.observable(),
            columns: ko.observableArray(),
            primaryKeys:ko.observableArray()
        };
        newTab.type.subscribe(function (value) {
            if (value == 'grid') {
                var item = $.grep(self.data.table(), function (row) { return row.id == self.form.table() + "Detail" })[0];
                if (item)
                    newTab.subtable(item.id);
            }
            else if (value == 'form') {
                newTab.subtable(self.form.table());
            }
        });
        newTab.columns.subscribe(self.tableDnDUpdate);
        newTab.subtable.subscribe(function (value) {
            self.edit.selectedTab.columns([]);
            self.edit.columntree2.url(self.getColumnUrl(value));
        });

        self.form.tabs.push(newTab);
    };
    
    this.edit.removeTab = function (row,event) {
        self.form.tabs.remove(row);

        if (row.title() == self.edit.selectedTitle())
            self.edit.selectedTitle(null);
    };
    this.edit.selectedTitle = ko.observable();
    this.edit.clickTab = function (row, event) {
        if (row.title() == self.edit.selectedTitle()) return;
 
        self.edit.selectedTitle(row.title());
        self.edit.selectedTab = row;
        self.edit.columntree2.url = ko.observable(self.getColumnUrl(self.edit.selectedTab.subtable()));

        var currentTr = $(event.srcElement).parent("td").parent("tr");
        currentTr.parent().find("tr.tree-node-selected").removeClass("tree-node-selected");
        currentTr.addClass("tree-node-selected");

        var tabTemplate = $('#template-edit-tab-setting').html();
        var wrapper = $('#edit-tab-setting').empty().html(tabTemplate);

        ko.cleanNode(wrapper[0]);
        ko.applyBindings(self, wrapper[0]);
        wrapper.find("table").tableDnD({ onDrop: self.tableDnDSort });
    };
    this.edit.resetTableKey = function () {
        var relationship = self.edit.selectedTab.relationship();
        self.data.tablekey([]);
        var cols = self.data.column();
        for (var i in cols)
            if (cols[i].IsIdentity || cols[i].IsPrimaryKey)
                self.data.tablekey.push(cols[i].ColumnName);

        self.edit.selectedTab.relationship(relationship);
        self.edit.selectedTab.primaryKeys(self.data.tablekey());
    };
    this.edit.setDefaultForm = function () {
        var arr = [
            { field: 'ApproveState', title: '审批状态', type: 'text', readonly: true },
            { field: 'ApproveRemark', title: '审批意见', type: 'text', readonly: true },
            { field: 'ApprovePerson', title: '审批人', type: 'text', readonly: true },
            { field: 'ApproveDate', title: '审批日期', type: 'datebox', readonly: true },
            { field: 'CreatePerson', title: '编制人', type: 'text', readonly: true },
            { field: 'CreateDate', title: '编制日期', type: 'datebox', readonly: true },
            { field: 'UpdatePerson', title: '修改人', type: 'text', readonly: true },
            { field: 'UpdateDate', title: '修改日期', type: 'datebox', readonly: true }
        ];

        var cols = self.data.column();
        var defaults = { field: '', title: '', hidden: false, sortable: true, align: 'left', width: 80, formatter: '', editor: 'text', type: '', readonly: true };
        for (var i in arr) {
            if (!$.grep(cols, function (item) { return item.ColumnName == arr[i].field; }).length)
                return;

            arr[i] = $.extend({}, defaults, arr[i]);
        }

        self.edit.selectedTab.columns(arr);

        var tree = self.edit.columntree2.$element();
        for (var i in arr) {
            var node = tree.tree('find', arr[i].field);
            if (node) tree.tree('check', node.target);
        }
    };

    this.initWizard = function () {
        var stepTemplate = $('#template-' + self.form.type);
        if (!stepTemplate.length) return;

        var wizard = $('#wizard').removeData('smartWizard').empty();
        ko.cleanNode(wizard[0]);
        
        wizard.html(stepTemplate.html());
        wizard.smartWizard({
            labelNext: '下一步',
            labelPrevious: '上一步',
            labelFinish: '生成',
            onFinish: self.generator
        });
        var resizeStep = function () {
            $(".step").height($(window).height() - 145)
                      .width($(window).width() - 205);
            $(".actionBar").width($(window).width() - 195);
            var index = wizard.smartWizard('currentStep');
            wizard.smartWizard('goToStep', index);
        };
        $(window).resize(resizeStep);
        resizeStep();
        ko.applyBindings(self, wizard[0]);
        wizard.find("table").tableDnD({ onDrop: self.tableDnDSort });

        for (var i in self.form) {
            if ($.isFunction(self.form[i]))
                if (self.form[i]() instanceof Array)
                    if (self.form[i].subscribe) 
                        self.form[i].subscribe(self.tableDnDUpdate);
        }
    };
    this.resetWizard = function () {
        var wizard = $("#wizard").smartWizard('goToStep', 1);
        for (var i = 1; i <= wizard.find(">ul>li").length; i++)
            wizard.smartWizard("disableStep", i);
    };

    this.tableDnDUpdate = function () {
        setTimeout('$("table").tableDnDUpdate()', 300);
    };
   
    this.tableDnDSort = function (table, row) {
        var name = $(table).find("tbody").attr("data-bind").replace('foreach:form.','');
        var array = self.form[name], i = 0;

        if (name == 'foreach:edit.selectedTab.columns')
            array = self.edit.selectedTab.columns;

        $("tr[id]", table).each(function () { array()[this.id].id = i++; });
        array.sort(function (left, right) { return left.id == right.id ? 0 : (left.id < right.id ? -1 : 1) });

        //for fix ko bug refresh ui
        var tempArr = array();
        array([]);
        array(tempArr);
    };
};

razor模板
model.cshtml

@using Zephyr.Core.Generator
using System;
using System.Collections.Generic;
using System.Text;
using Zephyr.Core;

namespace Zephyr.Web.@(@Model.Area).Models
{
    [Module("@Model.Database")]
    public class @(Model.TableName)Service : ServiceBase<@Model.TableName>
    {
       
    }

    public class @Model.TableName : ModelBase
    {
@foreach(TableSchema item in Model.Columns)
{
    if (item.IsIdentity)
    {
        @:[Identity]
    }

    if (item.IsPrimaryKey)
    {
        @:[PrimaryKey]   
    }
        @:public @item.TypeName @item.ColumnName { get; set; }
}
    }
}

其它各页面的模板我就不一一贴出来了,你们能够查看我之前的那些关于共通viewModel的博客,查询及编辑页面我都有详细介绍过,你们也能够本身提炼出本身的一套模板,而后也能够用这同一个思路去作一个代码生成器。

6、代码生成用户界面

这个用户界面咱们仍是要把三种页面的定义分开看: 

一、查询页面生成

第一步,选择代码类别search(查询页面),选择数据库,选择业务主表,再勾选字段便可实现查询条件部的设置,而且实现了拖拉排序功能。你们能够对照查询模板看。 
image

第二步,选择grid中要显示的列,而且设置属性,格式化等 
image

第三步,设置一些全局设定,主要根据这些参数肯定命名空间,生成文件名等信息 
image

点击生成按钮,按设定生成代码,生成后弹出文件夹,已分别生成MVC三层代码 
image

mms_receive.cs 
image

Index.cshtml 
image

ReceiveController.cs 
image

把这个代码直接拷贝到项目中直接运行,测试条件过滤都没有问题,grid会自适应高度,grid远程排序,选择分页翻页都没有问题,全部的功能均可用, 
只有lookup控件弹出是空值,由于只把控制设置为了lookup但没有为它设置更详细的选项。autocomplete也是一样 
即代码生成器已经生成了一个大的结构及UI,一些小细节仍是要手动修改下,代码生成的UI界面若是把每一个控件的选项也作进去会至关的复杂,也没有必要再细化了。 
image

二、编辑页面生成 
第一步,选择主表编辑区的字段及控件类型,控件类型中的高级还未实现,这个编辑的UI也能够参照编辑的模板看 
image

第二步,添加tab页签,选择页签类型(grid,form,empty) grid是指跟主表N:1关系,form是指跟主表1:1关系,empty是空页签,生成后本身能够添加内容 
这里我随便添加三个tab页签tab1 tab2 tab3 
tab1用来放人员变更grid(跟主表关系N:1)image 

tab2就选择form(跟主表关系1:1,也能够是主表自己)image

tab3也随便添些东西 
image

第三步,其它设置 
image

点击生成按钮,生成后自动打开文件夹 
image

把这些代码拷贝到项目中直接运行 
image

tab2 
image

tab3,修改主表数据,tab1,tab2,tab3点保存,能保存成功, 
image

审核按钮也可用,审核后单据不可修改 
image

这个编辑功能基本上能够囊括不少的录入页面了,能够算是比较通用了

三、查询编辑页面(查询编辑在同一个页面内)页面生成

第一步,选择查询条件并设置控件类型image

第二步,设置grid中的数据列,及编辑器 
image

第三步,其它设置 
image

点击生成按钮,生成后自动打开文件夹  
image

把代码直接拷贝到项目中运行,结果以下,经测试除了控件还须要进一步设置,全部按钮功能正常使用 
image

 

7、后述

有了这个代码生成的功能,5分钟作一个基本的页面应该是彻底没有问题的。
我这里分享下本身的代码生成的思路,权当抛砖引玉,你们有什么更好的方法欢迎留言。
若是你们感兴趣,就在右下角帮我【推荐】一下吧,谢谢你们了。

相关文章
相关标签/搜索