856 lines
35 KiB
HTML
856 lines
35 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>DemoUtilsPlugin 测试客户端</title>
|
||
<style>
|
||
body {
|
||
font-family: 'Microsoft YaHei', Arial, sans-serif;
|
||
line-height: 1.6;
|
||
margin: 0;
|
||
padding: 20px;
|
||
background-color: #f8f9fa;
|
||
color: #333;
|
||
}
|
||
.container {
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
background-color: #fff;
|
||
padding: 20px;
|
||
border-radius: 8px;
|
||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||
}
|
||
h1, h2, h3 {
|
||
color: #005e8a;
|
||
}
|
||
h1 {
|
||
text-align: center;
|
||
margin-bottom: 30px;
|
||
padding-bottom: 15px;
|
||
border-bottom: 1px solid #eee;
|
||
}
|
||
.section {
|
||
margin-bottom: 30px;
|
||
padding: 20px;
|
||
background-color: #f9f9f9;
|
||
border-radius: 5px;
|
||
border-left: 4px solid #005e8a;
|
||
}
|
||
.panel {
|
||
border: 1px solid #ddd;
|
||
border-radius: 5px;
|
||
padding: 15px;
|
||
margin-bottom: 20px;
|
||
background-color: white;
|
||
}
|
||
.panel-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
border-bottom: 1px solid #eee;
|
||
padding-bottom: 10px;
|
||
margin-bottom: 15px;
|
||
font-weight: bold;
|
||
}
|
||
button {
|
||
background-color: #005e8a;
|
||
color: white;
|
||
border: none;
|
||
padding: 8px 15px;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
transition: background-color 0.3s;
|
||
}
|
||
button:hover {
|
||
background-color: #004a6e;
|
||
}
|
||
button:disabled {
|
||
background-color: #cccccc;
|
||
cursor: not-allowed;
|
||
}
|
||
label {
|
||
display: block;
|
||
margin-bottom: 5px;
|
||
font-weight: bold;
|
||
}
|
||
input, select, textarea {
|
||
width: 100%;
|
||
padding: 8px;
|
||
margin-bottom: 15px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
box-sizing: border-box;
|
||
}
|
||
.input-group {
|
||
margin-bottom: 15px;
|
||
}
|
||
#operationForm {
|
||
max-width: 600px;
|
||
}
|
||
#resultPanel, #eventPanel {
|
||
max-height: 400px;
|
||
overflow-y: auto;
|
||
font-family: monospace;
|
||
background-color: #f5f5f5;
|
||
padding: 15px;
|
||
border-radius: 4px;
|
||
white-space: pre-wrap;
|
||
word-break: break-all;
|
||
}
|
||
.loading {
|
||
text-align: center;
|
||
margin: 20px 0;
|
||
font-style: italic;
|
||
color: #666;
|
||
}
|
||
.success {
|
||
color: #28a745;
|
||
font-weight: bold;
|
||
}
|
||
.error {
|
||
color: #dc3545;
|
||
font-weight: bold;
|
||
}
|
||
.tab-container {
|
||
margin-bottom: 20px;
|
||
}
|
||
.tab-header {
|
||
display: flex;
|
||
margin-bottom: 15px;
|
||
border-bottom: 1px solid #ddd;
|
||
padding-bottom: 5px;
|
||
}
|
||
.tab-button {
|
||
padding: 8px 15px;
|
||
background-color: #f0f0f0;
|
||
border: none;
|
||
margin-right: 5px;
|
||
border-radius: 4px 4px 0 0;
|
||
cursor: pointer;
|
||
}
|
||
.tab-button.active {
|
||
background-color: #005e8a;
|
||
color: white;
|
||
}
|
||
.tab-content {
|
||
display: none;
|
||
}
|
||
.tab-content.active {
|
||
display: block;
|
||
}
|
||
.grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 20px;
|
||
}
|
||
@media (max-width: 768px) {
|
||
.grid {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
}
|
||
.event-log {
|
||
padding: 5px;
|
||
margin: 5px 0;
|
||
border-bottom: 1px solid #eee;
|
||
}
|
||
.event-timestamp {
|
||
color: #666;
|
||
font-size: 0.8em;
|
||
}
|
||
.parameter-row {
|
||
display: flex;
|
||
margin-bottom: 10px;
|
||
}
|
||
.parameter-row .key {
|
||
flex: 1;
|
||
padding-right: 10px;
|
||
}
|
||
.parameter-row .value {
|
||
flex: 2;
|
||
}
|
||
.checkbox-group {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
.checkbox-group input {
|
||
width: auto;
|
||
margin-right: 10px;
|
||
}
|
||
#actionsList {
|
||
margin-bottom: 20px;
|
||
max-height: 200px;
|
||
overflow-y: auto;
|
||
}
|
||
.action-item {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
padding: 8px;
|
||
background-color: #f0f0f0;
|
||
margin-bottom: 5px;
|
||
border-radius: 4px;
|
||
}
|
||
.action-item:hover {
|
||
background-color: #e0e0e0;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<h1>DemoUtilsPlugin 测试客户端</h1>
|
||
|
||
<div class="section">
|
||
<h2>插件状态</h2>
|
||
<div class="panel">
|
||
<div class="panel-header">
|
||
<span>插件信息</span>
|
||
<button id="refreshStatus">刷新状态</button>
|
||
</div>
|
||
<div id="pluginStatus">正在加载插件信息...</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="section">
|
||
<h2>操作调用</h2>
|
||
<div class="tab-container">
|
||
<div class="tab-header">
|
||
<button class="tab-button active" data-tab="operations">可用操作</button>
|
||
<button class="tab-button" data-tab="execute">执行操作</button>
|
||
<button class="tab-button" data-tab="events">事件监听</button>
|
||
</div>
|
||
|
||
<!-- 可用操作列表 -->
|
||
<div id="operations" class="tab-content active">
|
||
<p>以下是 DemoUtilsPlugin 支持的所有操作:</p>
|
||
<div id="actionsList">
|
||
正在加载操作列表...
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 执行操作表单 -->
|
||
<div id="execute" class="tab-content">
|
||
<div class="grid">
|
||
<div class="panel">
|
||
<div class="panel-header">执行操作</div>
|
||
<form id="operationForm">
|
||
<div class="input-group">
|
||
<label for="operation">选择操作:</label>
|
||
<select id="operation" name="operation" required>
|
||
<option value="">-- 请选择操作 --</option>
|
||
</select>
|
||
</div>
|
||
<div id="parameterFields">
|
||
<!-- 参数字段将在这里动态生成 -->
|
||
</div>
|
||
<button type="submit">执行</button>
|
||
</form>
|
||
</div>
|
||
|
||
<div class="panel">
|
||
<div class="panel-header">执行结果</div>
|
||
<div id="resultPanel">
|
||
尚未执行任何操作。
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 事件监听 -->
|
||
<div id="events" class="tab-content">
|
||
<div class="grid">
|
||
<div class="panel">
|
||
<div class="panel-header">事件订阅</div>
|
||
<div class="input-group">
|
||
<label>订阅事件类型:</label>
|
||
<div class="checkbox-group">
|
||
<input type="checkbox" id="eventInitialized" value="initialized" checked>
|
||
<label for="eventInitialized">初始化</label>
|
||
</div>
|
||
<div class="checkbox-group">
|
||
<input type="checkbox" id="eventStarted" value="started" checked>
|
||
<label for="eventStarted">启动</label>
|
||
</div>
|
||
<div class="checkbox-group">
|
||
<input type="checkbox" id="eventStopped" value="stopped" checked>
|
||
<label for="eventStopped">停止</label>
|
||
</div>
|
||
<div class="checkbox-group">
|
||
<input type="checkbox" id="eventCustom" value="custom" checked>
|
||
<label for="eventCustom">自定义</label>
|
||
</div>
|
||
</div>
|
||
<button id="subscribeEvents">订阅事件</button>
|
||
<button id="unsubscribeEvents">取消订阅</button>
|
||
<button id="triggerCustomEvent">触发自定义事件</button>
|
||
</div>
|
||
|
||
<div class="panel">
|
||
<div class="panel-header">
|
||
<span>事件日志</span>
|
||
<button id="clearEvents">清空</button>
|
||
</div>
|
||
<div id="eventPanel">
|
||
尚未接收到任何事件。
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="section">
|
||
<h2>快速操作</h2>
|
||
<button id="testCalculate">计算测试 (10*5)</button>
|
||
<button id="testFormat">格式化测试 (大写转换)</button>
|
||
<button id="testStore">数据存储测试</button>
|
||
<button id="testRetrieve">数据检索测试</button>
|
||
<button id="testCounter">计数器操作测试</button>
|
||
<button id="testSort">排序测试</button>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
// 定义全局变量
|
||
const PLUGIN_NAME = 'DemoUtilsPlugin';
|
||
let operations = [];
|
||
let isSubscribed = false;
|
||
let eventSource = null;
|
||
|
||
// 当文档加载完成时执行
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
// 初始化标签页切换
|
||
initTabs();
|
||
|
||
// 加载插件状态
|
||
loadPluginStatus();
|
||
|
||
// 加载操作列表
|
||
loadOperations();
|
||
|
||
// 绑定表单提交事件
|
||
document.getElementById('operationForm').addEventListener('submit', executeOperation);
|
||
|
||
// 绑定操作选择变化事件
|
||
document.getElementById('operation').addEventListener('change', updateParameterFields);
|
||
|
||
// 绑定事件相关按钮
|
||
document.getElementById('subscribeEvents').addEventListener('click', subscribeEvents);
|
||
document.getElementById('unsubscribeEvents').addEventListener('click', unsubscribeEvents);
|
||
document.getElementById('clearEvents').addEventListener('click', clearEventLog);
|
||
document.getElementById('triggerCustomEvent').addEventListener('click', triggerCustomEvent);
|
||
|
||
// 绑定刷新状态按钮
|
||
document.getElementById('refreshStatus').addEventListener('click', loadPluginStatus);
|
||
|
||
// 绑定快速操作按钮
|
||
document.getElementById('testCalculate').addEventListener('click', () => quickTestOperation('calculate'));
|
||
document.getElementById('testFormat').addEventListener('click', () => quickTestOperation('format'));
|
||
document.getElementById('testStore').addEventListener('click', () => quickTestOperation('store'));
|
||
document.getElementById('testRetrieve').addEventListener('click', () => quickTestOperation('retrieve'));
|
||
document.getElementById('testCounter').addEventListener('click', () => quickTestOperation('counter'));
|
||
document.getElementById('testSort').addEventListener('click', () => quickTestOperation('sort'));
|
||
});
|
||
|
||
// 初始化标签页
|
||
function initTabs() {
|
||
const tabButtons = document.querySelectorAll('.tab-button');
|
||
tabButtons.forEach(button => {
|
||
button.addEventListener('click', function() {
|
||
// 移除所有活动类
|
||
document.querySelectorAll('.tab-button').forEach(btn => {
|
||
btn.classList.remove('active');
|
||
});
|
||
document.querySelectorAll('.tab-content').forEach(content => {
|
||
content.classList.remove('active');
|
||
});
|
||
|
||
// 添加活动类
|
||
this.classList.add('active');
|
||
const tabId = this.getAttribute('data-tab');
|
||
document.getElementById(tabId).classList.add('active');
|
||
});
|
||
});
|
||
}
|
||
|
||
// 加载插件状态
|
||
async function loadPluginStatus() {
|
||
try {
|
||
const statusElem = document.getElementById('pluginStatus');
|
||
statusElem.innerHTML = '正在加载插件信息...';
|
||
|
||
const response = await fetch('/api/plugins');
|
||
const plugins = await response.json();
|
||
|
||
const demoPlugin = plugins.find(p => p.name === PLUGIN_NAME);
|
||
|
||
if (demoPlugin) {
|
||
let html = `
|
||
<div>
|
||
<p><strong>名称:</strong> ${demoPlugin.name}</p>
|
||
<p><strong>版本:</strong> ${demoPlugin.version}</p>
|
||
<p><strong>描述:</strong> ${demoPlugin.description}</p>
|
||
<p><strong>作者:</strong> ${demoPlugin.author}</p>
|
||
<p><strong>类型:</strong> ${demoPlugin.type}</p>
|
||
<p><strong>状态:</strong> <span class="${demoPlugin.enabled ? 'success' : 'error'}">${demoPlugin.enabled ? '已启用' : '已禁用'}</span></p>
|
||
</div>
|
||
`;
|
||
|
||
if (demoPlugin.config && Object.keys(demoPlugin.config).length > 0) {
|
||
html += '<p><strong>配置:</strong></p><ul>';
|
||
for (const [key, value] of Object.entries(demoPlugin.config)) {
|
||
html += `<li><strong>${key}:</strong> ${value}</li>`;
|
||
}
|
||
html += '</ul>';
|
||
}
|
||
|
||
statusElem.innerHTML = html;
|
||
} else {
|
||
statusElem.innerHTML = '<p class="error">未找到 DemoUtilsPlugin 插件。请确保它已正确加载。</p>';
|
||
}
|
||
} catch (error) {
|
||
document.getElementById('pluginStatus').innerHTML = `<p class="error">加载插件状态失败: ${error.message}</p>`;
|
||
}
|
||
}
|
||
|
||
// 加载操作列表
|
||
async function loadOperations() {
|
||
try {
|
||
const actionsListElem = document.getElementById('actionsList');
|
||
const operationSelectElem = document.getElementById('operation');
|
||
|
||
actionsListElem.innerHTML = '正在加载操作列表...';
|
||
|
||
const response = await fetch(`/api/operations/${PLUGIN_NAME}`);
|
||
const data = await response.json();
|
||
|
||
if (data && data.operations) {
|
||
operations = data.operations;
|
||
|
||
let html = '';
|
||
operationSelectElem.innerHTML = '<option value="">-- 请选择操作 --</option>';
|
||
|
||
operations.forEach(op => {
|
||
// 更新可用操作列表
|
||
html += `
|
||
<div class="action-item">
|
||
<span><strong>${op.name}</strong>: ${op.description || '无描述'}</span>
|
||
<button onclick="selectOperation('${op.name}')">使用此操作</button>
|
||
</div>
|
||
`;
|
||
|
||
// 更新操作选择下拉框
|
||
operationSelectElem.innerHTML += `<option value="${op.name}">${op.name} - ${op.description || '无描述'}</option>`;
|
||
});
|
||
|
||
actionsListElem.innerHTML = html || '<p>没有可用的操作。</p>';
|
||
} else {
|
||
actionsListElem.innerHTML = '<p class="error">未找到任何操作。</p>';
|
||
operationSelectElem.innerHTML = '<option value="">无可用操作</option>';
|
||
}
|
||
} catch (error) {
|
||
document.getElementById('actionsList').innerHTML = `<p class="error">加载操作列表失败: ${error.message}</p>`;
|
||
}
|
||
}
|
||
|
||
// 选择操作
|
||
function selectOperation(operationName) {
|
||
// 切换到执行标签页
|
||
document.querySelectorAll('.tab-button').forEach(btn => {
|
||
btn.classList.remove('active');
|
||
});
|
||
document.querySelectorAll('.tab-content').forEach(content => {
|
||
content.classList.remove('active');
|
||
});
|
||
|
||
document.querySelector('[data-tab="execute"]').classList.add('active');
|
||
document.getElementById('execute').classList.add('active');
|
||
|
||
// 设置选定的操作
|
||
document.getElementById('operation').value = operationName;
|
||
|
||
// 更新参数字段
|
||
updateParameterFields();
|
||
}
|
||
|
||
// 更新参数字段
|
||
function updateParameterFields() {
|
||
const operationName = document.getElementById('operation').value;
|
||
const parameterFieldsElem = document.getElementById('parameterFields');
|
||
|
||
if (!operationName) {
|
||
parameterFieldsElem.innerHTML = '<p>请先选择一个操作。</p>';
|
||
return;
|
||
}
|
||
|
||
const operation = operations.find(op => op.name === operationName);
|
||
|
||
if (operation && operation.params && operation.params.length > 0) {
|
||
let html = '';
|
||
|
||
operation.params.forEach(param => {
|
||
html += `
|
||
<div class="input-group">
|
||
<label for="param-${param.name}">${param.name}${param.required ? ' *' : ''}:</label>
|
||
<div class="parameter-description">${param.description || ''}</div>
|
||
`;
|
||
|
||
if (param.type === 'boolean' || param.type === 'bool') {
|
||
html += `
|
||
<div class="checkbox-group">
|
||
<input type="checkbox" id="param-${param.name}" name="${param.name}" ${param.default ? 'checked' : ''}>
|
||
</div>
|
||
`;
|
||
} else if (param.type === 'integer' || param.type === 'int' || param.type === 'int64') {
|
||
html += `
|
||
<input type="number" id="param-${param.name}" name="${param.name}" value="${param.default || ''}" ${param.required ? 'required' : ''}>
|
||
`;
|
||
} else if (param.type === 'float' || param.type === 'float64') {
|
||
html += `
|
||
<input type="number" id="param-${param.name}" name="${param.name}" step="0.01" value="${param.default || ''}" ${param.required ? 'required' : ''}>
|
||
`;
|
||
} else {
|
||
html += `
|
||
<input type="text" id="param-${param.name}" name="${param.name}" value="${param.default || ''}" ${param.required ? 'required' : ''}>
|
||
`;
|
||
}
|
||
|
||
html += `</div>`;
|
||
});
|
||
|
||
parameterFieldsElem.innerHTML = html;
|
||
} else {
|
||
parameterFieldsElem.innerHTML = '<p>此操作不需要参数。</p>';
|
||
}
|
||
}
|
||
|
||
// 执行操作
|
||
async function executeOperation(event) {
|
||
event.preventDefault();
|
||
|
||
const operationName = document.getElementById('operation').value;
|
||
if (!operationName) {
|
||
alert('请选择一个操作');
|
||
return;
|
||
}
|
||
|
||
const operation = operations.find(op => op.name === operationName);
|
||
if (!operation) {
|
||
alert('未找到所选操作');
|
||
return;
|
||
}
|
||
|
||
// 收集参数
|
||
const parameters = {};
|
||
|
||
if (operation.params && operation.params.length > 0) {
|
||
operation.params.forEach(param => {
|
||
const element = document.getElementById(`param-${param.name}`);
|
||
if (element) {
|
||
if (param.type === 'boolean' || param.type === 'bool') {
|
||
parameters[param.name] = element.checked;
|
||
} else if (param.type === 'integer' || param.type === 'int' || param.type === 'int64') {
|
||
parameters[param.name] = parseInt(element.value || '0');
|
||
} else if (param.type === 'float' || param.type === 'float64') {
|
||
parameters[param.name] = parseFloat(element.value || '0');
|
||
} else {
|
||
parameters[param.name] = element.value;
|
||
}
|
||
}
|
||
});
|
||
}
|
||
|
||
try {
|
||
const resultElem = document.getElementById('resultPanel');
|
||
resultElem.innerHTML = '正在执行操作...';
|
||
|
||
const response = await fetch('/api/execute', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({
|
||
plugin: PLUGIN_NAME,
|
||
operation: operationName,
|
||
parameters: parameters
|
||
})
|
||
});
|
||
|
||
const data = await response.json();
|
||
|
||
if (data.success) {
|
||
resultElem.innerHTML = `
|
||
<div class="success">操作成功!</div>
|
||
<h3>结果:</h3>
|
||
<pre>${formatJSON(data.result)}</pre>
|
||
`;
|
||
} else {
|
||
resultElem.innerHTML = `
|
||
<div class="error">操作失败: ${data.error}</div>
|
||
<h3>请求参数:</h3>
|
||
<pre>${formatJSON(parameters)}</pre>
|
||
`;
|
||
}
|
||
} catch (error) {
|
||
document.getElementById('resultPanel').innerHTML = `
|
||
<div class="error">执行操作时发生错误: ${error.message}</div>
|
||
`;
|
||
}
|
||
}
|
||
|
||
// 订阅事件
|
||
function subscribeEvents() {
|
||
if (isSubscribed) {
|
||
alert('已经订阅了事件');
|
||
return;
|
||
}
|
||
|
||
const eventTypes = [];
|
||
if (document.getElementById('eventInitialized').checked) eventTypes.push('initialized');
|
||
if (document.getElementById('eventStarted').checked) eventTypes.push('started');
|
||
if (document.getElementById('eventStopped').checked) eventTypes.push('stopped');
|
||
if (document.getElementById('eventCustom').checked) eventTypes.push('custom');
|
||
|
||
if (eventTypes.length === 0) {
|
||
alert('请至少选择一种事件类型');
|
||
return;
|
||
}
|
||
|
||
// 使用EventSource订阅服务器发送的事件
|
||
const eventPanel = document.getElementById('eventPanel');
|
||
eventPanel.innerHTML = '<div class="success">正在连接事件流...</div>';
|
||
|
||
// 创建EventSource连接
|
||
eventSource = new EventSource(`/events/${PLUGIN_NAME}`);
|
||
|
||
// 连接建立时
|
||
eventSource.addEventListener('connected', function(e) {
|
||
const data = JSON.parse(e.data);
|
||
eventPanel.innerHTML = `<div class="success">已成功连接到插件 ${data.pluginName} 的事件流</div>`;
|
||
});
|
||
|
||
// 处理各种事件类型
|
||
for (const eventType of ['initialized', 'started', 'stopped', 'custom', 'error', 'loaded']) {
|
||
eventSource.addEventListener(eventType, function(e) {
|
||
const event = JSON.parse(e.data);
|
||
|
||
// 根据选中的事件类型过滤
|
||
if ((eventType === 'initialized' && !document.getElementById('eventInitialized').checked) ||
|
||
(eventType === 'started' && !document.getElementById('eventStarted').checked) ||
|
||
(eventType === 'stopped' && !document.getElementById('eventStopped').checked) ||
|
||
(eventType === 'custom' && !document.getElementById('eventCustom').checked)) {
|
||
return;
|
||
}
|
||
|
||
eventPanel.innerHTML += `
|
||
<div class="event-log">
|
||
<span class="event-timestamp">[${getCurrentTime()}]</span>
|
||
<strong>事件类型: ${eventType}</strong><br>
|
||
数据: ${formatJSON(event)}
|
||
</div>
|
||
`;
|
||
|
||
// 滚动到底部
|
||
eventPanel.scrollTop = eventPanel.scrollHeight;
|
||
});
|
||
}
|
||
|
||
// 处理通用消息
|
||
eventSource.onmessage = function(e) {
|
||
console.log('收到未处理的事件: ', e.data);
|
||
};
|
||
|
||
// 处理连接打开
|
||
eventSource.onopen = function() {
|
||
console.log('连接已打开');
|
||
isSubscribed = true;
|
||
document.getElementById('subscribeEvents').disabled = true;
|
||
document.getElementById('unsubscribeEvents').disabled = false;
|
||
};
|
||
|
||
// 处理错误
|
||
eventSource.onerror = function(e) {
|
||
console.error('SSE错误:', e);
|
||
eventPanel.innerHTML += `
|
||
<div class="event-log error">
|
||
<span class="event-timestamp">[${getCurrentTime()}]</span>
|
||
<strong>连接错误</strong><br>
|
||
尝试重新连接...
|
||
</div>
|
||
`;
|
||
};
|
||
}
|
||
|
||
// 取消订阅事件
|
||
function unsubscribeEvents() {
|
||
if (!isSubscribed || !eventSource) {
|
||
alert('尚未订阅事件');
|
||
return;
|
||
}
|
||
|
||
// 关闭EventSource连接
|
||
eventSource.close();
|
||
eventSource = null;
|
||
isSubscribed = false;
|
||
|
||
document.getElementById('subscribeEvents').disabled = false;
|
||
document.getElementById('unsubscribeEvents').disabled = true;
|
||
|
||
const eventPanel = document.getElementById('eventPanel');
|
||
eventPanel.innerHTML += '<div class="event-log"><span class="event-timestamp">[' + getCurrentTime() + ']</span> 已取消订阅事件</div>';
|
||
}
|
||
|
||
// 清空事件日志
|
||
function clearEventLog() {
|
||
const eventPanel = document.getElementById('eventPanel');
|
||
eventPanel.innerHTML = isSubscribed
|
||
? '<div class="success">已成功订阅事件,等待事件发生...</div>'
|
||
: '尚未接收到任何事件。';
|
||
}
|
||
|
||
// 触发自定义事件
|
||
async function triggerCustomEvent() {
|
||
try {
|
||
const eventPanel = document.getElementById('eventPanel');
|
||
|
||
// 调用服务器触发自定义事件
|
||
const response = await fetch('/api/trigger-event', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({
|
||
plugin: PLUGIN_NAME,
|
||
type: 'custom',
|
||
data: {
|
||
action: 'user_triggered',
|
||
time: new Date().toISOString(),
|
||
message: '用户触发的自定义事件'
|
||
}
|
||
})
|
||
});
|
||
|
||
const data = await response.json();
|
||
|
||
if (data.success) {
|
||
eventPanel.innerHTML += `
|
||
<div class="event-log">
|
||
<span class="event-timestamp">[${getCurrentTime()}]</span>
|
||
<span>已触发自定义事件,如果已订阅事件,应该会收到通知</span>
|
||
</div>
|
||
`;
|
||
} else {
|
||
eventPanel.innerHTML += `
|
||
<div class="event-log error">
|
||
<span class="event-timestamp">[${getCurrentTime()}]</span>
|
||
<span>触发自定义事件失败: ${data.message || '未知错误'}</span>
|
||
</div>
|
||
`;
|
||
}
|
||
} catch (error) {
|
||
const eventPanel = document.getElementById('eventPanel');
|
||
eventPanel.innerHTML += `
|
||
<div class="event-log error">
|
||
<span class="event-timestamp">[${getCurrentTime()}]</span>
|
||
<span>触发自定义事件时发生错误: ${error.message}</span>
|
||
</div>
|
||
`;
|
||
}
|
||
}
|
||
|
||
// 快速测试操作
|
||
async function quickTestOperation(operationType) {
|
||
let operationName = operationType;
|
||
let parameters = {};
|
||
|
||
// 根据操作类型设置默认参数
|
||
switch (operationType) {
|
||
case 'calculate':
|
||
parameters = { expression: '10*5' };
|
||
break;
|
||
case 'format':
|
||
parameters = { text: 'hello world', type: 'upper' };
|
||
break;
|
||
case 'store':
|
||
parameters = { key: 'test_key', value: '测试数据 ' + new Date().toLocaleString() };
|
||
break;
|
||
case 'retrieve':
|
||
parameters = { key: 'test_key' };
|
||
break;
|
||
case 'counter':
|
||
parameters = { action: 'increment' };
|
||
break;
|
||
case 'sort':
|
||
parameters = { items: '5,3,8,1,9,2', numeric: true };
|
||
break;
|
||
}
|
||
|
||
try {
|
||
const resultElem = document.getElementById('resultPanel');
|
||
resultElem.innerHTML = `正在执行快速测试: ${operationName}...`;
|
||
|
||
// 切换到执行标签页
|
||
document.querySelectorAll('.tab-button').forEach(btn => {
|
||
btn.classList.remove('active');
|
||
});
|
||
document.querySelectorAll('.tab-content').forEach(content => {
|
||
content.classList.remove('active');
|
||
});
|
||
|
||
document.querySelector('[data-tab="execute"]').classList.add('active');
|
||
document.getElementById('execute').classList.add('active');
|
||
|
||
const response = await fetch('/api/execute', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({
|
||
plugin: PLUGIN_NAME,
|
||
operation: operationName,
|
||
parameters: parameters
|
||
})
|
||
});
|
||
|
||
const data = await response.json();
|
||
|
||
if (data.success) {
|
||
resultElem.innerHTML = `
|
||
<div class="success">快速测试成功!</div>
|
||
<h3>操作: ${operationName}</h3>
|
||
<h3>参数:</h3>
|
||
<pre>${formatJSON(parameters)}</pre>
|
||
<h3>结果:</h3>
|
||
<pre>${formatJSON(data.result)}</pre>
|
||
`;
|
||
} else {
|
||
resultElem.innerHTML = `
|
||
<div class="error">快速测试失败: ${data.error}</div>
|
||
<h3>操作: ${operationName}</h3>
|
||
<h3>参数:</h3>
|
||
<pre>${formatJSON(parameters)}</pre>
|
||
`;
|
||
}
|
||
} catch (error) {
|
||
document.getElementById('resultPanel').innerHTML = `
|
||
<div class="error">执行快速测试时发生错误: ${error.message}</div>
|
||
`;
|
||
}
|
||
}
|
||
|
||
// 辅助函数:格式化JSON
|
||
function formatJSON(data) {
|
||
if (!data) return 'null';
|
||
try {
|
||
return JSON.stringify(data, null, 2);
|
||
} catch (e) {
|
||
return String(data);
|
||
}
|
||
}
|
||
|
||
// 辅助函数:获取当前时间
|
||
function getCurrentTime() {
|
||
const now = new Date();
|
||
return now.toLocaleTimeString() + '.' + now.getMilliseconds().toString().padStart(3, '0');
|
||
}
|
||
</script>
|
||
</body>
|
||
</html> |