Update application UI and functionality

This commit is contained in:
2026-07-26 16:20:36 +08:00
parent b9aff58f32
commit 97ea6fb7aa
48 changed files with 2790 additions and 628 deletions
+37 -10
View File
@@ -66,6 +66,7 @@ const route = useRoute();
const router = useRouter();
const currentPath = computed(() => normalizeAdminPath(route.path));
const loading = ref(false);
const loginPending = ref(false);
const toast = ref<ToastState | null>(null);
const autoRefreshPaused = ref(false);
const databaseFormEditing = ref(false);
@@ -490,7 +491,7 @@ function isAuthError(raw: string, message: string) {
}
async function loadCaptcha() {
captcha.value = await api<Captcha>("/api/admin/auth/captcha");
captcha.value = await adminFetch<Captcha>("/api/admin/auth/captcha", {}, { timeoutMs: 5000 });
}
async function loadAuthBootstrap() {
@@ -498,17 +499,35 @@ async function loadAuthBootstrap() {
}
async function login() {
await guarded(async () => {
const data = await api<{ csrfToken: string }>("/api/admin/auth/login", {
if (loginPending.value) return;
if (!loginForm.password || !loginForm.captcha || !captcha.value?.captchaId) {
setToast("请填写密码和验证码", "warn");
return;
}
loginPending.value = true;
loading.value = true;
try {
const data = await adminFetch<{ csrfToken: string }>("/api/admin/auth/login", {
method: "POST",
body: JSON.stringify({ ...loginForm, captchaId: captcha.value?.captchaId }),
});
}, { timeoutMs: 8000 });
csrf.value = data.csrfToken;
sessionStorage.setItem("ymhut.csrf", csrf.value);
localStorage.removeItem("ymhut.csrf");
connectAdminEvents();
navigate("/admin/dashboard");
});
} catch (error) {
const message = toChineseError(error instanceof Error ? error.message : String(error));
setToast(message, "error");
loginForm.captcha = "";
void loadCaptcha().catch(() => {
captcha.value = null;
});
} finally {
loading.value = false;
loginPending.value = false;
}
}
async function logout() {
@@ -747,6 +766,7 @@ async function uploadPackage() {
setToast("请选择要上传的发布包", "warn");
return;
}
let completed = false;
await guarded(async () => {
const form = new FormData();
form.append("file", uploadDraft.file as File);
@@ -771,6 +791,7 @@ async function uploadPackage() {
uploadDraft.status = "上传完成";
uploadDraft.file = null;
uploadDraft.notes = "";
completed = true;
setToast("发布包已上传并放入下载目录");
await loadReleases();
window.setTimeout(() => {
@@ -783,6 +804,12 @@ async function uploadPackage() {
}, 1200);
}).finally(() => {
uploadDraft.uploading = false;
if (!completed) {
uploadDraft.progress = 0;
uploadDraft.loadedBytes = 0;
uploadDraft.totalBytes = uploadDraft.file?.size || 0;
uploadDraft.status = "上传失败,可直接重试";
}
});
}
@@ -1709,19 +1736,19 @@ function connectAdminEvents() {
当前使用默认账号{{ authBootstrap.defaultUsername || "admin" }} / {{ authBootstrap.defaultPassword || "admin" }}
</p>
<form class="form-stack" @submit.prevent="login">
<label>账号<input v-model="loginForm.username" autocomplete="username" /></label>
<label>密码<input v-model="loginForm.password" type="password" autocomplete="current-password" /></label>
<label>账号<input v-model="loginForm.username" autocomplete="username" :disabled="loginPending" /></label>
<label>密码<input v-model="loginForm.password" type="password" autocomplete="current-password" :disabled="loginPending" /></label>
<label>
验证码
<div class="captcha-row">
<input v-model="loginForm.captcha" />
<button class="captcha-button" type="button" title="刷新验证码" @click="loadCaptcha">
<input v-model="loginForm.captcha" :disabled="loginPending" autocomplete="off" />
<button class="captcha-button" type="button" title="刷新验证码" :disabled="loginPending" @click="loadCaptcha">
<img v-if="captcha?.image" :src="captcha.image" alt="验证码" />
<span v-else>刷新</span>
</button>
</div>
</label>
<Button class="full" type="submit" label="登录" />
<Button class="full" type="submit" :label="loginPending ? '正在登录…' : '登录'" :loading="loginPending" :disabled="loginPending" />
</form>
</section>
</main>