Files
clean-chrome/popup.js

443 lines
18 KiB
JavaScript

document.addEventListener('DOMContentLoaded', () => {
const addBtn = document.getElementById('add-btn');
const addForm = document.getElementById('add-form');
const cancelBtn = document.getElementById('cancel-btn');
const saveBtn = document.getElementById('save-btn');
const envSelect = document.getElementById('env-select');
const clientInput = document.getElementById('client-input');
const usernameInput = document.getElementById('username-input');
const passwordInput = document.getElementById('password-input');
const rulesList = document.getElementById('rules-list');
const rulesContainer = document.querySelector('.rules-container');
const formTitle = document.getElementById('form-title');
const editingIdInput = document.getElementById('editing-id');
const loginView = document.getElementById('login-view');
const registerView = document.getElementById('register-view');
const appView = document.getElementById('app-view');
const authLoginBtn = document.getElementById('auth-login-btn');
const authRegBtn = document.getElementById('auth-reg-btn');
const goToRegister = document.getElementById('go-to-register');
const goToLogin = document.getElementById('go-to-login');
const logoutBtn = document.getElementById('logout-btn');
if (logoutBtn) {
logoutBtn.addEventListener('click', (e) => {
e.preventDefault();
if (confirm('确定要退出当前同步账号吗?')) {
chrome.storage.local.remove(['quickPurgeUser'], () => {
currentUser = null;
rules = [];
renderRules(); // 立即清空界面显示的 rules
showView('login');
});
}
});
}
const ENV_CONFIG = {
DD_PP: {
domain: 'app135148.dingtalkoxm.com',
url: 'https://app135148.dingtalkoxm.com/user/login'
},
DD_ATS: {
domain: 'app135149.dingtalkoxm.com',
url: 'https://app135149.dingtalkoxm.com/login'
},
STD_PP: {
domain: 'core.mokahr.com',
url: 'https://core.mokahr.com'
},
STD_ATS: {
domain: 'app.mokahr.com',
url: 'https://app.mokahr.com'
}
};
const API_BASE = 'https://sqd.zhouzishen.cn/api'; // 已更新为您的生产服务器地址
let currentUser = null;
let rules = [];
let dragStartIndex = -1;
// --- 工具函数 ---
function hasChinese(str) {
return /[\u4E00-\u9FA5]/.test(str);
}
// --- Auth View Controllers ---
function showView(viewName) {
if (loginView) loginView.classList.add('hidden');
if (registerView) registerView.classList.add('hidden');
if (appView) appView.classList.add('hidden');
if (viewName === 'app') {
if (appView) appView.classList.remove('hidden');
} else {
if (viewName === 'login' && loginView) loginView.classList.remove('hidden');
else if (viewName === 'register' && registerView) registerView.classList.remove('hidden');
}
}
if (goToRegister) goToRegister.addEventListener('click', (e) => { e.preventDefault(); showView('register'); });
if (goToLogin) goToLogin.addEventListener('click', (e) => { e.preventDefault(); showView('login'); });
if (authRegBtn) {
authRegBtn.addEventListener('click', async () => {
const username = document.getElementById('auth-reg-username').value.trim();
const password = document.getElementById('auth-reg-password').value;
const inviteCode = document.getElementById('auth-reg-invite').value.trim();
if (!username || !password || !inviteCode) return alert('请填写完整信息');
if (hasChinese(username) || hasChinese(password) || hasChinese(inviteCode)) {
return alert('账号、密码和邀请码不允许包含中文字符');
}
authRegBtn.textContent = '注册中...';
try {
const res = await fetch(`${API_BASE}/register`, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password, inviteCode })
});
const data = await res.json();
if (data.code === 0) {
alert('注册成功,请使用该账号登录');
document.getElementById('auth-login-username').value = username;
document.getElementById('auth-login-password').value = password;
showView('login');
} else alert(data.msg);
} catch (e) { alert('请求失败,请确保本地服务器已启动运行。' + e); }
finally { authRegBtn.textContent = '注册'; }
});
}
if (authLoginBtn) {
authLoginBtn.addEventListener('click', async () => {
const username = document.getElementById('auth-login-username').value.trim();
const password = document.getElementById('auth-login-password').value;
if (!username || !password) return alert('请输入账号和密码');
if (hasChinese(username) || hasChinese(password)) {
return alert('账号和密码格式不正确(不能包含中文)');
}
authLoginBtn.textContent = '登录中...';
try {
const res = await fetch(`${API_BASE}/login`, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
});
const data = await res.json();
if (data.code === 0) {
currentUser = { username, password };
rules = processLoadedRules(data.data.rules || []);
chrome.storage.local.set({ quickPurgeUser: { username, password, rules } }, () => {
showView('app');
renderRules();
});
} else alert(data.msg);
} catch (e) { alert('请求失败,请确保本地服务器已启动运行。' + e); }
finally { authLoginBtn.textContent = '登录'; }
});
}
// Load Init from local cache
function loadAndInitBaseApp() {
if (typeof chrome !== 'undefined' && chrome.storage) {
chrome.storage.local.get(['quickPurgeUser'], async (result) => {
const user = result.quickPurgeUser;
if (user && user.username && user.password) {
currentUser = user;
rules = processLoadedRules(user.rules || []);
showView('app'); // Proceed to app UI
renderRules();
// 静默发起登录获取云端最新数据
try {
const res = await fetch(`${API_BASE}/login`, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: user.username, password: user.password })
});
const data = await res.json();
if (data.code === 0) {
rules = processLoadedRules(data.data.rules || []);
chrome.storage.local.set({ quickPurgeUser: { ...currentUser, rules } });
renderRules();
}
} catch (e) {
console.log("Offline mode, using local storage cache fallback.");
}
} else {
showView('login'); // Not logged in
}
});
} else {
showView('login');
}
}
function processLoadedRules(rawRules) {
return rawRules.map(rule => {
if (rule.env === 'PP') rule.env = 'DD_PP';
if (rule.env === 'ATS') rule.env = 'DD_ATS';
return rule;
});
}
function saveRules() {
if (typeof chrome !== 'undefined' && chrome.storage && currentUser) {
chrome.storage.local.set({ quickPurgeUser: { ...currentUser, rules } }, () => {
renderRules();
// 异步防抖提交到自建服务云端
fetch(`${API_BASE}/sync`, {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: currentUser.username, rules })
}).catch(e => console.error("Sync error:", e));
});
} else {
renderRules();
}
}
function toggleAddForm(mode = 'add', ruleId = null) {
if (mode === 'edit') {
const rule = rules.find(r => r.id === ruleId);
if (rule) {
formTitle.textContent = '编辑配置';
editingIdInput.value = rule.id;
envSelect.value = rule.env;
clientInput.value = rule.clientName;
usernameInput.value = rule.username;
passwordInput.value = rule.password;
addForm.classList.remove('hidden');
rulesContainer.classList.add('hidden');
addBtn.classList.add('hidden'); // Optional: hide add btn while editing
}
} else {
formTitle.textContent = '添加配置';
editingIdInput.value = '';
resetForm();
addForm.classList.toggle('hidden');
if (addForm.classList.contains('hidden')) {
rulesContainer.classList.remove('hidden');
addBtn.classList.remove('hidden');
} else {
rulesContainer.classList.add('hidden');
addBtn.classList.add('hidden');
}
}
if (!addForm.classList.contains('hidden')) {
clientInput.focus();
}
}
addBtn.addEventListener('click', () => toggleAddForm('add'));
cancelBtn.addEventListener('click', () => {
addForm.classList.add('hidden');
rulesContainer.classList.remove('hidden');
addBtn.classList.remove('hidden');
resetForm();
});
function resetForm() {
editingIdInput.value = '';
envSelect.value = 'DD_PP';
clientInput.value = '';
usernameInput.value = '';
passwordInput.value = '';
}
saveBtn.addEventListener('click', () => {
const env = envSelect.value;
const clientName = clientInput.value.trim();
const username = usernameInput.value.trim();
const password = passwordInput.value;
const editingId = editingIdInput.value;
if (!clientName || !username || !password) {
alert('请填写完整的客户名称、账号和密码!');
return;
}
if (editingId) {
// Update existing rule
const index = rules.findIndex(r => r.id === editingId);
if (index !== -1) {
rules[index] = {
...rules[index],
env: env,
clientName: clientName,
username: username,
password: password
};
}
} else {
// Add new rule
rules.push({
id: Date.now().toString(),
env: env,
clientName: clientName,
username: username,
password: password
});
}
saveRules();
addForm.classList.add('hidden');
rulesContainer.classList.remove('hidden');
addBtn.classList.remove('hidden');
resetForm();
});
function renderRules() {
rulesList.innerHTML = '';
if (rules.length === 0) {
rulesList.innerHTML = `
<div class="empty-state">
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect>
<line x1="9" y1="9" x2="15" y2="15"></line>
<line x1="15" y1="9" x2="9" y2="15"></line>
</svg>
<span>暂无账号配置,点击上方 "+" 添加</span>
</div>`;
return;
}
rules.forEach((rule, index) => {
const li = document.createElement('li');
li.className = 'rule-item';
li.setAttribute('draggable', 'true');
li.setAttribute('data-index', index.toString());
li.innerHTML = `
<div class="drag-handle" title="拖拽排序">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M8 9h8M8 15h8" /></svg>
</div>
<div class="rule-info" data-id="${rule.id}" title="点击执行一键登录">
<div class="rule-domain">
${rule.clientName}
<span class="rule-env env-${rule.env.toLowerCase().replace('_', '-')}">${rule.env.replace('_', ' ')}</span>
</div>
<div class="rule-acc">帐号: ${rule.username}</div>
</div>
<div class="rule-actions">
<button class="icon-btn btn-edit" data-id="${rule.id}" title="编辑此配置">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
</svg>
</button>
<button class="icon-btn btn-delete" data-id="${rule.id}" title="删除此配置">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/></svg>
</button>
</div>
`;
addDragEvents(li);
rulesList.appendChild(li);
});
document.querySelectorAll('.rule-info').forEach(infoArea => {
infoArea.addEventListener('click', (e) => {
const id = e.currentTarget.getAttribute('data-id');
const rule = rules.find(r => r.id === id);
if (rule) {
executePurgeAndLogin(rule, e.currentTarget);
}
});
});
document.querySelectorAll('.btn-edit').forEach(btn => {
btn.addEventListener('click', (e) => {
e.stopPropagation();
const id = e.currentTarget.getAttribute('data-id');
toggleAddForm('edit', id);
});
});
document.querySelectorAll('.btn-delete').forEach(btn => {
btn.addEventListener('click', (e) => {
e.stopPropagation();
const id = e.currentTarget.getAttribute('data-id');
if (confirm('确定要删除此配置吗?')) {
rules = rules.filter(r => r.id !== id);
saveRules();
}
});
});
}
// --- Drag and Drop functionality ---
function addDragEvents(item) {
item.addEventListener('dragstart', (e) => {
dragStartIndex = parseInt(e.currentTarget.getAttribute('data-index'));
e.currentTarget.classList.add('dragging');
// Set empty ghost image
if (e.dataTransfer) {
e.dataTransfer.effectAllowed = 'move';
// Need to set data to allow dragging in Firefox
e.dataTransfer.setData('text/plain', dragStartIndex);
}
});
item.addEventListener('dragend', (e) => {
e.currentTarget.classList.remove('dragging');
});
item.addEventListener('dragover', (e) => {
e.preventDefault();
// Allow drop
return false;
});
item.addEventListener('dragenter', (e) => {
e.preventDefault();
});
item.addEventListener('drop', (e) => {
e.stopPropagation();
const dragEndIndex = parseInt(e.currentTarget.closest('.rule-item').getAttribute('data-index'));
if (dragStartIndex !== dragEndIndex) {
swapItems(dragStartIndex, dragEndIndex);
}
return false;
});
}
function swapItems(fromIndex, toIndex) {
// Array manipulation for drag sorting
const itemOne = rules[fromIndex];
rules.splice(fromIndex, 1);
rules.splice(toIndex, 0, itemOne);
saveRules();
}
function executePurgeAndLogin(rule, element) {
const originalContent = element.innerHTML;
element.innerHTML = `<div class="loading-state"><svg class="loading" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M12 2v4m0 12v4M4.93 4.93l2.83 2.83m8.48 8.48l2.83 2.83M2 12h4m12 0h4M4.93 19.07l2.83-2.83m8.48-8.48l2.83-2.83"/></svg><span>清理中...</span></div>`;
if (typeof chrome !== 'undefined' && chrome.runtime) {
const config = ENV_CONFIG[rule.env];
chrome.runtime.sendMessage({
action: "purgeAndRedirect",
domain: config.domain,
targetUrl: config.url,
username: rule.username,
password: rule.password
}, (response) => {
// Return text after a short delay
setTimeout(() => {
if (element) element.innerHTML = originalContent;
}, 1500);
});
}
}
loadAndInitBaseApp();
});