(function() { if(config.skipRedirect || window.skipRedirect) return; const parent = window.parent; const currentModule = config.currentModule; const currentMethod = config.currentMethod; const isIndexPage = currentModule === 'index' && currentMethod === 'index'; const moduleMethod = `${currentModule}-${currentMethod}`; const selfOpenList = new Set('index|tutorial|install|upgrade|sso|cron|misc|user-login|user-deny|user-logout|user-reset|user-forgetpassword|user-resetpassword|my-changepassword|my-preference|file-read|file-download|file-preview|file-uploadimages|file-ajaxwopifiles|report-annualdata|misc-captcha|execution-printkanban|traincourse-ajaxuploadlargefile|traincourse-playvideo|screen-view|zanode-create|screen-ajaxgetchart|instance-terminal|ai-chat|instance-logs'.split('|')); const iframeList = new Set(['cron-index', 'zanode-create']); const isAllowSelfOpen = !iframeList.has(moduleMethod) && (isIndexPage || location.hash === '#_single' || /(\?|\&)_single/.test(location.search) || currentMethod.startsWith('ajax') || selfOpenList.has(moduleMethod) || selfOpenList.has(currentModule) || $('body').hasClass('allow-self-open')); if(parent === window && !isAllowSelfOpen) { const shortUrl = location.pathname + location.search + location.hash; location.href = $.createLink('index', 'index', `open=${btoa(shortUrl)}`); return; } }()); (function() { let config = window.config; const isIndexPage = config.currentModule === 'index' && config.currentMethod === 'index'; const DEBUG = config.debug; const currentCode = (window.frameElement ? window.frameElement.name : window.name).split('-')[1]; const isInAppTab = parent.window !== window; const fetchTasks = new Map(); const ridSet = new Set(); const timers = {timeout: [], interval: []}; let currentAppUrl = isInAppTab ? '' : location.href; let zinbar = null; let historyState = parent.window.history.state; const hasZinBar = DEBUG && window.zin && window.zin.zinTool && !isIndexPage; const localCacheFirst = config.clientCache === 'local-first'; const isTutorial = top.config.currentModule === 'tutorial'; let openedOldPage = false; let oldPageCofnig = null; let workspaceType = ''; const zinCallbacks = { onSelectLang: null, onSelectVision: null, onChangeApp: null }; /** * 注册Zin回调函数。 * Register a Zin callback function. * * @param {string} name - 回调函数名称。 * @param {function} callback - 回调函数。 */ function registerZinCallback(name, callback) { if(zinCallbacks[name] === undefined) { console.warn('[ZIN] registerZinCallback unknown callback name:', name); return; } if(typeof callback === 'function') { zinCallbacks[name] = callback; } else { console.warn('[ZIN] registerZinCallback expects a function for', name); } } function getPageInfo() { let pageConfig = openedOldPage ? oldPageCofnig : window.config; if(openedOldPage && !pageConfig) { const oldPageLink = $.parseLink(openedOldPage); pageConfig = {currentModule: oldPageLink.moduleName, currentMethod: oldPageLink.methodName}; } return { app : currentCode, id : `${currentCode}.${pageConfig.currentModule}-${pageConfig.currentMethod}`, path : `${pageConfig.currentModule}-${pageConfig.currentMethod}`, url : currentAppUrl, config : pageConfig, currentModule: pageConfig.currentModule, currentMethod: pageConfig.currentMethod, }; } function getUrlID(url) { const info = $.parseLink(url || currentAppUrl); return info.moduleName ? `${info.moduleName}-${info.methodName}` : ''; } function isDiffPage(newUrl, oldUrl) { return getUrlID(newUrl) !== getUrlID(oldUrl || currentAppUrl); } function showLog(name, moreTitles, trace, moreInfos) { const titles = [`%c APP • ${currentCode} `]; const titleColors = ['color:#fff;font-weight:bold;background:#2196f3']; const urlID = getUrlID(); if(urlID) { titles.push(`%c ${urlID} `); titleColors.push('background:rgba(33, 150, 243, 0.2);color:#2196f3;'); } if(name) { titles.push(`%c ${name} `); titleColors.push('color:#2196f3;font-weight:bold;'); } if(!Array.isArray(moreTitles)) moreTitles = [moreTitles]; if(typeof moreTitles[0] === 'string' && (moreTitles[0].startsWith('success:') || moreTitles[0].startsWith('error:'))) { const message = moreTitles.shift(); const [type, content] = message.split(':', 2); titles.push(`%c ${content} `); titleColors.push(`color:${type === 'error' ? '#f56c6c' : '#67c23a'};`); } if(trace || moreInfos) { console.groupCollapsed(titles.join(''), ...titleColors, ...moreTitles); if(trace) console.trace(trace); if(moreInfos) { if($.isPlainObject(moreInfos)) Object.keys(moreInfos).forEach((infoName) => console.log(infoName, moreInfos[infoName])); else console.log(moreInfos); } console.groupEnd(); } else { console.log(titles.join(''), ...titleColors, ...moreTitles); } } $.apps = $.extend( { currentCode: currentCode, updateApp: function(code, url, title) { const state = typeof code === 'object' ? code : {url: url, title: title}; const oldState = window.history.state; if(title) document.title = title; if(oldState && oldState.url === url) { if(DEBUG) showLog('Update skipped', [code, title], {oldState, state, code, url, title}); return; } window.history.pushState(state, title, url); if(DEBUG) showLog('Update', [code, title], {state, code, url, title}); return state; }, updateAppUrl: function(url, title) { currentAppUrl = url; return $.apps.updateApp(currentCode, url, title); }, isOldPage: () => false, reloadApp: function(_code, url){loadPage(url);}, openApp: function(url, options){loadPage(url, options);}, goBack: function(){history.go(-1);}, changeAppsLang: changeAppLang, changeAppsTheme: changeAppTheme, updateUserToolbar: function(){loadPage({selector: '#toolbar', partial: true, target: '#toolbar'})}, triggerEvent: triggerEvent, }, parent.window.$.apps); const renderMap = { html: updatePageWithHtml, body: (data) => $('body').html(data), title: (data) => document.title = data, featureBar: updateFeatureBar, pageCSS: updatePageCSS, pageJS: updatePageJS, configJS: updateConfigJS, activeMenu: (data) => activeNav(data), navbar: updateNavbar, heading: updateHeading, fatal: showFatalError, hookCode: updateHookCode, zinDebug: (data, _info, options) => showZinDebugInfo(data, options), zinErrors: (data, _info, options) => showErrors(data, options.id === 'page'), }; function registerRender(name, callback) { renderMap[name] = callback; } function showFatalError(data, _info, options) { const isNormalPage = data.startsWith('URL: ${options.url}
${data}`, size: 'lg', html: !isNormalPage}) } function initZinbar() { if(!hasZinBar) return; let $bar = $('#zinbar'); if($bar.length) return; $bar = $('
').insertAfter('body'); zinbar = new zui.Zinbar($bar[0], typeof window.zin.zinTool === 'object' ? window.zin.zinTool : {}); } function registerTimer(callback, time, type) { type = type || 'timeout'; const id = type === 'interval' ? setInterval(callback, time) : setTimeout(callback, time); timers[type].push(id); return id; } function updateConfigJS(data) { $('#configJS').replaceWith(data); config = window.config; const $body = $(document.body); const classList = ($body.attr('class') || '').split(' ').filter(x => x.length && !x.startsWith('m-')); const pageID = `${config.currentModule}-${config.currentMethod}`; classList.push(`m-${pageID}`); $body.attr('class', classList.join(' ')).attr('data-page', pageID).attr('data-page-raw', `${config.rawModule}-${config.rawMethod}`); } function updatePageCSS(data, _info, options) { let $style = $(`style.zin-page-css[data-id="${options.pageID}"]`); if(!$style.length) $style = $('body>style.zin-page-css'); $style.html(data, false); } function updatePageJS(data, _info, options) { if(window.onPageUnmount) window.onPageUnmount(); $(document).trigger('pageunmount.app'); ['beforePageLoad', 'beforeRequestContent', 'onPageUnmount', 'beforePageUpdate', 'afterPageUpdate', 'onPageRender', 'afterPageRender', 'getPageFormHelper'].forEach(key => { if(window[key]) delete window[key]; }); if(timers.interval.length) timers.interval.forEach(clearInterval); if(timers.timeout.length) timers.timeout.forEach(clearTimeout); timers.interval = []; timers.timeout = []; if(!options.modal) zui.Modal.getAll().forEach(m => !m.options.modal && m.hide()); $('script.zin-page-js').replaceWith(data); } function updateHookCode(data) { let $code = $('#zinHookCode'); if(!$code.length) $code = $('').appendTo('body'); $code.html(data); } function updateZinbar(perf, errors, basePath) { if(!hasZinBar) return; if(zinbar && zinbar.$) zinbar.$.update(perf, errors, basePath); else requestAnimationFrame(() => updateZinbar(perf, errors, basePath)); } function getPerfData() { return (zinbar && zinbar.$) ? zinbar.$.state.pagePerf : null; } function triggerEvent(event, args, options) { if(!isInAppTab ||!$.apps.triggerAppEvent) return; $.apps.triggerAppEvent(currentCode, event, [getPageInfo(), args], options); } function triggerPerfEvent(stage) { if(!zinbar || !zinbar.$) return; if(zinbar.lastPerfEventType === stage) clearTimeout(zinbar.lastPerfEventTimer); zinbar.lastPerfEventTimer = setTimeout(() => { triggerEvent('updatePerfData.app', {stage: stage, perf: getPerfData()}, {silent: true}); }, 100); zinbar.lastPerfEventType = stage; } function updatePerfInfo(options, stage, info) { if(!hasZinBar) return; const perf = {id: options.id, url: options.url || currentAppUrl}; perf[stage] = options.time || performance.now(); if(stage === 'requestBegin') $.extend(perf, {requestEnd: undefined}); else if(stage === 'renderBegin') zinbar.waitZUI = {time: perf[stage], id: options.id, url: options.url}; if(info) { if(info.perf) $.extend(perf, info.perf); if(info.dataSize) perf.dataSize = info.dataSize; } updateZinbar(perf); triggerPerfEvent(stage); } function showZinDebugInfo(data, options) { if(!DEBUG) return; if(data.debug) { window.zinDebug = data.debug; data.debug.forEach(dump => { console.groupCollapsed(`%c ${currentCode.toUpperCase()} %c ${getUrlID()} %c DEBUG %c${dump.name}`, 'color:#fff;font-weight:bold;background:#ec4899', 'background:rgba(233,30,99,0.2);color:#ec4899;font-weight:bold', 'color:#ec4899;font-weight:bold', 'font-weight:bold'); dump.data.forEach(item => { if(typeof item === 'string') console.log('%c' + item, 'font-family:ui-monospace,monospace; padding:0 4px;border-left:1px solid #ff0000;opacity:0.8'); else console.log(item); }); console.groupCollapsed('trace'); console.log('%c' + dump.trace.join('\n'), 'font-family: ui-monospace,monospace; padding:0 4px;'); console.groupEnd(); console.groupEnd(); }); } updateZinbar({id: options.id, trace: data.trace, xhprof: data.xhprof}, data.errors, data.basePath); } function updatePageWithHtml(data) { const html = []; const skipTags = new Set(['SCRIPT', 'META']); $(data).each(function(_idx, node) { const nodeName = node.nodeName; if(nodeName === '#text') html.push(node.textContent); else if(nodeName === 'SCRIPT' && node.innerText.startsWith('window.config={')) html.push(node.outerHTML); else if(nodeName === 'TITLE') document.title = node.innerText; else if(skipTags.has(nodeName)) return; else html.push(node.outerHTML); }); $('body').html(html.join('')); window.zin = {config: window.config}; if(DEBUG) console.log('[ZIN] ', window.zin); console.warn('ZIN: load an old page.'); } function updateNavbar(data) { $('#navbar').morphInner(`
${data}
`); } function updateFeatureBar(data) { const $featureBar = $('#featureBar'); const isOpenSearch = $featureBar.find('.search-form-toggle.active').length && $featureBar.closest('.show-search-form').length; $featureBar.html(data); if(isOpenSearch) { const $searchToggle = $featureBar.find('.search-form-toggle'); if(!$searchToggle.hasClass('active')) { zui.toggleSearchForm({module: $searchToggle.data('module'), show: false}); } } } function updateHeading(data, info) { const $heading = $('#heading'); if(!$heading.length) return; const selector = parseSelector(info.selector); renderWithHtml($heading, data, selector); } function activeNav(activeID, nav) { const $nav = $(nav || '#navbar'); const $active = $nav.find('.nav-item>a.active'); if($active.data('id') === activeID) return; $active.removeClass('active'); $nav.find('.nav-item>a[data-id="' + activeID + '"]').addClass('active'); } function beforeUpdate($target, info, options) { if($target && $target.length) $target.find('[data-zin-events]').off('.zin.on'); if(window.beforePageUpdate) window.beforePageUpdate($target, info, options); } function afterUpdate($target, info, options) { if(window.afterPageUpdate) window.afterPageUpdate($target, info, options); triggerEvent('updatepart.app', {target: $target, info, options}, {silent: true}); } function renderWithHtml($target, html, selector, noMorph) { if(typeof selector === 'string') selector = parseSelector(selector); if(config.morphUpdate === false || $target.hasClass('no-morph')) noMorph = true; if(selector.inner) { if(noMorph) { $target.html(html); } else { const tagName = $target.prop('tagName').toLowerCase(); $target.morphInner(`<${tagName}>${html}`); } } else { if(noMorph) { $target.replaceWith(html); $target = $(selector.select); } else { $target.morph(html); } } return $target; } function renderPartial(info, options) { if(window.onPageRender && window.onPageRender(info, options)) return; if(options.onRender && options.onRender(info, options)) return; const render = (options.renders ? options.renders[info.name] : null) || renderMap[info.name]; const isHtml = info.type === 'html'; const selector = parseSelector(info.selector); let $target = selector ? $(selector.select) : null; if(render) { if(isHtml) beforeUpdate($target, info, options); render(info.data, info, options); if(isHtml) afterUpdate($target, info, options); return; } /* Common render */ if(!selector) { if(DEBUG) showLog('Render partial', 'error:cannot render partial content without selector.', {info, options}); else console.warn('[APP] ', 'cannot render partial content with data', info); return; } if(!$target.length) { if(DEBUG) showLog('Render partial', 'cannot find target element with selector.', {info, selector, options}); else console.warn('[APP] ', 'cannot find target element with selector.', info); return; } if(selector.first) $target = $target.first(); if(selector.type === 'json') { const props = info.data.props; if(typeof props !== 'object') return; const component = $target.zui(info.name); if(typeof component === 'object' && typeof component.render === 'function') { $target.closest('[zui-create-dtable]').attr(`zui-create-${component.constructor.ZUI || info.name}`, ''); beforeUpdate($target, info, options); component.render(props); afterUpdate($target, info, options); } return; } beforeUpdate($target, info, options); $target = renderWithHtml($target, info.data, selector, options.noMorph); afterUpdate($target, info, options); } function renderPage(list, options) { if(DEBUG) showLog('Render', [options.id, list.length, options.noMorph ? 'html' : 'morph'], list, {options}); let updateFullPage = false; list.forEach(item => { try{renderPartial(item, options);} catch(error) {console.error('[ZIN] ', 'Render partial failed', error, {item, options});} if(item.name === 'html' || item.name === 'body') updateFullPage = true; }); if(updateFullPage) { $('html').enableScroll(); } if(window.afterPageRender) window.afterPageRender(list, options); triggerEvent('updatepage.app', {updateFullPage: updateFullPage, options}); if(!options.partial) { const newState = $.apps.updateApp(currentCode, currentAppUrl, document.title); if(newState) historyState = newState; } } function toggleLoading(target, isLoading, loadingClass, loadingIndicatorDelay) { var $target = $(target); loadingClass = loadingClass || 'loading'; const position = $target.css('position'); if(!['relative', 'absolute', 'fixed'].includes(position)) $target.css('position', 'relative'); if(isLoading === undefined) isLoading = !$target.hasClass(loadingClass); $target.css('--load-indicator-delay', isLoading && loadingIndicatorDelay ? loadingIndicatorDelay : null); if(!$target.hasClass('load-indicator')) $target.addClass('load-indicator'); if(isLoading) setTimeout(() => $target.find('.load-indicator.loading').removeClass('loading'), loadingIndicatorDelay || 0); $target.toggleClass(loadingClass, isLoading); } /** * Request data from remote server * @param {Object} options * @param {string} options.id * @param {string} options.url * @param {string} options.selector * @param {string} [options.target] * @param {string} [options.loadingTarget] * @param {string} [options.method] * @param {FormData|Form|Record} [options.data] * @param {{selector: string, type: string}} [options.zinOptions] * @param {function} [options.success] * @param {function} [options.error] * @param {function} [options.complete] * @param {function} [onFinish] */ function requestContent(options, onFinish) { const target = options.target || '#main'; const selectors = (Array.isArray(options.selector) ? options.selector : options.selector.split(',')).map(selector => selector.replace(':component', ':type=json&data=props')); const url = options.url; if(DEBUG && !selectors.includes('zinDebug()')) selectors.push('zinDebug()'); const isDebugRequest = DEBUG && selectors.length === 1 || selectors[0] === 'zinDebug()'; if(options.modal === undefined) options.modal = $(target[0] !== '#' && target[0] !== '.' ? `#${target}` : target).closest('.modal').length; const headers = { 'X-ZIN-Options': JSON.stringify($.extend({selector: selectors, type: 'list'}, options.zinOptions)), 'X-ZIN-App': currentCode, 'X-Zin-Cache-Time': 0, 'X-ZIN-UID': zui.uid ? zui.uid() : '' }; if(options.modal) headers['X-Zui-Modal'] = 'true'; if(isTutorial && top.getCurrentStepID) headers['X-ZIN-Tutorial'] = top.getCurrentStepID(); const requestMethod = (options.method || 'GET').toUpperCase(); if(!options.cache && options.cache !== false) options.cache = !!(requestMethod === 'GET' && config.clientCache && !options.partial); if(options.cache === true) options.cache = url + (url.includes('?') ? '&zin=' : '?zin=') + encodeURIComponent(selectors.join(',')); const cacheKey = options.cache; const rid = options.rid; let cache; let cacheHit; const renderPageData = (data, onlyZinDebug) => { const renderOptions = $.extend({noMorph: !config.morphUpdate || (!options.partial && options.isDiffPage)}, options); if(!onlyZinDebug) updatePerfInfo(options, 'renderBegin'); renderPage(data.reduce((list, item, idx) => { if(Array.isArray(item)) item = {name: item[0].split(':')[0], data: item[1], type: item[2] || 'data'}; item.selector = selectors[idx]; if(!onlyZinDebug || item.name === 'zinDebug') list.push(item); return list; }, []), renderOptions); if(!onlyZinDebug) updatePerfInfo(options, 'renderEnd', {perf: {clientCache: cacheHit ? cacheKey : null}}); }; const callCallback = (name, args) => { let callback = options[name]; if(!callback) return; if(typeof callback === 'string') callback = zui.evalValue(callback); return callback.apply(null, args); }; const ajax = new zui.Ajax( { url: url + (url.includes('?') ? '&zin=1' : '?zin=1'), headers: headers, type: requestMethod, data: options.data, convert: options.convert, type: (options.method || 'GET').toUpperCase(), beforeSend: () => { updatePerfInfo(options, 'requestBegin'); if(isDebugRequest) return; if(options.loadingTarget !== false) toggleLoading(options.loadingTarget || target, true, options.loadingClass, options.loadingIndicatorDelay || (options.partial ? '.2s' : (cache ? '5s' : '1s'))); if(options.before) options.before(); if(!options.partial) $('body').addClass('loading-page'); }, success(rawData) { if(!ridSet.has(rid)) { if(DEBUG) showLog('Request', `error:canceled request ${rid} on success`, {url, rid, options}); return; } const response = this.response; if(response && response.redirected) { if(!isInAppTab) { location.href = response.url; return; } if(isInAppTab && currentCode !== $.apps.getAppCode(response.url, currentCode)) { $.apps.openApp(response.url, response.url.includes('#app=') ? null : currentCode); return; } } updatePerfInfo(options, 'requestEnd', {dataSize: rawData.length, perf: {clientCache: cache ? cacheKey : null}}); options.result = 'success'; let hasFatal = false; let data; if(!rawData && !ajax.sendedAgain) { ajax.sendedAgain = true; ajax.setting.headers['X-Zin-Debug'] = true; delete ajax.data; delete ajax.error; ajax.send(); ajax.canceled = true; return; } ajax.canceled = false; try { data = $.parseRawData(rawData); } catch(e) { if(DEBUG) showLog('Request', 'error:parse data failed', e, {url, options, rawData}); if(!isInAppTab && config.zin) return; hasFatal = rawData.includes('Fatal error') || rawData.includes('Uncaught TypeError:') || rawData.startsWith(' x.name === 'navbar'); if(navbarData && !navbarData.data.includes(`__WORKSPACE_${workspaceType}__`)) { window.open(response.url, '_blank'); ajax.canceled = true; return; } } if(!options.partial && !hasFatal) currentAppUrl = (response && response.url) ? (response.url.split('?zin=')[0].split('&zin=')[0]) : url; let newCacheData = (cacheKey && !hasFatal) ? rawData : null; if(newCacheData && DEBUG) { const parts = newCacheData.split(',["zinDebug:",'); if(parts.length > 1) newCacheData = parts[0] + ']'; } cacheHit = !hasFatal && cache && newCacheData === cache.data.data; if(!cacheHit || !localCacheFirst) { renderPageData(data); } else if(DEBUG) { showLog('Request', 'success:skip render with effective caching', {cacheKey, data}) renderPageData(data, true); } $(document).trigger('pagerender.app'); callCallback('success', [data]); if(cacheKey) { const cacheTime = +response.headers.get('X-Zin-Cache-Time'); if(cacheTime) { $.db.setCacheData(cacheKey, {data: newCacheData, url: currentAppUrl, partial: options.partial, selectors: selectors.join(','), clientTime: Date.now()}, cacheTime, 'zinFetch'); } } } else { if(options.success && options.success.call(ajax, data, options) === false) return; if(data.closeModal) zui.Modal.hide(typeof data.closeModal === 'string' ? data.closeModal : undefined); if(data.autoLoad) autoLoad(data.autoLoad); if(data.result === 'fail') { if(data.message) zui.Messager.show({content: data.message, type: 'danger', className: 'bg-danger text-canvas gap-2 messager-fail'}); } if(data.open) { openUrl(data.open); } else if(data.load) { if(data.load === 'table') loadTable(); if(data.load === 'login') window.top.location.href = $.createLink('user', 'login', 'referer=' + btoa(url)); else if(typeof data.load === 'string') loadPage(data.load); else if(data.load === true) loadCurrentPage(); else if(typeof data.load === 'object') { if(data.load.autoLoad) autoLoad(data.load.autoLoad); if('back' in data.load) { openUrl(data.load); } else if('confirm' in data.load) { $(document).trigger('locate.zt', data.load); } else if('alert' in data.load) { zui.Modal.alert(data.load.alert).then(function() { if(data.load.modal) loadModal(data.load.modal); if(data.load.locate) openUrl(data.load.locate); }); } else { loadPage(data.load); } } } if(data.callback) { const callback = data.callback; if(typeof callback === 'string') { const func = $.runJS(callback); if (typeof func === 'function' && !callback.endsWith(';')) func(); } else if(typeof callback === 'object') { const func = $.runJS(callback.name); if (typeof func === 'function') { func.apply(null, Array.isArray(callback.params) ? callback.params : [callback.params]); } } } } }, error: (error, type) => { if(!ridSet.has(rid)) { if(DEBUG) showLog('Request', `error:canceled request ${rid} on error`, {url, rid, options}); return; } const data = ajax.data; if(!data && !ajax.sendedAgain) { ajax.sendedAgain = true; ajax.setting.headers['X-Zin-Debug'] = true; delete ajax.data; delete ajax.error; ajax.send(); ajax.canceled = true; return; } else if(data) { showFatalError(data, null, options); ajax.canceled = false; } if(ajax.canceled) return; updatePerfInfo(options, 'requestEnd', {error: error}); if(type === 'abort') return console.log('[ZIN] ', 'Abord fetch data from ' + url, {type, error}); if(DEBUG) console.error('[ZIN] ', 'Fetch data failed from ' + url, {type, error}); if(!data) zui.Messager.show('ZIN: Fetch data failed from ' + url); callCallback('error', [data, error]); }, complete: () => { const frameElement = window.frameElement; if(frameElement) { frameElement.classList.remove('loading'); frameElement.classList.add('in'); } if(!options.partial) $('body').removeClass('loading-page'); if(options.loadingTarget !== false) toggleLoading(options.loadingTarget || target, false, options.loadingClass); if(ajax.canceled) return; if(onFinish) onFinish(); $(document).data('zinCache', null); callCallback('complete', []); $(document).trigger('pageload.app'); } }); ajax.sendedAgain = true; // Disable the request again. updatePerfInfo(options, 'requestBegin', {perf: {renderBegin: undefined, renderEnd: undefined}}); if(DEBUG) showLog('Request', `${ajax.setting.type}:${options.id} ${getUrlID(url)} task ${rid}`, options, {cacheKey, ajax}); if(currentCode) $.cookie.set('tab', currentCode, {expires: config.cookieLife, path: config.webRoot}); if(cacheKey) { $.db.getCache(cacheKey, 'zinFetch').then(localCache => { if(!ridSet.has(rid)) { if(DEBUG) showLog('Request', `error:canceled request ${rid} on load cache`, {url, rid, options}); return; } cache = localCache; if(cache && localCacheFirst) { ajax.setting.headers['X-Zin-Cache-Time'] = cache.updateTime; if($(document).data('zinCache') !== [cache.key, cache.time].join('#')) { try { const data = $.parseRawData(cache.data.data); if(DEBUG) showLog('Request', 'success:render with cache', {url, cacheKey, data, options}); if(!cache.data.partial) currentAppUrl = cache.data.url; renderPageData(data); options.isDiffPage = false; $(document).data('zinCache', [cache.key, cache.time].join('#')).trigger('pagecaheload.app'); } catch(error) { if(DEBUG) showLog('Request', 'error:Parse cache data failed', {url, cacheKey, options}); $.db.setCacheData(cacheKey, null, cache.time, 'zinFetch'); } } else { if(DEBUG) showLog('Request', 'success:skip render data with same cache', {url, cacheKey, options}); } } else { if(DEBUG && localCacheFirst) showLog('Request', 'no local cache', {url, cacheKey, options}); } ajax.send(); }); } else { ajax.send(); } return ajax; } function fetchContent(url, selectors, options) { if(typeof url === 'object') { options = url; url = options.url; selectors = options.selector; } if(typeof options === 'string') options = {id: options}; else if(typeof options === 'function') options = {success: options}; selectors = Array.isArray(selectors) ? selectors.join(',') : selectors; const id = selectors.includes('pageJS') ? 'page' : (options.id || selectors); let task = fetchTasks.get(id); if(task && !options.partial && task.url === url) { if(DEBUG) showLog('Request', [`success:same request ${task.rid}`, url], {task, options}); return; } options = $.extend({}, options, {url: url, selectors: selectors, id: id, isDiffPage: isDiffPage(url), pageID: getUrlID(url)}); const dispatchTask = () => { if(task) { if(task.xhr) task.xhr.abort(); if(task.timerID) clearTimeout(task.timerID); if(task.rid) ridSet.delete(task.rid); } const rid = $.guid++; task = {url: url, selectors: selectors, options: options, rid: rid}; options.rid = rid; fetchTasks.set(id, task); ridSet.add(rid); task.timerID = setTimeout(() => { task.timerID = 0; task.xhr = requestContent(options, () => { if(task.xhr) { const runID = task.xhr.getResponseHeader('Xhprof-RunID'); if(runID) updateZinbar({id: id, xhprof: `${config.webRoot}xhprof/xhprof_html/index.php?run=${runID}&source=${config.currentModule}_${config.currentMethod}`}); task.xhr = null; } fetchTasks.delete(id); ridSet.delete(rid); }); }, options.delayTime || 0); }; if(window.beforeRequestContent) { const result = window.beforeRequestContent(options, task); if(result === false) return; if(result instanceof Promise) { result.then((result) => result && dispatchTask()); return; } } dispatchTask(); } /** Load an old page. */ function loadOldPage(url) { let $page = $('#oldPage'); const clearTimer = () => { const timer = $page.data('timer'); if(timer) { clearTimeout(timer); $page.data('timer', 0); } }; if(!$page.length) { $page = $('
') .append($('