快速開發之代碼生成器(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分鐘作一個基本的頁面應該是徹底沒有問題的。
我這裏分享下本身的代碼生成的思路,權當拋磚引玉,你們有什麼更好的方法歡迎留言。
若是你們感興趣,就在右下角幫我【推薦】一下吧,謝謝你們了。

相關文章
相關標籤/搜索