極極極極極簡的的增刪查改(CRUD)解決方案

去年這個時候寫過一篇全自動數據表格的文章 http://www.cnblogs.com/liuyh/p/5747331.html。文章對本身寫的一個js組件作了個概述,不少人把它看成了一款功能類似的純前端表格組件,然而這並非個人初衷,個人本意是讓後臺系統的開發從重複的增刪查改中脫離出來,我想即便不使用代碼生成器也能作到極快速的開發。它不是優秀的表格組件,而是一個極簡的增刪查改解決方案。
 
爲何不使用現有的表格組件
成熟的表格組件有不少,功能強大的也有很多。它們都大大減小了咱們的工做量,那麼爲何我還要本身再折騰一個呢?由於還能減小更多...好比:
一、現有的絕大部分表格組件依然避免不了咱們須要手寫新增、編輯頁面相關的代碼。
二、即便有表格內部支持編輯模式,可是幾乎都不支持圖片和富文本格式。
三、對於查詢和分頁等功能不少都須要本身去實現,至少服務端如此。
 
其實不是表格的設計者們沒有想到這些問題,而是要實現這些功能對於一個表格組件來講顯得太重了,我想不少表格的使用者們也不能接受使用表格前先引入一個ueditor吧。可是對於咱們來講,在輕量級表格與輕量級的工做面前,我想結果是顯而易見的,這纔是我寫表格組件bode.grid.js的初衷。
 
如何省去新增和編輯頁面
不少人有一個慣性的思惟,認爲每一個表格的新增、編輯頁面都不一樣,字段不一樣,字段的類型也不一樣,有些是數字,有些是下拉選擇,有些又要上傳圖片,有些仍是富文本,都不能提取共性,更何談省去。萬事萬物都是有共性的,就像人與老虎同屬哺乳動物,鳥和雞都是鳥類,難道說人和鳥就沒有共性麼?不是的,他們都是生物。好吧,扯遠了...看看這個數據結構,它能知足咱們的要求:
[
    {
        title:"名稱",
        data:"字段名",
        type:"字段類型",
        value:""
    }
]

 對於一個簡單的form表單,這個結構足以描述。太複雜的頁面?本身寫...。而就個人實踐經驗告訴我,這個結構足以知足90%後臺系統中的新增與修改。javascript

 
共性提取出來了,如今所須要作的就是按字段類型渲染不一樣的組件,好比圖片類型的上傳插件、富文本類型的編輯器等,只是dom結構的拼接以及組件的初始化,示例代碼以下:
this.initForm = function () {
            var form = $('<form id="' + this.formId + '" class="form-horizontal layer-row-container" role="form"></form>');
            form.appendTo($("body"));

            for (var i = 0, iLen = this.columns.length; i < iLen; i++) {
                var colType = this.columns[i]["type"];
                if (!colType) continue;
                var row;
                if (["text", "number", "datepicker", "timepicker", "hide"].indexOf(colType) >= 0) {
                    row = $('<div class="form-group"><label for="' +
                        this.columns[i]["data"] +
                        '" class="col-sm-3 control-label">' +
                        this.columns[i]["title"] +
                        ':</label><div class="col-sm-9"><input type="text" class="form-control" id="' +
                        this.columns[i]["data"] +
                        '"></div></div>');
                    if (colType === "number") {
                        $.bode.tools.input.formatDiscount(row.find("#" + this.columns[i]["data"]));
                    } else if (colType === "datepicker" || colType === "timepicker") {
                        var showTime = colType === "timepicker";
                        $.bode.tools.input.formatTime(row.find("#" + this.columns[i]["data"]), showTime);
                    } else if (colType === "hide") {
                        row.hide();
                    }
                    row.appendTo(form);
                } else if (["switch", "dropdown"].indexOf(colType) >= 0) {
                    var source = this.columns[i]["source"];
                    var valueFiled = source.valueField || "value";
                    var textField = source.textField || "text";
                    row = $('<div class="form-group"><label for="' +
                        this.columns[i]["data"] +
                        '"  class="col-sm-3 control-label">' +
                        this.columns[i]["title"] +
                        ':</label><div class="col-sm-9"><select class="form-control" id="' +
                        this.columns[i]["data"] +
                        '"></select></div></div>');
                    var select = row.find("#" + this.columns[i]["data"]);
                    for (var j = 0, jLen = source["data"].length; j < jLen; j++) {
                        var option = source["data"][j];
                        $('<option value="' +
                                option[valueFiled] +
                                '">' +
                                option[textField
                        ] +
                                '</option>')
                            .appendTo(select);
                    }
                    row.appendTo(form);
                } else if (colType === "img") {
                    row = $('<div class="form-group"><label for="' +
                        this.columns[i]["data"] +
                        '"  class="col-sm-3 control-label">' +
                        this.columns[i]["title"] +
                        ':</label><div class="col-sm-9"><div class="uploader-list"><div class="file-item thumbnail"><img style="width:160px;height:90px;" id="img_' +
                        this.columns[i]["data"] +
                        '" src="" /></div></div><div id="' +
                        this.columns[i]["data"] +
                        '">選擇圖片</div></div></div>');
                    row.appendTo(form);
                    // 初始化Web Uploader
                    var uploader = WebUploader.create({
                        auto: true, // 選完文件後,是否自動上傳。
                        swf: '/Content/js/plugs/webuploader/Uploader.swf', // swf文件路徑
                        server: this.imgSaveUrl, // 文件接收服務端。
                        // 選擇文件的按鈕。可選。
                        // 內部根據當前運行是建立,多是input元素,也多是flash.
                        pick: '#' + this.columns[i]["data"],

                        // 只容許選擇圖片文件。
                        accept: {
                            title: 'Images',
                            extensions: 'gif,jpg,jpeg,bmp,png',
                            mimeTypes: 'image/*'
                        }
                    });
                    uploader.on("uploadSuccess",
                        function (file, resp) {
                            $("#img_" + this.options.pick.substring(1)).attr("src", resp);
                        });
                } else if (colType === "textarea") {
                    row = $('<div class="form-group"><label for="' +
                        this.columns[i]["data"] +
                        '"  class="col-sm-3 control-label">' +
                        this.columns[i]["title"] +
                        ':</label><div class="col-sm-9"><textarea class="form-control" id="' +
                        this.columns[i]["data"] +
                        '" style="overflow: hidden; word-wrap: break-word; resize: horizontal; height: 48px;"></textarea></div></div>');
                    row.appendTo(form);
                    row.find('textarea').autosize({ append: "\n" });
                } else if (colType === "richtext") {
                    row = $('<div class="form-group"><label for="' +
                        this.columns[i]["data"] +
                        '"  class="col-sm-3 control-label">' +
                        this.columns[i]["title"] +
                        ':</label><div class="col-sm-9"><textarea class="form-control" id="' +
                        this.columns[i]["data"] +
                        '" style="heght:150px;"></textarea></div></div>');
                    row.appendTo(form);

                    var editor = new wangEditor(this.columns[i]["data"]);
                    editor.config.uploadImgUrl = this.imgSaveUrl;
                    editor.config.withCredentials = false;

                    editor.create();
                    editorHash[this.columns[i]["data"]] = editor;
                }
            }
            $('<hr class="wide" />').appendTo(form);
            this.isFormInited = true;
        }
View Code

 最後在公共的js裏將整個表單加入頁面上,這樣就完成了省去新增和編輯頁面的工做。頁面是省去了,可是數據如何保存,試想咱們通常的新增和編輯接口,其提交的數據結構大概是這樣:css

{
    字段名1:值1,
    字段名2:值2,
    字段名3:值3
}

 而從上文的數據結構中咱們能很容易的提取出須要提交的結構,因此咱們只須要一個提交的服務端地址便可,這即是省去新增、編輯頁面的整個思路。可是新增、編輯過程當中還會有不少細節性的問題,好比:html

一、字段的可操做性:隱藏、不可編輯。
二、下拉選擇中的數據源如何獲取。
三、上傳插件的服務端接口如何配置。
恕我不能一一介紹了,有興趣的能夠查看源碼或者本身思考也是很不錯的。
 
如何省去複雜查詢帶來的代碼量
幾乎每個數據表格都會有篩選、排序、分頁,其中排序和篩選條件千奇百怪,對應的編碼也是各不相同。那麼如何節省查詢的代碼,甚至說作到業務層零查詢代碼實現功能完備的查詢機制。咱們先假想一個功能完備的查詢機制以下:
一、每列都可排序,排序支持正序和倒序。
二、每列都可篩選,其篩選條件可由用戶靈活組裝。
三、分頁。
由此咱們可設計以下查詢數據結構:
{
    "pageIndex":1,
    "pageSize":15,
    "sortConditions":[
        {
            "sortField":"name",
            "listSortDirection":1
        }
    ],
    "filterGroup":{
        "rules":[
            {
                "field":"name",
                "operate":"contains",
                "value":"a"
            }
        ]
    }
}

排序很容易,難點在於篩選條件的處理,特別是結合EF的使用,如何將filterGroup轉化爲查詢的表達式,仍是看代碼吧:前端

using Abp.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace Abp.Application.Services.Query
{
    /// <summary>
    /// 查詢表達式輔助操做類
    /// </summary>
    public static class FilterHelper
    {
        #region 字段

        private static readonly Dictionary<FilterOperate, Func<Expression, Expression, Expression>> ExpressionDict =
            new Dictionary<FilterOperate, Func<Expression, Expression, Expression>>
            {
                {
                    FilterOperate.Equal, Expression.Equal
                },
                {
                    FilterOperate.NotEqual, Expression.NotEqual
                },
                {
                    FilterOperate.Less, Expression.LessThan
                },
                {
                    FilterOperate.Greater, Expression.GreaterThan
                },
                {
                    FilterOperate.LessOrEqual, Expression.LessThanOrEqual
                },
                {
                    FilterOperate.GreaterOrEqual, Expression.GreaterThanOrEqual
                },
                {
                    FilterOperate.StartsWith,
                    (left, right) =>
                    {
                        if (left.Type != typeof(string))
                        {
                            throw new NotSupportedException("「StartsWith」比較方式只支持字符串類型的數據");
                        }
                        return Expression.Call(left, typeof(string).GetMethod("StartsWith", new[] { typeof(string) }), right);
                    }
                },
                {
                    FilterOperate.EndsWith,
                    (left, right) =>
                    {
                        if (left.Type != typeof(string))
                        {
                            throw new NotSupportedException("「EndsWith」比較方式只支持字符串類型的數據");
                        }
                        return Expression.Call(left, typeof(string).GetMethod("EndsWith", new[] { typeof(string) }), right);
                    }
                },
                {
                    FilterOperate.Contains,
                    (left, right) =>
                    {
                        if (left.Type != typeof(string))
                        {
                            throw new NotSupportedException("「Contains」比較方式只支持字符串類型的數據");
                        }
                        return Expression.Call(left, typeof(string).GetMethod("Contains", new[] { typeof(string) }), right);
                    }
                }
            };

        #endregion

        /// <summary>
        /// 獲取指定查詢條件組的查詢表達式
        /// </summary>
        /// <typeparam name="T">表達式實體類型</typeparam>
        /// <param name="group">查詢條件組,若是爲null,則直接返回 true 表達式</param>
        public static Expression<Func<T, bool>> GetExpression<T>(FilterGroup group)
        {
            ParameterExpression param = Expression.Parameter(typeof(T), "m");
            Expression body = GetExpressionBody(param, group);
            Expression<Func<T, bool>> expression = Expression.Lambda<Func<T, bool>>(body, param);
            return expression;
        }

        /// <summary>
        /// 獲取指定查詢條件的查詢表達式
        /// </summary>
        /// <typeparam name="T">表達式實體類型</typeparam>
        /// <param name="rule">查詢條件,若是爲null,則直接返回 true 表達式</param>
        /// <returns></returns>
        public static Expression<Func<T, bool>> GetExpression<T>(FilterRule rule = null)
        {
            ParameterExpression param = Expression.Parameter(typeof(T), "m");
            Expression body = GetExpressionBody(param, rule);
            Expression<Func<T, bool>> expression = Expression.Lambda<Func<T, bool>>(body, param);
            return expression;
        }

        /// <summary>
        /// 把查詢操做的枚舉表示轉換爲操做碼
        /// </summary>
        /// <param name="operate">查詢操做的枚舉表示</param>
        public static string ToOperateCode(this FilterOperate operate)
        {
            Type type = operate.GetType();
            MemberInfo[] members = type.GetMember(operate.To<string>());
            if (members.Length > 0)
            {
                OperateCodeAttribute attribute = members[0].GetAttribute<OperateCodeAttribute>();
                return attribute == null ? null : attribute.Code;
            }
            return null;
        }

        /// <summary>
        /// 獲取操做碼的查詢操做枚舉表示
        /// </summary>
        /// <param name="code">操做碼</param>
        /// <returns></returns>
        public static FilterOperate GetFilterOperate(string code)
        {
            Type type = typeof(FilterOperate);
            MemberInfo[] members = type.GetMembers(BindingFlags.Public | BindingFlags.Static);
            foreach (MemberInfo member in members)
            {
                FilterOperate operate = member.Name.To<FilterOperate>();
                if (operate.ToOperateCode() == code)
                {
                    return operate;
                }
            }
            throw new NotSupportedException("獲取操做碼的查詢操做枚舉表示時不支持代碼:" + code);
        }

        #region 私有方法

        private static Expression GetExpressionBody(ParameterExpression param, FilterGroup group)
        {
            //若是無條件或條件爲空,直接返回 true表達式
            if (group == null || (group.Rules.Count == 0 && group.Groups.Count == 0))
            {
                return Expression.Constant(true);
            }
            List<Expression> bodys = new List<Expression>();
            bodys.AddRange(group.Rules.Select(rule => GetExpressionBody(param, rule)));
            bodys.AddRange(group.Groups.Select(subGroup => GetExpressionBody(param, subGroup)));

            if (group.Operate == FilterOperate.And)
            {
                return bodys.Aggregate(Expression.AndAlso);
            }
            if (group.Operate == FilterOperate.Or)
            {
                return bodys.Aggregate(Expression.OrElse);
            }
            throw new Exception("查詢參數序列化失敗");
        }

        private static Expression GetExpressionBody(ParameterExpression param, FilterRule rule)
        {
            if (rule == null || rule.Value == null || string.IsNullOrEmpty(rule.Value.ToString()))
            {
                return Expression.Constant(true);
            }
            LambdaExpression expression = GetPropertyLambdaExpression(param, rule);
            Expression constant = ChangeTypeToExpression(rule, expression.Body.Type);
            return ExpressionDict[rule.Operate](expression.Body, constant);
        }

        private static LambdaExpression GetPropertyLambdaExpression(ParameterExpression param, FilterRule rule)
        {
            string[] propertyNames = rule.Field.Split('.');
            Expression propertyAccess = param;
            Type type = param.Type;
            foreach (string propertyName in propertyNames)
            {
                PropertyInfo property = type.GetProperty(propertyName);
                if (property == null)
                {
                    throw new InvalidOperationException(string.Format("指定屬性{0}在類型{1}中不存在.", rule.Field, type.FullName));
                }
                type = property.PropertyType;
                propertyAccess = Expression.MakeMemberAccess(propertyAccess, property);
            }
            return Expression.Lambda(propertyAccess, param);
        }

        private static Expression ChangeTypeToExpression(FilterRule rule, Type conversionType)
        {
            Type elementType = conversionType.GetUnNullableType();
            object value = rule.Value is string
                ? rule.Value.ToString().To(conversionType)
                : Convert.ChangeType(rule.Value, elementType);
            return Expression.Constant(value, conversionType);
        }

        #endregion
    }
}
View Code
using Abp.Application.Services.Dto;
using Abp.Domain.Entities;
using System;
using System.Linq;
using System.Linq.Expressions;
using Abp.Extensions;
using System.Collections.Generic;
using System.ComponentModel;

namespace Abp.Application.Services.Query
{
    /// <summary>
    /// 集合擴展輔助操做類
    /// </summary>
    public static class CollectionExtensions
    {
        /// <summary>
        /// 從指定<see cref="IQueryable{T}"/>集合中查詢指定數據篩選的分頁信息
        /// </summary>
        /// <typeparam name="TEntity">實體類型</typeparam>
        /// <typeparam name="TResult">分頁數據類型</typeparam>
        /// <param name="source">要查詢的數據集</param>
        /// <param name="predicate">查詢條件謂語表達式</param>
        /// <param name="pageCondition">分頁查詢條件</param>
        /// <param name="selector">數據篩選表達式</param>
        /// <returns>分頁結果信息</returns>
        public static PagedResultDto<TResult> ToPage<TEntity, TResult>(this IQueryable<TEntity> source,
            Expression<Func<TEntity, bool>> predicate,
            PageCondition pageCondition,
            Expression<Func<TEntity, TResult>> selector)
        {
            return source.ToPage(predicate,
                pageCondition.PageIndex,
                pageCondition.PageSize,
                pageCondition.SortConditions,
                selector);
        }

        /// <summary>
        /// 從指定<see cref="IQueryable{T}"/>集合中查詢指定數據篩選的分頁信息
        /// </summary>
        /// <typeparam name="TEntity">實體類型</typeparam>
        /// <typeparam name="TResult">分頁數據類型</typeparam>
        /// <param name="source">要查詢的數據集</param>
        /// <param name="predicate">查詢條件謂語表達式</param>
        /// <param name="pageIndex">分頁索引</param>
        /// <param name="pageSize">分頁大小</param>
        /// <param name="sortConditions">排序條件集合</param>
        /// <param name="selector">數據篩選表達式</param>
        /// <returns>分頁結果信息</returns>
        public static PagedResultDto<TResult> ToPage<TEntity, TResult>(this IQueryable<TEntity> source,
            Expression<Func<TEntity, bool>> predicate,
            int pageIndex,
            int pageSize,
            SortCondition[] sortConditions,
            Expression<Func<TEntity, TResult>> selector)
        {
            int total;
            TResult[] data = source.Where(predicate, pageIndex, pageSize, out total, sortConditions).Select(selector).ToArray();
            return new PagedResultDto<TResult>() { TotalCount = total, Items = data };
        }

        /// <summary>
        /// 從指定<see cref="IQueryable{T}"/>集合中查詢指定分頁條件的子數據集
        /// </summary>
        /// <typeparam name="TEntity">實體類型</typeparam>
        /// <param name="source">要查詢的數據集</param>
        /// <param name="predicate">查詢條件謂語表達式</param>
        /// <param name="pageCondition">分頁查詢條件</param>
        /// <param name="total">輸出符合條件的總記錄數</param>
        /// <returns></returns>
        public static IQueryable<TEntity> Where<TEntity>(this IQueryable<TEntity> source,
            Expression<Func<TEntity, bool>> predicate,
            PageCondition pageCondition,
            out int total)
        {
            return source.Where(predicate, pageCondition.PageIndex, pageCondition.PageSize, out total, pageCondition.SortConditions);
        }

        /// <summary>
        /// 從指定<see cref="IQueryable{T}"/>集合中查詢指定分頁條件的子數據集
        /// </summary>
        /// <typeparam name="TEntity">動態實體類型</typeparam>
        /// <param name="source">要查詢的數據集</param>
        /// <param name="predicate">查詢條件謂語表達式</param>
        /// <param name="pageIndex">分頁索引</param>
        /// <param name="pageSize">分頁大小</param>
        /// <param name="total">輸出符合條件的總記錄數</param>
        /// <param name="sortConditions">排序條件集合</param>
        /// <returns></returns>
        public static IQueryable<TEntity> Where<TEntity>(this IQueryable<TEntity> source,
            Expression<Func<TEntity, bool>> predicate,
            int pageIndex,
            int pageSize,
            out int total,
            ICollection<SortCondition> sortConditions = null)
        {
            if (!typeof(TEntity).IsEntityType())
            {
                string message = $"類型「{typeof(TEntity).FullName}」不是實體類型";
                throw new InvalidOperationException(message);
            }

            total = source.Count(predicate);
            source = source.Where(predicate);
            if (sortConditions == null || sortConditions.Count == 0)
            {
                source = source.OrderBy("Id", ListSortDirection.Descending);
            }
            else
            {
                int count = 0;
                IOrderedQueryable<TEntity> orderSource = null;
                foreach (SortCondition sortCondition in sortConditions)
                {
                    var sortField = sortCondition.SortField.ToPascalCase();
                    orderSource = count == 0
                        ? CollectionPropertySorter<TEntity>.OrderBy(source, sortField, sortCondition.ListSortDirection)
                        : CollectionPropertySorter<TEntity>.ThenBy(orderSource, sortField, sortCondition.ListSortDirection);
                    count++;
                }
                source = orderSource.ThenBy("Id", ListSortDirection.Descending);
            }
            return source?.Skip((pageIndex - 1) * pageSize).Take(pageSize) ?? Enumerable.Empty<TEntity>().AsQueryable();
        }

        /// <summary>
        /// 從指定<see cref="IQueryable{T}"/>集合中查詢指定分頁條件的子數據集
        /// </summary>
        /// <typeparam name="TEntity">動態實體類型</typeparam>
        /// <param name="source">要查詢的數據集</param>
        /// <param name="input">查詢條件</param>
        /// <param name="total">輸出符合條件的總記錄數</param>
        /// <returns></returns>
        public static IQueryable<TEntity> Where<TEntity>(this IQueryable<TEntity> source, QueryListPagedRequestInput input, out int total)
        {
            Expression<Func<TEntity, bool>> predicate = p => true;
            if (input.FilterGroup != null)
            {
                foreach (var item in input.FilterGroup.Rules)
                {
                    item.Field = item.Field.ToPascalCase();
                    //TODO:處理FilterGroup.Groups
                }
                predicate = FilterHelper.GetExpression<TEntity>(input.FilterGroup);
            }
            return source.Where<TEntity>(predicate, input.PageIndex, input.PageSize, out total, input.SortConditions);
        }
    }
}
View Code

最後咱們爲IQueryable添加了擴展方法供業務層使用,因而乎,幾乎全部業務層表格的查詢代碼都能統一爲:java

/// <inheritdoc/>
        public async Task<PagedResultDto<GetActivityListOutput>> GetActivityPagedList(QueryListPagedRequestInput input)
        {
            int total;
            var list = await _activityRepository.GetAll().Where(input, out total).ToListAsync();
            return new PagedResultDto<GetActivityListOutput>(total, list.MapTo<List<GetActivityListOutput>>());
        }

簡潔卻又功能強大,減小了工做量的同時也規範了編碼。對於多表聯合查詢,只須要組裝須要的IQueryable後同樣可使用該方法。如此,咱們服務端的查詢幾乎能夠說是零代碼了,那麼前端呢?如何省去相關代碼,排序依然很容易,只是在表頭上添加事件便可,而篩選,動態添加dom結構並綁定事件難度也不高,咱們同樣在公共js中能夠完成相關工做,實現具體頁面中零代碼。源碼都在這裏,有興趣的能夠看看:jquery

服務端公共查詢實現:
表格js源碼:
 
結合母版頁再次減小代碼量
作完上面兩個步驟,其實頁面的代碼量已經不多了,可是咱們仍是能夠繼續精簡,好比將表格頁面結構、js、css等放入母版頁,子頁面只須要重寫部分配置參數,由母版頁進行表格的初始化。這樣能夠作到表格頁面零HTML代碼,只須要寫一個js配置便可。母版頁代碼以下:
 
@{
    Layout = "~/Areas/Admin/Views/Shared/_AdminLayout.cshtml";
}

@section header{
    <link href="~/Content/css/dataTables.bootstrap.css" rel="stylesheet" type="text/css" />
    <link href="~/Content/css/bootstrap-datetimepicker.min.css" rel="stylesheet" type="text/css" />
    <link href="~/Content/js/plugs/wangEditor-2.1.23/dist/css/wangEditor.min.css" rel="stylesheet" type="text/css" />
    <link href="~/Content/js/plugs/webuploader/webuploader.css" rel="stylesheet" type="text/css" />
    <style>
        .query-input {
            height: 32px;
            line-height: 32px;
            width: 30%;
            vertical-align: middle;
        }

        .form-inline .radio input[type=radio], .form-inline .checkbox input[type=checkbox] {
            position: absolute;
        }
    </style>
}

@section footer{
    <script src="~/Content/js/plugs/select2/select2.js" type="text/javascript"></script>
    <script src="~/Content/js/plugs/datetime/bootstrap-datetimepicker.min.js" type="text/javascript"></script>
    <script src="~/Content/js/plugs/datetime/bootstrap-datetimepicker.zh-CN.js" type="text/javascript"></script>
    <script src="~/Content/js/plugs/webuploader/webuploader.js" type="text/javascript"></script>
    <script src="~/Content/js/plugs/wangEditor-2.1.23/dist/js/wangEditor.js" type="text/javascript"></script>
    <script src="~/Content/js/plugs/textarea/jquery.autosize.js" type="text/javascript"></script>
    <script src="~/Content/js/zooming.js" type="text/javascript"></script>
    <script src="~/Content/js/bode/bode.grid.js" type="text/javascript"></script>

    <script type="text/javascript">
        var datatable;

        var tableOption = {
            url: {},
            columns: [],
            permission: {},
            pageSize: 15,
            actions: [],
            formWidth: "40%",
            isBatch: false,
            extraFilters: [],
            imgSaveUrl: $.bode.config.imgSaveUrl,
            loadDataComplete: function (data) { }
        };

        var startfunction = function () { };
        var endfunction = function () { };
    </script>
    @RenderSection("customScript", true)

    <script type="text/javascript">
        $(function () {
            startfunction();
            //初始化數據
            datatable = new $.bode.grid("#dataTable", tableOption);
            endfunction();
        });
    </script>
}

@RenderSection("headHtml", false)

<div class="page-container">
    <div class="page-body" style="padding:0;">
        <div class="row">
            <div class="col-xs-12">
                <div class="widget flat radius-bordered">
                    @*<div class="widget-header bg-info">
                            <span class="widget-caption"><strong>@ViewBag.Title</strong></span>
                        </div>*@
                    <div class="widget-body">
                        <div role="grid" id="editabledatatable_wrapper" class="dataTables_wrapper form-inline no-footer">
                            <div class="row" style="padding-bottom: 10px;">
                                <div class="col-sm-4">
                                    <select style="width: 25%"></select>
                                    <select style="width: 25%"></select>
                                    <input type="text" class="query-input">
                                    <a class="btn btn-info btn-sm icon-only query-add" href="javascript:void(0);"><i class="fa fa-plus-square-o"></i></a>
                                </div>

                                <div class="col-sm-8">
                                    <div class="form-group" style="float: right" id="actionArea"></div>
                                </div>
                            </div>
                            <table class="table table-bordered table-hover table-striped dataTable no-footer" id="dataTable" aria-describedby="editabledatatable_info">
                                <thead>
                                    <tr></tr>
                                </thead>
                                <tbody></tbody>
                            </table>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>
@RenderBody()
@RenderSection("footHtml", false)

最後須要寫的頁面代碼就只剩下這麼點了: git

@{
    ViewBag.Title = "Table";
    Layout = "~/Areas/Admin/Views/Shared/_GridLayout.cshtml";
}

@section customScript{
    <script type="text/javascript">
        var enums = @Html.Raw(Json.Encode(@ViewBag.Enums));
        tableOption.url = {
            read: "/api/services/activity/activities/GetPagedList",
            add: "/api/services/activity/activities/Create",
            edit: "/api/services/activity/activities/Update",
            delete: "/api/services/activity/activities/Delete"
        };
        tableOption.columns = [
                { data: "id", title: "編號",type:"hide" },
                { data: "name", title: "類別名稱", type: "text", query: true, editor: {},display:{} },
                { data: "isStatic", title: "是否靜態的類別", type: "switch", query: true, editor: {} },
                { data: "order", title: "排序號", type: "number", query: true, editor: {} },
                { data: "creationTime", title: "建立時間", type: "datepicker", editor: {} },
                {
                    title: "操做選項",
                    type: "command",
                    actions: [
                        {
                            name: "操做",
                            icon: "fa-trash-o",
                            onClick: function (d) {
                                alert(d["id"]);
                            }
                        }
                    ]
                }
        ];
    </script>
}

 而且徹底實現了分頁、查詢、排序、新增、編輯、刪除等基礎功能,而且提供了自定義功能按鈕的支持。github

其餘功能web

通過一年多的使用與不斷的調整,我在表格的基礎上新增了不少的功能特性,包括:express

一、結合樹的使用,樹表操做和純表格相差無幾,一樣是極簡的思路。

二、結合abp的權限實現表格按鈕級的權限控制,沒有權限的按鈕不顯示。

三、支持非表格之外的字段篩選,而且保存時自動提交該字段。

因爲功能介紹不是本篇博客的重點,就暫不展開介紹。本文提供了節省增刪查改編碼量的思路與部分示例代碼,歡迎交流,也推薦你們本身動手嘗試。

相關文章
相關標籤/搜索