198 lines
6.6 KiB
JavaScript
198 lines
6.6 KiB
JavaScript
// app.js
|
|
import { createApp, ref, reactive, computed, onMounted } from 'vue';
|
|
|
|
// 导入各模块
|
|
import { apiFetch } from './modules/api.js';
|
|
import { login, loadAuthState, clearAuthState, saveAuthState } from './modules/auth.js';
|
|
import { loadConfig, saveConfig as saveConfigApi } from './modules/config.js';
|
|
import { loadProxies, createProxy, updateProxy, deleteProxy, filterProxies } from './modules/proxies.js';
|
|
import { getFrpcStatus, reloadFrpc, startFrpc, stopFrpc } from './modules/frpc.js';
|
|
import {
|
|
activeTab, switchTab,
|
|
dialogVisible, dialogMode, dialogForm,
|
|
openAddDialog, openEditDialog, closeDialog,
|
|
searchKeyword,
|
|
} from './modules/ui.js';
|
|
import { importToml, exportToml } from './modules/import.js'; // 如果有的话
|
|
|
|
const app = createApp({
|
|
setup() {
|
|
// ===== 登录态 =====
|
|
const loggedIn = ref(false);
|
|
const loading = ref(false);
|
|
const loginError = ref('');
|
|
const loginForm = reactive({ username: '', password: '' });
|
|
const token = ref('');
|
|
const tokenExpireTime = ref(0);
|
|
const transitioning = ref(false);
|
|
const contentVisible = ref(false);
|
|
let expireTimer = null;
|
|
|
|
// ===== 数据 =====
|
|
const globalConfig = reactive({});
|
|
const proxies = ref([]);
|
|
const frpcRunning = ref(false);
|
|
|
|
// ===== 初始化 =====
|
|
onMounted(() => {
|
|
const saved = loadAuthState();
|
|
if (saved.valid) {
|
|
token.value = saved.token;
|
|
tokenExpireTime.value = saved.expire;
|
|
loginForm.username = saved.username;
|
|
loggedIn.value = true;
|
|
loadAllData().then(() => {
|
|
contentVisible.value = true;
|
|
startExpireTimer();
|
|
});
|
|
}
|
|
});
|
|
|
|
const startExpireTimer = () => {
|
|
if (expireTimer) clearInterval(expireTimer);
|
|
expireTimer = setInterval(() => {
|
|
if (Date.now() >= tokenExpireTime.value) {
|
|
doLogout();
|
|
alert('登录已超时,请重新登录');
|
|
}
|
|
}, 5000);
|
|
};
|
|
|
|
const loadAllData = async () => {
|
|
const [cfg, list, running] = await Promise.all([
|
|
loadConfig(),
|
|
loadProxies(),
|
|
getFrpcStatus(),
|
|
]);
|
|
Object.assign(globalConfig, cfg);
|
|
proxies.value = list;
|
|
frpcRunning.value = running;
|
|
};
|
|
|
|
// ===== 登录/退出 =====
|
|
const doLogin = async () => {
|
|
if (!loginForm.username || !loginForm.password) {
|
|
loginError.value = '请输入用户名和密码';
|
|
return;
|
|
}
|
|
loading.value = true;
|
|
loginError.value = '';
|
|
const result = await login(loginForm.username, loginForm.password);
|
|
if (result.success) {
|
|
token.value = result.token;
|
|
tokenExpireTime.value = result.expire;
|
|
transitioning.value = true;
|
|
loggedIn.value = true;
|
|
setTimeout(async () => {
|
|
transitioning.value = false;
|
|
await loadAllData();
|
|
loading.value = false;
|
|
setTimeout(() => {
|
|
contentVisible.value = true;
|
|
startExpireTimer();
|
|
}, 300);
|
|
}, 500);
|
|
} else {
|
|
loginError.value = result.error;
|
|
loading.value = false;
|
|
}
|
|
};
|
|
|
|
const doLogout = () => {
|
|
if (expireTimer) clearInterval(expireTimer);
|
|
contentVisible.value = false;
|
|
loggedIn.value = false;
|
|
token.value = '';
|
|
tokenExpireTime.value = 0;
|
|
loginForm.username = '';
|
|
loginForm.password = '';
|
|
loading.value = false;
|
|
transitioning.value = false;
|
|
clearAuthState();
|
|
};
|
|
|
|
// ===== 保存配置 =====
|
|
const saveConfig = async () => {
|
|
const result = await saveConfigApi(globalConfig);
|
|
if (result.code === 0) {
|
|
frpcRunning.value = await getFrpcStatus();
|
|
alert('配置已更新并热加载');
|
|
} else {
|
|
alert('保存失败: ' + result.msg);
|
|
}
|
|
};
|
|
|
|
// ===== 隧道操作 =====
|
|
const toggleProxy = async (p) => {
|
|
const result = await updateProxy(p);
|
|
if (result.code !== 0) {
|
|
alert('更新失败: ' + result.msg);
|
|
proxies.value = await loadProxies();
|
|
} else {
|
|
frpcRunning.value = await getFrpcStatus();
|
|
}
|
|
};
|
|
|
|
const removeProxy = async (id) => {
|
|
if (!confirm('确定要删除这条隧道吗?')) return;
|
|
const result = await deleteProxy(id);
|
|
if (result.code === 0) {
|
|
proxies.value = await loadProxies();
|
|
frpcRunning.value = await getFrpcStatus();
|
|
} else {
|
|
alert('删除失败: ' + result.msg);
|
|
}
|
|
};
|
|
|
|
const confirmDialog = async () => {
|
|
const form = dialogForm.value;
|
|
if (!form.name || !form.localIP || !form.localPort || !form.remotePort) {
|
|
alert('请填写完整信息');
|
|
return;
|
|
}
|
|
const result = dialogMode.value === 'add'
|
|
? await createProxy(form)
|
|
: await updateProxy(form);
|
|
if (result.code === 0) {
|
|
closeDialog();
|
|
proxies.value = await loadProxies();
|
|
frpcRunning.value = await getFrpcStatus();
|
|
} else {
|
|
alert('操作失败: ' + result.msg);
|
|
}
|
|
};
|
|
|
|
// ===== 计算属性 =====
|
|
const filteredProxies = computed(() => filterProxies(proxies.value, searchKeyword.value));
|
|
|
|
return {
|
|
loggedIn,
|
|
loading,
|
|
loginError,
|
|
loginForm,
|
|
doLogin,
|
|
doLogout,
|
|
activeTab,
|
|
switchTab,
|
|
globalConfig,
|
|
saveConfig,
|
|
proxies,
|
|
filteredProxies,
|
|
toggleProxy,
|
|
removeProxy,
|
|
frpcRunning,
|
|
dialogVisible,
|
|
dialogMode,
|
|
dialogForm,
|
|
openAddDialog,
|
|
openEditDialog,
|
|
closeDialog,
|
|
confirmDialog,
|
|
searchKeyword,
|
|
transitioning,
|
|
contentVisible,
|
|
};
|
|
},
|
|
});
|
|
|
|
app.mount('#app'); |