From 2e621bf970ac0a2a35c229a73ea1067ad5022f83 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 5 Jul 2026 23:40:13 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=8F=B7=E7=A0=81=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E6=98=BE=E7=A4=BA=E6=9D=A1=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- application/admin/lang/zh-cn/split/number.php | 4 + .../admin/lang/zh-cn/split/number.php | 4 + .../public/assets/js/backend/split/number.js | 504 ++++++++++++++++++ public/assets/js/backend/split/number.js | 504 ++++++++++++++++++ 4 files changed, 1016 insertions(+) diff --git a/application/admin/lang/zh-cn/split/number.php b/application/admin/lang/zh-cn/split/number.php index 22c3bef..be5af1c 100755 --- a/application/admin/lang/zh-cn/split/number.php +++ b/application/admin/lang/zh-cn/split/number.php @@ -63,4 +63,8 @@ return [ 'Ratio deferred cleanup confirm' => '将清除 %d 条无效降权记录(无落地页访问或访问未超过同步基线)。确定继续?', 'Ratio deferred cleanup success' => '已清理 %d 条无效降权记录', 'Ratio deferred cleanup none' => '当前没有需要清理的无效降权记录', + 'Stream loading' => '加载中...', + 'Stream loaded' => '已加载 %loaded% / %total%,继续滚动加载更多', + 'Stream loading bottom' => '加载中...', + 'Stream loading pending' => '即将加载更多,可切换显示条数取消...', ]; diff --git a/patches/application/admin/lang/zh-cn/split/number.php b/patches/application/admin/lang/zh-cn/split/number.php index 22c3bef..be5af1c 100755 --- a/patches/application/admin/lang/zh-cn/split/number.php +++ b/patches/application/admin/lang/zh-cn/split/number.php @@ -63,4 +63,8 @@ return [ 'Ratio deferred cleanup confirm' => '将清除 %d 条无效降权记录(无落地页访问或访问未超过同步基线)。确定继续?', 'Ratio deferred cleanup success' => '已清理 %d 条无效降权记录', 'Ratio deferred cleanup none' => '当前没有需要清理的无效降权记录', + 'Stream loading' => '加载中...', + 'Stream loaded' => '已加载 %loaded% / %total%,继续滚动加载更多', + 'Stream loading bottom' => '加载中...', + 'Stream loading pending' => '即将加载更多,可切换显示条数取消...', ]; diff --git a/patches/public/assets/js/backend/split/number.js b/patches/public/assets/js/backend/split/number.js index d9046b6..a75125f 100755 --- a/patches/public/assets/js/backend/split/number.js +++ b/patches/public/assets/js/backend/split/number.js @@ -25,6 +25,11 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin fixedColumns: true, fixedRightNumber: 1, searchFormVisible: true, + pageSize: 10, + pageList: Controller.api.streamLoad.PAGE_LIST, + queryParams: function (params) { + return Controller.api.streamLoad.wrapQueryParams(table, params); + }, columns: [ [ {checkbox: true}, @@ -122,6 +127,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin }); Table.api.bindevent(table); + Controller.api.streamLoad.bind(table); Controller.api.bindDeferredFilter(table); Controller.api.bindDeferredCleanup(table); }, @@ -167,6 +173,504 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin } }, + /** + * 分页选「全部」时改为分块滚动加载,避免一次渲染过多 DOM 导致卡顿 + */ + streamLoad: { + CHUNK: 50, + SCROLL_THRESHOLD: 80, + LOAD_DELAY: 1500, + PAGE_LIST: [10, 30, 50, 100, 200, 300, 500, 'All'], + + createState: function () { + return { + enabled: false, + loading: false, + pendingLoad: false, + loadTimer: null, + loaded: 0, + total: 0, + chunk: 50, + pendingReset: false, + pendingAllSelect: false, + appendQuery: false + }; + }, + + getState: function (table) { + var state = table.data('splitStreamLoad'); + if (!state) { + state = Controller.api.streamLoad.createState(); + table.data('splitStreamLoad', state); + } + return state; + }, + + getAllLabel: function () { + var localeKey = (typeof Config !== 'undefined' && Config.language === 'zh-cn') ? 'zh-CN' : 'en-US'; + var locale = $.fn.bootstrapTable.locales && $.fn.bootstrapTable.locales[localeKey]; + if (locale && typeof locale.formatAllRows === 'function') { + return locale.formatAllRows(); + } + if ($.fn.bootstrapTable.defaults && typeof $.fn.bootstrapTable.defaults.formatAllRows === 'function') { + return $.fn.bootstrapTable.defaults.formatAllRows(); + } + return 'All'; + }, + + /** + * initServer 首次请求时实例尚未写入 data('bootstrap.table'),getOptions 会退回 jQuery 对象 + */ + safeGetOptions: function (table) { + if (!table || !table.length) { + return null; + } + var opts = table.bootstrapTable('getOptions'); + if (opts && typeof opts.formatAllRows === 'function') { + return opts; + } + var instance = table.data('bootstrap.table'); + if (instance && instance.options && typeof instance.options.formatAllRows === 'function') { + return instance.options; + } + return null; + }, + + getDefaultPageList: function () { + return Controller.api.streamLoad.PAGE_LIST; + }, + + getMaxNormalPageSize: function (pageList) { + var api = Controller.api.streamLoad; + var maxNormal = 0; + $.each(pageList || api.getDefaultPageList(), function (i, value) { + if (api.isAllPageSize(value)) { + return; + } + var num = parseInt(value, 10); + if (!isNaN(num) && num > maxNormal) { + maxNormal = num; + } + }); + return maxNormal; + }, + + isAllPageSize: function (pageSize) { + if (pageSize === undefined || pageSize === null || pageSize === '') { + return false; + } + return String(pageSize).toUpperCase() === String(Controller.api.streamLoad.getAllLabel()).toUpperCase(); + }, + + isLargeStoredPageSize: function (table, params) { + var api = Controller.api.streamLoad; + var options = api.safeGetOptions(table); + var pageSize = options ? options.pageSize : null; + var limit = params ? (parseInt(params.limit, 10) || 0) : 0; + + if (pageSize !== null && api.isAllPageSize(pageSize)) { + return true; + } + var maxNormal = api.getMaxNormalPageSize(options ? options.pageList : null); + if (typeof pageSize === 'number' && pageSize > maxNormal) { + return true; + } + if (!options && limit > maxNormal) { + return true; + } + return false; + }, + + shouldEnable: function (table, params) { + var api = Controller.api.streamLoad; + var state = api.getState(table); + if (state.pendingAllSelect || state.enabled) { + return true; + } + var options = api.safeGetOptions(table); + var limit = parseInt(params.limit, 10) || 0; + + if (options) { + var totalRows = parseInt(options.totalRows, 10) || 0; + if (api.isAllPageSize(options.pageSize)) { + return true; + } + if (api.isLargeStoredPageSize(table, params)) { + return true; + } + return totalRows > 0 && limit >= totalRows; + } + + // 表格构造阶段:All 模式 totalRows 尚未就绪时 limit 可能为 0 + if (limit === 0) { + var stored = localStorage.getItem('pagesize'); + if (stored && api.isAllPageSize(stored)) { + return true; + } + } + return api.isLargeStoredPageSize(table, params); + }, + + enable: function (table) { + var state = Controller.api.streamLoad.getState(table); + state.enabled = true; + state.loaded = 0; + state.loading = false; + state.pendingReset = true; + }, + + disable: function (table) { + var api = Controller.api.streamLoad; + var state = api.getState(table); + api.cancelPendingLoad(table); + state.enabled = false; + state.loaded = 0; + state.total = 0; + state.loading = false; + state.pendingReset = false; + state.pendingAllSelect = false; + api.updateHint(table); + api.togglePaginationNav(table, false); + api.showBottomLoading(table, false); + }, + + wrapQueryParams: function (table, params) { + var api = Controller.api.streamLoad; + var state = api.getState(table); + + if (api.shouldEnable(table, params)) { + state.enabled = true; + state.pendingAllSelect = false; + } + + if (!state.enabled) { + return params; + } + + params.limit = state.chunk; + if (state.appendQuery) { + params.offset = parseInt(params.offset, 10) || 0; + } else { + params.offset = 0; + state.pendingReset = true; + } + state.appendQuery = false; + return params; + }, + + bind: function (table) { + var api = Controller.api.streamLoad; + var $wrapper = table.closest('.bootstrap-table'); + + $wrapper.on('click.splitStream', '.page-list .dropdown-menu li a', function () { + api.cancelPendingLoad(table); + var label = $.trim($(this).text()); + if (api.isAllPageSize(label)) { + api.getState(table).pendingAllSelect = true; + api.enable(table); + } + }); + + table.on('page-change.bs.table', function (e, pageNumber, pageSize) { + var options = api.safeGetOptions(table); + if (!options) { + return; + } + if (api.isAllPageSize(pageSize) + || (typeof pageSize === 'number' && pageSize === options.totalRows && options.totalRows > 0)) { + api.enable(table); + return; + } + if (typeof pageSize === 'number' && pageSize < options.totalRows) { + api.disable(table); + } + }); + + table.on('refresh.bs.table search.bs.table common-search.bs.table', function () { + var state = api.getState(table); + api.cancelPendingLoad(table); + if (!state.enabled) { + return; + } + state.loaded = 0; + state.pendingReset = true; + }); + + table.on('sort.bs.table', function () { + var state = api.getState(table); + api.cancelPendingLoad(table); + if (!state.enabled) { + return; + } + state.loaded = 0; + state.pendingReset = true; + }); + + table.on('load-success.bs.table', function (e, data) { + var state = api.getState(table); + if (!state.enabled || !data || typeof data.total === 'undefined') { + return; + } + + state.total = parseInt(data.total, 10) || 0; + if (state.pendingReset) { + state.loaded = $.isArray(data.rows) ? data.rows.length : 0; + state.pendingReset = false; + } + + api.updateHint(table); + api.togglePaginationNav(table, true); + api.bindScrollContainer(table); + api.autoFillViewport(table); + + var tableOptions = api.safeGetOptions(table); + if (tableOptions && tableOptions.fixedColumns) { + table.bootstrapTable('resetView'); + } + }); + + table.on('post-body.bs.table', function () { + if (!api.getState(table).enabled) { + return; + } + api.updateHint(table); + }); + }, + + bindScrollContainer: function (table) { + var api = Controller.api.streamLoad; + var $body = table.closest('.bootstrap-table').find('.fixed-table-body'); + if (!$body.length || $body.data('splitStreamBound')) { + return; + } + $body.data('splitStreamBound', true); + $body.on('scroll.splitStream', function () { + api.onScroll(table, this); + }); + }, + + onScroll: function (table, el) { + var api = Controller.api.streamLoad; + var state = api.getState(table); + if (!state.enabled || state.loading || state.loaded >= state.total) { + return; + } + var atBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - api.SCROLL_THRESHOLD; + if (!atBottom) { + api.cancelPendingLoad(table); + return; + } + if (state.pendingLoad) { + return; + } + api.scheduleLoadMore(table); + }, + + isNearBottom: function (table) { + var api = Controller.api.streamLoad; + var $body = table.closest('.bootstrap-table').find('.fixed-table-body'); + if (!$body.length) { + return false; + } + var el = $body[0]; + return el.scrollTop + el.clientHeight >= el.scrollHeight - api.SCROLL_THRESHOLD; + }, + + scheduleLoadMore: function (table) { + var api = Controller.api.streamLoad; + var state = api.getState(table); + if (state.loading || state.pendingLoad || !state.enabled || state.loaded >= state.total) { + return; + } + state.pendingLoad = true; + api.showBottomLoading(table, true); + api.updateHint(table, false, true); + + state.loadTimer = setTimeout(function () { + state.pendingLoad = false; + state.loadTimer = null; + if (!state.enabled) { + api.showBottomLoading(table, false); + api.updateHint(table); + return; + } + if (!api.isNearBottom(table)) { + api.showBottomLoading(table, false); + api.updateHint(table); + return; + } + api.loadMore(table); + }, api.LOAD_DELAY); + }, + + cancelPendingLoad: function (table) { + var api = Controller.api.streamLoad; + var state = api.getState(table); + if (state.loadTimer) { + clearTimeout(state.loadTimer); + state.loadTimer = null; + } + state.pendingLoad = false; + if (!state.loading) { + api.showBottomLoading(table, false); + api.updateHint(table); + } + }, + + showBottomLoading: function (table, show) { + var $body = table.closest('.bootstrap-table').find('.fixed-table-body'); + if (!$body.length) { + return; + } + var $bar = $body.children('.split-stream-loading-bar'); + if (show) { + if (!$bar.length) { + var loadingText = __('Stream loading bottom'); + if (!loadingText || loadingText === 'Stream loading bottom') { + loadingText = '加载中...'; + } + $bar = $('
' + + ' ' + Fast.api.escape(loadingText) + + '
'); + $body.append($bar); + } + $bar.show(); + } else if ($bar.length) { + $bar.hide(); + } + }, + + buildRequestParams: function (table, offset, limit) { + var options = Controller.api.streamLoad.safeGetOptions(table); + if (!options) { + return {offset: offset, limit: limit}; + } + var state = Controller.api.streamLoad.getState(table); + state.appendQuery = true; + return options.queryParams({ + search: options.searchText, + sort: options.sortName, + order: options.sortOrder, + offset: offset, + limit: limit + }); + }, + + loadMore: function (table) { + var api = Controller.api.streamLoad; + var state = api.getState(table); + if (state.loading || !state.enabled || state.loaded >= state.total) { + return; + } + + state.loading = true; + api.updateHint(table, true); + + var options = api.safeGetOptions(table); + if (!options) { + state.loading = false; + return; + } + $.ajax({ + url: options.url, + type: options.method, + data: api.buildRequestParams(table, state.loaded, state.chunk), + dataType: 'json' + }).done(function (res) { + state.loading = false; + api.showBottomLoading(table, false); + if (!res || typeof res.rows === 'undefined') { + Toastr.error(__('Unknown data format')); + api.updateHint(table); + return; + } + + var rows = res.rows || []; + if (rows.length === 0) { + state.loaded = state.total; + api.updateHint(table); + return; + } + + table.bootstrapTable('append', rows); + state.loaded += rows.length; + api.updateHint(table); + api.autoFillViewport(table); + + if (options.fixedColumns) { + table.bootstrapTable('resetView'); + } + }).fail(function () { + state.loading = false; + api.showBottomLoading(table, false); + api.updateHint(table); + Toastr.error(__('Unknown data format')); + }); + }, + + autoFillViewport: function (table) { + var api = Controller.api.streamLoad; + var state = api.getState(table); + if (!state.enabled || state.loading || state.pendingLoad || state.loaded >= state.total) { + return; + } + var $body = table.closest('.bootstrap-table').find('.fixed-table-body'); + if (!$body.length) { + return; + } + var el = $body[0]; + if (el.scrollHeight <= el.clientHeight + api.SCROLL_THRESHOLD) { + api.scheduleLoadMore(table); + } + }, + + togglePaginationNav: function (table, hide) { + table.closest('.bootstrap-table').find('.fixed-table-pagination .pagination').toggle(!hide); + }, + + getHintText: function (loaded, total, loading, pending) { + if (pending) { + var pendingText = __('Stream loading pending'); + if (!pendingText || pendingText === 'Stream loading pending') { + pendingText = '即将加载更多,可切换显示条数取消...'; + } + return pendingText; + } + if (loading) { + var loadingText = __('Stream loading'); + return loadingText && loadingText !== 'Stream loading' ? loadingText : '加载中...'; + } + var template = __('Stream loaded'); + if (!template || template === 'Stream loaded') { + template = '已加载 %loaded% / %total%,继续滚动加载更多'; + } + return template + .replace('%loaded%', String(loaded)) + .replace('%total%', String(total)); + }, + + updateHint: function (table, loading, pending) { + var api = Controller.api.streamLoad; + var state = api.getState(table); + var $wrapper = table.closest('.bootstrap-table'); + var $detail = $wrapper.find('.pagination-detail'); + + if (!state.enabled) { + $wrapper.find('.split-stream-hint').remove(); + return; + } + + var $hint = $wrapper.find('.split-stream-hint'); + if (!$hint.length) { + $hint = $(''); + $detail.append($hint); + } + var isPending = pending !== undefined ? pending : state.pendingLoad; + var isLoading = loading !== undefined ? loading : state.loading; + $hint.text(api.getHintText(state.loaded, state.total, isLoading, isPending)); + } + }, + formatter: { platformStatus: function (value, row) { var text = row.platform_status_text != null && row.platform_status_text !== '' diff --git a/public/assets/js/backend/split/number.js b/public/assets/js/backend/split/number.js index 07ca9ed..ca518dd 100755 --- a/public/assets/js/backend/split/number.js +++ b/public/assets/js/backend/split/number.js @@ -25,6 +25,11 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin fixedColumns: true, fixedRightNumber: 1, searchFormVisible: true, + pageSize: 10, + pageList: Controller.api.streamLoad.PAGE_LIST, + queryParams: function (params) { + return Controller.api.streamLoad.wrapQueryParams(table, params); + }, columns: [ [ {checkbox: true}, @@ -122,6 +127,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin }); Table.api.bindevent(table); + Controller.api.streamLoad.bind(table); Controller.api.bindDeferredFilter(table); Controller.api.bindDeferredCleanup(table); }, @@ -177,6 +183,504 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefin } }, + /** + * 分页选「全部」时改为分块滚动加载,避免一次渲染过多 DOM 导致卡顿 + */ + streamLoad: { + CHUNK: 50, + SCROLL_THRESHOLD: 80, + LOAD_DELAY: 1500, + PAGE_LIST: [10, 30, 50, 100, 200, 300, 500, 'All'], + + createState: function () { + return { + enabled: false, + loading: false, + pendingLoad: false, + loadTimer: null, + loaded: 0, + total: 0, + chunk: 50, + pendingReset: false, + pendingAllSelect: false, + appendQuery: false + }; + }, + + getState: function (table) { + var state = table.data('splitStreamLoad'); + if (!state) { + state = Controller.api.streamLoad.createState(); + table.data('splitStreamLoad', state); + } + return state; + }, + + getAllLabel: function () { + var localeKey = (typeof Config !== 'undefined' && Config.language === 'zh-cn') ? 'zh-CN' : 'en-US'; + var locale = $.fn.bootstrapTable.locales && $.fn.bootstrapTable.locales[localeKey]; + if (locale && typeof locale.formatAllRows === 'function') { + return locale.formatAllRows(); + } + if ($.fn.bootstrapTable.defaults && typeof $.fn.bootstrapTable.defaults.formatAllRows === 'function') { + return $.fn.bootstrapTable.defaults.formatAllRows(); + } + return 'All'; + }, + + /** + * initServer 首次请求时实例尚未写入 data('bootstrap.table'),getOptions 会退回 jQuery 对象 + */ + safeGetOptions: function (table) { + if (!table || !table.length) { + return null; + } + var opts = table.bootstrapTable('getOptions'); + if (opts && typeof opts.formatAllRows === 'function') { + return opts; + } + var instance = table.data('bootstrap.table'); + if (instance && instance.options && typeof instance.options.formatAllRows === 'function') { + return instance.options; + } + return null; + }, + + getDefaultPageList: function () { + return Controller.api.streamLoad.PAGE_LIST; + }, + + getMaxNormalPageSize: function (pageList) { + var api = Controller.api.streamLoad; + var maxNormal = 0; + $.each(pageList || api.getDefaultPageList(), function (i, value) { + if (api.isAllPageSize(value)) { + return; + } + var num = parseInt(value, 10); + if (!isNaN(num) && num > maxNormal) { + maxNormal = num; + } + }); + return maxNormal; + }, + + isAllPageSize: function (pageSize) { + if (pageSize === undefined || pageSize === null || pageSize === '') { + return false; + } + return String(pageSize).toUpperCase() === String(Controller.api.streamLoad.getAllLabel()).toUpperCase(); + }, + + isLargeStoredPageSize: function (table, params) { + var api = Controller.api.streamLoad; + var options = api.safeGetOptions(table); + var pageSize = options ? options.pageSize : null; + var limit = params ? (parseInt(params.limit, 10) || 0) : 0; + + if (pageSize !== null && api.isAllPageSize(pageSize)) { + return true; + } + var maxNormal = api.getMaxNormalPageSize(options ? options.pageList : null); + if (typeof pageSize === 'number' && pageSize > maxNormal) { + return true; + } + if (!options && limit > maxNormal) { + return true; + } + return false; + }, + + shouldEnable: function (table, params) { + var api = Controller.api.streamLoad; + var state = api.getState(table); + if (state.pendingAllSelect || state.enabled) { + return true; + } + var options = api.safeGetOptions(table); + var limit = parseInt(params.limit, 10) || 0; + + if (options) { + var totalRows = parseInt(options.totalRows, 10) || 0; + if (api.isAllPageSize(options.pageSize)) { + return true; + } + if (api.isLargeStoredPageSize(table, params)) { + return true; + } + return totalRows > 0 && limit >= totalRows; + } + + // 表格构造阶段:All 模式 totalRows 尚未就绪时 limit 可能为 0 + if (limit === 0) { + var stored = localStorage.getItem('pagesize'); + if (stored && api.isAllPageSize(stored)) { + return true; + } + } + return api.isLargeStoredPageSize(table, params); + }, + + enable: function (table) { + var state = Controller.api.streamLoad.getState(table); + state.enabled = true; + state.loaded = 0; + state.loading = false; + state.pendingReset = true; + }, + + disable: function (table) { + var api = Controller.api.streamLoad; + var state = api.getState(table); + api.cancelPendingLoad(table); + state.enabled = false; + state.loaded = 0; + state.total = 0; + state.loading = false; + state.pendingReset = false; + state.pendingAllSelect = false; + api.updateHint(table); + api.togglePaginationNav(table, false); + api.showBottomLoading(table, false); + }, + + wrapQueryParams: function (table, params) { + var api = Controller.api.streamLoad; + var state = api.getState(table); + + if (api.shouldEnable(table, params)) { + state.enabled = true; + state.pendingAllSelect = false; + } + + if (!state.enabled) { + return params; + } + + params.limit = state.chunk; + if (state.appendQuery) { + params.offset = parseInt(params.offset, 10) || 0; + } else { + params.offset = 0; + state.pendingReset = true; + } + state.appendQuery = false; + return params; + }, + + bind: function (table) { + var api = Controller.api.streamLoad; + var $wrapper = table.closest('.bootstrap-table'); + + $wrapper.on('click.splitStream', '.page-list .dropdown-menu li a', function () { + api.cancelPendingLoad(table); + var label = $.trim($(this).text()); + if (api.isAllPageSize(label)) { + api.getState(table).pendingAllSelect = true; + api.enable(table); + } + }); + + table.on('page-change.bs.table', function (e, pageNumber, pageSize) { + var options = api.safeGetOptions(table); + if (!options) { + return; + } + if (api.isAllPageSize(pageSize) + || (typeof pageSize === 'number' && pageSize === options.totalRows && options.totalRows > 0)) { + api.enable(table); + return; + } + if (typeof pageSize === 'number' && pageSize < options.totalRows) { + api.disable(table); + } + }); + + table.on('refresh.bs.table search.bs.table common-search.bs.table', function () { + var state = api.getState(table); + api.cancelPendingLoad(table); + if (!state.enabled) { + return; + } + state.loaded = 0; + state.pendingReset = true; + }); + + table.on('sort.bs.table', function () { + var state = api.getState(table); + api.cancelPendingLoad(table); + if (!state.enabled) { + return; + } + state.loaded = 0; + state.pendingReset = true; + }); + + table.on('load-success.bs.table', function (e, data) { + var state = api.getState(table); + if (!state.enabled || !data || typeof data.total === 'undefined') { + return; + } + + state.total = parseInt(data.total, 10) || 0; + if (state.pendingReset) { + state.loaded = $.isArray(data.rows) ? data.rows.length : 0; + state.pendingReset = false; + } + + api.updateHint(table); + api.togglePaginationNav(table, true); + api.bindScrollContainer(table); + api.autoFillViewport(table); + + var tableOptions = api.safeGetOptions(table); + if (tableOptions && tableOptions.fixedColumns) { + table.bootstrapTable('resetView'); + } + }); + + table.on('post-body.bs.table', function () { + if (!api.getState(table).enabled) { + return; + } + api.updateHint(table); + }); + }, + + bindScrollContainer: function (table) { + var api = Controller.api.streamLoad; + var $body = table.closest('.bootstrap-table').find('.fixed-table-body'); + if (!$body.length || $body.data('splitStreamBound')) { + return; + } + $body.data('splitStreamBound', true); + $body.on('scroll.splitStream', function () { + api.onScroll(table, this); + }); + }, + + onScroll: function (table, el) { + var api = Controller.api.streamLoad; + var state = api.getState(table); + if (!state.enabled || state.loading || state.loaded >= state.total) { + return; + } + var atBottom = el.scrollTop + el.clientHeight >= el.scrollHeight - api.SCROLL_THRESHOLD; + if (!atBottom) { + api.cancelPendingLoad(table); + return; + } + if (state.pendingLoad) { + return; + } + api.scheduleLoadMore(table); + }, + + isNearBottom: function (table) { + var api = Controller.api.streamLoad; + var $body = table.closest('.bootstrap-table').find('.fixed-table-body'); + if (!$body.length) { + return false; + } + var el = $body[0]; + return el.scrollTop + el.clientHeight >= el.scrollHeight - api.SCROLL_THRESHOLD; + }, + + scheduleLoadMore: function (table) { + var api = Controller.api.streamLoad; + var state = api.getState(table); + if (state.loading || state.pendingLoad || !state.enabled || state.loaded >= state.total) { + return; + } + state.pendingLoad = true; + api.showBottomLoading(table, true); + api.updateHint(table, false, true); + + state.loadTimer = setTimeout(function () { + state.pendingLoad = false; + state.loadTimer = null; + if (!state.enabled) { + api.showBottomLoading(table, false); + api.updateHint(table); + return; + } + if (!api.isNearBottom(table)) { + api.showBottomLoading(table, false); + api.updateHint(table); + return; + } + api.loadMore(table); + }, api.LOAD_DELAY); + }, + + cancelPendingLoad: function (table) { + var api = Controller.api.streamLoad; + var state = api.getState(table); + if (state.loadTimer) { + clearTimeout(state.loadTimer); + state.loadTimer = null; + } + state.pendingLoad = false; + if (!state.loading) { + api.showBottomLoading(table, false); + api.updateHint(table); + } + }, + + showBottomLoading: function (table, show) { + var $body = table.closest('.bootstrap-table').find('.fixed-table-body'); + if (!$body.length) { + return; + } + var $bar = $body.children('.split-stream-loading-bar'); + if (show) { + if (!$bar.length) { + var loadingText = __('Stream loading bottom'); + if (!loadingText || loadingText === 'Stream loading bottom') { + loadingText = '加载中...'; + } + $bar = $('
' + + ' ' + Fast.api.escape(loadingText) + + '
'); + $body.append($bar); + } + $bar.show(); + } else if ($bar.length) { + $bar.hide(); + } + }, + + buildRequestParams: function (table, offset, limit) { + var options = Controller.api.streamLoad.safeGetOptions(table); + if (!options) { + return {offset: offset, limit: limit}; + } + var state = Controller.api.streamLoad.getState(table); + state.appendQuery = true; + return options.queryParams({ + search: options.searchText, + sort: options.sortName, + order: options.sortOrder, + offset: offset, + limit: limit + }); + }, + + loadMore: function (table) { + var api = Controller.api.streamLoad; + var state = api.getState(table); + if (state.loading || !state.enabled || state.loaded >= state.total) { + return; + } + + state.loading = true; + api.updateHint(table, true); + + var options = api.safeGetOptions(table); + if (!options) { + state.loading = false; + return; + } + $.ajax({ + url: options.url, + type: options.method, + data: api.buildRequestParams(table, state.loaded, state.chunk), + dataType: 'json' + }).done(function (res) { + state.loading = false; + api.showBottomLoading(table, false); + if (!res || typeof res.rows === 'undefined') { + Toastr.error(__('Unknown data format')); + api.updateHint(table); + return; + } + + var rows = res.rows || []; + if (rows.length === 0) { + state.loaded = state.total; + api.updateHint(table); + return; + } + + table.bootstrapTable('append', rows); + state.loaded += rows.length; + api.updateHint(table); + api.autoFillViewport(table); + + if (options.fixedColumns) { + table.bootstrapTable('resetView'); + } + }).fail(function () { + state.loading = false; + api.showBottomLoading(table, false); + api.updateHint(table); + Toastr.error(__('Unknown data format')); + }); + }, + + autoFillViewport: function (table) { + var api = Controller.api.streamLoad; + var state = api.getState(table); + if (!state.enabled || state.loading || state.pendingLoad || state.loaded >= state.total) { + return; + } + var $body = table.closest('.bootstrap-table').find('.fixed-table-body'); + if (!$body.length) { + return; + } + var el = $body[0]; + if (el.scrollHeight <= el.clientHeight + api.SCROLL_THRESHOLD) { + api.scheduleLoadMore(table); + } + }, + + togglePaginationNav: function (table, hide) { + table.closest('.bootstrap-table').find('.fixed-table-pagination .pagination').toggle(!hide); + }, + + getHintText: function (loaded, total, loading, pending) { + if (pending) { + var pendingText = __('Stream loading pending'); + if (!pendingText || pendingText === 'Stream loading pending') { + pendingText = '即将加载更多,可切换显示条数取消...'; + } + return pendingText; + } + if (loading) { + var loadingText = __('Stream loading'); + return loadingText && loadingText !== 'Stream loading' ? loadingText : '加载中...'; + } + var template = __('Stream loaded'); + if (!template || template === 'Stream loaded') { + template = '已加载 %loaded% / %total%,继续滚动加载更多'; + } + return template + .replace('%loaded%', String(loaded)) + .replace('%total%', String(total)); + }, + + updateHint: function (table, loading, pending) { + var api = Controller.api.streamLoad; + var state = api.getState(table); + var $wrapper = table.closest('.bootstrap-table'); + var $detail = $wrapper.find('.pagination-detail'); + + if (!state.enabled) { + $wrapper.find('.split-stream-hint').remove(); + return; + } + + var $hint = $wrapper.find('.split-stream-hint'); + if (!$hint.length) { + $hint = $(''); + $detail.append($hint); + } + var isPending = pending !== undefined ? pending : state.pendingLoad; + var isLoading = loading !== undefined ? loading : state.loading; + $hint.text(api.getHintText(state.loaded, state.total, isLoading, isPending)); + } + }, + formatter: { platformStatus: function (value, row) { var text = row.platform_status_text != null && row.platform_status_text !== ''