Anyon 27ad3ff7ce feat(static): 同步 v8 前端静态资源
同步 v8 运行所需的公开静态资源与主题样式,保留目标仓库中需要跟踪的发布产物。

主要内容:

- 更新登录页、控制台主题、布局变量和短工具类样式。

- 同步 layui、ckeditor、editor、jquery area 等前端依赖文件。

- 移除 v6 admin/require 静态入口,改为 v8 static/system 资源组织。

- 保留测试依赖的 public/static/theme 编译结果,确保目标仓库可直接验证。
2026-05-08 15:30:57 +08:00

354 lines
14 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// +----------------------------------------------------------------------
// | Static Plugin for ThinkAdmin
// +----------------------------------------------------------------------
// | 版权所有 2014~2024 ThinkAdmin [ thinkadmin.top ]
// +----------------------------------------------------------------------
// | 官方网站: https://thinkadmin.top
// +----------------------------------------------------------------------
// | 开源协议 ( https://mit-license.org )
// | 免责声明 ( https://thinkadmin.top/disclaimer )
// +----------------------------------------------------------------------
// | gitee 代码仓库https://gitee.com/zoujingli/think-plugs-static
// | github 代码仓库https://github.com/zoujingli/think-plugs-static
// +----------------------------------------------------------------------
$(function () {
window.$body = $('body');
let loginI18n = window.taLoginI18n || {};
function t(key, fallback) {
return typeof loginI18n[key] === 'string' && loginI18n[key].length > 0 ? loginI18n[key] : fallback;
}
/*! 登录界面背景切换 */
$('[data-bg-transition]').each(function (i, el) {
el.idx = 0, el.imgs = [], el.SetBackImage = function (css) {
window.setTimeout(function () {
$(el).removeClass(el.imgs.join(' ')).addClass(css)
}, 1000) && $body.removeClass(el.imgs.join(' ')).addClass(css)
}, el.lazy = window.setInterval(function () {
el.imgs.length > 0 && el.SetBackImage(el.imgs[++el.idx] || el.imgs[el.idx = 0]);
}, 5000) && el.dataset.bgTransition.split(',').forEach(function (image) {
layui.img(image, function (img, cssid, style) {
style = document.createElement('style'), cssid = 'LoginBackImage' + (el.imgs.length + 1);
style.innerHTML = '.' + cssid + '{background-image:url("' + encodeURI(image) + '")!important}';
document.head.appendChild(style) && el.imgs.push(cssid);
});
});
});
let ambientFrame = 0, ambientPoint = {
x: Math.round(window.innerWidth * 0.78),
y: Math.round(window.innerHeight * 0.22)
};
function paintAmbient() {
ambientFrame = 0;
$('.login-container').css({
'--cursor-x': ambientPoint.x + 'px',
'--cursor-y': ambientPoint.y + 'px'
});
}
function queueAmbient(x, y) {
ambientPoint.x = x;
ambientPoint.y = y;
if (ambientFrame) return;
ambientFrame = (window.requestAnimationFrame || window.setTimeout)(paintAmbient, 16);
}
queueAmbient(ambientPoint.x, ambientPoint.y);
$(document).on('mousemove touchmove', function (event) {
let point = event.touches && event.touches[0] ? event.touches[0] : event;
queueAmbient(point.clientX, point.clientY);
});
$(window).on('resize', function () {
queueAmbient(Math.round(window.innerWidth * 0.78), Math.round(window.innerHeight * 0.22));
});
function decodeBase64ToArrayBuffer(value) {
let binary = atob(value), bytes = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
return bytes.buffer;
}
function encodeArrayBufferToBase64(buffer) {
let bytes = new Uint8Array(buffer), binary = '';
for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
return btoa(binary);
}
async function encryptPassword(form, password) {
let publicKey = form.dataset.loginPasswordKey || '';
if (!publicKey || !window.crypto || !window.crypto.subtle || typeof TextEncoder === 'undefined' || window.isSecureContext === false) {
return {mode: 'plain', value: password};
}
try {
// PHP openssl_private_decrypt with OAEP padding only interoperates with the SHA-1 variant here.
let key = await window.crypto.subtle.importKey(
'spki',
decodeBase64ToArrayBuffer(publicKey),
{name: 'RSA-OAEP', hash: 'SHA-1'},
false,
['encrypt']
);
let result = await window.crypto.subtle.encrypt({name: 'RSA-OAEP'}, key, new TextEncoder().encode(password));
return {mode: 'rsa', value: encodeArrayBufferToBase64(result)};
} catch (e) {
return {mode: 'plain', value: password};
}
}
function reloadLoginPage() {
try {
let url = new URL(location.href);
url.hash = '';
url.searchParams.set('_login_reload', String(Date.now()));
location.replace(url.toString());
} catch (e) {
location.href = location.pathname + '?_login_reload=' + Date.now();
}
}
function createLoginSlider(form) {
let $form = $(form), $container = $form.closest('.login-container');
let $popup = $container.find('[data-login-slider-popup]');
let $panel = $popup.find('[data-login-slider-panel]');
if ($panel.length < 1) {
return {
syncError: $.noop,
ensureVerified: function () {
return true;
}
};
}
let $bg = $panel.find('[data-slider-bg]');
let $piece = $panel.find('[data-slider-piece]');
let $stage = $panel.find('.slider-stage');
let $track = $panel.find('[data-slider-track]');
let $message = $panel.find('[data-slider-message]');
let $status = $panel.find('[data-slider-status]');
let $handle = $panel.find('[data-slider-handle]');
let $refresh = $panel.find('[data-slider-refresh]');
let $uniqid = $form.find('[name="uniqid"]');
let $verify = $form.find('[name="verify"]');
let $mode = $form.find('[name="password_mode"]');
let request = form.dataset.loginSlider || '';
let check = form.dataset.loginCheck || '';
let state = {
bgWidth: 0,
currentLeft: 0,
dragging: false,
loaded: false,
maxLeft: 0,
originX: 0,
pieceWidth: 100,
required: false,
sourceWidth: 600,
startLeft: 0,
verified: false,
working: false,
};
function setStatus(text, type) {
$panel.removeClass('is-error is-success');
type && $panel.addClass(type);
$message.text(text);
$status.text(text);
}
function setPosition(left) {
state.currentLeft = Math.max(0, Math.min(left, state.maxLeft));
$handle.css('left', state.currentLeft + 'px');
$track.css('width', (state.currentLeft + $handle.outerWidth()) + 'px');
$piece.css('left', state.currentLeft + 'px');
}
function recalculate() {
state.bgWidth = $stage.innerWidth();
state.maxLeft = Math.max(state.bgWidth - $handle.outerWidth(), 0);
$piece.css('width', (state.pieceWidth / state.sourceWidth * 100) + '%');
setPosition(state.currentLeft);
}
function resetChallenge() {
state.verified = false;
state.working = false;
$uniqid.val('');
$verify.val('');
$panel.removeClass('is-error is-success');
setPosition(0);
setStatus(t('dragToVerify', '请按住滑块,拖动完成验证'));
}
function hideChallenge() {
$popup.removeClass('is-visible');
$body.removeClass('login-verify-active');
window.setTimeout(function () {
if (!$popup.hasClass('is-visible')) {
$popup.addClass('layui-hide');
}
}, 220);
}
function loadChallenge() {
if (request.length < 5) return $.msg.tips(t('sliderApiMissing', '请设置滑块验证接口'));
resetChallenge();
let handleChallenge = function (ret) {
if (parseInt(ret.code, 10) !== 200) {
ret.data && ret.data.reload && reloadLoginPage();
return false;
}
state.sourceWidth = parseInt(ret.data.width || 600);
state.pieceWidth = parseInt(ret.data.piece_width || 100);
state.loaded = true;
$uniqid.val(ret.data.uniqid || '');
$bg.attr('src', ret.data.bgimg || '');
$piece.attr('src', ret.data.water || '');
(window.requestAnimationFrame || window.setTimeout)(recalculate, 0);
setStatus(t('dragToVerify', '请按住滑块,拖动完成验证'));
return false;
};
handleChallenge.allowHttpError = true;
$.form.load(request, {token: form.dataset.loginToken || ''}, 'post', handleChallenge, false);
}
function showChallenge(refresh) {
state.required = true;
$popup.removeClass('layui-hide');
$body.addClass('login-verify-active');
(window.requestAnimationFrame || window.setTimeout)(function () {
$popup.addClass('is-visible');
recalculate();
}, 0);
if (refresh || !$uniqid.val()) loadChallenge();
}
function verifyCurrentPosition() {
if (state.working || !$uniqid.val() || check.length < 5) return;
state.working = true;
setStatus(t('verifying', '正在校验...'));
$.form.load(check, {
uniqid: $uniqid.val(),
verify: Math.round(state.currentLeft * state.sourceWidth / Math.max(state.bgWidth, 1))
}, 'post', function (ret) {
state.working = false;
let value = Math.round(state.currentLeft * state.sourceWidth / Math.max(state.bgWidth, 1));
let result = parseInt(ret.data && ret.data.state || -1);
if (result === 1) {
state.verified = true;
state.required = false;
$verify.val(String(value));
$panel.removeClass('is-error').addClass('is-success');
$message.text(t('verifyPassedContinue', '验证通过,请继续登录'));
$status.text(t('sliderVerified', '滑块验证通过'));
window.setTimeout(hideChallenge, 260);
} else if (result === 0) {
state.verified = false;
state.required = true;
$verify.val('');
$panel.removeClass('is-success').addClass('is-error');
$message.text(t('wrongPositionRetry', '位置不正确,请重试'));
$status.text(t('wrongPositionRetry', '位置不正确,请重试'));
window.setTimeout(function () {
if (!state.verified) {
$panel.removeClass('is-error');
setPosition(0);
setStatus(t('dragToVerify', '请按住滑块,拖动完成验证'));
}
}, 500);
} else {
loadChallenge();
}
return false;
}, false);
}
function getPoint(event) {
return event.touches && event.touches[0] ? event.touches[0] : event;
}
function startDrag(event) {
if (state.working || state.verified || !$uniqid.val()) return;
let point = getPoint(event);
state.dragging = true;
state.originX = point.clientX;
state.startLeft = state.currentLeft;
$handle.addClass('is-active');
event.preventDefault();
}
function moveDrag(event) {
if (!state.dragging) return;
let point = getPoint(event);
setPosition(state.startLeft + point.clientX - state.originX);
event.preventDefault();
}
function endDrag() {
if (!state.dragging) return;
state.dragging = false;
$handle.removeClass('is-active');
verifyCurrentPosition();
}
$bg.on('load', recalculate);
$(window).on('resize', recalculate);
$handle.on('mousedown touchstart', startDrag);
$(document).on('mousemove touchmove', moveDrag);
$(document).on('mouseup touchend touchcancel', endDrag);
$refresh.on('click', function () {
showChallenge(true);
});
return {
syncError: function (data) {
if (data && data.need_verify) {
state.verified = false;
state.required = true;
showChallenge(!!data.refresh_verify);
}
},
ensureVerified: function () {
if (state.required && !state.verified) {
showChallenge(false);
$.msg.tips(t('needVerifyFirst', '请先完成滑块验证'));
return false;
}
$mode.val('plain');
return true;
},
setPasswordMode: function (mode) {
$mode.val(mode);
}
};
}
/*! 后台登录提交处理 */
$body.find('form[data-login-form]').each(function (idx, form) {
let slider = createLoginSlider(form);
$(form).vali(function (data) {
if (!slider.ensureVerified()) return false;
encryptPassword(form, data.password || '').then(function (cipher) {
let payload = $.extend({}, data, {password: cipher.value, password_mode: cipher.mode});
slider.setPasswordMode(cipher.mode);
let handleSubmit = function (ret) {
if (parseInt(ret.code, 10) !== 200) {
if (ret.data && ret.data.reload) {
reloadLoginPage();
return false;
}
slider.syncError(ret.data || {});
return false;
}
};
handleSubmit.allowHttpError = true;
$.form.load(location.href, payload, "post", handleSubmit, null, null, 'false');
});
});
});
});