feat(extensions): 新增系统工具页面与前台投稿分片上传

This commit is contained in:
2026-10-06 11:09:05 +08:00
parent 613b8a57a3
commit b75bf197a3
25 changed files with 1982 additions and 58 deletions
@@ -6,6 +6,16 @@ export const extensionsSection = {
icon: renderFaIcon('fa fas fa-puzzle-piece'),
route: '/extensions',
children: [
schemaPage({
key: 'extensions-system',
name: 'ExtensionsSystem',
label: '系统工具',
route: '/extensions/system',
title: '系统工具',
desc: 'WordPress 优化、上传优化与编辑器显示的系统级开关。',
canonicalDomain: 'extensions',
section: 'system',
}),
schemaPage({
key: 'extensions-ip',
name: 'ExtensionsIp',
+1 -1
View File
@@ -16,7 +16,7 @@ const SECTION_DEFAULT_CHILDREN = {
permissions: 'permissions-comment',
users: 'users-auth',
payment: 'payment-knowledge',
extensions: 'extensions-ip',
extensions: 'extensions-system',
tools: 'tools-import-export',
'theme-info': 'theme-info-dashboard',
docs: 'docs-dashboard',
@@ -3,12 +3,14 @@
* Each key corresponds to a sub-page under the "扩展&增强" section.
*/
import { systemSchema } from './system.js'
import { ipSchema } from './ip.js'
import { auditSchema } from './audit.js'
import { pushSchema } from './push.js'
import { securitySchema } from './security.js'
export const extensionsSchemas = {
system: systemSchema,
ip: ipSchema,
audit: auditSchema,
push: pushSchema,
@@ -0,0 +1,91 @@
export const systemSchema = {
key: 'extensions.system',
title: '系统工具',
icon: 'fa fas fa-gavel',
description: 'WordPress 优化、上传优化与编辑器显示的系统级开关。',
defaults: {
hide_admin_bar: true,
remove_emoji: true,
remove_open_sans: true,
remove_more_wp_head: true,
remove_dashboard_widgets: true,
display_wp_update: true,
newfilename: false,
newfilename_type: 'time',
split_upload_s: true,
split_minimum_size: 20,
disabled_large_thumbnail: true,
admin_user_del_fields: true,
admin_frontend_set: true,
close_gutenberg: false,
disabled_autoembed: true,
mce_float_toolbar: ['pc_s', 'm_s'],
time_ago_s: true,
time_format: 'n月j日 H:i',
},
groups: [
{
key: 'optimize',
title: 'WordPress 优化',
icon: 'fa fas fa-bolt',
description: '精简WordPress自带的冗余功能,提升前台与后台的加载速度。',
fields: [
{ key: 'hide_admin_bar', label: '前台关闭顶部admin_bar', type: 'switch', default: true, help: '开启后前台不显示WordPress顶部黑条' },
{ key: 'remove_emoji', label: '删除WordPress自带Emoji表情', type: 'switch', default: true },
{ key: 'remove_open_sans', label: '禁用Google字体', type: 'switch', default: true },
{ key: 'remove_more_wp_head', label: '清理多余的头部Meta标签', type: 'switch', default: true },
{ key: 'remove_dashboard_widgets', label: 'WordPress 后台加速', type: 'switch', default: true, help: '移除仪表盘的站点健康和WordPress活动等不必要功能,并禁止检测浏览器和PHP版本(这两个非常耗时),优化后台加载速度' },
{ key: 'display_wp_update', label: '禁止WordPress检测更新', type: 'switch', default: true, help: '开启后将禁用WordPress核心、插件和主题的自动更新及更新检测' },
],
},
{
key: 'upload',
title: '上传优化',
icon: 'fa fas fa-cloud-upload-alt',
description: '上传文件的命名与缩略图裁剪优化,减少硬盘占用并提高上传效率。',
fields: [
{ key: 'split_upload_s', label: '大文件分片上传', type: 'switch', default: true, help: '分片上传大文件,优化大文件上传效率(支持断点续传)' },
{ key: 'split_minimum_size', label: '分片上传最小文件', type: 'number', default: 20, min: 4, max: 100, unit: 'M', dependsOn: { key: 'split_upload_s', value: true }, help: '大于此大小的文件才会分片上传,请根据服务器性能合理设置;较大文件直接上传会比较耗时且无法突破系统上传大小限制,分片上传可以有效解决此问题;前端上传大小限制在「功能权限 → 上传权限」中设置。注意:能否顺利上传大文件还取决于服务器性能、带宽、CDN等因素,如果网站使用了OSS/COS等第三方云储存,则不建议在网站上传大文件' },
{ key: 'newfilename', label: '上传文件自动重命名', type: 'switch', default: false, help: '上传文件时系统会查询重名文件,开启此功能后可减少重名文件,从而提高上传效率' },
{
key: 'newfilename_type',
label: '重命名格式',
type: 'radio',
buttonStyle: false,
default: 'time',
dependsOn: { key: 'newfilename', value: true },
options: [
{ label: '随机字串符', value: 'random' },
{ label: '时间+原文件名', value: 'time' },
],
},
{ key: 'disabled_large_thumbnail', label: '禁止WP大尺寸缩略图裁剪', type: 'switch', default: true, help: '开启后将禁用WordPress自动裁剪medium_large、1536x1536、2048x2048的缩略图,可减少服务器硬盘压力,同时提高上传效率;如果您使用的插件必须要这些尺寸的缩略图,或使用了云储存的图片处理功能,则请关闭' },
],
},
{
key: 'editor',
title: '编辑器与显示',
icon: 'fa fas fa-edit',
description: '编辑器模式、用户资料页与前台时间显示格式的定制。',
fields: [
{ key: 'admin_user_del_fields', label: '关闭后台用户编辑多余的选项', type: 'switch', default: true, help: '开启后在后台编辑用户资料时将不显示无用的多余选项' },
{ key: 'admin_frontend_set', label: '前端页面编辑', type: 'switch', default: true, help: '管理员可在前台快速修改页面、文章参数' },
{ key: 'close_gutenberg', label: '禁用古腾堡编辑器', type: 'switch', default: false, help: '后台文章编辑器将退回使用经典编辑器;禁用后主题的编辑器增强功能将不能使用,请酌情禁用!' },
{ key: 'disabled_autoembed', label: '禁用Auto Embed', type: 'switch', default: true, help: 'Auto Embed是WordPress自动检测并嵌入链接的一种功能,主要适配海外媒体网站,国内几乎用不上,建议禁用,可大幅度提高页面渲染速度' },
{
key: 'mce_float_toolbar',
label: '经典编辑器增强浮动工具栏',
type: 'checkbox',
default: ['pc_s', 'm_s'],
options: [
{ label: 'PC端开启', value: 'pc_s' },
{ label: '移动端开启', value: 'm_s' },
],
help: '开启后前端编辑器会显示浮动的工具栏,更加方便书写',
},
{ key: 'time_ago_s', label: '倒计时显示', type: 'switch', default: true, help: '时间格式化为:X分钟前,X小时前,X天前....' },
{ key: 'time_format', label: '自定义时间格式', type: 'input', default: 'n月j日 H:i', dependsOn: { key: 'time_ago_s', operator: 'falsy' }, help: '关闭倒计时显示后生效,时间格式接受标准PHP时间格式,请注意控制长度!' },
],
},
],
}
@@ -238,8 +238,6 @@ export const permissionsSchemas = {
{ type: 'admin', pattern: 'allow', val: 'svg=image/svg+xml' },
{ type: 'all', pattern: 'allow', val: '' },
],
newfilename: false,
newfilename_type: 'time',
},
groups: [
{
@@ -314,26 +312,6 @@ export const permissionsSchemas = {
},
],
},
{
key: 'behavior',
title: '上传行为',
icon: 'fa fas fa-cogs',
fields: [
{ key: 'newfilename', label: '上传文件自动重命名', type: 'switch', help: '开启后上传文件按规则重命名,减少重名文件查询,提高上传效率。' },
{
key: 'newfilename_type',
label: '重命名格式',
type: 'radio',
buttonStyle: false,
default: 'time',
dependsOn: { key: 'newfilename', value: true },
options: [
{ label: '时间 + 原文件名', value: 'time' },
{ label: '随机字符串', value: 'random' },
],
},
],
},
],
},
+200 -9
View File
@@ -121,27 +121,100 @@
return true;
}
var SPLIT_CHUNK_BYTES = 2 * 1048576;
function splitUploadConfig() {
var split = cfg.splitUpload || {};
var minSize = parseInt(split.minSize, 10);
if (!isFinite(minSize) || minSize < 4) minSize = 4;
return { enabled: !!split.enabled, minSize: minSize };
}
function requestJson(url, options) {
var init = options || {};
init.credentials = 'same-origin';
init.headers = { 'X-WP-Nonce': cfg.restNonce || '' };
return fetch(url, init).then(function (response) {
return response.json().catch(function () { return {}; }).then(function (payload) {
if (!response.ok) {
throw new Error((payload && (payload.message || (payload.data && payload.data.message))) || i18n.error || '文件上传失败');
}
return payload;
});
});
}
// 分片上传:状态(断点续传)→ 逐片上传 → 合并走标准落库管线,响应与整传一致。
function uploadInChunks(file, type) {
var uploadKey = type === 'file' ? 'resource' : type;
var base = String(cfg.restUrl || '').replace(/\/$/, '');
if (!base) return Promise.reject(new Error(i18n.uploadUnavailable || '当前账号不能上传此类文件'));
var postId = currentPostId();
function chunkUrl(action) {
var target = base + '/front-submit/' + action + '/' + uploadKey;
if (postId) target += (target.indexOf('?') === -1 ? '?' : '&') + 'post_id=' + encodeURIComponent(String(postId));
return target;
}
var total = Math.ceil(file.size / SPLIT_CHUNK_BYTES);
return requestJson(chunkUrl('upload-status')).catch(function () { return { uploaded: [] }; }).then(function (status) {
var uploaded = {};
(Array.isArray(status.uploaded) ? status.uploaded : []).forEach(function (index) { uploaded[index] = true; });
var chain = Promise.resolve();
for (var index = 0; index < total; index++) {
(function (i) {
chain = chain.then(function () {
if (uploaded[i]) return;
var body = new FormData();
body.append('file', file.slice(i * SPLIT_CHUNK_BYTES, Math.min(file.size, (i + 1) * SPLIT_CHUNK_BYTES)));
body.append('chunk', String(i));
body.append('chunks', String(total));
body.append('name', file.name);
body.append('size', String(file.size));
return requestJson(chunkUrl('upload-chunk'), { method: 'POST', body: body });
});
})(index);
}
return chain;
}).then(function () {
var body = new FormData();
body.append('chunks', String(total));
body.append('name', file.name);
body.append('size', String(file.size));
return requestJson(chunkUrl('upload-merge'), { method: 'POST', body: body });
}).then(function (payload) {
if (!payload || !payload.success || !payload.url) {
throw new Error((payload && (payload.message || (payload.data && payload.data.message))) || i18n.error || '文件上传失败');
}
return payload;
});
}
function uploadMedia(file, type) {
var uploadKey = type === 'file' ? 'resource' : type;
var upload = (cfg.uploads || {})[uploadKey] || null;
if (!upload || !upload.url) return Promise.reject(new Error(i18n.uploadUnavailable || '当前账号不能上传此类文件'));
if (!acceptsFile(file, type)) return Promise.reject(new Error(i18n.invalidFile || '请选择有效文件'));
if (upload.maxSize && file.size > upload.maxSize) return Promise.reject(new Error(i18n.fileTooLarge || '文件大小超过限制'));
var split = splitUploadConfig();
if (split.enabled && typeof file.slice === 'function' && file.size > split.minSize * 1048576) {
if (Math.ceil(file.size / SPLIT_CHUNK_BYTES) > 1) {
return uploadInChunks(file, type);
}
}
var body = new FormData();
body.append('file', file);
var uploadUrl = String(upload.url);
if (currentPostId()) {
uploadUrl += (uploadUrl.indexOf('?') === -1 ? '?' : '&') + 'post_id=' + encodeURIComponent(String(currentPostId()));
}
return fetch(uploadUrl, { method: 'POST', credentials: 'same-origin', headers: { 'X-WP-Nonce': cfg.restNonce || '' }, body: body })
.then(function (response) {
return response.json().catch(function () { return {}; }).then(function (payload) {
if (!response.ok || !payload || !payload.success || !payload.url) {
throw new Error((payload && (payload.message || (payload.data && payload.data.message))) || i18n.error || '文件上传失败');
}
return payload;
});
});
return requestJson(uploadUrl, { method: 'POST', body: body }).then(function (payload) {
if (!payload || !payload.success || !payload.url) {
throw new Error((payload && (payload.message || (payload.data && payload.data.message))) || i18n.error || '文件上传失败');
}
return payload;
});
}
function imageBatchLimit() {
@@ -578,6 +651,120 @@
});
}
function toggleBlockFormat(editor, format) {
editor.formatter.toggle(format);
editor.nodeChanged();
}
function removeEditorBlock(editor, node) {
if (!node || node === editor.getBody()) return;
editor.dom.remove(node);
editor.nodeChanged();
}
function replaceButtonIconWithSvg(button, pathData) {
var node = button && button.$el && button.$el[0];
var icon = node ? node.querySelector('.mce-i-c-svg') : null;
if (!icon) return;
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('viewBox', '0 0 1024 1024');
svg.setAttribute('style', 'width:21px;height:21px;fill:currentColor;');
var path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
path.setAttribute('d', pathData);
svg.appendChild(path);
icon.parentNode.replaceChild(svg, icon);
}
// 经典编辑器增强浮动工具栏(后台「扩展&增强 → 系统工具」mce_float_toolbar):
// 复刻参考主题 editextend.js 的 wptoolbar 上下文工具条。
function registerFloatToolbar(editor) {
editor.addButton('aksrt_h2', {
title: 'Heading 2',
icon: 'c-svg heading2',
stateSelector: 'h2',
onclick: function () { toggleBlockFormat(editor, 'h2'); },
onPostRender: function () {
replaceButtonIconWithSvg(this, 'M143.616 219.648v228.864h278.016V219.648h89.856V768H421.632v-242.688H143.616V768H53.76V219.648h89.856z m660.48-10.752c52.992 0 96.768 15.36 131.328 46.08 33.792 30.72 50.688 69.888 50.688 119.04 0 47.616-18.432 90.624-53.76 129.792-16.554667 17.706667-43.093333 39.082667-78.933333 64.426667l-22.613334 15.701333-12.117333 8.192c-52.309333 34.389333-85.248 64.810667-99.413333 91.178667l-2.730667 5.589333h270.336V768h-382.464c0-56.064 17.664-104.448 54.528-145.92 8.746667-10.069333 21.76-22.186667 38.912-36.352l15.786667-12.586667c5.589333-4.352 11.52-8.874667 17.834666-13.568l19.84-14.506666 21.888-15.488 11.690667-8.106667c35.328-24.576 59.904-45.312 75.264-61.44 23.808-26.88 36.096-56.064 36.096-86.784 0-29.952-8.448-52.224-23.808-66.816-16.128-14.592-39.936-21.504-71.424-21.504-33.792 0-59.136 11.52-76.032 34.56-15.36 19.541333-24.362667 48.64-27.050667 86.058667l-0.597333 11.477333h-89.856c0.768-61.44 18.432-110.592 53.76-148.224 36.096-39.936 83.712-59.904 142.848-59.904z');
},
});
editor.addButton('aksrt_h3', {
title: 'Heading 3',
icon: 'c-svg heading3',
stateSelector: 'h3',
onclick: function () { toggleBlockFormat(editor, 'h3'); },
onPostRender: function () {
replaceButtonIconWithSvg(this, 'M801.024 208.896c55.296 0 100.608 13.056 134.4 39.936 33.024 26.88 49.92 63.744 49.92 111.36 0 59.904-30.72 99.84-91.392 119.808 32.256 9.984 57.6 24.576 74.496 44.544 18.432 20.736 27.648 47.616 27.648 79.872 0 50.688-17.664 92.16-52.992 124.416-36.864 33.024-85.248 49.92-145.152 49.92-56.832 0-102.912-14.592-137.472-43.776-38.4-32.256-59.904-79.872-64.512-141.312h91.392c1.536 35.328 12.288 62.976 33.792 82.176 19.2 17.664 44.544 26.88 76.032 26.88 34.56 0 62.208-9.984 82.176-29.184 17.664-17.664 26.88-39.168 26.88-65.28 0-31.488-9.984-54.528-28.416-69.12-18.432-15.36-45.312-22.272-80.64-22.272h-38.4V449.28h38.4c32.256 0 56.832-6.912 73.728-20.736 16.128-13.824 24.576-34.56 24.576-61.44 0-26.88-7.68-46.848-22.272-60.672-16.128-13.824-39.936-20.736-71.424-20.736-32.256 0-56.832 7.68-74.496 23.808-18.432 16.128-29.184 40.704-32.256 73.728h-88.32c4.608-55.296 24.576-98.304 61.44-129.024 34.56-30.72 79.104-45.312 132.864-45.312z m-657.408 10.752v228.864h278.016V219.648h89.856V768H421.632v-242.688H143.616V768H53.76V219.648h89.856z');
},
});
editor.addButton('aksrt_remove', {
tooltip: i18n.removeBlock || '删除',
icon: 'dashicon dashicons-no-alt',
onclick: function () {
removeEditorBlock(editor, editor.selection.getNode());
},
});
editor.on('wptoolbar', function (event) {
if (event.toolbar) return;
var probe = document.createElement('div');
probe.innerHTML = editor.getContent();
if (probe.querySelectorAll('p,h1,h2,h3,h4,h5,h6,ul,ol,div,blockquote,pre,img,iframe,table,hr').length <= 4) return;
var bars = floatToolbarButtons(editor, event);
if (bars.length && editor.wp && typeof editor.wp._createToolbar === 'function') {
event.toolbar = editor.wp._createToolbar(bars);
}
});
}
// 复刻参考主题 get_all_toolbar:按光标所在块与是否选中文字给出上下文按钮。
function floatToolbarButtons(editor, event) {
var element = event.element;
if (!element) return [];
var nodeName = element.nodeName;
var innerText = element.innerText ? String(element.innerText).trim() : '';
var hasSelection = !!editor.selection.getContent();
var parent = element.parentElement;
var inRoot = !!(parent && parent.id === 'tinymce');
var bars = [];
if (['P', 'H1', 'H2', 'H3', 'H4', 'H5', 'UL', 'LI', 'STRONG', 'SPAN', 'CODE'].indexOf(nodeName) !== -1) {
if (inRoot || (nodeName === 'SPAN' && parent && parent.parentElement && parent.parentElement.id === 'tinymce')) {
bars.push('aksrt_h2', 'aksrt_h3');
}
if (hasSelection) {
bars = ['H1', 'H2', 'H3', 'H4', 'H5'].indexOf(nodeName) === -1 ? ['bold', 'link', 'wp_code'] : [];
bars.push('forecolor');
if (nodeName === 'P' && editorCfg.canHide) bars.push('aksrt_hide');
bars.push('aksrt_remove');
return bars;
}
if (innerText) {
bars.push('bullist', 'numlist', 'aligncenter');
if (nodeName === 'P' && inRoot) {
bars.push('aksrt_quote');
if (editorCfg.canHide) bars.push('aksrt_hide');
}
bars.push('aksrt_remove');
} else {
bars.push('bullist', 'aksrt_quote');
if (editorCfg.canHide) bars.push('aksrt_hide');
if (editorCfg.canUploadImage) bars.push('aksrt_image');
bars.push('aksrt_video', 'aksrt_precode');
}
}
if (!bars.length && (nodeName === 'DIV' || nodeName === 'PRE') && !(element.classList && element.classList.contains('file-download-name'))) {
bars.push('aksrt_remove');
}
return bars;
}
function registerEditorTools() {
if (!window.tinymce || !window.tinymce.PluginManager || window.aksrtSubmitEditorToolsRegistered) {
return false;
@@ -711,6 +898,10 @@
menu: menu,
});
}
if (editorCfg.floatToolbar) {
registerFloatToolbar(editor);
}
});
return true;
}
+63
View File
@@ -0,0 +1,63 @@
/**
* AKSRT Page Admin Set — 前端页面编辑弹窗提交(后台「扩展&增强 → 系统工具」)。
*
* 弹窗内容由 PageAdminSet::ajax_modal() 经 aksrt-modal-runtime 注入,
* 这里只负责表单提交:序列化 → admin-ajax → 成功后刷新页面。
*
* @package AKSRT
*/
(function (window, document) {
'use strict';
function notify(message, type) {
if (typeof window.notyf === 'function') {
window.notyf(message, type || 'error', 4, 'aksrt-page-admin-set-toast');
}
}
document.addEventListener('submit', function (event) {
var form = event.target.closest('form.aksrt-admin-set-form');
if (!form) return;
event.preventDefault();
var submitButton = form.querySelector('.aksrt-admin-set-submit');
if (submitButton && submitButton.disabled) return;
if (submitButton) submitButton.disabled = true;
var action = (form.querySelector('input[name="action"]') || {}).value || '';
var ajaxUrl = (window.aksrtData && window.aksrtData.ajaxUrl) || (window._win && window._win.ajax_url) || '';
if (!ajaxUrl) {
notify('请求地址缺失', 'error');
if (submitButton) submitButton.disabled = false;
return;
}
var url = ajaxUrl + (ajaxUrl.indexOf('?') === -1 ? '?' : '&') + 'action=' + encodeURIComponent(action);
fetch(url, {
method: 'POST',
credentials: 'same-origin',
body: new FormData(form),
})
.then(function (response) {
return response.json().catch(function () { return {}; }).then(function (payload) {
if (!response.ok || !payload || !payload.success) {
throw new Error((payload && payload.data && payload.data.msg) || '保存失败,请重试');
}
return payload;
});
})
.then(function (payload) {
var data = payload.data || {};
notify(data.msg || '保存成功', 'success');
if (data.reload !== false) {
window.setTimeout(function () { window.location.reload(); }, 600);
} else if (submitButton) {
submitButton.disabled = false;
}
})
.catch(function (error) {
notify(error.message || '保存失败,请重试', 'error');
if (submitButton) submitButton.disabled = false;
});
});
})(window, document);