Improve installer extraction, login failures, and upload handling
This commit is contained in:
@@ -67,6 +67,7 @@ const router = useRouter();
|
||||
const currentPath = computed(() => normalizeAdminPath(route.path));
|
||||
const loading = ref(false);
|
||||
const loginPending = ref(false);
|
||||
const captchaPending = ref(false);
|
||||
const toast = ref<ToastState | null>(null);
|
||||
const autoRefreshPaused = ref(false);
|
||||
const databaseFormEditing = ref(false);
|
||||
@@ -75,6 +76,7 @@ let refreshTimer: number | undefined;
|
||||
let systemRefreshTimer: number | undefined;
|
||||
let toastTimer: number | undefined;
|
||||
let events: EventSource | null = null;
|
||||
let captchaRequestSerial = 0;
|
||||
|
||||
const authStore = createAuthStore();
|
||||
const dashboardStore = createDashboardStore();
|
||||
@@ -491,7 +493,26 @@ function isAuthError(raw: string, message: string) {
|
||||
}
|
||||
|
||||
async function loadCaptcha() {
|
||||
captcha.value = await adminFetch<Captcha>("/api/admin/auth/captcha", {}, { timeoutMs: 5000 });
|
||||
const serial = ++captchaRequestSerial;
|
||||
captchaPending.value = true;
|
||||
try {
|
||||
const next = await adminFetch<Captcha>("/api/admin/auth/captcha", {}, { timeoutMs: 5000 });
|
||||
if (serial === captchaRequestSerial) {
|
||||
captcha.value = next;
|
||||
loginForm.captcha = "";
|
||||
}
|
||||
} finally {
|
||||
if (serial === captchaRequestSerial) captchaPending.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshCaptcha() {
|
||||
try {
|
||||
await loadCaptcha();
|
||||
} catch {
|
||||
captcha.value = null;
|
||||
setToast("验证码加载失败,请检查服务端连接", "error");
|
||||
}
|
||||
}
|
||||
|
||||
async function loadAuthBootstrap() {
|
||||
@@ -511,7 +532,7 @@ async function login() {
|
||||
const data = await adminFetch<{ csrfToken: string }>("/api/admin/auth/login", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ ...loginForm, captchaId: captcha.value?.captchaId }),
|
||||
}, { timeoutMs: 8000 });
|
||||
}, { timeoutMs: 12000 });
|
||||
csrf.value = data.csrfToken;
|
||||
sessionStorage.setItem("ymhut.csrf", csrf.value);
|
||||
localStorage.removeItem("ymhut.csrf");
|
||||
@@ -520,8 +541,7 @@ async function login() {
|
||||
} catch (error) {
|
||||
const message = toChineseError(error instanceof Error ? error.message : String(error));
|
||||
setToast(message, "error");
|
||||
loginForm.captcha = "";
|
||||
void loadCaptcha().catch(() => {
|
||||
await loadCaptcha().catch(() => {
|
||||
captcha.value = null;
|
||||
});
|
||||
} finally {
|
||||
@@ -1742,9 +1762,9 @@ function connectAdminEvents() {
|
||||
验证码
|
||||
<div class="captcha-row">
|
||||
<input v-model="loginForm.captcha" :disabled="loginPending" autocomplete="off" />
|
||||
<button class="captcha-button" type="button" title="刷新验证码" :disabled="loginPending" @click="loadCaptcha">
|
||||
<button class="captcha-button" type="button" title="刷新验证码" :disabled="loginPending || captchaPending" @click="refreshCaptcha">
|
||||
<img v-if="captcha?.image" :src="captcha.image" alt="验证码" />
|
||||
<span v-else>刷新</span>
|
||||
<span v-else>{{ captchaPending ? "加载中" : "刷新" }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
@@ -32,6 +32,9 @@ const exactMessages: Record<string, string> = {
|
||||
const codeMessages: Record<string, string> = {
|
||||
UNAUTHORIZED: "需要登录后继续操作",
|
||||
LOGIN_FAILED: "登录失败,请检查密码和验证码",
|
||||
LOGIN_LOCKED: "登录失败次数过多,请 5 分钟后重试",
|
||||
CAPTCHA_INVALID: "验证码错误或已过期,请输入新的验证码",
|
||||
CREDENTIALS_INVALID: "账号或密码不正确",
|
||||
LOGIN_TIMEOUT: "登录校验超时,请稍后重试",
|
||||
PASSWORD_CHANGE_FAILED: "密码修改失败",
|
||||
INVALID_PAYLOAD: "提交内容格式不正确",
|
||||
@@ -50,6 +53,7 @@ const codeMessages: Record<string, string> = {
|
||||
UPLOAD_STORAGE_FAILED: "服务端无法保存上传文件",
|
||||
MANIFEST_UPDATE_FAILED: "发布包已回滚,更新清单写入失败",
|
||||
PACKAGE_UPLOAD_FAILED: "发布包上传失败",
|
||||
UPLOAD_INTERRUPTED: "上传连接已中断,请保持页面打开后重试",
|
||||
SOURCE_SAVE_FAILED: "接口源保存失败",
|
||||
CHECK_FAILED: "接口健康检测失败",
|
||||
SYNC_FAILED: "同步操作失败",
|
||||
|
||||
Reference in New Issue
Block a user