- 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>
169 lines
5.4 KiB
Vue
169 lines
5.4 KiB
Vue
<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>
|