Files
course/frontend/src/components/ZhihuishuLoginModal.vue
T
nanxunandClaude Fable 5 cd486a36ef feat: UOOC/Zhihuishu dual-platform brushing platform
- ASP.NET Core 9 Web API backend with JWT auth, EF Core MySQL
- Vue 3 + Vite + Pinia + Ant Design Vue frontend
- Multi-platform connection management (UOOC & Zhihuishu)
- Video brushing with AES-CBC encryption for Zhihuishu
- Multi-task queue with cross-platform parallel execution
- Task persistence via MySQL database
- Progress tracking with inline catalog enrichment
- Mobile-responsive UI

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-24 15:31:32 +08:00

169 lines
5.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { onMounted, onUnmounted, ref } from 'vue';
import { message } from 'ant-design-vue';
import { zhihuishuLogin } from '../lib/api';
import { usePlatformStore } from '../stores/platform';
import type { PlatformConnectionDto } from '../types/api';
declare global {
interface Window {
initNECaptcha?: (config: Record<string, unknown>, onload: (instance: any) => void, onerror: (err: unknown) => void) => void;
}
}
const props = defineProps<{ platformId: number; connectionName: string }>();
const emit = defineEmits<{ success: [connection: PlatformConnectionDto]; close: [] }>();
const account = ref(''); const password = ref('');
const loading = ref(false); const error = ref('');
const captchaReady = ref(false);
let captchaInstance: any = null;
let pendingResolve: ((v: string) => void) | null = null;
let pendingReject: ((e: Error) => void) | null = null;
onMounted(() => { initCaptcha(); });
onUnmounted(() => {
const mount = document.getElementById('zhs-captcha-mount');
if (mount) mount.innerHTML = '';
});
async function initCaptcha(): Promise<void> {
// Load SDK script
if (!document.querySelector('script[src*="cstaticdun.126.net/load.min.js"]')) {
await new Promise<void>(resolve => {
const s = document.createElement('script');
s.src = 'https://cstaticdun.126.net/load.min.js';
s.onload = () => resolve();
s.onerror = () => resolve();
document.head.appendChild(s);
});
}
// Wait for SDK to be available
const ready = await new Promise<boolean>(resolve => {
let n = 0;
const check = () => {
if (typeof window.initNECaptcha === 'function') { resolve(true); return; }
if (++n > 50) { resolve(false); return; }
setTimeout(check, 200);
};
check();
});
if (!ready) {
console.error('[Zhihuishu] 网易易盾 SDK 未就绪');
return;
}
const mount = document.getElementById('zhs-captcha-mount');
if (!mount) return;
mount.innerHTML = '';
window.initNECaptcha!(
{
captchaId: '75f9f716460a422f89a628f50fd8cc2b',
element: '#zhs-captcha-mount',
mode: 'popup',
onVerify(err: unknown, data: { validate: string }) {
if (err) {
pendingReject?.(new Error('验证未通过。'));
} else {
pendingResolve?.(data.validate);
}
pendingResolve = null;
pendingReject = null;
},
},
(instance: any) => {
captchaInstance = instance;
captchaReady.value = true;
console.log('[Zhihuishu] 网易易盾 captcha ready');
},
(err: unknown) => {
console.error('[Zhihuishu] captcha init error:', err);
captchaReady.value = false;
},
);
}
function triggerCaptcha(): Promise<string> {
return new Promise((resolve, reject) => {
if (!captchaInstance) {
reject(new Error('验证码组件未就绪,请刷新页面后重试。'));
return;
}
pendingResolve = resolve;
pendingReject = reject;
captchaInstance.popUp();
});
}
async function submit() {
error.value = '';
const acc = account.value.trim(); const pwd = password.value.trim();
if (!acc) { error.value = '请输入手机号。'; return; }
if (!pwd) { error.value = '请输入密码。'; return; }
loading.value = true;
try {
if (!captchaInstance) {
await initCaptcha();
if (!captchaInstance) throw new Error('验证码初始化失败,请刷新页面后重试。');
}
const captchaValidate = await triggerCaptcha();
const r = await zhihuishuLogin({
platformId: props.platformId,
connectionName: props.connectionName.trim() || null,
account: acc,
password: pwd,
captchaValidate,
});
await usePlatformStore().loadConnections();
if (r.connection) {
message.success(r.message || '智慧树登录成功。');
emit('success', r.connection);
}
} catch (err) {
error.value = err instanceof Error ? err.message : '登录失败。';
} finally {
loading.value = false;
}
}
</script>
<template>
<a-modal :open="true" title="登录智慧树平台" width="480px" :footer="null" :mask-closable="false" @cancel="emit('close')">
<a-alert
v-if="!captchaReady"
type="warning"
message="正在加载网易易盾验证码组件,请稍候..."
style="margin-bottom: 16px;"
/>
<a-alert v-if="error" type="error" :message="error" closable style="margin-bottom: 16px;" @close="error = ''" />
<a-form layout="vertical" :disabled="loading">
<a-form-item label="手机号" required>
<a-input v-model:value="account" placeholder="请输入智慧树绑定的手机号" autocomplete="tel" />
</a-form-item>
<a-form-item label="密码" required>
<a-input-password v-model:value="password" placeholder="请输入智慧树密码" autocomplete="current-password" />
<template #help>
密码将通过加密传输至智慧树服务器系统不会存储明文密码
</template>
</a-form-item>
</a-form>
<div id="zhs-captcha-mount" style="margin: 8px 0; min-height: 40px;" />
<a-flex justify="end" :gap="8" style="margin-top: 16px;">
<a-button @click="emit('close')" :disabled="loading">取消</a-button>
<a-button type="primary" :loading="loading" :disabled="!captchaReady" @click="submit">
{{ loading ? '登录中...' : '登录智慧树' }}
</a-button>
</a-flex>
</a-modal>
</template>