提交登录修改

This commit is contained in:
super
2026-05-23 18:32:45 +08:00
parent 2e2de02476
commit 1e087c1aae
52 changed files with 2139 additions and 1572 deletions

View File

@@ -236,6 +236,29 @@
<script>
(function() {
var JAVA_API_BASE = "{{ java_api_base or '' }}".replace(/\/+$/, '');
var AUTH_TOKEN_KEY = 'aiimage_auth_token';
function buildJavaUrl(path) { return JAVA_API_BASE + path; }
function getAuthToken() {
try { return localStorage.getItem(AUTH_TOKEN_KEY) || ''; } catch (e) { return ''; }
}
function authHeaders(extra) {
var headers = extra || {};
var token = getAuthToken();
if (token) headers['Authorization'] = 'Bearer ' + token;
return headers;
}
function jsonHeaders() {
return authHeaders({ 'Content-Type': 'application/json' });
}
function parseJson(r) { return r.json().catch(function() { return {}; }); }
function errMsg(res) {
return (res && (res.message || res.error)) || '请求失败';
}
if (!JAVA_API_BASE) {
alert('Java 服务地址未配置,请联系管理员设置 java_api_base');
}
// Tab 切换
document.querySelectorAll('.tab').forEach(function(t) {
t.onclick = function() {
@@ -268,17 +291,21 @@
}
function loadUsers(page) {
userPage = page || 1;
fetch('/api/admin/users?' + buildUserListQuery(userPage))
.then(function(r) { return r.json(); })
fetch(buildJavaUrl('/api/admin/users?' + buildUserListQuery(userPage)), {
credentials: 'include',
headers: authHeaders()
})
.then(parseJson)
.then(function(res) {
var tbody = document.getElementById('userListBody');
if (!res.success) {
tbody.innerHTML = '<tr><td colspan="6" class="empty-tip">加载失败: ' + (res.error || '') + '</td></tr>';
tbody.innerHTML = '<tr><td colspan="6" class="empty-tip">加载失败: ' + errMsg(res) + '</td></tr>';
return;
}
currentUserRole = res.current_user_role || 'admin';
adminsList = res.admins || [];
var items = res.items || [];
var data = res.data || {};
currentUserRole = data.current_user_role || 'admin';
adminsList = data.admins || [];
var items = data.items || [];
if (items.length === 0) {
tbody.innerHTML = '<tr><td colspan="6" class="empty-tip">暂无用户</td></tr>';
} else {
@@ -290,7 +317,7 @@
'</td></tr>';
}).join('');
}
renderPagination('userPagination', res.total, res.page, res.page_size, loadUsers);
renderPagination('userPagination', data.total, data.page, data.page_size, loadUsers);
bindUserActions();
updateCreateFormByRole();
updateUserFilterByRole();
@@ -362,11 +389,15 @@
document.querySelectorAll('[data-delete]').forEach(function(btn) {
btn.onclick = function() {
if (!confirm('确定删除用户 "' + (btn.dataset.name || '') + '" 吗?')) return;
fetch('/api/admin/user/' + btn.dataset.delete, { method: 'DELETE' })
.then(function(r) { return r.json(); })
fetch(buildJavaUrl('/api/admin/user/' + btn.dataset.delete), {
method: 'DELETE',
credentials: 'include',
headers: authHeaders()
})
.then(parseJson)
.then(function(res) {
if (res.success) { loadUsers(userPage); }
else { alert(res.error || '删除失败'); }
else { alert(errMsg(res)); }
});
};
});
@@ -393,22 +424,23 @@
return;
}
var body = { username: username, password: password, role: role };
if (role === 'normal' && currentUserRole === 'super_admin' && createdById) body.created_by_id = createdById;
fetch('/api/admin/user', {
if (role === 'normal' && currentUserRole === 'super_admin' && createdById) body.createdById = createdById;
fetch(buildJavaUrl('/api/admin/user'), {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
headers: jsonHeaders(),
body: JSON.stringify(body)
})
.then(function(r) { return r.json(); })
.then(parseJson)
.then(function(res) {
if (res.success) {
msgEl.textContent = res.msg || '创建成功';
msgEl.textContent = res.message || '创建成功';
msgEl.classList.add('ok');
document.getElementById('username').value = '';
document.getElementById('password').value = '';
loadUsers(1);
} else {
msgEl.textContent = res.error || '创建失败';
msgEl.textContent = errMsg(res);
msgEl.classList.add('err');
}
})
@@ -429,20 +461,21 @@
if (password) body.password = password;
if (currentUserRole === 'super_admin' && editRoleEl && editRoleEl.offsetParent !== null)
body.role = editRoleEl.value || 'normal';
fetch('/api/admin/user/' + uid, {
fetch(buildJavaUrl('/api/admin/user/' + uid), {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
headers: jsonHeaders(),
body: JSON.stringify(body)
})
.then(function(r) { return r.json(); })
.then(parseJson)
.then(function(res) {
if (res.success) {
msgEl.textContent = res.msg || '保存成功';
msgEl.textContent = res.message || '保存成功';
msgEl.classList.add('ok');
document.getElementById('editUserModal').classList.remove('show');
loadUsers(userPage);
} else {
msgEl.textContent = res.error || '保存失败';
msgEl.textContent = errMsg(res);
msgEl.classList.add('err');
}
});
@@ -469,15 +502,19 @@
}
function loadHistory(page) {
historyPage = page || 1;
fetch('/api/admin/history?' + buildHistoryQuery(historyPage))
.then(function(r) { return r.json(); })
fetch(buildJavaUrl('/api/admin/history?' + buildHistoryQuery(historyPage)), {
credentials: 'include',
headers: authHeaders()
})
.then(parseJson)
.then(function(res) {
var tbody = document.getElementById('historyListBody');
if (!res.success) {
tbody.innerHTML = '<tr><td colspan="5" class="empty-tip">加载失败: ' + (res.error || '') + '</td></tr>';
tbody.innerHTML = '<tr><td colspan="5" class="empty-tip">加载失败: ' + errMsg(res) + '</td></tr>';
return;
}
var items = res.items || [];
var data = res.data || {};
var items = data.items || [];
if (items.length === 0) {
tbody.innerHTML = '<tr><td colspan="5" class="empty-tip">暂无记录</td></tr>';
} else {
@@ -492,20 +529,24 @@
'<div class="thumb-wrap">' + (thumbs || '-') + '</div></td></tr>';
}).join('');
}
renderPagination('historyPagination', res.total, res.page, res.page_size, loadHistory);
renderPagination('historyPagination', data.total, data.page, data.page_size, loadHistory);
})
.catch(function() {
document.getElementById('historyListBody').innerHTML = '<tr><td colspan="5" class="empty-tip">请求失败</td></tr>';
});
}
function loadUserOptions() {
fetch('/api/admin/users?page=1&page_size=999')
.then(function(r) { return r.json(); })
fetch(buildJavaUrl('/api/admin/users?page=1&page_size=50'), {
credentials: 'include',
headers: authHeaders()
})
.then(parseJson)
.then(function(res) {
var sel = document.getElementById('filterUser');
var cur = sel.value;
sel.innerHTML = '<option value="">全部用户</option>';
(res.items || []).forEach(function(u) {
var data = res.data || {};
(data.items || []).forEach(function(u) {
var opt = document.createElement('option');
opt.value = u.id;
opt.textContent = u.username + ' (' + roleLabel(u.role || 'normal') + ')';
@@ -520,6 +561,9 @@
function renderPagination(elId, total, page, pageSize, onPage) {
var el = document.getElementById(elId);
if (!el) return;
total = total || 0;
page = page || 1;
pageSize = pageSize || 15;
var totalPages = Math.max(1, Math.ceil(total / pageSize));
el.innerHTML = '<span>共 ' + total + ' 条</span>' +
'<button ' + (page <= 1 ? 'disabled' : '') + ' data-p="' + (page - 1) + '">上一页</button>' +

View File

@@ -818,6 +818,15 @@
return localStorage.getItem('uid') || '';
}
var JAVA_API_BASE = "{{ java_api_base or '' }}".replace(/\/+$/, '');
var AUTH_TOKEN_KEY = 'aiimage_auth_token';
function buildJavaUrl(path) {
return JAVA_API_BASE + path;
}
function getAuthToken() {
try { return localStorage.getItem(AUTH_TOKEN_KEY) || ''; } catch (e) { return ''; }
}
function withUidHeaders(headers) {
headers = headers || {};
headers.uid = getUid();
@@ -871,19 +880,28 @@
return;
}
} catch (e) { }
fetch('/api/admin/user/' + uid + '/column-permissions?menu_type=app', { credentials: 'same-origin' })
var token = getAuthToken();
if (!JAVA_API_BASE || !token) {
showAllNavSections();
finishNavPermissionLoading();
return;
}
fetch(buildJavaUrl('/api/admin/permission-users/' + uid + '/column-permissions?menuType=app'), {
credentials: 'include',
headers: { 'Authorization': 'Bearer ' + token }
})
.then(function (r) { return r.json(); })
.then(function (res) {
showAllNavSections();
if (!res || !res.success || !Array.isArray(res.items)) {
if (!res || !res.success || !Array.isArray(res.data)) {
finishNavPermissionLoading();
return;
}
try {
localStorage.setItem(cacheKey, JSON.stringify(res.items));
localStorage.setItem(cacheKey, JSON.stringify(res.data));
} catch (e) { }
var allowedKeys = res.items.map(function (item) {
return (item.column_key || '').toLowerCase();
var allowedKeys = res.data.map(function (item) {
return (item.column_key || item.columnKey || '').toLowerCase();
});
document.querySelectorAll('.nav-section[data-column-key]').forEach(function (section) {
var key = (section.getAttribute('data-column-key') || '').toLowerCase();

View File

@@ -193,7 +193,7 @@
{% endif %}
<span>{{ username }}</span>
<button type="button" class="header-update-btn" id="homeUpdateToggle" title="检测更新"></button>
<a href="/logout" onclick="handleLogout()">退出</a>
<a href="/logout" onclick="return handleLogout(event);">退出</a>
<div class="header-update-panel" id="homeUpdatePanel">
<div class="header-update-title"><span></span> 软件更新</div>
<div class="update-block">
@@ -220,22 +220,29 @@
function getAppPermissionCacheKey(uid) {
return 'app_column_permissions:' + String(uid || '');
}
// 权限校验:根据 column-permissions 接口按 column_key 显示入口
// 权限校验:根据 Java column-permissions 接口按 column_key 显示入口
(function() {
localStorage.setItem("uid",{{ user_id }})
var uid = document.body.getAttribute('data-user-id');
if (!uid) return;
var cacheKey = getAppPermissionCacheKey(uid);
var baseUrl = window.location.origin;
var apiUrl = baseUrl + '/api/admin/user/' + uid + '/column-permissions';
fetch(apiUrl, { credentials: 'same-origin' })
var AUTH_TOKEN_KEY = 'aiimage_auth_token';
var JAVA_API_BASE = "{{ java_api_base or '' }}".replace(/\/+$/, '');
var token = '';
try { token = localStorage.getItem(AUTH_TOKEN_KEY) || ''; } catch (e) {}
if (!JAVA_API_BASE || !token) return;
var apiUrl = JAVA_API_BASE + '/api/admin/permission-users/' + uid + '/column-permissions?menuType=app';
fetch(apiUrl, {
credentials: 'include',
headers: { 'Authorization': 'Bearer ' + token }
})
.then(function(r) { return r.json(); })
.then(function(res) {
if (!res || !res.success || !Array.isArray(res.items)) return;
if (!res || !res.success || !Array.isArray(res.data)) return;
try {
localStorage.setItem(cacheKey, JSON.stringify(res.items));
localStorage.setItem(cacheKey, JSON.stringify(res.data));
} catch (e) {}
var allowedKeys = res.items.map(function(item) { return (item.column_key || '').toLowerCase(); });
var allowedKeys = res.data.map(function(item) { return (item.columnKey || item.column_key || '').toLowerCase(); });
document.querySelectorAll('.entrances .entrance-btn[data-column-key]').forEach(function(btn) {
var key = (btn.getAttribute('data-column-key') || '').toLowerCase();
btn.style.display = allowedKeys.indexOf(key) >= 0 ? '' : 'none';
@@ -255,16 +262,45 @@
};
});
function handleLogout() {
try{
localStorage.removeItem('maixiang_api_key');
var uid = document.body.getAttribute('data-user-id');
if (uid) {
localStorage.removeItem(getAppPermissionCacheKey(uid));
}
}catch (e) {
console.log(e)
function handleLogout(ev) {
if (ev && ev.preventDefault) ev.preventDefault();
var AUTH_TOKEN_KEY = 'aiimage_auth_token';
var JAVA_API_BASE = "{{ java_api_base or '' }}".replace(/\/+$/, '');
var token = '';
try { token = localStorage.getItem(AUTH_TOKEN_KEY) || ''; } catch (e) {}
function cleanLocal() {
try {
localStorage.removeItem('maixiang_api_key');
localStorage.removeItem(AUTH_TOKEN_KEY);
var uid = document.body.getAttribute('data-user-id');
if (uid) {
localStorage.removeItem(getAppPermissionCacheKey(uid));
}
} catch (e) { console.log(e); }
}
function gotoPythonLogout() {
cleanLocal();
window.location.href = '/logout';
}
if (!JAVA_API_BASE || !token) {
gotoPythonLogout();
return false;
}
// 先让 Java 端清 cookie/会话,再走 Python /logout 清 Python cookie
var headers = { 'Authorization': 'Bearer ' + token };
try {
fetch(JAVA_API_BASE + '/logout', {
method: 'POST',
credentials: 'include',
headers: headers
}).then(gotoPythonLogout).catch(gotoPythonLogout);
} catch (e) {
gotoPythonLogout();
}
return false;
}
// 软件更新(与 index 设置中逻辑一致)
@@ -346,19 +382,6 @@
};
btnDownload.style.display = 'none';
})();
// (function() {
// fetch('/api/auth/check', { credentials: 'same-origin' })
// .then(function(r) { return r.json(); })
// .then(function(res) {
// if (res.logged_in && res.redirect) {
// window.location.href = res.redirect;
// }else{
// window.location.href = "/login"
// // handleLogout()
// }
// })
// .catch(function() {});
// })();
</script>
</body>
</html>

View File

@@ -117,6 +117,10 @@
</form>
</div>
<script>
var JAVA_API_BASE = "{{ java_api_base or '' }}";
var AUTH_TOKEN_KEY = 'aiimage_auth_token';
var DEVICE_ID_KEY = 'aiimage_device_id';
function clearAppPermissionCaches() {
try {
var keysToRemove = [];
@@ -132,55 +136,126 @@
} catch (e) {}
}
function waitForPywebview(timeoutMs) {
return new Promise(function(resolve) {
if (window.pywebview && window.pywebview.api && window.pywebview.api.get_device_id) {
resolve(window.pywebview.api);
return;
}
var done = false;
var finish = function() {
if (done) return;
done = true;
var api = (window.pywebview && window.pywebview.api) || null;
resolve(api);
};
window.addEventListener('pywebviewready', finish, { once: true });
setTimeout(finish, timeoutMs || 1500);
});
}
function fetchDeviceId() {
return waitForPywebview(2000).then(function(api) {
if (!api || typeof api.get_device_id !== 'function') {
return '';
}
try {
var ret = api.get_device_id();
return Promise.resolve(ret).then(function(res) {
if (res && res.success && res.device_id) {
try { localStorage.setItem(DEVICE_ID_KEY, res.device_id); } catch (e) {}
return res.device_id;
}
return '';
}).catch(function() { return ''; });
} catch (e) {
return '';
}
});
}
function buildJavaUrl(path) {
var base = (JAVA_API_BASE || '').replace(/\/+$/, '');
return base + path;
}
(function() {
var params = new URLSearchParams(window.location.search || '');
if (params.get('logout') === '1' || params.get('switch') === '1') {
return;
try { localStorage.removeItem(AUTH_TOKEN_KEY); } catch (e) {}
}
fetch('/api/auth/check', { credentials: 'same-origin' })
.then(function(r) { return r.json(); })
.then(function(res) {
if (res.logged_in && res.redirect) {
window.location.href = res.redirect;
}
})
.catch(function() {});
})();
document.getElementById('loginForm').onsubmit = function(e) {
e.preventDefault();
var btn = document.getElementById('btnLogin');
btn.disabled = true;
btn.textContent = '登录中...';
var form = e.target;
var fd = new FormData(form);
fetch(form.action, {
method: 'POST',
body: fd,
headers: { 'X-Requested-With': 'XMLHttpRequest' }
})
.then(function(r) { return r.json(); })
.then(function(res) {
if (res.success) {
<<<<<<<< HEAD:backend/web_source/login.html
window.location.href = res.redirect || '/admin';
========
clearAppPermissionCaches();
window.location.href = res.redirect || '/home';
>>>>>>>> gitee/master:app/web_source/login.html
} else {
var errEl = document.querySelector('.error-msg');
if (!errEl) {
errEl = document.createElement('p');
errEl.className = 'error-msg';
form.insertBefore(errEl, form.firstChild);
}
errEl.textContent = res.error || '登录失败';
btn.disabled = false;
btn.textContent = '登录';
var username = (form.username.value || '').trim();
var password = form.password.value || '';
function showError(msg) {
var errEl = document.querySelector('.error-msg');
if (!errEl) {
errEl = document.createElement('p');
errEl.className = 'error-msg';
form.insertBefore(errEl, form.firstChild);
}
})
.catch(function() {
form.submit();
errEl.textContent = msg || '登录失败';
btn.disabled = false;
btn.textContent = '登录';
}
fetchDeviceId().then(function(deviceId) {
if (!deviceId) {
showError('未获取到设备ID请在桌面端打开');
return;
}
var payload = { username: username, password: password, deviceId: deviceId };
return fetch(buildJavaUrl('/login'), {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
'X-Device-Id': deviceId,
'X-Requested-With': 'XMLHttpRequest'
},
body: JSON.stringify(payload)
})
.then(function(r) { return r.json(); })
.then(function(res) {
if (!res || !res.success) {
showError((res && res.message) || '登录失败');
return;
}
var data = res.data || {};
if (data.token) {
try { localStorage.setItem(AUTH_TOKEN_KEY, data.token); } catch (e) {}
}
// 把 Java 签发的 JWT 同步到 Python 套壳同源 cookie供后续页面跳转携带
return fetch('/api/auth/sync', {
method: 'POST',
credentials: 'same-origin',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
},
body: JSON.stringify({ token: data.token })
})
.then(function(r) { return r.json().catch(function() { return {}; }); })
.then(function() {
clearAppPermissionCaches();
window.location.href = '/home';
})
.catch(function() {
clearAppPermissionCaches();
window.location.href = '/home';
});
})
.catch(function() {
showError('网络异常,请稍后重试');
});
});
};
</script>