MediaWiki:WikiAI.js
MediaWiki界面页面
更多操作
注意:在发布之后,您可能需要清除浏览器缓存才能看到所作出的更改的影响。
- Firefox或Safari:按住Shift的同时单击刷新,或按Ctrl-F5或Ctrl-R(Mac为⌘-R)
- Google Chrome:按Ctrl-Shift-R(Mac为⌘-Shift-R)
- Edge:按住Ctrl的同时单击刷新,或按Ctrl-F5。
(function() {
'use strict';
// 配置
const CONFIG = {
iframeSrc: window.ECWikiAIConfig?.src || 'https://service.wiki.easecation.net/ai',
fabPosition: window.ECWikiAIConfig?.position || { bottom: '64px', right: '64px' },
mobileBreakpoint: 640,
debounceDelay: 150
};
// 防止重复初始化
if (document.getElementById('ec-ai-fab')) {
console.log('[EC Wiki AI] 已初始化,跳过');
return;
}
// AI 图标 SVG
const AI_ICON_SVG = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" style="width:20px;height:20px;vertical-align:middle;"><path d="M15.9991 2.99995C19.3131 2.99995 22 5.69516 22 8.9941V21H8.00099C4.68693 21 2.00001 18.3048 2.00001 15.0058V11H4.00001V15.0058C4.00001 17.2042 5.79547 19 8.00099 19H20V8.9941C20 6.79576 18.2046 4.99995 15.9991 4.99995H10V2.99995H15.9991ZM10 13H8.00002V11H10V13ZM16 13H14V11H16V13ZM3.52931 1.31928C3.70584 0.89349 4.29418 0.893492 4.47071 1.31928L4.72364 1.93061C5.15555 2.9734 5.96155 3.80612 6.97462 4.25679L7.6924 4.57612C8.10268 4.75894 8.10263 5.35615 7.6924 5.53902L6.93263 5.87691C5.94498 6.31619 5.15339 7.11941 4.71388 8.12789L4.46681 8.69332C4.28636 9.10745 3.71366 9.10745 3.53321 8.69332L3.28614 8.12789C2.84661 7.11942 2.05506 6.31619 1.06739 5.87691L0.307623 5.53902C-0.102517 5.35615 -0.102565 4.75894 0.307623 4.57612L1.0254 4.25679C2.03845 3.80613 2.84446 2.97343 3.27638 1.93061L3.52931 1.31928Z"></path></svg>`;
// 判断是否为移动端
const isMobile = () => window.innerWidth <= CONFIG.mobileBreakpoint;
// 防抖函数
const debounce = (fn, delay) => {
let timer = null;
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
};
// 存储事件处理器引用,用于清理
const eventHandlers = {};
// 创建样式
const style = document.createElement('style');
style.textContent = `
#ec-ai-fab {
position: fixed;
bottom: ${CONFIG.fabPosition.bottom};
right: ${CONFIG.fabPosition.right};
width: 48px;
height: 48px;
padding: 0;
background: #206588;
color: #fff;
border: none;
border-radius: 14px;
cursor: pointer;
font-size: 14px;
font-weight: 600;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
z-index: 10000;
display: flex;
align-items: center;
justify-content: center;
transition: opacity 0.4s ease, transform 0.2s ease, box-shadow 0.2s ease, background 0.2s ease;
user-select: none;
opacity: 0;
}
#ec-ai-fab.visible {
opacity: 1;
}
#ec-ai-fab:hover {
background: #0090e0;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
}
#ec-ai-fab:active {
transform: translateY(0);
}
/* 移动端悬浮按钮适配 */
@media (max-width: ${CONFIG.mobileBreakpoint}px) {
#ec-ai-fab {
bottom: 80px;
right: 20px;
width: 40px;
height: 40px;
padding: 0;
}
}
/* 遮罩层 */
#ec-ai-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.4);
z-index: 10001;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
}
#ec-ai-overlay.active {
opacity: 1;
visibility: visible;
}
/* iframe 容器 - 桌面端右侧滑出 */
#ec-ai-sidebar {
position: fixed;
top: 0;
right: 0;
width: 500px;
height: 100vh;
height: 100dvh;
background: #fff;
border-left: 1px solid #e1e4e8;
box-shadow: -2px 0 8px rgba(0,0,0,0.08);
z-index: 10002;
display: flex;
flex-direction: column;
transform: translateX(100%);
opacity: 0;
transition: transform 0.3s ease, opacity 0.3s ease;
}
#ec-ai-sidebar.mobile {
width: 100%;
height: 100vh;
height: 100dvh;
border-left: none;
/* 适配底部安全区域 */
padding-bottom: env(safe-area-inset-bottom);
}
#ec-ai-sidebar.active {
transform: translateX(0);
opacity: 1;
}
/* 始终预留滚动条空间,防止隐藏时出现布局抖动 */
html {
scrollbar-gutter: stable;
}
/* 头部栏 */
#ec-ai-header {
background: #fff;
border-bottom: 1px solid #e1e4e8;
padding: 12px 16px;
display: flex;
justify-content: space-between;
align-items: center;
flex-shrink: 0;
height: 56px;
}
#ec-ai-title {
font-size: 16px;
font-weight: 600;
color: #24292e;
display: flex;
align-items: center;
gap: 8px;
}
/* 关闭按钮 - 始终显示在右上角 */
#ec-ai-close {
width: 32px;
height: 32px;
border: none;
background: transparent;
color: #666;
cursor: pointer;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
font-weight: 300;
transition: all 0.2s;
}
#ec-ai-close:hover {
background: #f5f7fa;
color: #24292e;
}
/* 关闭按钮文字(X) */
#ec-ai-close::before {
content: "×";
line-height: 1;
}
/* iframe 容器 */
#ec-ai-iframe-container {
flex: 1;
position: relative;
background: #f5f7fa;
/* 移动端适配底部安全区域 */
padding-bottom: env(safe-area-inset-bottom);
}
#ec-ai-iframe {
width: 100%;
height: 100%;
border: none;
display: block;
}
/* 加载指示器 */
#ec-ai-loader {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
gap: 4px;
}
#ec-ai-loader span {
width: 8px;
height: 8px;
background: #206588;
border-radius: 50%;
animation: ec-ai-pulse 1.4s infinite;
}
#ec-ai-loader span:nth-child(2) { animation-delay: 0.2s; }
#ec-ai-loader span:nth-child(3) { animation-delay: 0.4s; }
@keyframes ec-ai-pulse {
0%, 100% { opacity: 0.4; transform: scale(1); }
50% { opacity: 1; transform: scale(1.2); }
}
/* 加载错误提示 */
#ec-ai-loader .ec-ai-error {
text-align: center;
padding: 20px;
color: #666;
font-size: 14px;
}
/* 滚动条样式 */
#ec-ai-sidebar ::-webkit-scrollbar {
width: 6px;
height: 6px;
}
#ec-ai-sidebar ::-webkit-scrollbar-track {
background: transparent;
}
#ec-ai-sidebar ::-webkit-scrollbar-thumb {
background: #64acc4;
border-radius: 3px;
}
#ec-ai-sidebar ::-webkit-scrollbar-thumb:hover {
background: #4a8fa6;
}
#ec-ai-sidebar {
scrollbar-width: thin;
scrollbar-color: #64acc4 transparent;
}
/* 深色模式 - 合并所有深色样式 */
@media (prefers-color-scheme: dark) {
/* 滚动条 */
#ec-ai-sidebar ::-webkit-scrollbar-thumb {
background: #28494f;
}
#ec-ai-sidebar ::-webkit-scrollbar-thumb:hover {
background: #3a6b73;
}
#ec-ai-sidebar {
scrollbar-color: #28494f transparent;
}
/* 侧边栏 */
#ec-ai-sidebar {
background: #1a1a1a;
border-left-color: #444;
}
#ec-ai-header {
background: #2a2a2a;
border-bottom-color: #444;
}
#ec-ai-title {
color: #e0e0e0;
}
#ec-ai-close {
color: #aaa;
}
#ec-ai-close:hover {
background: #333;
color: #e0e0e0;
}
#ec-ai-iframe-container {
background: #1a1a1a;
}
#ec-ai-loader span {
background: #64b5f6;
}
#ec-ai-loader .ec-ai-error {
color: #aaa;
}
}
`;
document.head.appendChild(style);
// 创建悬浮按钮
const fab = document.createElement('button');
fab.id = 'ec-ai-fab';
fab.innerHTML = AI_ICON_SVG;
fab.title = '打开 AI 助手';
fab.setAttribute('aria-label', '打开 AI 助手');
fab.addEventListener('click', openSidebar);
document.body.appendChild(fab);
// 触发淡入动画
requestAnimationFrame(() => {
fab.classList.add('visible');
});
// 创建遮罩层
const overlay = document.createElement('div');
overlay.id = 'ec-ai-overlay';
overlay.addEventListener('click', closeSidebar);
document.body.appendChild(overlay);
// 创建侧边栏容器
const sidebar = document.createElement('div');
sidebar.id = 'ec-ai-sidebar';
// 根据当前设备类型设置初始类名
if (isMobile()) {
sidebar.classList.add('mobile');
}
// 创建头部
const header = document.createElement('div');
header.id = 'ec-ai-header';
const titleDiv = document.createElement('div');
titleDiv.id = 'ec-ai-title';
titleDiv.innerHTML = 'EaseCation Wiki AI';
const closeBtn = document.createElement('button');
closeBtn.id = 'ec-ai-close';
closeBtn.title = '关闭';
closeBtn.setAttribute('aria-label', '关闭');
closeBtn.addEventListener('click', closeSidebar);
header.appendChild(titleDiv);
header.appendChild(closeBtn);
sidebar.appendChild(header);
// 创建 iframe 容器
const iframeContainer = document.createElement('div');
iframeContainer.id = 'ec-ai-iframe-container';
// 创建加载动画
const loader = document.createElement('div');
loader.id = 'ec-ai-loader';
loader.innerHTML = '<span></span><span></span><span></span>';
iframeContainer.appendChild(loader);
// 创建 iframe(懒加载)
let iframe = null;
sidebar.appendChild(iframeContainer);
document.body.appendChild(sidebar);
// 监听窗口大小变化(带防抖)
eventHandlers.resize = debounce(function() {
if (isMobile()) {
sidebar.classList.add('mobile');
} else {
sidebar.classList.remove('mobile');
}
}, CONFIG.debounceDelay);
window.addEventListener('resize', eventHandlers.resize);
// 键盘快捷键支持
eventHandlers.keydown = function(e) {
// ESC 关闭
if (e.key === 'Escape' && sidebar.classList.contains('active')) {
closeSidebar();
}
// 开启快捷键: Ctrl/Cmd + Shift + A
if ((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'A') {
e.preventDefault();
openSidebar();
}
};
document.addEventListener('keydown', eventHandlers.keydown);
// 监听系统主题变化并通知 iframe
eventHandlers.themeChange = function(e) {
if (iframe && iframe.contentWindow) {
try {
iframe.contentWindow.postMessage({
type: 'theme-change',
prefersDark: e.matches
}, '*');
} catch (err) {
// 忽略跨域错误
}
}
};
const darkModeQuery = window.matchMedia('(prefers-color-scheme: dark)');
if (darkModeQuery.addEventListener) {
darkModeQuery.addEventListener('change', eventHandlers.themeChange);
} else if (darkModeQuery.addListener) {
// 兼容旧浏览器
darkModeQuery.addListener(eventHandlers.themeChange);
}
// 打开侧边栏
function openSidebar() {
sidebar.classList.toggle('mobile', isMobile());
document.body.style.overflow = 'hidden';
sidebar.classList.add('active');
overlay.classList.add('active');
// 首次加载 iframe
if (!iframe) {
iframe = document.createElement('iframe');
iframe.id = 'ec-ai-iframe';
iframe.src = CONFIG.iframeSrc;
iframe.title = 'EaseCation Wiki AI 助手';
iframe.setAttribute('allow', 'fullscreen');
iframe.setAttribute('sandbox', 'allow-same-origin allow-scripts allow-popups allow-forms');
// 加载完成后隐藏 loader
eventHandlers.iframeLoad = function() {
loader.style.display = 'none';
// 发送当前主题
if (iframe.contentWindow) {
try {
iframe.contentWindow.postMessage({
type: 'theme-change',
prefersDark: darkModeQuery.matches
}, '*');
} catch (err) {
// 忽略跨域错误
}
}
};
iframe.addEventListener('load', eventHandlers.iframeLoad);
// 加载失败处理
eventHandlers.iframeError = function() {
loader.innerHTML = '<div class="ec-ai-error">加载失败,请刷新重试</div>';
};
iframe.addEventListener('error', eventHandlers.iframeError);
iframeContainer.appendChild(iframe);
}
}
// 关闭侧边栏
function closeSidebar() {
sidebar.classList.remove('active');
overlay.classList.remove('active');
document.body.style.overflow = '';
}
// 清理方法
function destroy() {
// 移除事件监听
window.removeEventListener('resize', eventHandlers.resize);
document.removeEventListener('keydown', eventHandlers.keydown);
if (darkModeQuery.removeEventListener) {
darkModeQuery.removeEventListener('change', eventHandlers.themeChange);
} else if (darkModeQuery.removeListener) {
darkModeQuery.removeListener(eventHandlers.themeChange);
}
if (iframe) {
iframe.removeEventListener('load', eventHandlers.iframeLoad);
iframe.removeEventListener('error', eventHandlers.iframeError);
}
// 移除 DOM 元素
style.remove();
fab.remove();
overlay.remove();
sidebar.remove();
// 清理 body overflow
document.body.style.overflow = '';
// 清理全局接口
delete window.ECWikiAI;
console.log('[EC Wiki AI] 已销毁');
}
// 全局接口
window.ECWikiAI = {
open: openSidebar,
close: closeSidebar,
toggle: function() {
if (sidebar.classList.contains('active')) {
closeSidebar();
} else {
openSidebar();
}
},
isOpen: function() {
return sidebar.classList.contains('active');
},
destroy: destroy
};
console.log('[EC Wiki AI] 加载成功');
})();