|
|
@@ -14,7 +14,7 @@
|
|
|
<span class="url-text">{{ currentUrl }}</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
- <div class="toolbar-right">
|
|
|
+ <div v-if="!isAdminMode" class="toolbar-right">
|
|
|
<el-tag :type="statusType" size="small">
|
|
|
{{ statusText }}
|
|
|
</el-tag>
|
|
|
@@ -40,8 +40,9 @@
|
|
|
<!-- 内嵌浏览器 -->
|
|
|
<div class="browser-content">
|
|
|
<webview
|
|
|
+ v-if="webviewReady"
|
|
|
ref="webviewRef"
|
|
|
- :src="initialUrl"
|
|
|
+ :src="webviewSrc"
|
|
|
:partition="webviewPartition"
|
|
|
class="embedded-browser"
|
|
|
allowpopups
|
|
|
@@ -53,8 +54,8 @@
|
|
|
@dom-ready="handleDomReady"
|
|
|
/>
|
|
|
|
|
|
- <!-- 登录成功遮罩 -->
|
|
|
- <div v-if="loginStatus === 'success'" class="success-overlay">
|
|
|
+ <!-- 登录成功遮罩(管理模式下不显示) -->
|
|
|
+ <div v-if="loginStatus === 'success' && !isAdminMode" class="success-overlay">
|
|
|
<div class="success-content">
|
|
|
<el-icon class="success-icon"><CircleCheck /></el-icon>
|
|
|
<h3>登录成功!</h3>
|
|
|
@@ -78,7 +79,7 @@
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts">
|
|
|
-import { ref, computed, onMounted, onUnmounted, watch } from 'vue';
|
|
|
+import { ref, computed, onMounted, onUnmounted, watch, nextTick } from 'vue';
|
|
|
import { ArrowLeft, ArrowRight, Refresh, Loading, Lock, CircleCheck } from '@element-plus/icons-vue';
|
|
|
import { ElMessage } from 'element-plus';
|
|
|
import { accountsApi } from '@/api/accounts';
|
|
|
@@ -106,6 +107,8 @@ const canGoForward = ref(false);
|
|
|
const saving = ref(false);
|
|
|
const checking = ref(false);
|
|
|
const loginStatus = ref<'pending' | 'checking' | 'success' | 'failed'>('pending');
|
|
|
+const webviewSrc = ref('about:blank'); // 初始为空白页,等 Cookie 设置完再导航
|
|
|
+const webviewReady = ref(false); // 控制 webview 是否渲染
|
|
|
const accountInfo = ref<{
|
|
|
accountId: string;
|
|
|
accountName: string;
|
|
|
@@ -123,7 +126,15 @@ let hasShownSuccessMessage = false; // 防止重复显示成功消息
|
|
|
// 计算属性
|
|
|
const platform = computed(() => props.tab.browserData?.platform as PlatformType);
|
|
|
|
|
|
+// 是否是管理后台模式(从后台按钮打开,不需要登录检测和保存账号)
|
|
|
+const isAdminMode = computed(() => !!props.tab.browserData?.isAdminMode);
|
|
|
+
|
|
|
const initialUrl = computed(() => {
|
|
|
+ // 优先使用 browserData 中指定的 URL
|
|
|
+ if (props.tab.browserData?.url) {
|
|
|
+ return props.tab.browserData.url;
|
|
|
+ }
|
|
|
+ // 否则使用平台默认登录 URL
|
|
|
const platformInfo = PLATFORMS[platform.value];
|
|
|
return platformInfo?.loginUrl || 'about:blank';
|
|
|
});
|
|
|
@@ -176,9 +187,24 @@ function handleNavigate(event: Electron.DidNavigateEvent) {
|
|
|
updateNavigation();
|
|
|
|
|
|
// 页面跳转后立即检测一次登录状态(加快响应)
|
|
|
- if (loginStatus.value !== 'success') {
|
|
|
+ // 管理模式下跳过检测
|
|
|
+ if (!isAdminMode.value && loginStatus.value !== 'success') {
|
|
|
setTimeout(() => checkLoginSilently(), 500);
|
|
|
}
|
|
|
+
|
|
|
+ // 百家号特殊处理:如果已经登录成功但账号信息是默认值,尝试重新获取
|
|
|
+ if (platform.value === 'baijiahao' &&
|
|
|
+ loginStatus.value === 'success' &&
|
|
|
+ accountInfo.value?.accountName === '百家号账号' &&
|
|
|
+ event.url.includes('baijiahao.baidu.com/builder')) {
|
|
|
+ console.log('[BrowserTab] 百家号跳转到后台页面,尝试重新获取账号信息');
|
|
|
+ setTimeout(async () => {
|
|
|
+ const info = await fetchBaijiahaoAccountInfo();
|
|
|
+ if (info && info.accountName !== '百家号账号') {
|
|
|
+ accountInfo.value = info;
|
|
|
+ }
|
|
|
+ }, 1500);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
function handleNavigateInPage(event: Electron.DidNavigateInPageEvent) {
|
|
|
@@ -198,8 +224,10 @@ function handleDomReady() {
|
|
|
updateNavigation();
|
|
|
currentUrl.value = webviewRef.value?.getURL() || '';
|
|
|
|
|
|
- // 开始自动检测登录状态
|
|
|
- startAutoCheck();
|
|
|
+ // 管理模式下不自动检测登录状态
|
|
|
+ if (!isAdminMode.value) {
|
|
|
+ startAutoCheck();
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
function updateNavigation() {
|
|
|
@@ -283,7 +311,10 @@ function isLoggedInByUrl(): boolean {
|
|
|
/mp\.toutiao\.com\/profile/,
|
|
|
],
|
|
|
baijiahao: [
|
|
|
- /baijiahao\.baidu\.com\/builder\/rc/,
|
|
|
+ /baijiahao\.baidu\.com\/builder\/rc/, // 创作中心首页
|
|
|
+ /baijiahao\.baidu\.com\/builder\/app/, // 应用页面
|
|
|
+ /baijiahao\.baidu\.com\/builder\/content/, // 内容管理
|
|
|
+ /baijiahao\.baidu\.com\/builder\/theme/, // 主题页面
|
|
|
],
|
|
|
};
|
|
|
|
|
|
@@ -304,22 +335,158 @@ async function checkLoginSilently() {
|
|
|
// 简单判断是否有登录相关的 cookie
|
|
|
const hasLoginCookie = checkHasLoginCookie(cookies);
|
|
|
|
|
|
+ // 调试日志
|
|
|
+ if (platform.value === 'baijiahao') {
|
|
|
+ console.log('[BrowserTab] 百家号检测 - URL:', currentUrl.value);
|
|
|
+ console.log('[BrowserTab] 百家号检测 - hasLoginCookie:', hasLoginCookie);
|
|
|
+ console.log('[BrowserTab] 百家号检测 - cookies 数量:', cookies.length);
|
|
|
+ const bduss = cookies.find(c => c.name === 'BDUSS');
|
|
|
+ console.log('[BrowserTab] 百家号检测 - BDUSS:', bduss ? '存在' : '不存在');
|
|
|
+ }
|
|
|
+
|
|
|
if (hasLoginCookie && loginStatus.value !== 'success') {
|
|
|
// 检查 URL 是否已经在登录后的页面
|
|
|
const urlIndicatesLoggedIn = isLoggedInByUrl();
|
|
|
|
|
|
+ if (platform.value === 'baijiahao') {
|
|
|
+ console.log('[BrowserTab] 百家号检测 - urlIndicatesLoggedIn:', urlIndicatesLoggedIn);
|
|
|
+ }
|
|
|
+
|
|
|
if (urlIndicatesLoggedIn) {
|
|
|
// URL 表明已登录,快速响应:先显示成功,再异步获取详情
|
|
|
+ console.log('[BrowserTab] URL 表明已登录,调用 quickLoginSuccess');
|
|
|
await quickLoginSuccess(cookies);
|
|
|
} else {
|
|
|
// 还在登录页面,通过服务器验证
|
|
|
+ console.log('[BrowserTab] 通过服务器验证登录');
|
|
|
await verifyLoginWithServer(cookies, true);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
} catch (error) {
|
|
|
// 静默失败,不处理
|
|
|
+ console.error('[BrowserTab] checkLoginSilently 异常:', error);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 通过 webview 执行脚本获取百家号账号信息
|
|
|
+async function fetchBaijiahaoAccountInfo(retryCount = 0): Promise<{
|
|
|
+ accountId: string;
|
|
|
+ accountName: string;
|
|
|
+ avatarUrl: string;
|
|
|
+ fansCount: number;
|
|
|
+ worksCount: number;
|
|
|
+} | null> {
|
|
|
+ console.log('[BrowserTab] fetchBaijiahaoAccountInfo 开始, retryCount:', retryCount);
|
|
|
+
|
|
|
+ if (!webviewRef.value) {
|
|
|
+ console.log('[BrowserTab] webviewRef 不存在');
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 等待页面加载完成
|
|
|
+ console.log('[BrowserTab] 等待1秒让页面加载...');
|
|
|
+ await new Promise(resolve => setTimeout(resolve, 1000));
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 先获取当前页面的 URL
|
|
|
+ const currentPageUrl = await webviewRef.value.executeJavaScript('window.location.href');
|
|
|
+ console.log('[BrowserTab] 当前页面 URL:', currentPageUrl);
|
|
|
+
|
|
|
+ // 检查是否在百家号域名下
|
|
|
+ if (!currentPageUrl.includes('baijiahao.baidu.com')) {
|
|
|
+ console.log('[BrowserTab] 当前页面不在百家号域名下,跳过获取');
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log('[BrowserTab] 正在获取百家号账号信息...');
|
|
|
+ const script = `
|
|
|
+ (async () => {
|
|
|
+ try {
|
|
|
+ console.log('[Baijiahao] 当前 URL:', window.location.href);
|
|
|
+ console.log('[Baijiahao] 当前 Cookie:', document.cookie.substring(0, 100));
|
|
|
+ console.log('[Baijiahao] Fetching settingInfo...');
|
|
|
+
|
|
|
+ const response = await fetch('https://baijiahao.baidu.com/user-ui/cms/settingInfo', {
|
|
|
+ method: 'GET',
|
|
|
+ credentials: 'include',
|
|
|
+ headers: {
|
|
|
+ 'Accept': 'application/json, text/plain, */*',
|
|
|
+ 'Referer': 'https://baijiahao.baidu.com/'
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ console.log('[Baijiahao] Response status:', response.status);
|
|
|
+
|
|
|
+ if (!response.ok) {
|
|
|
+ return { success: false, error: 'HTTP ' + response.status };
|
|
|
+ }
|
|
|
+
|
|
|
+ const text = await response.text();
|
|
|
+ console.log('[Baijiahao] Response text (first 300):', text.substring(0, 300));
|
|
|
+
|
|
|
+ let data;
|
|
|
+ try {
|
|
|
+ data = JSON.parse(text);
|
|
|
+ } catch (e) {
|
|
|
+ return { success: false, error: 'JSON parse error: ' + e.message };
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log('[Baijiahao] settingInfo response:', JSON.stringify(data).substring(0, 300));
|
|
|
+
|
|
|
+ if (data.errno === 0 && data.data) {
|
|
|
+ const accountId = data.data.new_uc_id ? String(data.data.new_uc_id) : 'baijiahao_' + Date.now();
|
|
|
+ const accountName = data.data.name || '百家号账号';
|
|
|
+ const avatarUrl = data.data.avatar || '';
|
|
|
+ console.log('[Baijiahao] 获取成功: id=' + accountId + ', name=' + accountName);
|
|
|
+ return {
|
|
|
+ success: true,
|
|
|
+ accountId: accountId,
|
|
|
+ accountName: accountName,
|
|
|
+ avatarUrl: avatarUrl,
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ return { success: false, errno: data.errno, errmsg: data.errmsg || 'Unknown error' };
|
|
|
+ } catch (e) {
|
|
|
+ console.error('[Baijiahao] Error:', e);
|
|
|
+ return { success: false, error: e.message || String(e) };
|
|
|
+ }
|
|
|
+ })()
|
|
|
+ `;
|
|
|
+
|
|
|
+ console.log('[BrowserTab] 执行 JavaScript 脚本...');
|
|
|
+ const result = await webviewRef.value.executeJavaScript(script);
|
|
|
+ console.log('[BrowserTab] 百家号账号信息结果:', JSON.stringify(result));
|
|
|
+
|
|
|
+ if (result?.success) {
|
|
|
+ console.log('[BrowserTab] 获取百家号账号信息成功:', result.accountName);
|
|
|
+ return {
|
|
|
+ accountId: result.accountId,
|
|
|
+ accountName: result.accountName,
|
|
|
+ avatarUrl: result.avatarUrl,
|
|
|
+ fansCount: 0,
|
|
|
+ worksCount: 0,
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log('[BrowserTab] 获取百家号账号信息失败:', result?.error || result?.errmsg);
|
|
|
+
|
|
|
+ // 如果失败且重试次数少于3次,等待后重试
|
|
|
+ if (retryCount < 3) {
|
|
|
+ console.log(`[BrowserTab] 获取百家号账号信息失败,${retryCount + 1}/3 次重试...`);
|
|
|
+ await new Promise(resolve => setTimeout(resolve, 2000));
|
|
|
+ return fetchBaijiahaoAccountInfo(retryCount + 1);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('[BrowserTab] 获取百家号账号信息异常:', error);
|
|
|
+ // 失败时重试
|
|
|
+ if (retryCount < 3) {
|
|
|
+ await new Promise(resolve => setTimeout(resolve, 2000));
|
|
|
+ return fetchBaijiahaoAccountInfo(retryCount + 1);
|
|
|
+ }
|
|
|
}
|
|
|
+ return null;
|
|
|
}
|
|
|
|
|
|
// 快速登录成功(URL 已表明登录成功时使用)
|
|
|
@@ -343,6 +510,17 @@ async function quickLoginSuccess(cookies: Electron.Cookie[]) {
|
|
|
|
|
|
// 异步获取账号详细信息
|
|
|
try {
|
|
|
+ // 百家号使用特殊方式获取账号信息(通过 webview 执行脚本)
|
|
|
+ if (platform.value === 'baijiahao') {
|
|
|
+ const baijiahaoInfo = await fetchBaijiahaoAccountInfo();
|
|
|
+ if (baijiahaoInfo) {
|
|
|
+ accountInfo.value = baijiahaoInfo;
|
|
|
+ isVerifying = false;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 其他平台通过服务器 API 验证
|
|
|
const result = await accountsApi.verifyLoginCookie(platform.value, cookieString);
|
|
|
if (result.success && result.accountInfo) {
|
|
|
accountInfo.value = result.accountInfo;
|
|
|
@@ -444,7 +622,26 @@ async function verifyLoginWithServer(cookies: Electron.Cookie[], silent = false)
|
|
|
cookieData.value = cookieString;
|
|
|
|
|
|
try {
|
|
|
- // 调用服务器 API 验证并获取账号信息
|
|
|
+ // 百家号使用特殊方式获取账号信息(通过 webview 执行脚本)
|
|
|
+ if (platform.value === 'baijiahao') {
|
|
|
+ const baijiahaoInfo = await fetchBaijiahaoAccountInfo();
|
|
|
+ if (baijiahaoInfo) {
|
|
|
+ if (loginStatus.value !== 'success') {
|
|
|
+ loginStatus.value = 'success';
|
|
|
+ accountInfo.value = baijiahaoInfo;
|
|
|
+ stopAutoCheck();
|
|
|
+
|
|
|
+ if (!hasShownSuccessMessage) {
|
|
|
+ hasShownSuccessMessage = true;
|
|
|
+ ElMessage.success('检测到登录成功!');
|
|
|
+ }
|
|
|
+ }
|
|
|
+ isVerifying = false;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 其他平台调用服务器 API 验证并获取账号信息
|
|
|
const result = await accountsApi.verifyLoginCookie(platform.value, cookieString);
|
|
|
|
|
|
if (result.success && result.accountInfo) {
|
|
|
@@ -533,10 +730,131 @@ async function handleSaveAccount() {
|
|
|
}
|
|
|
|
|
|
// 生命周期
|
|
|
-onMounted(() => {
|
|
|
- currentUrl.value = initialUrl.value;
|
|
|
+onMounted(async () => {
|
|
|
+ // 调试:输出 browserData 信息
|
|
|
+ console.log('[BrowserTab] onMounted, browserData:', JSON.stringify(props.tab.browserData, null, 2));
|
|
|
+ console.log('[BrowserTab] isAdminMode:', isAdminMode.value);
|
|
|
+ console.log('[BrowserTab] initialUrl:', initialUrl.value);
|
|
|
+
|
|
|
+ const targetUrl = initialUrl.value;
|
|
|
+ currentUrl.value = targetUrl;
|
|
|
+
|
|
|
+ // 如果有预设的 Cookie,先设置到 session 中,然后再加载页面
|
|
|
+ if (props.tab.browserData?.cookieData) {
|
|
|
+ console.log('[BrowserTab] 检测到预设 Cookie,先设置再加载页面');
|
|
|
+ await setPresetCookies(props.tab.browserData.cookieData);
|
|
|
+ // 等待一小段时间确保 Cookie 设置完成
|
|
|
+ await new Promise(resolve => setTimeout(resolve, 100));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 设置目标 URL 并渲染 webview
|
|
|
+ webviewSrc.value = targetUrl;
|
|
|
+ webviewReady.value = true;
|
|
|
+ console.log('[BrowserTab] webview 准备就绪,加载页面:', targetUrl);
|
|
|
});
|
|
|
|
|
|
+// 设置预设的 Cookies
|
|
|
+async function setPresetCookies(cookieDataStr: string) {
|
|
|
+ if (!window.electronAPI?.setWebviewCookies) {
|
|
|
+ console.warn('electronAPI.setWebviewCookies 不可用');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 尝试解析 JSON 格式的 cookie
|
|
|
+ let cookieList: Array<{ name: string; value: string; domain?: string; path?: string }>;
|
|
|
+
|
|
|
+ try {
|
|
|
+ cookieList = JSON.parse(cookieDataStr);
|
|
|
+ } catch {
|
|
|
+ // 如果不是 JSON,尝试解析 "name=value; name2=value2" 格式
|
|
|
+ cookieList = parseCookieString(cookieDataStr);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (cookieList.length === 0) {
|
|
|
+ console.warn('没有有效的 Cookie 数据');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取目标 URL(优先使用 browserData 中的 url)
|
|
|
+ const targetUrl = props.tab.browserData?.url || PLATFORMS[platform.value]?.loginUrl || '';
|
|
|
+
|
|
|
+ // 获取平台的根域名
|
|
|
+ const platformDomainMap: Record<string, string> = {
|
|
|
+ douyin: '.douyin.com',
|
|
|
+ xiaohongshu: '.xiaohongshu.com',
|
|
|
+ kuaishou: '.kuaishou.com',
|
|
|
+ weixin_video: '.qq.com',
|
|
|
+ bilibili: '.bilibili.com',
|
|
|
+ };
|
|
|
+ const rootDomain = platformDomainMap[platform.value] || '';
|
|
|
+
|
|
|
+ console.log(`[BrowserTab] 设置 Cookie, platform=${platform.value}, targetUrl=${targetUrl}, domain=${rootDomain}`);
|
|
|
+
|
|
|
+ // 转换为 Electron 需要的格式,保留原始 Cookie 的 domain
|
|
|
+ const cookiesForElectron = cookieList.map(cookie => {
|
|
|
+ // 优先使用 Cookie 自带的 domain,否则使用平台根域名
|
|
|
+ let cookieDomain = cookie.domain;
|
|
|
+ if (!cookieDomain || cookieDomain === '') {
|
|
|
+ cookieDomain = rootDomain;
|
|
|
+ }
|
|
|
+ // 确保域名以点开头(用于子域名匹配)
|
|
|
+ if (cookieDomain && !cookieDomain.startsWith('.') && !cookieDomain.includes('localhost')) {
|
|
|
+ cookieDomain = '.' + cookieDomain;
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ url: targetUrl,
|
|
|
+ name: cookie.name,
|
|
|
+ value: cookie.value,
|
|
|
+ domain: cookieDomain,
|
|
|
+ path: cookie.path || '/',
|
|
|
+ };
|
|
|
+ });
|
|
|
+
|
|
|
+ console.log(`[BrowserTab] 准备设置 ${cookiesForElectron.length} 个 Cookies`);
|
|
|
+
|
|
|
+ // 设置 cookies
|
|
|
+ const success = await window.electronAPI.setWebviewCookies(
|
|
|
+ webviewPartition.value,
|
|
|
+ cookiesForElectron
|
|
|
+ );
|
|
|
+
|
|
|
+ if (success) {
|
|
|
+ console.log(`[BrowserTab] 预设 ${cookieList.length} 个 Cookies 成功`);
|
|
|
+ // 标记已有登录 cookie
|
|
|
+ loginStatus.value = 'checking';
|
|
|
+ } else {
|
|
|
+ console.warn('[BrowserTab] 设置 Cookies 失败');
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('[BrowserTab] 设置预设 Cookies 失败:', error);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 解析 "name=value; name2=value2" 格式的 cookie 字符串
|
|
|
+function parseCookieString(cookieString: string): Array<{ name: string; value: string }> {
|
|
|
+ const cookies: Array<{ name: string; value: string }> = [];
|
|
|
+
|
|
|
+ const pairs = cookieString.split(';');
|
|
|
+ for (const pair of pairs) {
|
|
|
+ const trimmed = pair.trim();
|
|
|
+ if (!trimmed) continue;
|
|
|
+
|
|
|
+ const eqIndex = trimmed.indexOf('=');
|
|
|
+ if (eqIndex === -1) continue;
|
|
|
+
|
|
|
+ const name = trimmed.substring(0, eqIndex).trim();
|
|
|
+ const value = trimmed.substring(eqIndex + 1).trim();
|
|
|
+
|
|
|
+ if (name && value) {
|
|
|
+ cookies.push({ name, value });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return cookies;
|
|
|
+}
|
|
|
+
|
|
|
onUnmounted(() => {
|
|
|
stopAutoCheck();
|
|
|
|