JS元件系列——在ABP中封裝BootstrapTable
前言:關於ABP框架,博主關注差不多有兩年了吧,一直遲遲沒有嘗試。一方面博主覺得像這種複雜的開發框架肯定有它的過人之處,系統的穩定性和健壯性比一般的開源框架肯定強很多,可是另一方面每每想到它繁瑣的封裝和複雜的開發流程就望而卻步,就這樣遲遲沒有行動。最近在專案裡面用到了ABP框架,沒辦法,只有硬著頭皮上了。經過這一段時間的熟悉,算是對這個框架有了一個大概的瞭解。今天來分享下如何在ABP框架的模式裡面使用bootstrapTable元件。
一、關於ABP
ABP是“ASP.NET Boilerplate Project (ASP.NET樣板專案)”的簡稱,它是一個成熟的開源框架,基於DDD+Repository模式,自帶Zero許可權和認證模組,避免了從零開始搭建框架的煩惱。關於ABP的框架優勢就此打住,因為這樣說下去要說三天三夜,脫離文字主題。
關於ABP的入門,博主不想說太多,園子裡面tkb至簡和陽光銘睿有很多入門級的文章,有興趣的可以瞭解下,還是給出它的官網和開源地址。
點選CREATE MY DEMO按鈕,系統會自動為你生成演示地址
進入對應的Demo URL
使用演示的使用者名稱和密碼登陸進去
可以看到Zero模組的實現效果。
二、jTable在ABP中的運用
如果你下載ABP的原始碼,並且選擇的是混合開發模式(ABP提供了兩種開發模式,一種是基於MVVM的Angular.js的模式;另一種就是MVC+jQuery的混合開發模式),如下圖:
當你Down下來原始碼之後你就會發現,ABP的原始碼裡面的UI部分的表格都是使用jTable去實現的。為什麼會用jTable?原因很簡單,jTable是ABP的作者kalkan寫的一款開源外掛,自己寫的肯定用自己的東西嘍。下面jTable的效果來一發。
來一個jtable的父子表:
如果是不帶父子表的簡單表格,其實jTable的效果其實還行,可是加上一些複雜的功能之後,那一片片藍色的區域不忍直視,並且jTable的api還有待完善,很多需要的功能都需要自己去實現,於是就接到了將所有的表格元件換成BootstrapTable的需求,才有了今天的主題:在ABP中封裝BootstrapTable。
三、Bootstrap Table在ABP中的封裝
接到需求,博主各種百度、各種谷歌,都找不到Bootstrap Table元件在ABP中的封裝,有的只是在ABP的專案裡面簡單的用傳統的方式去初始化元件,這並不是博主想要的。說到這裡不得不說一下,如果你使用ABP開發的過程中遇到一些難題,你會發現很難從百度裡面搜尋到相關答案,谷歌裡面有時能找到,但大部分都是英文社群,所以如果你英文較弱,在查詢資料上面會很吃虧,有時一個簡單的配置問題需要折騰很久。
1、jTable在ABP專案裡面的初始化
首先來看看jTable在一般的ABP專案裡面是如何初始化的。比如我們在Application裡面有一個如下的介面和實現
public interface IRequisitionAppService : IApplicationService { Task<PagedResultDto<RequisitionListDto>> GetRequisitionListAsync(GetRequisitionListInput input); }
[AbpAuthorize(OrderAppPermissions.Pages_Order_Requisition)] public class RequisitionAppService : AbpZeroTemplateAppServiceBase, IRequisitionAppService { private readonly IRepository<Requisition, long> _requisitionRepository; public RequisitionAppService(IRepository<Requisition, long> requisitionRepository) { _requisitionRepository = requisitionRepository; } public async Task<PagedResultDto<RequisitionListDto>> GetRequisitionListAsync(GetRequisitionListInput input) { var query = _requisitionRepository.GetAll() .WhereIf(input.Status != null, w => (int)w.Status == input.Status.Value) .WhereIf( !input.Filter.IsNullOrWhiteSpace(), u => u.No.Contains(input.Filter) || u.Remark.Contains(input.Filter) ); var count = await query.CountAsync(); var list = await query .OrderBy(input.Sorting) .PageBy(input) .ToListAsync(); var dtos = list.MapTo<List<RequisitionListDto>>(); return new PagedResultDto<RequisitionListDto>( count, dtos ); } }
然後我們前端有一個頁面的列表資料從這個介面GetRequisitionListAsync()獲取
<div class="portlet-body"> <div id="dataListTable"></div> </div>
(function () { $(function () { var _$dataListTable = $('#dataListTable'); var _service = abp.services.app.requisition; _$dataListTable.jtable({ paging: true, sorting: true, selecting: true, actions: { listAction: { method: _service.getRequisitionListAsync } }, fields: { id: { key: true, list: false }, details: { width: '1%', sorting: false, edit: false, create: false, listClass: 'child-opener-image-column', display: function (detailData) { var $img = $('<img class="child-opener-image" src="/Common/Images/list_metro.png" title="申購明細" />'); $img.click(function () { _$dataListTable.jtable('openChildTable', $img.closest('tr'), { title: "申購明細", showCloseButton: true, actions: { listAction: { method: _service.getRequisitionDetailListByIdAsync } }, fields: { materialClassParentNameAndName: { title: app.localize('MaterialClassName'), width: '8%' }, materialInfoTypeNo: { title: app.localize('TypeNo'), width: '5%' }, materialInfoLengthDisplayName: { title: app.localize('LengthDisplayName'), width: '3%' }, materialInfoWeight: { title: app.localize('Weight'), width: '5%', display: function (data) { return data.record.materialInfoMinWeight + '-' + data.record.materialInfoMaxWeight; } }, materialInfoMouldTypeDisplayName: { title: app.localize('MouldTypeDisplayName'), width: '6%' }, materialInfoProductionRemark: { title: app.localize('ProductionRemark'), width: '8%' }, materialInfoBundleCountDisplayName: { title: app.localize('BundleCountDisplayName'), width: '3%' }, materialInfoUnitDisplayName: { title: app.localize('UnitDisplayName'), width: '3%' }, materialInfoProcessCost: { title: app.localize('ProcessCost'), width: '6%' }, materialInfoProductRemark: { title: app.localize('ProductRemark'), width: '6%' }, materialInfoRemark: { title: app.localize('Remark'), width: '6%' }, count: { title: app.localize('申購數量'), width: '6%' }, remark: { title: app.localize('申購備註'), width: '6%' } } }, function (data) { data.childTable.jtable('load', { requisitionId: detailData.record.id } ); }); }); return $img; } }, no: { title: "申購單號", width: '20%' }, creatorUserName: { title: "申購人", width: '20%' }, creationTime: { title: "申購時間", width: '10%', display: function (data) { return moment(data.record.creationTime).format('YYYY-MM-DD HH:mm:ss'); } }, sumCount: { title: "總數", width: '10%' }, status: { title: "狀態", width: '20%', display: function (data) { if (data.record.status === app.order.requisitionAuditStatus.audit) return '<span class="label label-info">' + app.localize('Autdit') + '</span>' else if (data.record.status === app.order.requisitionAuditStatus.auditPass) return '<span class="label label-success">' + app.localize('Pass') + '</span>' else if (data.record.status === app.order.requisitionAuditStatus.auditReject) return '<span class="label label-danger">' + app.localize('Reject') + '</span>' else if (data.record.status === app.order.requisitionAuditStatus.delete) return '<span class="label label-danger">' + app.localize('Abandon') + '</span>' else return '<span class="label label-danger">' + app.localize('Unknown') + '</span>' } } } }); }); })();
得到如下效果:
程式碼釋疑:
(1) var _service = abp.services.app.requisition; 這一句聲明當前頁面需要使用哪個服務。
(2) _service.getRequisitionListAsync 這一句對應的是服務呼叫的方法,你會發現在後臺方法名是GetRequisitionListAsync(),而在js裡面卻變成了getRequisitionListAsync(),我們暫且稱之為“潛規則”。
2、bootstrapTable在ABP專案裡面的封裝
通過上述程式碼你會發現,ABP在application層裡面定義的方法,最終會生成某一些js對應的function,這裡難點來了。我們找遍了bootstrapTable元件的api,都沒有通過某一個function去獲取資料的啊。這可如何是好?為這個問題,博主折騰了兩天。最開始博主想,function最終還不是要換成http請求的,我們只要拿到http請求的url,然後將function轉換為url不就行了麼:
我們使用bootstrapTable元件初始化的時候宣告 {url:'/api/services/app/requisition/GetRequisitionListAsync'} 這樣不就行了麼?呵呵,經過測試,這樣確實能正確取到資料。但是不夠理想,因為這前面的字首是ABP給我們生成的,是否會變化我們尚且不說,給每一個url加上這麼一長串著實看著很不爽,於是進一步想,是否我們的bootstrapTable也可以使用function去初始化呢,元件沒有,難道我們就不能給他擴充套件一個嗎?我們不用url獲取資料,通過呼叫這個function取到資料,然後將資料渲染到元件不就行了。思路有了,那麼這裡有兩個難題:一是如何將原來url的方式變成這裡的呼叫function的方式呢?二是引數的封裝。經過檢視元件的原始碼發現,如果是服務端分頁,元件最終是進入到initServer()這個方法去獲取資料,然後渲染到頁面上面的,元件原始的initServer()方法如下:
BootstrapTable.prototype.initServer = function (silent, query) { var that = this, data = {}, params = { pageSize: this.options.pageSize === this.options.formatAllRows() ? this.options.totalRows : this.options.pageSize, pageNumber: this.options.pageNumber, searchText: this.searchText, sortName: this.options.sortName, sortOrder: this.options.sortOrder }, request; if (!this.options.url && !this.options.ajax) { return; } if (this.options.queryParamsType === 'limit') { params = { search: params.searchText, sort: params.sortName, order: params.sortOrder }; if (this.options.pagination) { params.limit = this.options.pageSize === this.options.formatAllRows() ? this.options.totalRows : this.options.pageSize; params.offset = this.options.pageSize === this.options.formatAllRows() ? 0 : this.options.pageSize * (this.options.pageNumber - 1); } } if (!($.isEmptyObject(this.filterColumnsPartial))) { params['filter'] = JSON.stringify(this.filterColumnsPartial, null); } data = calculateObjectValue(this.options, this.options.queryParams, [params], data); $.extend(data, query || {}); // false to stop request if (data === false) { return; } if (!silent) { this.$tableLoading.show(); } request = $.extend({}, calculateObjectValue(null, this.options.ajaxOptions), { type: this.options.method, url: this.options.url, data: this.options.contentType === 'application/json' && this.options.method === 'post' ? JSON.stringify(data) : data, cache: this.options.cache, contentType: this.options.contentType, dataType: this.options.dataType, success: function (res) { res = calculateObjectValue(that.options, that.options.responseHandler, [res], res); that.load(res); that.trigger('load-success', res); }, error: function (res) { that.trigger('load-error', res.status, res); }, complete: function () { if (!silent) { that.$tableLoading.hide(); } } }); if (this.options.ajax) { calculateObjectValue(this, this.options.ajax, [request], null); } else { $.ajax(request); } };元件原始initServer()方法
程式碼不難讀懂,解析引數,整合引數,得到引數,傳送ajax請求,在success事件裡面將得到的資料渲染到介面。讀懂了這段程式碼,我們再來封裝function就容易多了。
最終我們封裝的程式碼如下:
(function ($) { 'use strict'; //debugger; //通過建構函式獲取到bootstrapTable裡面的初始化方法 var BootstrapTable = $.fn.bootstrapTable.Constructor, _initData = BootstrapTable.prototype.initData, _initPagination = BootstrapTable.prototype.initPagination, _initBody = BootstrapTable.prototype.initBody, _initServer = BootstrapTable.prototype.initServer, _initContainer = BootstrapTable.prototype.initContainer; //重寫 BootstrapTable.prototype.initData = function () { _initData.apply(this, Array.prototype.slice.apply(arguments)); }; BootstrapTable.prototype.initPagination = function () { _initPagination.apply(this, Array.prototype.slice.apply(arguments)); }; BootstrapTable.prototype.initBody = function (fixedScroll) { _initBody.apply(this, Array.prototype.slice.apply(arguments)); }; BootstrapTable.prototype.initServer = function (silent, query) { //構造自定義引數 for (var key in this.options.methodParams) { $.fn.bootstrapTable.defaults.methodParams[key] = this.options.methodParams[key]; } //如果傳了url,則走原來的邏輯 if (this.options.url) { _initServer.apply(this, Array.prototype.slice.apply(arguments)); return; } //如果定義了abpMethod,則走abpMethod的邏輯 if (!this.options.abpMethod) { return; } var that = this, data = {}, params = { pageSize: this.options.pageSize === this.options.formatAllRows() ? this.options.totalRows : this.options.pageSize, pageNumber: this.options.pageNumber, searchText: this.searchText, sortName: this.options.sortName, sortOrder: this.options.sortOrder }, request; //debugger; if (this.options.queryParamsType === 'limit') { params = { search: params.searchText, sort: params.sortName, order: params.sortOrder }; if (this.options.pagination) { params.limit = this.options.pageSize === this.options.formatAllRows() ? this.options.totalRows : this.options.pageSize; params.offset = this.options.pageSize === this.options.formatAllRows() ? 0 : this.options.pageSize * (this.options.pageNumber - 1); } } if (!($.isEmptyObject(this.filterColumnsPartial))) { params['filter'] = JSON.stringify(this.filterColumnsPartial, null); } data = $.fn.bootstrapTable.utils.calculateObjectValue(this.options, this.options.queryParams, [params], data); $.extend(data, query || {}); // false to stop request if (data === false) { return; } if (!silent) { this.$tableLoading.show(); } this.options.abpMethod(data).done(function (result) { result = $.fn.bootstrapTable.utils.calculateObjectValue(that.options, that.options.responseHandler, [result], result); that.load(result); that.trigger('load-success', result); }); request = $.extend({}, $.fn.bootstrapTable.utils.calculateObjectValue(null, this.options.ajaxOptions), { type: this.options.method, url: this.options.url, data: this.options.contentType === 'application/json' && this.options.method === 'post' ? JSON.stringify(data) : data, cache: this.options.cache, contentType: this.options.contentType, dataType: this.options.dataType, success: function (res) { debugger; res = $.fn.bootstrapTable.utils.calculateObjectValue(that.options, that.options.responseHandler, [res], res); that.load(res); that.trigger('load-success', res); }, error: function (res) { that.trigger('load-error', res.status, res); }, complete: function () { if (!silent) { that.$tableLoading.hide(); } } }); if (this.options.ajax) { $.fn.bootstrapTable.utils.calculateObjectValue(this, this.options.ajax, [request], null); } else { $.ajax(request); } } BootstrapTable.prototype.initContainer = function () { _initContainer.apply(this, Array.prototype.slice.apply(arguments)); }; abp.bootstrapTableDefaults = { striped: false, classes: 'table table-striped table-bordered table-advance table-hover', pagination: true, cache: false, sidePagination: 'server', uniqueId: 'id', showRefresh: false, search: false, method: 'post', //toolbar: '#toolbar', pageSize: 10, paginationPreText: '上一頁', paginationNextText: '下一頁', queryParams: function (param) { //$.fn.bootstrapTable.defaults.methodParams.propertyIsEnumerable() var abpParam = { Sorting: param.sort, filter: param.search, skipCount: param.offset, maxResultCount: param.limit }; for (var key in $.fn.bootstrapTable.defaults.methodParams) { abpParam[key] = $.fn.bootstrapTable.defaults.methodParams[key]; } return abpParam; }, responseHandler: function (res) { if (res.totalCount) return { total: res.totalCount, rows: res.items }; else return { total: res.result.totalCount, rows: res.result.items }; }, methodParams: {}, abpMethod: function () { } }; $.extend($.fn.bootstrapTable.defaults, abp.bootstrapTableDefaults); })(jQuery);
程式碼釋疑:增加兩個引數 methodParams: {},abpMethod: function () { } 來獲取abp的function和引數,然後獲取資料的時候如果定義了abpMethod,則通過function獲取資料,否則還是走原來的邏輯。
然後我們呼叫就簡單了
//選取介面上要先資料的表格 var _$SendOrdersTable = $('#SendOrdersTable'); //獲取服務層方法 var _SendOrderService = abp.services.app.sendOrder; _$SendOrdersTable.bootstrapTable({ abpMethod: _SendOrderService.getSendOrderListAsync, detailView: true, onExpandRow: function (index, row, $detail) { var cur_table = $detail.html('<table></table>').find('table'); $(cur_table).bootstrapTable({ showRefresh: false, search: false, pagination: false, abpMethod: _SendOrderService.getSendOrderDetailListAsync, methodParams: { SendOrderId: row.id }, columns: [ { field: 'materialClassName', title: app.localize('MaterialClassName'), width: '8%' }, { field: 'typeNo', title: app.localize('TypeNo'), width: '8%' } ] }); }, columns: [{ field: 'no', title: app.localize('SendOrderNO'), align: 'center' }, { field: 'supplierName', title: app.localize('SupplierName'), align: 'center' }, { title: app.localize('SendOrderTime'), align: 'center', field: 'createdDate', formatter: function (data) { return moment(data).format('YYYY-MM-DD HH:mm:ss'); } }, { field: 'status', align: 'center', title: app.localize('SendOrderStatus'), formatter: function (data) { var value = ""; if (data == 1) { value = '<span class="label label-info">' + app.localize('Autdit') + '</span>'; } else if (data == 2) { value = '<span class="label label-success">' + app.localize('Pass') + '</span>'; } else if (data == 3) { value = '<span class="label label-default">' + app.localize('Reject') + '</span>'; } else value = '<span class="label label-default">' + app.localize('Abandon') + '</span>'; return value; } }, { field: 'createName', align: 'center', title: app.localize('SendOrderCreator'), }, { field: 'sumCount', align: 'center', title: app.localize('SendOrderTotalCount'), }, ] });
得到如下效果
四、總結
通過以上一個簡單的封裝,順利將bootstrapTable融入到了ABP的操作方式裡面。是不是很easy!在使用ABP的過程中,博主還做了其他一些封裝,以後有機會再來介紹,關於ABP的技術交流歡迎聯絡博主。這一篇就到這裡,歡迎交流。如果你覺得本文能夠幫助你,可以右邊隨意 打賞 博主,打賞後可以獲得博主永久免費的技術支援。
歡迎各位轉載,但是未經作者本人同意,轉載文章之後必須在文章頁面明顯位置給出作者和原文連線,否則保留追究法律責任的權利
相關推薦
JS元件系列——在ABP中封裝BootstrapTable
前言:關於ABP框架,博主關注差不多有兩年了吧,一直遲遲沒有嘗試。一方面博主覺得像這種複雜的開發框架肯定有它的過人之處,系統的穩定性和健壯性比一般的開源框架肯定強很多,可是另一方面每每想到它繁瑣的封裝和複雜的開發流程就望而卻步,就這樣遲遲沒有行動。最近在專案裡面用到了ABP框架,沒辦法,只有硬著頭皮上了。經
JS元件系列——自己動手擴充套件BootstrapTable的 凍結列 功能:徹底解決高度問題
前言:一年前,博主分享過一篇關於bootstrapTable元件凍結列的解決方案 JS元件系列——Bootstrap Table 凍結列功能IE瀏覽器相容性問題解決方案 ,通過該篇,確實可以實現bootstrapTable的凍結列效果,並且可以相容ie瀏覽器。這一年的時間,不斷有園友以及群裡面的朋友問過我關
JS元件系列——自己動手擴充套件BootstrapTable的treegrid功能
前言:上篇 JS元件系列——自己動手封裝bootstrap-treegrid元件 博主自己動手封裝了下treegrid的功能,但畢竟那個元件只是一個單獨針對樹形表格做的,適用性還比較有限。關注博主的園友應該知道,博主的部落格裡面寫了很多bootstrapTable的擴充套件,今天打算在直接在bootstra
JS元件系列——自己動手封裝bootstrap-treegrid元件
前言:最近產品需要設計一套相對完整的組織架構的解決方案,由於組織架構涉及到層級關係,在表格裡面展示層級關係,自然就要用到所謂的treegrid。可惜的是,一些輕量級的表格元件本身並沒有自帶樹形表格的功能,比如bootstrapTable就沒有這個功能,怎麼辦呢?如果是jqgrid、easyUI的表格,tree
JS元件系列——分享自己封裝的Bootstrap樹形元件:jqTree
前言:之前的一篇介紹了下如何封裝自己的元件,這篇再次來體驗下自己封裝元件的樂趣。看過博主部落格的園友應該記得之前分享過一篇樹形選單的使用JS元件系列——Bootstrap 樹控制元件使用經驗分享,這篇裡面第一個Jquery Tree,只是用簡單樣式和js去實現了效果,沒有給出一個系統的封裝,這篇博主就來試試
JS元件系列——BootstrapTable 行內編輯解決方案:x-editable
前言:之前介紹bootstrapTable元件的時候有提到它的行內編輯功能,只不過為了展示功能,將此一筆帶過了,罪過罪過!最近專案裡面還是打算將行內編輯用起來,於是再次研究了下x-editable元件,遇到過一些坑,再此做個採坑記錄吧!想要了解bootstrapTable的園友可以移步 JS元件系列——表格元
JS元件系列——BootstrapTable+KnockoutJS實現增刪改查解決方案(一)
前言:出於某種原因,需要學習下Knockout.js,這個元件很早前聽說過,但一直沒嘗試使用,這兩天學習了下,覺得它真心不錯,雙向繫結的機制簡直太爽了。今天打算結合bootstrapTable和Knockout去實現一個簡單的增刪改查,來體驗一把神奇的MVVM。關於WebApi的剩餘部分,博主一定抽時間補上。
JS元件系列——BootstrapTable+KnockoutJS實現增刪改查解決方案(二)
前言:上篇 JS元件系列——BootstrapTable+KnockoutJS實現增刪改查解決方案(一) 介紹了下knockout.js的一些基礎用法,由於篇幅的關係,所以只能分成兩篇,望見諒!昨天就覺得應該快點完成下篇,要不然有點標題黨的感覺,思及此,博主心有不安,於是加班趕出了下篇。如果你也打算用ko去做
JS元件系列——BootstrapTable+KnockoutJS實現增刪改查解決方案(三):兩個Viewmodel搞定增刪改查
前言:之前博主分享過knockoutJS和BootstrapTable的一些基礎用法,都是寫基礎應用,根本談不上封裝,僅僅是避免了html控制元件的取值和賦值,遠遠沒有將MVVM的精妙展現出來。最近專案打算正式將ko用起來,於是乎對ko和bootstraptable做了一些封裝,在此分享出來供園友們參考。封裝
JS元件系列——BootstrapTable+KnockoutJS實現增刪改查解決方案(四):自定義T4模板快速生成頁面
前言:上篇介紹了下ko增刪改查的封裝,確實節省了大量的js程式碼。博主是一個喜歡偷懶的人,總覺得這些基礎的增刪改查效果能不能通過一個什麼工具直接生成頁面效果,啥程式碼都不用寫了,那該多爽。於是研究了下T4的語法,雖然沒有完全掌握,但是算是有了一個大致的瞭解,給需要自定義模板的園友們提供一個參考。於是乎有了今天
JS元件系列——封裝自己的JS元件,你也可以
前言:之前分享了那麼多bootstrap元件的使用經驗,這篇博主打算研究下JS元件的擴充套件和封裝,我們來感受下JQuery為我們提供$.Extend的神奇,看看我們怎麼自定義自己的元件,比如我們想擴充套件一個$("#id").MyJsControl({})做我們自己的元件,我們該如何去做呢,別急,我們慢慢來
JS元件系列——使用HTML標籤的data屬性初始化JS元件
前言:最近使用bootstrap元件的時候發現一個易用性問題,很多簡單的元件初始化都需要在JS裡面寫很多的初始化程式碼,比如一個簡單的select標籤,因為僅僅只是需要從後臺獲取資料填充到option裡面,可是從後臺取資料就需要js的初始化,所以導致頁面初始化的時候js的初始化程式碼裡面出現很多重複
JS元件系列——兩種bootstrap multiselect元件大比拼[轉載]
轉載原文地址:https://www.cnblogs.com/landeanfen/p/5013452.html 前言:今天繼續來看看bootstrap的另一個元件:multiselect。記得在專案開始之前,博主專案組幾個同事就使用哪些js元件展開過討論,其中就說到了select元件,
JS元件系列——表格元件神器:bootstrap table(三:終結篇,最後的乾貨福利)
前言:前面介紹了兩篇關於bootstrap table的基礎用法,這章我們繼續來看看它比較常用的一些功能,來個終結篇吧,毛爺爺告訴我們做事要有始有終~~bootstrap table這東西要想所有功能覆蓋似乎不太現實,博主挑選了一些自認為比較常用的功能在此分享給各位園友。原始
JS元件系列——表格元件神器:bootstrap table
前言:之前一直在忙著各種什麼效果,殊不知最基礎的Bootstrap Table用法都沒有涉及,罪過,罪過。今天補起來吧。上午博主由零開始自己從頭到尾使用了一遍Bootstrap Table ,遇到不少使用方面的問題,也做了一部分筆記,在此分享出來供需要使用的園友參考。還記得前
JS元件系列——表格元件神器:bootstrap table(二:父子表和行列調序)
前言:上篇 JS元件系列——表格元件神器:bootstrap table 簡單介紹了下Bootstrap Table的基礎用法,沒想到討論還挺熱烈的。有園友在評論中提到了父子表的用法,今天就結合Bootstrap table的父子表和行列調序的用法再來介紹下它稍微高階點的用法
JS元件系列——又一款MVVM元件:Vue(一:30分鐘搞定前端增刪改查)
正文 前言:關於Vue框架,好幾個月之前就聽說過,瞭解一項新技術之後,總是處於觀望狀態,一直在猶豫要不要系統學習下。正好最近有點空,就去官網瞭解了下,看上去還不錯的一個元件,就抽空研究了下。最近園子裡vue也確實挺火,各種入門博文眼花繚亂,博主也不敢說寫
JS元件系列——又一款MVVM元件:Vue(二:構建自己的Vue元件)
前言:轉眼距離上篇 JS元件系列——又一款MVVM元件:Vue(一:30分鐘搞定前端增刪改查) 已有好幾個月了,今天打算將它撿起來,發現好久不用,Vue相關技術點都生疏不少。經過這幾個月的時間,Vue的發展也是異常迅猛,不過這好像和博主都沒什麼太大的關係,博主還是老老實實研究自己的技術吧。技術之路還很長,且行
JS元件系列——不容錯過的兩款Bootstrap Icon圖示選擇元件
前言:最近好多朋友在群裡面聊到bootstrap icon圖示的問題,比如最常見的選單管理,每個選單肯定需要一個對應的選單圖示,要是有一個視覺化的圖示選擇元件就好了,最好是直接選擇圖示,就能得到對應的class樣式。於是乎各種百度,皇天不負有心人,最後被博主找到了,感覺效果還不錯,並且支援自定義的圖示,今天就
JS元件系列——圖片切換特效:簡易抽獎系統
<div id="randomize"> <div class="content container" style="text-align: center;max-width: 900px;"> <h1>簡易遊戲機</h