add licensed random user creation flow

This commit is contained in:
2026-04-03 12:22:12 +08:00
parent df9dd80bb3
commit b1f0dbc6f4
6 changed files with 221 additions and 7 deletions

View File

@@ -10,6 +10,8 @@ const state = {
const elements = {
userList: document.getElementById('user-list'),
userCount: document.getElementById('user-count'),
createUser: document.getElementById('create-user'),
sidebarStatus: document.getElementById('sidebar-status'),
searchInput: document.getElementById('search-input'),
refreshUsers: document.getElementById('refresh-users'),
refreshMail: document.getElementById('refresh-mail'),
@@ -56,6 +58,16 @@ function clearStatus() {
elements.mailStatus.className = 'status hidden';
}
function setSidebarStatus(message, tone = 'neutral') {
elements.sidebarStatus.textContent = message;
elements.sidebarStatus.className = `sidebar-status ${tone}`;
}
function clearSidebarStatus() {
elements.sidebarStatus.textContent = '';
elements.sidebarStatus.className = 'sidebar-status hidden';
}
function renderUserList() {
elements.userCount.textContent = `${state.filteredUsers.length} 个账号`;
@@ -184,8 +196,8 @@ function renderMessage(message) {
renderMessageBody(message);
}
async function requestJson(url) {
const response = await fetch(url);
async function requestJson(url, init) {
const response = await fetch(url, init);
const payload = await response.json().catch(() => ({}));
if (response.status === 401) {
@@ -237,6 +249,56 @@ async function loadUsers() {
}
}
function wait(ms) {
return new Promise((resolve) => {
window.setTimeout(resolve, ms);
});
}
async function refreshUsersUntilPresent(userPrincipalName, attempts = 6) {
for (let attempt = 0; attempt < attempts; attempt += 1) {
await loadUsers();
const createdUser = state.users.find((user) => user.userPrincipalName === userPrincipalName);
if (createdUser) {
return createdUser;
}
if (attempt < attempts - 1) {
await wait(1500);
}
}
return null;
}
async function createRandomUser() {
elements.createUser.disabled = true;
setSidebarStatus('正在创建随机用户...', 'loading');
try {
const payload = await requestJson('/api/users/random', {
method: 'POST',
});
setSidebarStatus('用户已创建,正在同步账号列表...', 'loading');
const createdUser = await refreshUsersUntilPresent(payload.user.userPrincipalName);
if (createdUser) {
state.selectedUser = createdUser;
renderUserList();
setSidebarStatus(`已创建: ${payload.user.userPrincipalName}`, 'success');
await loadLatestEmail(createdUser.id);
return;
}
setSidebarStatus(`用户已创建,但列表尚未同步: ${payload.user.userPrincipalName}`, 'warning');
} catch (error) {
setSidebarStatus(error.message, 'warning');
} finally {
elements.createUser.disabled = false;
}
}
async function loadLatestEmail(userId) {
const user = state.users.find((entry) => entry.id === userId);
@@ -297,6 +359,10 @@ elements.refreshUsers.addEventListener('click', () => {
loadUsers();
});
elements.createUser.addEventListener('click', () => {
createRandomUser();
});
elements.refreshMail.addEventListener('click', () => {
if (state.selectedUser) {
loadLatestEmail(state.selectedUser.id);

View File

@@ -11,8 +11,12 @@
<aside class="sidebar">
<div class="sidebar-header">
<p class="eyebrow">Microsoft 365</p>
<h1>邮箱控制台</h1>
<div class="sidebar-title-row">
<h1>邮箱控制台</h1>
<button id="create-user" type="button">随机建号</button>
</div>
<p class="sidebar-copy">列出租户账号,点击后读取该账号收件箱中的最新一封邮件。</p>
<p id="sidebar-status" class="sidebar-status hidden"></p>
</div>
<label class="search-box" for="search-input">

View File

@@ -62,11 +62,41 @@ input {
.user-address,
.user-meta,
.user-empty,
.sidebar-status,
.status,
.empty-block p {
color: var(--muted);
}
.sidebar-title-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
}
.sidebar-title-row button {
padding: 9px 12px;
white-space: nowrap;
}
.sidebar-status {
margin: 12px 0 0;
font-size: 13px;
}
.sidebar-status.loading {
color: var(--accent);
}
.sidebar-status.success {
color: #9fe8b2;
}
.sidebar-status.warning {
color: var(--warning);
}
.eyebrow {
margin: 0 0 8px;
color: var(--accent);
@@ -292,4 +322,9 @@ a {
align-items: flex-start;
flex-direction: column;
}
.sidebar-title-row {
align-items: flex-start;
flex-direction: column;
}
}