comp spinner/indeterminate

This commit is contained in:
devezhao 2020-03-18 19:50:11 +08:00
parent d11fb94920
commit ccc10985aa
6 changed files with 183 additions and 49 deletions

View file

@ -32,7 +32,7 @@ if (currentUser != null) {
<meta name="rb.isAdminVerified" content="<%=AppUtils.isAdminVerified(request)%>">
<%}}%>
<%if (AppUtils.isIE(request)) {%>
<script src="${baseUrl}/assets/lib/react/polyfill.min.js?v=7.6.0"></script>
<script id="ie-polyfill" src="${baseUrl}/assets/lib/react/polyfill.min.js?v=7.6.0"></script>
<!--[if lt IE 10]><script>location.href='${baseUrl}/error/unsupported-browser'</script><![endif]-->
<%}%>
<c:if test="${markWatermark}"><script src="${baseUrl}/assets/lib/watermark.js?v=2.3.2"></script></c:if>

View file

@ -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;
}

View file

@ -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;
}

View file

@ -4,8 +4,8 @@ Copyright (c) REBUILD <https://getrebuild.com/> 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('<span class="spinner-' + (spinner === 'grow' ? 'grow' : 'border') + '"></span>') }, 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
}
})

View file

@ -50,12 +50,12 @@ class RbList extends React.Component {
<div className="col-sm-12">
<div className="rb-scroller" ref={(c) => this._rblistScroller = c}>
<table className="table table-hover table-striped">
<thead ref={(c) => this._rblistHead = c}>
<thead>
<tr>
{this.props.uncheckbox !== true && <th className="column-checkbox">
<div>
<label className="custom-control custom-control-sm custom-checkbox">
<input className="custom-control-input" type="checkbox" onChange={(e) => this.toggleRows(e)} />
<input className="custom-control-input" type="checkbox" onChange={(e) => this._toggleRows(e)} ref={(c) => this._checkAll = c} />
<span className="custom-control-label"></span>
</label>
</div>
@ -64,7 +64,7 @@ class RbList extends React.Component {
const cWidth = item.width || that.__defaultColumnWidth
const styles = { width: cWidth + 'px' }
return <th key={'column-' + item.field} style={styles} className={`unselect ${item.unsort ? '' : 'sortable'}`} data-field={item.field}
onClick={item.unsort ? null : this.sortField.bind(this, item.field)}>
onClick={item.unsort ? null : this._sortField.bind(this, item.field)}>
<div style={styles}>
<span style={{ width: (cWidth - 8) + 'px' }}>{item.label}</span>
<i className={'zmdi ' + (item.sort || '')} />
@ -79,11 +79,11 @@ class RbList extends React.Component {
{this.state.rowsData.map((item) => {
const lastPrimary = item[lastIndex]
const rowKey = 'row-' + lastPrimary.id
return <tr key={rowKey} data-id={lastPrimary.id} onClick={(e) => this.clickRow(e, true)}>
return <tr key={rowKey} data-id={lastPrimary.id} onClick={(e) => this._clickRow(e, true)}>
{this.props.uncheckbox !== true && <td key={rowKey + '-checkbox'} className="column-checkbox">
<div>
<label className="custom-control custom-control-sm custom-checkbox">
<input className="custom-control-input" type="checkbox" onChange={(e) => this.clickRow(e)} />
<input className="custom-control-input" type="checkbox" onChange={(e) => this._clickRow(e)} />
<span className="custom-control-label"></span>
</label>
</div>
@ -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 (
<div className="row rb-datatable-footer">
<div className="col-12 col-md-4">
<div className="dataTables_info" key="page-rowsTotal">{this.state.rowsTotal > 0 ? `${this.state.rowsTotal} 条数据` : ''}</div>
<div className="dataTables_info" key="page-rowsTotal">
{this.state.selectedTotal > 0 && <span className="mr-2">已选中 {this.state.selectedTotal} .</span>}
{this.state.rowsTotal > 0 && <span> {this.state.rowsTotal} 条数据</span>}
</div>
</div>
<div className="col-12 col-md-8">
<div className="float-right paging_sizes">

View file

@ -93,8 +93,8 @@
<a href="forgot-passwd">${bundle.lang('ForgotPassword')}</a>
</div>
</div>
<div class="form-group login-submit" style="margin-bottom:1.15rem">
<button class="btn btn-primary btn-xl" type="submit">${bundle.lang('Login')}</button>
<div class="form-group login-submit mb-2">
<button class="btn btn-primary btn-xl" type="submit" data-spinner>${bundle.lang('Login')}</button>
<div class="mt-4 text-center">${bundle.lang('NoAccountYet')}&nbsp;<a href="signup">${bundle.lang('SignupNow')}</a></div>
</div>
<div class="select-lang text-center mb-2">