From ccc10985aa99af53e05099e3d36ba49933625325 Mon Sep 17 00:00:00 2001 From: devezhao Date: Wed, 18 Mar 2020 19:50:11 +0800 Subject: [PATCH] comp spinner/indeterminate --- src/main/webapp/_include/Head.jsp | 2 +- src/main/webapp/assets/css/rb-base.css | 52 +++++++++++++ src/main/webapp/assets/css/rb-page.css | 48 ++++++++++++ src/main/webapp/assets/js/rb-base.js | 35 +++++---- src/main/webapp/assets/js/rb-datalist.jsx | 91 +++++++++++++++-------- src/main/webapp/user/login.jsp | 4 +- 6 files changed, 183 insertions(+), 49 deletions(-) diff --git a/src/main/webapp/_include/Head.jsp b/src/main/webapp/_include/Head.jsp index 7783589ba..691ae1293 100644 --- a/src/main/webapp/_include/Head.jsp +++ b/src/main/webapp/_include/Head.jsp @@ -32,7 +32,7 @@ if (currentUser != null) { <%}}%> <%if (AppUtils.isIE(request)) {%> - + <%}%> \ No newline at end of file diff --git a/src/main/webapp/assets/css/rb-base.css b/src/main/webapp/assets/css/rb-base.css index 3f5cdae19..eed184089 100644 --- a/src/main/webapp/assets/css/rb-base.css +++ b/src/main/webapp/assets/css/rb-base.css @@ -25796,4 +25796,56 @@ div.table-responsive>div.dataTables_wrapper>div.row>div[class^="col-"]:last-chil .file-icon[data-type=gif], .file-icon[data-type=png] { background: #f4b400; +} + +/* BS v4.4 */ + +/* https://getbootstrap.com/docs/4.4/components/spinners/ */ + +.spinner-border, +.spinner-grow { + display: inline-block; + width: 2rem; + height: 2rem; + vertical-align: text-bottom; +} + +.spinner-border-sm, +.spinner-grow-sm { + width: 1rem; + height: 1rem; +} + +@keyframes spinner-border { + to { + transform: rotate(360deg); + } +} + +.spinner-border { + border: 3px solid currentColor; + border-right-color: transparent; + border-radius: 50%; + animation: spinner-border .75s linear infinite; +} + +.spinner-border-sm { + border-width: 2px; +} + +@keyframes spinner-grow { + 0% { + transform: scale(0); + } + + 50% { + opacity: 1; + } +} + +.spinner-grow { + background-color: currentColor; + border-radius: 50%; + opacity: 0; + animation: spinner-grow .75s linear infinite; } \ No newline at end of file diff --git a/src/main/webapp/assets/css/rb-page.css b/src/main/webapp/assets/css/rb-page.css index 698cc6f94..c87405236 100644 --- a/src/main/webapp/assets/css/rb-page.css +++ b/src/main/webapp/assets/css/rb-page.css @@ -1146,6 +1146,7 @@ i.split.ui-draggable-dragging { right: 0; color: #4285f4; padding-top: 1px; + font-weight: 500; } .data-list .table thead th>div>i.zmdi.sort-asc::before { @@ -3254,4 +3255,51 @@ a.icon-link>.zmdi { .popover { border-radius: 2px; border-width: 0; +} + +.btn .spinner-border, +.btn .spinner-grow { + width: 1rem; + height: 1rem; +} + +.btn.btn-xl .spinner-border, +.btn.btn-xl .spinner-grow { + width: 1.5rem; + height: 1.5rem; +} + +.custom-checkbox.indeterminate .custom-control-label::after { + background-color: #4285f4; + border: 0; + width: 12px; + height: 12px; + margin-top: 5px; + margin-left: 5px !important; +} + +.custom-checkbox.indeterminate .custom-control-label::before { + border-color: #4285f4; +} + +.custom-checkbox.custom-control-sm.indeterminate .custom-control-label::after { + width: 8px; + height: 8px; +} + +.custom-radio .custom-control-input:checked~.custom-control-label::after { + font-size: 0 !important; + width: 11px; + height: 11px; + background-color: #4285f4; + border-radius: 50%; + margin-top: 6px; + margin-left: 6px; +} + +.custom-radio.custom-control-sm .custom-control-input:checked~.custom-control-label::after { + width: 8px; + height: 8px; + margin-top: 5px; + margin-left: 5px; } \ No newline at end of file diff --git a/src/main/webapp/assets/js/rb-base.js b/src/main/webapp/assets/js/rb-base.js index 491b5919e..f4648df0b 100644 --- a/src/main/webapp/assets/js/rb-base.js +++ b/src/main/webapp/assets/js/rb-base.js @@ -4,8 +4,8 @@ Copyright (c) REBUILD and its owners. All rights reser rebuild is dual-licensed under commercial and open source licenses (GPLv3). See LICENSE and COMMERCIAL in the project root for license information. */ - /* eslint-disable no-unused-vars */ + /*! https://github.com/carhartl/jquery-cookie */ // eslint-disable-next-line (function (factory) { if (typeof define === "function" && define.amd) { define(["jquery"], factory) } else { if (typeof exports === "object") { factory(require("jquery")) } else { factory(jQuery) } } }(function ($) { var pluses = /\+/g; function encode(s) { return config.raw ? s : encodeURIComponent(s) } function decode(s) { return config.raw ? s : decodeURIComponent(s) } function stringifyCookieValue(value) { return encode(config.json ? JSON.stringify(value) : String(value)) } function parseCookieValue(s) { if (s.indexOf('"') === 0) { s = s.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, "\\") } try { s = decodeURIComponent(s.replace(pluses, " ")); return config.json ? JSON.parse(s) : s } catch (e) { } } function read(s, converter) { var value = config.raw ? s : parseCookieValue(s); return $.isFunction(converter) ? converter(value) : value } var config = $.cookie = function (key, value, options) { if (value !== undefined && !$.isFunction(value)) { options = $.extend({}, config.defaults, options); if (typeof options.expires === "number") { var days = options.expires, t = options.expires = new Date(); t.setTime(+t + days * 86400000) } return (document.cookie = [encode(key), "=", stringifyCookieValue(value), options.expires ? "; expires=" + options.expires.toUTCString() : "", options.path ? "; path=" + options.path : "", options.domain ? "; domain=" + options.domain : "", options.secure ? "; secure" : ""].join("")) } var result = key ? undefined : {}; var cookies = document.cookie ? document.cookie.split("; ") : []; for (var i = 0, l = cookies.length; i < l; i++) { var parts = cookies[i].split("="); var name = decode(parts.shift()); var cookie = parts.join("="); if (key && key === name) { result = read(cookie, value); break } if (!key && (cookie = read(cookie)) !== undefined) { result[name] = cookie } } return result }; config.defaults = {}; $.removeCookie = function (key, options) { if ($.cookie(key) === undefined) { return false } $.cookie(key, "", $.extend({}, options, { expires: -1 })); return !$.cookie(key) } })); @@ -17,24 +17,28 @@ See LICENSE and COMMERCIAL in the project root for license information. $.fn.extend({ 'button': function (state) { return this.each(function () { - var el = $(this) - if (!(el.prop('nodeName') === 'BUTTON' || el.prop('nodeName') === 'A')) return + var $el = $(this) + if (!($el.prop('nodeName') === 'BUTTON' || $el.prop('nodeName') === 'A')) return if (state === 'loading') { - el.attr('disabled', true) - var loadingText = el.data('loading-text') + $el.attr('disabled', true) + + var spinner = $el.data('spinner') + if ($('#ie-polyfill').length > 0) spinner = undefined + var loadingText = $el.data('loading-text') + this.__textHold = $el.html() + + var _this = this if (loadingText) { - var _this = this - this.__loadingTextTimer = setTimeout(function () { - _this.__textHold = el.html() - el.text(loadingText) - }, 200) + this.__loadingTimer = setTimeout(function () { $el.text(loadingText) }, 200) + } else if (spinner !== undefined) { + this.__loadingTimer = setTimeout(function () { $el.html('') }, 200) } } else if (state === 'reset') { - el.attr('disabled', false) - if (this.__loadingTextTimer) { - clearTimeout(this.__loadingTextTimer) - this.__loadingTextTimer = null - if (this.__textHold) el.html(this.__textHold) + $el.attr('disabled', false) + if (this.__loadingTimer) { + clearTimeout(this.__loadingTimer) + this.__loadingTimer = null + if (this.__textHold) $el.html(this.__textHold) } } }) @@ -60,7 +64,6 @@ See LICENSE and COMMERCIAL in the project root for license information. beforeSend: function (xhr, settings) { // URL prefix if (settings.url.substr(0, 1) === '/' && rb.baseUrl) settings.url = rb.baseUrl + settings.url - console.log(settings) return settings } }) diff --git a/src/main/webapp/assets/js/rb-datalist.jsx b/src/main/webapp/assets/js/rb-datalist.jsx index fb742cdfd..6347b2c8e 100644 --- a/src/main/webapp/assets/js/rb-datalist.jsx +++ b/src/main/webapp/assets/js/rb-datalist.jsx @@ -50,12 +50,12 @@ class RbList extends React.Component {
this._rblistScroller = c}> - this._rblistHead = c}> + {this.props.uncheckbox !== true && this.clickRow(e, true)}> + return this._clickRow(e, true)}> {this.props.uncheckbox !== true &&
@@ -64,7 +64,7 @@ class RbList extends React.Component { const cWidth = item.width || that.__defaultColumnWidth const styles = { width: cWidth + 'px' } return
+ onClick={item.unsort ? null : this._sortField.bind(this, item.field)}>
{item.label} @@ -79,11 +79,11 @@ class RbList extends React.Component { {this.state.rowsData.map((item) => { const lastPrimary = item[lastIndex] const rowKey = 'row-' + lastPrimary.id - return
@@ -146,16 +146,6 @@ class RbList extends React.Component { if (wpc.advFilter !== true) this.fetchList(this.__buildQuick()) } - componentDidUpdate() { - // 按钮状态 - const $oper = $('.dataTables_oper') - $oper.find('.J_delete, .J_view, .J_edit, .J_assign, .J_share, .J_unshare').attr('disabled', true) - const selected = this.getSelectedIds(true).length - if (selected > 0) $oper.find('.J_delete, .J_assign, .J_share, .J_unshare').attr('disabled', false) - else $(this._rblistHead).find('.custom-control-input').prop('checked', false) - if (selected === 1) $oper.find('.J_view, .J_edit').attr('disabled', false) - } - fetchList(filter) { const fields = [] let field_sort = null @@ -184,7 +174,10 @@ class RbList extends React.Component { }, 400) $.post(`/app/${entity}/data-list`, JSON.stringify(query), (res) => { if (res.error_code === 0) { - this.setState({ rowsData: res.data.data || [], inLoad: false }, () => RbList.renderAfter()) + this.setState({ rowsData: res.data.data || [], inLoad: false }, () => { + RbList.renderAfter() + this._clearSelected() + }) if (res.data.total > 0) this._pagination.setState({ rowsTotal: res.data.total, pageNo: this.pageNo }) } else { RbHighbar.error(res.error_msg) @@ -218,33 +211,69 @@ class RbList extends React.Component { } // 全选 - toggleRows(e, noUpdate) { + _toggleRows(e, uncheck) { const $body = $(this._rblistBody) - if (e.target.checked) $body.find('>tr').addClass('active').find('.custom-control-input').prop('checked', true) - else $body.find('>tr').removeClass('active').find('.custom-control-input').prop('checked', false) - // this.setState({ checkedChanged: true }) - if (!noUpdate) this.componentDidUpdate() // perform + if (e.target.checked) { + $body.find('>tr').addClass('active').find('.custom-control-input').prop('checked', true) + } else { + $body.find('>tr').removeClass('active').find('.custom-control-input').prop('checked', false) + } + if (!uncheck) this._checkSelected() } // 单选 - clickRow(e, unhold) { + _clickRow(e, unhold) { const $target = $(e.target) if ($target.hasClass('custom-control-label')) return if ($target.hasClass('custom-control-input') && unhold) return const $tr = $target.parents('tr') if (unhold) { - this.toggleRows({ target: { checked: false } }, true) + this._toggleRows({ target: { checked: false } }, true) $tr.addClass('active').find('.custom-control-input').prop('checked', true) } else { if (e.target.checked) $tr.addClass('active') else $tr.removeClass('active') } - // this.setState({ checkedChanged: true }) - this.componentDidUpdate() // for perform + + this._checkSelected() } - sortField(field, e) { + _checkSelected() { + const chkSelected = $(this._rblistBody).find('>tr .custom-control-input:checked').length + console.log('_checkSelected', chkSelected) + + // 全选/半选/全清 + const chkAll = this.state.rowsData.length + if (chkSelected === 0) { + $(this._checkAll).prop('checked', false).parent().removeClass('indeterminate') + } else if (chkSelected !== chkAll) { + $(this._checkAll).prop('checked', false).parent().addClass('indeterminate') + } + + if (chkSelected > 0 && chkSelected === chkAll) { + $(this._checkAll).prop('checked', true).parent().removeClass('indeterminate') + } + + // 操作按钮状态 + const $oper = $('.dataTables_oper') + $oper.find('.J_delete, .J_view, .J_edit, .J_assign, .J_share, .J_unshare').attr('disabled', true) + if (chkSelected > 0) { + $oper.find('.J_delete, .J_assign, .J_share, .J_unshare').attr('disabled', false) + if (chkSelected === 1) $oper.find('.J_view, .J_edit').attr('disabled', false) + } + + // 分页组件 + this._pagination && this._pagination.setState({ selectedTotal: chkSelected }) + } + + _clearSelected() { + $(this._checkAll).prop('checked', false) + this._toggleRows({ target: { checked: false } }) + } + + // 排序 + _sortField(field, e) { const fields = this.state.fields for (let i = 0; i < fields.length; i++) { if (fields[i].field === field) { @@ -319,9 +348,8 @@ class RbList extends React.Component { */ getSelectedIds(noWarn) { const selected = [] - $(this._rblistBody).find('>tr .custom-control-input').each(function () { - const $this = $(this) - if ($this.prop('checked')) selected.push($this.parents('tr').data('id')) + $(this._rblistBody).find('>tr .custom-control-input:checked').each(function () { + selected.push($(this).parents('tr').data('id')) }) if (selected.length === 0 && noWarn !== true) RbHighbar.create('未选中任何记录') return selected @@ -477,7 +505,10 @@ class RbListPagination extends React.Component { return (
-
{this.state.rowsTotal > 0 ? `共 ${this.state.rowsTotal} 条数据` : ''}
+
+ {this.state.selectedTotal > 0 && 已选中 {this.state.selectedTotal} 条.} + {this.state.rowsTotal > 0 && 共 {this.state.rowsTotal} 条数据} +
diff --git a/src/main/webapp/user/login.jsp b/src/main/webapp/user/login.jsp index 6fd12ca32..2f8537885 100644 --- a/src/main/webapp/user/login.jsp +++ b/src/main/webapp/user/login.jsp @@ -93,8 +93,8 @@ ${bundle.lang('ForgotPassword')}
-