chore: sync current WechatHookBot workspace
This commit is contained in:
67
utils/webui_static/composables/useApi.js
Normal file
67
utils/webui_static/composables/useApi.js
Normal file
@@ -0,0 +1,67 @@
|
||||
window.useApi = function useApi() {
|
||||
async function request(url, options = {}, extra = {}) {
|
||||
const { silent = false, skipAuthRedirect = false } = extra;
|
||||
try {
|
||||
const mergedHeaders = {
|
||||
'Content-Type': 'application/json',
|
||||
...(options.headers || {}),
|
||||
};
|
||||
const res = await fetch(url, {
|
||||
credentials: 'same-origin',
|
||||
...options,
|
||||
headers: mergedHeaders,
|
||||
});
|
||||
|
||||
let json = null;
|
||||
try {
|
||||
json = await res.json();
|
||||
} catch (e) {
|
||||
if (!silent) ElementPlus.ElMessage.error('服务端返回格式错误');
|
||||
return null;
|
||||
}
|
||||
|
||||
if (res.status === 401) {
|
||||
if (!skipAuthRedirect) {
|
||||
window.dispatchEvent(new CustomEvent('webui-auth-required'));
|
||||
}
|
||||
if (!silent) ElementPlus.ElMessage.error(json.error || '未登录或会话已过期');
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!json.ok) {
|
||||
if (!silent) ElementPlus.ElMessage.error(json.error || '请求失败');
|
||||
return null;
|
||||
}
|
||||
return json;
|
||||
} catch (e) {
|
||||
if (!silent) ElementPlus.ElMessage.error('网络请求失败');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
getAuthStatus: () => request('/api/auth/status', {}, { silent: true, skipAuthRedirect: true }),
|
||||
login: (username, password) => request('/api/auth/login', {
|
||||
method: 'POST', body: JSON.stringify({ username, password })
|
||||
}, { skipAuthRedirect: true }),
|
||||
logout: () => request('/api/auth/logout', { method: 'POST' }, { silent: true }),
|
||||
changeCredentials: (payload) => request('/api/auth/change-credentials', {
|
||||
method: 'POST', body: JSON.stringify(payload)
|
||||
}),
|
||||
|
||||
getConfig: () => request('/api/config'),
|
||||
saveConfig: (data) => request('/api/config', {
|
||||
method: 'POST', body: JSON.stringify({ data })
|
||||
}),
|
||||
getPlugins: () => request('/api/plugins'),
|
||||
togglePlugin: (name, enable) => request('/api/plugins/toggle', {
|
||||
method: 'POST', body: JSON.stringify({ name, enable })
|
||||
}),
|
||||
getPluginConfig: (name) =>
|
||||
request(`/api/plugins/${encodeURIComponent(name)}/config`),
|
||||
savePluginConfig: (name, data) =>
|
||||
request(`/api/plugins/${encodeURIComponent(name)}/config`, {
|
||||
method: 'POST', body: JSON.stringify({ data })
|
||||
}),
|
||||
};
|
||||
};
|
||||
35
utils/webui_static/composables/useWebSocket.js
Normal file
35
utils/webui_static/composables/useWebSocket.js
Normal file
@@ -0,0 +1,35 @@
|
||||
window.useWebSocket = function useWebSocket() {
|
||||
const { ref, onUnmounted } = Vue;
|
||||
|
||||
const connected = ref(false);
|
||||
const logs = ref([]);
|
||||
const paused = ref(false);
|
||||
let ws = null;
|
||||
let reconnectTimer = null;
|
||||
|
||||
function connect() {
|
||||
const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
ws = new WebSocket(`${proto}//${location.host}/ws`);
|
||||
|
||||
ws.onopen = () => { connected.value = true; };
|
||||
ws.onclose = () => {
|
||||
connected.value = false;
|
||||
reconnectTimer = setTimeout(connect, 3000);
|
||||
};
|
||||
ws.onmessage = (e) => {
|
||||
// 使用新数组引用,确保依赖 logs 的 watch/computed 能稳定触发(用于自动滚动到底部)
|
||||
const nextLogs = [...logs.value, e.data];
|
||||
logs.value = nextLogs.length > 2000 ? nextLogs.slice(-1500) : nextLogs;
|
||||
};
|
||||
}
|
||||
|
||||
function clear() { logs.value = []; }
|
||||
function togglePause() { paused.value = !paused.value; }
|
||||
|
||||
function destroy() {
|
||||
if (reconnectTimer) clearTimeout(reconnectTimer);
|
||||
if (ws) ws.close();
|
||||
}
|
||||
|
||||
return { connected, logs, paused, connect, clear, togglePause, destroy };
|
||||
};
|
||||
Reference in New Issue
Block a user