push
首次推送
This commit is contained in:
@@ -0,0 +1 @@
|
||||
模板开发中....
|
||||
@@ -0,0 +1 @@
|
||||
模板开发中....
|
||||
@@ -0,0 +1,198 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>TVbox规则编辑器 - <?php echo isset($_GET['file']) ? htmlspecialchars($_GET['file']) : ''; ?></title>
|
||||
<link rel="stylesheet" href="/assets/css/ui.css?t=<?php echo time();?>">
|
||||
<link rel="stylesheet" href="/assets/css/main.css?t=<?php echo time();?>">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="container">
|
||||
<div class="header-container">
|
||||
<div class="file-info">
|
||||
<div class="file-path">
|
||||
<span class="file-icon">📄</span>
|
||||
<span class="file-name"><?php echo isset($_GET['file']) ? htmlspecialchars(basename($_GET['file'])) : '未选择文件'; ?></span>
|
||||
</div>
|
||||
<?php if(isset($_GET['file'])): ?>
|
||||
<div class="full-path"><?php echo htmlspecialchars($_GET['file']); ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="header-actions">
|
||||
<div class="btn-group">
|
||||
<!-- <button id="advancedModeBtn" class="btn warning-btn">普通模式</button> -->
|
||||
<button id="saveBtn" class="btn primary-btn">保存修改</button>
|
||||
<!-- <button id="helpBtn" class="btn secondary-btn">语法帮助</button> -->
|
||||
<button id="editBtn" class="btn secondary-btn">在线编辑</button>
|
||||
<button id="variableBtn" class="btn secondary-btn">变量设置</button>
|
||||
<button id="autoTestBtn" class="btn primary-btn">一键测试</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<form id="ruleForm">
|
||||
<div class="tabs">
|
||||
<div class="tab-btn active" onclick="openTab(event, 'basic')">基础信息</div>
|
||||
<div class="tab-btn" onclick="openTab(event, 'home')">首页规则</div>
|
||||
<div class="tab-btn" onclick="openTab(event, 'category')">分类规则</div>
|
||||
<div class="tab-btn" onclick="openTab(event, 'detail')">详情规则</div>
|
||||
<div class="tab-btn" onclick="openTab(event, 'play')">播放规则</div>
|
||||
<div class="tab-btn" onclick="openTab(event, 'search')">搜索规则</div>
|
||||
</div>
|
||||
|
||||
<div id="basic" class="tab-content active">基础内容</div>
|
||||
<div id="home" class="tab-content">首页内容</div>
|
||||
|
||||
<div id="category" class="tab-content">
|
||||
<div class="tabs">
|
||||
<div class="tab-btn active" onclick="openTab(event, 'category-rules-basic')">分类规则</div>
|
||||
<div class="tab-btn" onclick="openTab(event, 'category-filter-menu')">筛选菜单</div>
|
||||
</div>
|
||||
<div id="category-rules-basic" class="tab-content active">分类规则内容</div>
|
||||
<div id="category-filter-menu" class="tab-content">筛选菜单内容</div>
|
||||
</div>
|
||||
|
||||
<div id="detail" class="tab-content">详情内容</div>
|
||||
<div id="play" class="tab-content">播放内容</div>
|
||||
<div id="search" class="tab-content">搜索内容</div>
|
||||
</form>
|
||||
|
||||
|
||||
<div class="toast-container"></div>
|
||||
|
||||
<script id="test-modal-template" type="text/x-handlebars-template">
|
||||
<div class="form-group">
|
||||
<label for="testUrl">测试URL</label>
|
||||
<div class="input-with-buttons">
|
||||
<input type="text" id="testUrl" placeholder="输入要测试的网页URL">
|
||||
<button type="button" id="toggleSourceBtn" class="btn secondary-btn">源码</button>
|
||||
</div>
|
||||
</div>
|
||||
<textarea id="sourceHtmlInput" placeholder="当这里不为空时,将优先使用此处源码进行测试" style="display:none; width:100%; min-height:80px; margin-top: -10px; margin-bottom: 10px;"></textarea>
|
||||
<div class="form-group">
|
||||
<label for="testSelectorInput">CSS选择器</label>
|
||||
<div class="input-with-buttons">
|
||||
<input type="text" id="testSelectorInput">
|
||||
<button id="applySelectorBtn" class="btn primary-btn">应用</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="test-result-container" style="display:none;">
|
||||
<div class="controls-container" style="justify-content:space-between; margin-bottom:10px;">
|
||||
<h4 style="margin:0; font-size: 1em;">测试结果:</h4>
|
||||
<button id="toggleResultModeBtn" class="btn secondary-btn">切换模式</button>
|
||||
</div>
|
||||
<div id="testResultContent" style="background:#f8f9fa; border:1px solid #e9ecef; padding:10px; border-radius:6px; min-height: 100px; max-height: 200px; overflow-y:auto;"></div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script id="variable-modal-template" type="text/x-handlebars-template">
|
||||
<div id="variableInputs">
|
||||
{{! The content will be dynamically generated by JS }}
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script id="help-modal-template" type="text/x-handlebars-template">
|
||||
<div class="help-content">
|
||||
<p>以下是TVbox规则中常用的CSS选择器及语法规则的简要说明:</p>
|
||||
<h4>1. 选择器分隔符 <code>&&</code></h4>
|
||||
<ul>
|
||||
<li><strong>作用:</strong> 用于将多个选择器连接起来,表示在前一个选择器找到的元素内部继续查找。</li>
|
||||
<li><strong>示例:</strong> <code>.parent&&.child</code> - 先找到所有 class 为 <code>parent</code> 的元素,再在其内部寻找 class 为 <code>child</code> 的子元素。</li>
|
||||
</ul>
|
||||
<h4>2. 文本内容筛选 <code>:contains()</code></h4>
|
||||
<ul>
|
||||
<li><strong>作用:</strong> 在CSS选择器后使用,用于筛选出包含特定文本内容的元素。</li>
|
||||
<li><strong>示例:</strong> <code>.sDes:contains(主演:)</code> - 找到所有 class 为 <code>sDes</code> 且内部文本包含“主演:”的元素。</li>
|
||||
</ul>
|
||||
<h4>3. 元素索引 <code>,</code></h4>
|
||||
<ul>
|
||||
<li><strong>作用:</strong> 在选择器后使用逗号加数字,用于精确选取特定索引位置的元素。索引从0开始。</li>
|
||||
<li><strong>示例:</strong> <code>.item,1</code> - 选取所有 class 为 <code>item</code> 的元素中的**第二个**元素。</li>
|
||||
<li><strong>倒数索引:</strong> <code>.item,-1</code> - 选取所有 class 为 <code>item</code> 的元素中的**倒数第一个**元素。</li>
|
||||
</ul>
|
||||
<h4>4. 内容提取 <code>Text</code> / <code>Html</code> / 属性名</h4>
|
||||
<ul>
|
||||
<li><strong>作用:</strong> 在选择器末尾使用,指定要提取的元素内容类型。</li>
|
||||
<li><strong><code>&&Text</code>:</strong> 提取元素的纯文本内容,会忽略HTML标签。</li>
|
||||
<li><strong><code>&&Html</code>:</strong> 提取元素的内部HTML代码,包含子标签。</li>
|
||||
<li><strong><code>&&属性名</code>:</strong> 提取元素的某个属性值,例如:<code>&&href</code>(链接地址),<code>&&data-src</code>(懒加载图片地址)。</li>
|
||||
</ul>
|
||||
<h4>5. 文本删除 <code>!</code></h4>
|
||||
<ul>
|
||||
<li><strong>作用:</strong> 在规则末尾使用,用于删除提取到的文本中指定的内容。</li>
|
||||
<li><strong>示例:</strong> <code>&&Text!主演:</code> - 提取文本后,删除其中的“主演:”这几个字。</li>
|
||||
</ul>
|
||||
<h4>6. 高级属性选择器</h4>
|
||||
<ul>
|
||||
<li><strong><code>[属性^="值"]</code>:</strong> 选取属性值以某个字符串**开头**的元素。
|
||||
<div class="example-code">img[src^="https://"] - 选取所有 src 属性值以“https://”开头的图片。</div>
|
||||
</li>
|
||||
<li><strong><code>[属性$="值"]</code>:</strong> 选取属性值以某个字符串**结尾**的元素。
|
||||
<div class="example-code">a[href$=".m3u8"] - 选取所有 href 属性值以“.m3u8”结尾的链接。</div>
|
||||
</li>
|
||||
<li><strong><code>[属性*="值"]</code>:</strong> 选取属性值**包含**某个字符串的元素。
|
||||
<div class="example-code">a[href*="m3u8"] - 选取所有 href 属性值包含“m3u8”的链接。</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h4>7. 排除特定内容的元素 <code>:not(:matches())</code></h4>
|
||||
<ul>
|
||||
<li><strong>作用:</strong> 这是一个复合选择器,用于选取所有符合某选择器的元素,但排除其中包含特定文本或满足另一条件的元素。
|
||||
<div class="example-code">.module-item:not(:matches(主角|主演))</div>
|
||||
</li>
|
||||
<li><strong>解释:</strong> 上述示例会选取所有 class 为 <code>module-item</code> 的元素,但会**排除**那些其文本内容为“主角”或“主演”的元素。
|
||||
<ul>
|
||||
<li><code>:not()</code> 是否定伪类,用于排除符合括号内选择器的元素。</li>
|
||||
<li><code>:matches()</code> 是匹配伪类,用于同时匹配多个选择器。</li>
|
||||
<li><code>|</code>(竖线)在规则中代表“或”,可以在 <code>:matches()</code> 中连接多个要排除的文本。</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<h4>8. 结构伪类 <code>:first-child</code> / <code>:last-child</code></h4>
|
||||
<ul>
|
||||
<li><strong>作用:</strong> 用于选取作为其父元素的第一个或最后一个子元素的特定元素。</li>
|
||||
<li><strong><code>:first-child</code>:</strong> 选取第一个子元素。
|
||||
<div class="example-code">.detail-info>ul>li:first-child&&Text - 选取 class 为 detail-info 的元素下 ul 的第一个 li 元素的文本内容。</div>
|
||||
</li>
|
||||
<li><strong><code>:last-child</code>:</strong> 选取最后一个子元素。
|
||||
<div class="example-code">.detail-info>ul>li:last-child&&Text - 选取 class 为 detail-info 的元素下 ul 的最后一个 li 元素的文本内容。</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script id="form-field-template" type="text/x-handlebars-template">
|
||||
<div class="form-group {{#if isAdvanced}}advanced-field{{/if}}">
|
||||
<label for="{{id}}">{{key}}</label>
|
||||
<div class="input-with-buttons">
|
||||
{{#if (eq type "textarea")}}
|
||||
<textarea id="{{id}}" name="{{id}}"></textarea>
|
||||
{{else}}
|
||||
<input type="{{#if type}}{{type}}{{else}}text{{/if}}" id="{{id}}" name="{{id}}">
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script>
|
||||
const fileContentFromServer = <?php echo $file_content_for_js; ?>;
|
||||
const filePathFromServer = <?php echo $file_path_for_js; ?>;
|
||||
</script>
|
||||
|
||||
<script src="/assets/js/handlebars.min.js"></script>
|
||||
<script src="/assets/js/utils.js?t=<?php echo time();?>"></script>
|
||||
<script src="/assets/js/el.js"></script>
|
||||
<script src="/assets/js/script.js?t=<?php echo time();?>"></script>
|
||||
<button id="scrollToTopBtn" title="返回顶部">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M12 19V5M5 12l7-7 7 7"/>
|
||||
</svg>
|
||||
</button>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,376 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>正在编辑 - <?php echo isset($_GET['file']) ? htmlspecialchars(basename($_GET['file'])) : 'N/A'; ?></title>
|
||||
<link rel="stylesheet" href="/assets/css/ui.css?t=<?php echo time();?>">
|
||||
|
||||
<style>
|
||||
html, body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: hidden; /* 防止出现滚动条 */
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
.editor-page-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh; /* 占满整个视口高度 */
|
||||
}
|
||||
.editor-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 8px 15px;
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
flex-shrink: 0; /* 防止头部被压缩 */
|
||||
}
|
||||
.file-info .file-name {
|
||||
font-weight: 600;
|
||||
color: #343a40;
|
||||
}
|
||||
.file-info .full-path {
|
||||
font-size: 12px;
|
||||
color: #6c757d;
|
||||
margin-top: 2px;
|
||||
}
|
||||
#editor-container {
|
||||
flex-grow: 1; /* 编辑器占满所有剩余空间 */
|
||||
position: relative;
|
||||
}
|
||||
.checkbox-group { margin: 0; } /* 修正弹窗内边距 */
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="editor-page-wrapper">
|
||||
<header class="editor-header">
|
||||
<div class="file-info">
|
||||
<div class="file-path">
|
||||
<span class="file-icon">📄</span>
|
||||
<span class="file-name"><?php echo isset($_GET['file']) ? htmlspecialchars(basename($_GET['file'])) : '未选择文件'; ?></span>
|
||||
</div>
|
||||
<?php if(isset($_GET['file'])): ?>
|
||||
<div class="full-path"><?php echo htmlspecialchars($_GET['file']); ?></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="header-actions">
|
||||
<div class="btn-group">
|
||||
<button id="vscodeBtn" class="btn secondary-btn" title="在VSCode中打开">VSCode</button>
|
||||
<button id="fullscreenBtn" class="btn secondary-btn">全屏</button>
|
||||
<button id="settingsBtn" class="btn secondary-btn">设置</button>
|
||||
<button id="saveBtn" class="btn primary-btn">保存</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main id="editor-container"></main>
|
||||
</div>
|
||||
|
||||
<script id="settings-modal-template" type="text/x-handlebars-template">
|
||||
<div class="form-group">
|
||||
<label for="theme-switcher">编辑器主题</label>
|
||||
<select id="theme-switcher" class="form-control"></select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="font-size-switcher">字体大小</label>
|
||||
<select id="font-size-switcher" class="form-control">
|
||||
<option value="12">12px</option>
|
||||
<option value="14" selected>14px</option>
|
||||
<option value="16">16px</option>
|
||||
<option value="18">18px</option>
|
||||
<option value="20">20px</option>
|
||||
<option value="24">24px</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="soft-wrap-toggle">自动换行</label>
|
||||
<select id="soft-wrap-toggle" class="form-control">
|
||||
<option value="true">开启</option>
|
||||
<option value="false">关闭</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="tab-size-switcher">Tab 宽度</label>
|
||||
<select id="tab-size-switcher" class="form-control">
|
||||
<option value="2">2空格</option>
|
||||
<option value="4">4空格</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="keybinding-switcher">键盘快捷键方案</label>
|
||||
<select id="keybinding-switcher" class="form-control">
|
||||
<option value="">默认 (Ace)</option>
|
||||
<option value="ace/keyboard/vscode">VSCode</option>
|
||||
<option value="ace/keyboard/vim">Vim</option>
|
||||
<option value="ace/keyboard/emacs">Emacs</option>
|
||||
<option value="ace/keyboard/sublime">Sublime</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group checkbox-group">
|
||||
<input type="checkbox" id="line-number-toggle" style="width: auto;">
|
||||
<label for="line-number-toggle">隐藏行号</label>
|
||||
</div>
|
||||
<div class="form-group checkbox-group">
|
||||
<input type="checkbox" id="highlight-line-toggle" style="width: auto;">
|
||||
<label for="highlight-line-toggle">高亮当前行</label>
|
||||
</div>
|
||||
<div class="form-group checkbox-group">
|
||||
<input type="checkbox" id="show-indent-guides-toggle" style="width: auto;">
|
||||
<label for="show-indent-guides-toggle">显示缩进向导</label>
|
||||
</div>
|
||||
<div class="form-group checkbox-group">
|
||||
<input type="checkbox" id="live-autocomplete-toggle" style="width: auto;">
|
||||
<label for="live-autocomplete-toggle">实时自动补全</label>
|
||||
</div>
|
||||
<div class="form-group checkbox-group">
|
||||
<input type="checkbox" id="enable-snippets-toggle" style="width: auto;">
|
||||
<label for="enable-snippets-toggle">启用代码片段</label>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<div class="toast-container"></div>
|
||||
|
||||
<script src="/assets/js/ace/ace.js"></script>
|
||||
<script src="/assets/js/ace/ext-modelist.js"></script>
|
||||
<script src="/assets/js/ace/ext-language_tools.js"></script>
|
||||
<script src="/assets/js/handlebars.min.js"></script>
|
||||
<script src="/assets/js/utils.js?t=<?php echo time();?>"></script>
|
||||
|
||||
<script>
|
||||
const fileContentFromServer = <?php echo $file_content_for_js ?? '""'; ?>;
|
||||
const filePathFromServer = <?php echo $file_path_for_js ?? '""'; ?>;
|
||||
const fileFullPath = '<?php echo $file_path ?? '""'; ?>';
|
||||
|
||||
const aceThemes = {
|
||||
"亮色主题 (Light)": [
|
||||
{ name: "Chrome", path: "ace/theme/chrome" },
|
||||
{ name: "GitHub", path: "ace/theme/github" },
|
||||
{ name: "Solarized Light", path: "ace/theme/solarized_light" },
|
||||
{ name: "Xcode", path: "ace/theme/xcode" },
|
||||
],
|
||||
"暗色主题 (Dark)": [
|
||||
{ name: "Monokai", path: "ace/theme/monokai" },
|
||||
{ name: "Dracula", path: "ace/theme/dracula" },
|
||||
{ name: "Nord Dark", path: "ace/theme/nord_dark" },
|
||||
{ name: "Solarized Dark", path: "ace/theme/solarized_dark" },
|
||||
{ name: "Twilight", path: "ace/theme/twilight" },
|
||||
]
|
||||
};
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
let settingsModal = null;
|
||||
const editor = ace.edit("editor-container");
|
||||
|
||||
/**
|
||||
* ---------------------------------------------------------------------
|
||||
* 编辑器初始化
|
||||
* ---------------------------------------------------------------------
|
||||
*/
|
||||
editor.setShowPrintMargin(false);
|
||||
const modelist = ace.require("ace/ext/modelist");
|
||||
const mode = modelist.getModeForPath(filePathFromServer).mode;
|
||||
editor.session.setMode(mode);
|
||||
|
||||
if (fileContentFromServer && !fileContentFromServer.startsWith('错误:')) {
|
||||
editor.setValue(fileContentFromServer, -1);
|
||||
} else if (fileContentFromServer) {
|
||||
editor.setValue(`// ${fileContentFromServer}`);
|
||||
}
|
||||
|
||||
const savedSettings = {
|
||||
theme: localStorage.getItem('ace_editor_theme') || 'ace/theme/monokai',
|
||||
fontSize: parseInt(localStorage.getItem('ace_editor_fontSize') || '14', 10),
|
||||
showGutter: localStorage.getItem('ace_editor_showGutter') !== 'false',
|
||||
softWrap: localStorage.getItem('ace_editor_softWrap') === 'true',
|
||||
tabSize: parseInt(localStorage.getItem('ace_editor_tabSize') || '4', 10),
|
||||
highlightLine: localStorage.getItem('ace_editor_highlightLine') !== 'false',
|
||||
keybinding: localStorage.getItem('ace_editor_keybinding') || '',
|
||||
liveAutocomplete: localStorage.getItem('ace_editor_liveAutocomplete') === 'true',
|
||||
enableSnippets: localStorage.getItem('ace_editor_enableSnippets') !== 'false',
|
||||
showIndentGuides: localStorage.getItem('ace_editor_showIndentGuides') !== 'false'
|
||||
};
|
||||
|
||||
editor.setTheme(savedSettings.theme);
|
||||
editor.setFontSize(savedSettings.fontSize);
|
||||
editor.renderer.setShowGutter(savedSettings.showGutter);
|
||||
editor.session.setUseWrapMode(savedSettings.softWrap);
|
||||
editor.session.setTabSize(savedSettings.tabSize);
|
||||
editor.setHighlightActiveLine(savedSettings.highlightLine);
|
||||
editor.setKeyboardHandler(savedSettings.keybinding || null);
|
||||
editor.setDisplayIndentGuides(savedSettings.showIndentGuides);
|
||||
editor.setOptions({
|
||||
enableBasicAutocompletion: true,
|
||||
enableLiveAutocompletion: savedSettings.liveAutocomplete,
|
||||
enableSnippets: savedSettings.enableSnippets
|
||||
});
|
||||
|
||||
|
||||
/**
|
||||
* ---------------------------------------------------------------------
|
||||
* 页面主要按钮事件绑定
|
||||
* ---------------------------------------------------------------------
|
||||
*/
|
||||
document.getElementById('saveBtn').addEventListener('click', () => {
|
||||
if (!filePathFromServer) {
|
||||
showToast('文件路径未知,无法保存。', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const newContent = editor.getValue();
|
||||
const formData = new FormData();
|
||||
formData.append('filePath', filePathFromServer);
|
||||
formData.append('fileContent', newContent);
|
||||
|
||||
showToast('正在保存...', 'info');
|
||||
fetch('/index.php/Edit/save', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(result => {
|
||||
if (result.success) {
|
||||
showToast(result.message, 'success');
|
||||
} else {
|
||||
throw new Error(result.message);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
showToast(`保存失败: ${err.message}`, 'error');
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById('vscodeBtn')?.addEventListener('click', () => {
|
||||
openInVSCode(fileFullPath);
|
||||
});
|
||||
|
||||
document.getElementById('settingsBtn').addEventListener('click', () => {
|
||||
if (!settingsModal) {
|
||||
const template = Handlebars.compile(document.getElementById('settings-modal-template').innerHTML);
|
||||
settingsModal = new Modal({
|
||||
id: 'editor-settings-modal',
|
||||
title: '编辑器设置',
|
||||
content: template(),
|
||||
footer: `
|
||||
<button type="button" class="btn secondary-btn" data-close-modal>取消</button>
|
||||
<button type="button" class="btn primary-btn" id="apply-settings">应用</button>
|
||||
`
|
||||
});
|
||||
|
||||
const modalBody = settingsModal.getBodyElement();
|
||||
const themeSelect = modalBody.querySelector('#theme-switcher');
|
||||
|
||||
themeSelect.innerHTML = '';
|
||||
for (const groupName in aceThemes) {
|
||||
const optgroup = document.createElement('optgroup');
|
||||
optgroup.label = groupName;
|
||||
aceThemes[groupName].forEach(theme => {
|
||||
const option = new Option(theme.name, theme.path);
|
||||
optgroup.appendChild(option);
|
||||
});
|
||||
themeSelect.appendChild(optgroup);
|
||||
}
|
||||
|
||||
settingsModal.getFooterElement().querySelector('#apply-settings').addEventListener('click', () => {
|
||||
const newSettings = {
|
||||
theme: modalBody.querySelector('#theme-switcher').value,
|
||||
fontSize: parseInt(modalBody.querySelector('#font-size-switcher').value, 10),
|
||||
showGutter: !modalBody.querySelector('#line-number-toggle').checked,
|
||||
softWrap: modalBody.querySelector('#soft-wrap-toggle').value === 'true',
|
||||
tabSize: parseInt(modalBody.querySelector('#tab-size-switcher').value, 10),
|
||||
highlightLine: modalBody.querySelector('#highlight-line-toggle').checked,
|
||||
keybinding: modalBody.querySelector('#keybinding-switcher').value,
|
||||
liveAutocomplete: modalBody.querySelector('#live-autocomplete-toggle').checked,
|
||||
enableSnippets: modalBody.querySelector('#enable-snippets-toggle').checked,
|
||||
showIndentGuides: modalBody.querySelector('#show-indent-guides-toggle').checked
|
||||
};
|
||||
|
||||
editor.setTheme(newSettings.theme);
|
||||
editor.setFontSize(newSettings.fontSize);
|
||||
editor.renderer.setShowGutter(newSettings.showGutter);
|
||||
editor.session.setUseWrapMode(newSettings.softWrap);
|
||||
editor.session.setTabSize(newSettings.tabSize);
|
||||
editor.setHighlightActiveLine(newSettings.highlightLine);
|
||||
editor.setKeyboardHandler(newSettings.keybinding || null);
|
||||
editor.setDisplayIndentGuides(newSettings.showIndentGuides);
|
||||
editor.setOptions({
|
||||
enableBasicAutocompletion: true,
|
||||
enableLiveAutocompletion: newSettings.liveAutocomplete,
|
||||
enableSnippets: newSettings.enableSnippets
|
||||
});
|
||||
|
||||
for (const key in newSettings) {
|
||||
localStorage.setItem(`ace_editor_${key}`, newSettings[key]);
|
||||
}
|
||||
|
||||
showToast('设置已应用', 'success');
|
||||
settingsModal.close();
|
||||
});
|
||||
|
||||
settingsModal.getFooterElement().querySelector('[data-close-modal]').addEventListener('click', () => settingsModal.close());
|
||||
}
|
||||
|
||||
const modalBody = settingsModal.getBodyElement();
|
||||
modalBody.querySelector('#theme-switcher').value = localStorage.getItem('ace_editor_theme') || 'ace/theme/monokai';
|
||||
modalBody.querySelector('#font-size-switcher').value = localStorage.getItem('ace_editor_fontSize') || '14';
|
||||
modalBody.querySelector('#line-number-toggle').checked = localStorage.getItem('ace_editor_showGutter') === 'false';
|
||||
modalBody.querySelector('#soft-wrap-toggle').value = localStorage.getItem('ace_editor_softWrap') || 'false';
|
||||
modalBody.querySelector('#tab-size-switcher').value = localStorage.getItem('ace_editor_tabSize') || '4';
|
||||
modalBody.querySelector('#highlight-line-toggle').checked = localStorage.getItem('ace_editor_highlightLine') !== 'false';
|
||||
modalBody.querySelector('#keybinding-switcher').value = localStorage.getItem('ace_editor_keybinding') || '';
|
||||
modalBody.querySelector('#live-autocomplete-toggle').checked = localStorage.getItem('ace_editor_liveAutocomplete') === 'true';
|
||||
modalBody.querySelector('#enable-snippets-toggle').checked = localStorage.getItem('ace_editor_enableSnippets') !== 'false';
|
||||
modalBody.querySelector('#show-indent-guides-toggle').checked = localStorage.getItem('ace_editor_showIndentGuides') !== 'false';
|
||||
|
||||
settingsModal.open();
|
||||
});
|
||||
|
||||
const fullscreenButton = document.getElementById('fullscreenBtn');
|
||||
if (fullscreenButton) {
|
||||
function toggleFullScreen() {
|
||||
const doc = document;
|
||||
const docEl = doc.documentElement;
|
||||
const requestFullScreen = docEl.requestFullscreen || docEl.mozRequestFullScreen || docEl.webkitRequestFullscreen || docEl.msRequestFullscreen;
|
||||
const cancelFullScreen = doc.exitFullscreen || doc.mozCancelFullScreen || doc.webkitExitFullscreen || doc.msExitFullscreen;
|
||||
|
||||
if (!doc.fullscreenElement && !doc.mozFullScreenElement && !doc.webkitFullscreenElement && !doc.msFullscreenElement) {
|
||||
if (requestFullScreen) {
|
||||
requestFullScreen.call(docEl);
|
||||
}
|
||||
} else {
|
||||
if (cancelFullScreen) {
|
||||
cancelFullScreen.call(doc);
|
||||
}
|
||||
}
|
||||
}
|
||||
fullscreenButton.addEventListener('click', toggleFullScreen);
|
||||
document.addEventListener('fullscreenchange', () => {
|
||||
const isFullScreen = !!document.fullscreenElement;
|
||||
fullscreenButton.textContent = isFullScreen ? '退出全屏' : '全屏';
|
||||
});
|
||||
}
|
||||
|
||||
function openInVSCode(fullPath) {
|
||||
if (!fullPath) {
|
||||
alert('无效的文件路径');
|
||||
return;
|
||||
}
|
||||
const vscodeUri = `vscode://file${fullPath}`;
|
||||
window.location.href = vscodeUri;
|
||||
setTimeout(() => {
|
||||
if (!document.hidden) {
|
||||
alert(`无法自动打开VSCode,请确认:\n\n1. 您已在本地安装了VSCode。\n2. 文件路径对您的本地环境是可访问的。\n\n路径: ${fullPath}`);
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1 @@
|
||||
编辑器不存在!
|
||||
@@ -0,0 +1,294 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>TVbox 规则加载器</title>
|
||||
<link rel="stylesheet" href="assets/css/ui.css?t=<?php echo time();?>">
|
||||
<link rel="stylesheet" href="assets/css/main.css?t=<?php echo time();?>">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="container">
|
||||
<header class="main-header d-flex align-items-center flex-wrap">
|
||||
<div class="file-path me-auto">
|
||||
<span class="file-icon">📖</span>
|
||||
<span id="file-name-display" class="file-name">选择文件或输入链接</span>
|
||||
</div>
|
||||
|
||||
<div class="layout-selector">
|
||||
<label for="column-select">列表布局:</label>
|
||||
<select id="column-select" name="column-select">
|
||||
<option value="1">每行1个</option>
|
||||
<option value="2" selected="">每行2个</option>
|
||||
<option value="3">每行3个</option>
|
||||
<option value="4">每行4个</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="global-actions">
|
||||
<div class="btn-group gbtn-sm">
|
||||
<button id="saveBtn" class="btn primary-btn">保存修改</button>
|
||||
<button id="historyBtn" class="btn secondary-btn">文件历史</button>
|
||||
<button id="online-edit-btn" class="btn secondary-btn">在线编辑</button>
|
||||
<button id="downloadRulesBtn" class="btn secondary-btn">下载</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="input-with-buttons w-100 mt-2">
|
||||
<input type="text" id="jsonUrlInput" placeholder="请输入TVbox规则集合的JSON链接">
|
||||
<div class="btn-group">
|
||||
<button id="readUrlBtn" class="btn primary-btn">加载</button>
|
||||
<button id="viewSourceBtn" class="btn secondary-btn">查看源码</button>
|
||||
<button id="selectFileBtn" class="btn secondary-btn">选择文件</button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="main-content">
|
||||
<div class="tabs">
|
||||
<div class="tab-btn active" onclick="openTab(event, 'basic')" data-tab="basic">基础信息</div>
|
||||
<div class="tab-btn" onclick="openTab(event, 'lives')" data-tab="lives">直播规则</div>
|
||||
<div class="tab-btn" onclick="openTab(event, 'sites')" data-tab="sites">爬虫规则</div>
|
||||
<div class="tab-btn" onclick="openTab(event, 'parses')" data-tab="parses">解析接口</div>
|
||||
<div class="tab-btn" onclick="openTab(event, 'filters')" data-tab="filters">广告过滤</div>
|
||||
</div>
|
||||
|
||||
<div id="basic" class="tab-content active" style="display: block;"></div>
|
||||
<div id="lives" class="tab-content" style="display: none;"></div>
|
||||
<div id="sites" class="tab-content" style="display: none;"></div>
|
||||
<div id="parses" class="tab-content" style="display: none;"></div>
|
||||
<div id="filters" class="tab-content" style="display: none;"></div>
|
||||
</main>
|
||||
|
||||
|
||||
|
||||
<div id="loading" style="display: none; text-align: center; padding: 20px; font-size: 16px;">正在读取内容...</div>
|
||||
</div>
|
||||
|
||||
<div id="templates" style="display: none;">
|
||||
|
||||
<script id="add-site-modal-template" type="text/x-handlebars-template">
|
||||
<div id="create-spider-form-modal" class="details-panel create-panel active" style="max-height:none; opacity:1; padding:0; background:none;">
|
||||
<div class="form-group"><label for="new-site-name-modal">规则名称</label><input id="new-site-name-modal" type="text" placeholder="例如:酷云影视"></div>
|
||||
<div class="form-group"><label for="new-site-key-modal">唯一标识</label><input id="new-site-key-modal" type="text" placeholder="例如:ky_m"></div>
|
||||
<div class="form-group" style="grid-column: 1 / -1;"><label for="new-site-ext-modal">规则链接</label><input id="new-site-ext-modal" type="text" placeholder="http://.../rule.json"></div>
|
||||
<div class="form-group"><label for="new-site-api-modal">爬虫接口</label><input id="new-site-api-modal" type="text" value="csp_XYQHiker"></div>
|
||||
<div class="form-group"><label for="new-site-type-modal">类型</label><select id="new-site-type-modal"><option value="1">1 (csp)</option><option value="0">0 (vod)</option><option value="2">2</option><option value="3" selected>3</option></select></div>
|
||||
<div class="form-group"><label for="new-site-jar-modal">Jar文件</label><input id="new-site-jar-modal" type="text" placeholder="例如:./libs/Panda.jar"></div>
|
||||
<div class="form-group checkbox-group">
|
||||
<input type="checkbox" id="new-site-searchable-modal" style="width: auto;" checked>
|
||||
<label>可搜索</label>
|
||||
<input type="checkbox" id="new-site-filterable-modal" style="width: auto;" checked>
|
||||
<label>可筛选</label>
|
||||
<input type="checkbox" id="new-site-quick-modal" style="width: auto;" checked>
|
||||
<label>快速搜索</label>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script id="add-parse-modal-template" type="text/x-handlebars-template">
|
||||
<div id="create-parse-form-modal" class="details-panel create-panel active" style="max-height:none; opacity:1; padding:0; background:none;">
|
||||
<div class="details-form-grid">
|
||||
<div class="form-group"><label for="new-parse-name-modal">接口名称</label><input id="new-parse-name-modal" type="text" placeholder="例如:XX解析"></div>
|
||||
<div class="form-group"><label for="new-parse-type-modal">类型</label><input id="new-parse-type-modal" type="text" placeholder="0, 1, 2, 3"></div>
|
||||
<div class="form-group" style="grid-column: 1 / -1;"><label for="new-parse-url-modal">接口地址(URL)</label><input id="new-parse-url-modal" type="text" placeholder="http://..."></div>
|
||||
<div class="form-group" style="grid-column: 1 / -1;"><label for="new-parse-ext-modal">扩展参数(ext)</label><textarea id="new-parse-ext-modal" rows="3" placeholder='例如:{"header":{"user-agent":"PC_UA"}}'></textarea></div>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script id="add-filter-modal-template" type="text/x-handlebars-template">
|
||||
<div id="create-filter-form-modal" class="details-panel create-panel active" style="max-height:none; opacity:1; padding:0; background:none;">
|
||||
<div class="details-form-grid">
|
||||
<div class="form-group"><label for="new-filter-name-modal">规则名称</label><input id="new-filter-name-modal" type="text" placeholder="例如:非凡过滤"></div>
|
||||
<div class="form-group"><label for="new-filter-host-modal">主机名</label><input id="new-filter-host-modal" type="text" placeholder="例如:vip.ffzy"></div>
|
||||
<div class="form-group" style="grid-column: 1 / -1;"><label for="new-filter-hosts-modal">主机列表</label><textarea id="new-filter-hosts-modal" rows="3" placeholder='例如:["vip.ffzy"]'></textarea></div>
|
||||
<div class="form-group" style="grid-column: 1 / -1;"><label for="new-filter-rules-modal">规则列表</label><textarea id="new-filter-rules-modal" rows="3" placeholder='例如:["playwm/?video_id="]'></textarea></div>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script id="basic-tab-template" type="text/x-handlebars-template">
|
||||
<div class="form-group">
|
||||
<label for="spider-url">爬虫Jar (spider) <span id="status-spider" class="download-status"></span></label>
|
||||
<input type="text" id="spider-url" name="spider-url" value="{{spiderPath}}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="wallpaper-url">壁纸 (wallpaper)</label>
|
||||
<input type="text" id="wallpaper-url" name="wallpaper-url" value="{{wallpaper}}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="ijk-url">播放器 (ijk)</label>
|
||||
<textarea id="ijk-url" name="ijk-url" rows="5">{{ijk}}</textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="warning-text">警告文本 (warningText)</label>
|
||||
<textarea id="warning-text" name="warning-text" rows="3">{{warningText}}</textarea>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script id="simple-item-template" type="text/x-handlebars-template">
|
||||
<div id="{{itemType}}-item-{{index}}" class="rule-item-container" data-index="{{index}}" data-item-type="{{itemType}}">
|
||||
<button type="button" class="delete-item-btn">×</button>
|
||||
<div class="form-group">
|
||||
<label for="{{itemType}}-{{index}}">{{name}}</label>
|
||||
<div class="input-with-buttons">
|
||||
<input type="text" id="{{itemType}}-{{index}}" value="{{url}}" readonly>
|
||||
<div class="action-btn-group">
|
||||
<button type="button" class="btn btn-sm secondary-btn action-btn" data-action="test-url" data-url="{{url}}">测试</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script id="site-item-template" type="text/x-handlebars-template">
|
||||
<div id="site-item-{{index}}" class="rule-item-container" data-api="{{api}}" data-index="{{index}}" data-item-type="sites">
|
||||
<button type="button" class="delete-item-btn">×</button>
|
||||
<div class="form-group">
|
||||
<label for="site-{{index}}">
|
||||
{{name}}
|
||||
{{#if hasAssets}}
|
||||
<span id="status-site-item-{{index}}" class="download-status {{combinedStatus}}"></span>
|
||||
{{/if}}
|
||||
</label>
|
||||
<div class="input-with-buttons">
|
||||
<input type="text" id="site-{{index}}" value="{{displayValue}}" readonly>
|
||||
<div class="action-btn-group">
|
||||
<button type="button" class="btn btn-sm secondary-btn action-btn" data-action="edit-file">编辑</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script id="filter-item-template" type="text/x-handlebars-template">
|
||||
<div id="rules-item-{{index}}" class="rule-item-container" data-index="{{index}}" data-item-type="rules">
|
||||
<button type="button" class="delete-item-btn">×</button>
|
||||
<div class="form-group">
|
||||
<label for="rules-{{index}}">{{displayName}}</label>
|
||||
<textarea id="rules-{{index}}" readonly rows="3">{{displayValue}}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
<script id="tab-content-template" type="text/x-handlebars-template">
|
||||
<div class="controls-container d-flex justify-between align-items-center">
|
||||
<div class="left-controls">
|
||||
{{#if showCreateButton}}
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn primary-btn create-new-btn" data-item-type="{{itemType}}">+ 新增</button>
|
||||
<button type="button" class="btn danger-btn delete-all-btn" data-item-type="{{itemType}}">清空</button>
|
||||
</div>
|
||||
{{else}}
|
||||
<button type="button" class="btn danger-btn delete-all-btn" data-item-type="{{itemType}}">清空</button>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="right-controls">
|
||||
{{#if (eq itemType "sites")}}
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn secondary-btn site-filter-btn" data-filter-type="equals" data-filter-value="csp_XYQHiker">只看XYQH</button>
|
||||
<button type="button" class="btn secondary-btn site-filter-btn" data-filter-type="equals" data-filter-value="csp_XBPQ">只看XBPQ</button>
|
||||
<button type="button" class="btn secondary-btn site-filter-btn" data-filter-type="endsWith" data-filter-value=".js">只看Js</button>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="rule-list-grid"></div>
|
||||
</script>
|
||||
<script id="tab-content-template1" type="text/x-handlebars-template">
|
||||
<div class="controls-container">
|
||||
<div class="left-controls">
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn danger-btn delete-all-btn" data-item-type="{{itemType}}">删除全部</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right-controls">
|
||||
{{{rightControls}}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="rule-list-grid"></div>
|
||||
</script>
|
||||
|
||||
<script id="details-modal-body-template" type="text/x-handlebars-template">
|
||||
<div class="details-form-grid">
|
||||
{{#each fields}}
|
||||
<div class="details-item" {{#if this.fullWidth}}style="grid-column: 1 / -1;"{{/if}}>
|
||||
<label class="details-label" for="{{this.id}}">{{this.label}}</label>
|
||||
{{#if this.isBoolean}}
|
||||
<div class="input-with-buttons">
|
||||
<input class="details-input" type="text" id="{{this.id}}" value="{{this.value}}">
|
||||
<button type="button" class="btn btn-sm success-btn bool-setter" data-target-id="{{this.id}}" data-value="{{this.trueValue}}">{{this.trueText}}</button>
|
||||
<button type="button" class="btn btn-sm danger-btn bool-setter" data-target-id="{{this.id}}" data-value="{{this.falseValue}}">{{this.falseText}}</button>
|
||||
</div>
|
||||
{{else if this.isTextarea}}
|
||||
<textarea class="details-input" id="{{this.id}}" rows="3">{{this.value}}</textarea>
|
||||
{{else}}
|
||||
<input class="details-input" type="text" id="{{this.id}}" value="{{this.value}}">
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</script>
|
||||
|
||||
<script id="file-browser-body-template" type="text/x-handlebars-template">
|
||||
{{#if files.length}}
|
||||
<ul class="file-list">
|
||||
{{#each files}}
|
||||
{{#if (eq type "dir")}}
|
||||
<li class="dir collapsed">
|
||||
<div class="file-list-item is-dir">
|
||||
<span class="icon toggle-icon">+</span>
|
||||
<span class="icon">📁</span> {{name}}
|
||||
</div>
|
||||
{{{buildList children}}}
|
||||
</li>
|
||||
{{else}}
|
||||
<li>
|
||||
{{#if (endsWith name ".json")}}
|
||||
<div class="file-list-item is-file">
|
||||
<label>
|
||||
<input type="radio" name="server-file-radio" value="{{path}}">
|
||||
<span class="icon">📄</span> {{name}}
|
||||
</label>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="file-list-item is-file" style="padding-left: 30px;">
|
||||
<span class="icon">▫️</span> {{name}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</li>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{else}}
|
||||
<p>服务器上的 "box" 目录为空或不存在。</p>
|
||||
{{/if}}
|
||||
</script>
|
||||
<script id="download-modal-template" type="text/x-handlebars-template">
|
||||
<div class="form-group">
|
||||
<label for="download-dir-input">存放目录名 (在服务器box/目录下创建)</label>
|
||||
<input type="text" id="download-dir-input" placeholder="例如: my_config">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="download-filename-input">配置文件名</label>
|
||||
<input type="text" id="download-filename-input" value="config.json">
|
||||
</div>
|
||||
</script>
|
||||
|
||||
</div>
|
||||
|
||||
<input type="file" id="localFileInput" accept=".json" style="display: none;">
|
||||
<div class="toast-container"></div>
|
||||
|
||||
<script src="assets/js/handlebars.min.js"></script>
|
||||
<script src="assets/js/utils.js?t=<?php echo time();?>"></script>
|
||||
<script src="assets/js/main.js?t=<?php echo time();?>"></script>
|
||||
<button id="scrollToTopBtn" title="返回顶部">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M12 19V5M5 12l7-7 7 7"/>
|
||||
</svg>
|
||||
</button>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,95 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>登录 - TVbox 规则加载器</title>
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
.login-container {
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
padding: 40px;
|
||||
margin: 20px;
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 25px rgba(0, 0, 0, .05);
|
||||
text-align: center;
|
||||
}
|
||||
h1 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 25px;
|
||||
font-weight: 600;
|
||||
font-size: 24px;
|
||||
color: #343a40;
|
||||
}
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
input[type="password"] {
|
||||
padding: 12px 15px;
|
||||
font-size: 16px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #ced4da;
|
||||
transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out;
|
||||
}
|
||||
input[type="password"]:focus {
|
||||
border-color: #80bdff;
|
||||
outline: 0;
|
||||
box-shadow: 0 0 0 .2rem rgba(0, 123, 255, .25);
|
||||
}
|
||||
button {
|
||||
padding: 12px 20px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
border-radius: 6px;
|
||||
border: none;
|
||||
background-color: #007bff;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
transition: background-color .2s ease-in-out, transform .1s ease;
|
||||
}
|
||||
button:hover {
|
||||
background-color: #0069d9;
|
||||
}
|
||||
button:active {
|
||||
transform: scale(.98);
|
||||
}
|
||||
.error-message {
|
||||
color: #dc3545;
|
||||
margin-top: 15px;
|
||||
margin-bottom: 0;
|
||||
text-align: left;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="login-container">
|
||||
<h1>登录</h1>
|
||||
<form method="POST" action="/<?php echo WWW_URL('Login', 'doLogin'); ?>">
|
||||
<input type="password" name="password" placeholder="请输入密码" required autofocus>
|
||||
<button type="submit">进 入</button>
|
||||
</form>
|
||||
<?php if (!empty($error)): ?>
|
||||
<p class="error-message"><?php echo htmlspecialchars($error); ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user