github-actions[bot]
commited on
Commit
·
11a8eb2
1
Parent(s):
3ed7406
Update from GitHub Actions
Browse files- components.d.ts +2 -0
- functions/api/debug/cleanup.ts +43 -0
- functions/api/debug/list.ts +47 -0
- functions/api/debug/screenshot.ts +61 -0
- functions/types.d.ts +1 -0
- functions/utils/authService.ts +109 -11
- functions/utils/debugStorage.ts +135 -0
- index.ts +21 -0
- src/App.vue +5 -0
- src/router/index.ts +6 -0
- src/views/DebugView.vue +161 -0
components.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ declare module 'vue' {
|
|
| 11 |
MonacoEditor: typeof import('./src/components/MonacoEditor.vue')['default']
|
| 12 |
RouterLink: typeof import('vue-router')['RouterLink']
|
| 13 |
RouterView: typeof import('vue-router')['RouterView']
|
|
|
|
| 14 |
TAside: typeof import('tdesign-vue-next')['Aside']
|
| 15 |
TButton: typeof import('tdesign-vue-next')['Button']
|
| 16 |
TCard: typeof import('tdesign-vue-next')['Card']
|
|
@@ -36,6 +37,7 @@ declare module 'vue' {
|
|
| 36 |
TMenuItem: typeof import('tdesign-vue-next')['MenuItem']
|
| 37 |
TPagination: typeof import('tdesign-vue-next')['Pagination']
|
| 38 |
TTable: typeof import('tdesign-vue-next')['Table']
|
|
|
|
| 39 |
TTbody: typeof import('tdesign-vue-next')['Tbody']
|
| 40 |
TTd: typeof import('tdesign-vue-next')['Td']
|
| 41 |
TTextarea: typeof import('tdesign-vue-next')['Textarea']
|
|
|
|
| 11 |
MonacoEditor: typeof import('./src/components/MonacoEditor.vue')['default']
|
| 12 |
RouterLink: typeof import('vue-router')['RouterLink']
|
| 13 |
RouterView: typeof import('vue-router')['RouterView']
|
| 14 |
+
TAlert: typeof import('tdesign-vue-next')['Alert']
|
| 15 |
TAside: typeof import('tdesign-vue-next')['Aside']
|
| 16 |
TButton: typeof import('tdesign-vue-next')['Button']
|
| 17 |
TCard: typeof import('tdesign-vue-next')['Card']
|
|
|
|
| 37 |
TMenuItem: typeof import('tdesign-vue-next')['MenuItem']
|
| 38 |
TPagination: typeof import('tdesign-vue-next')['Pagination']
|
| 39 |
TTable: typeof import('tdesign-vue-next')['Table']
|
| 40 |
+
TTag: typeof import('tdesign-vue-next')['Tag']
|
| 41 |
TTbody: typeof import('tdesign-vue-next')['Tbody']
|
| 42 |
TTd: typeof import('tdesign-vue-next')['Td']
|
| 43 |
TTextarea: typeof import('tdesign-vue-next')['Textarea']
|
functions/api/debug/cleanup.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { CORS_HEADERS as corsHeaders } from '../../utils/cors.js';
|
| 2 |
+
import { cleanupOldDebugFiles } from '../../utils/debugStorage.js';
|
| 3 |
+
|
| 4 |
+
export async function onRequestPOST(context: any) {
|
| 5 |
+
const { request } = context;
|
| 6 |
+
const url = new URL(request.url);
|
| 7 |
+
const daysToKeep = parseInt(url.searchParams.get('days') || '7');
|
| 8 |
+
|
| 9 |
+
try {
|
| 10 |
+
const deletedCount = await cleanupOldDebugFiles(daysToKeep);
|
| 11 |
+
|
| 12 |
+
return new Response(JSON.stringify({
|
| 13 |
+
success: true,
|
| 14 |
+
message: `已清理 ${deletedCount} 条超过 ${daysToKeep} 天的调试数据`,
|
| 15 |
+
deletedCount
|
| 16 |
+
}), {
|
| 17 |
+
headers: {
|
| 18 |
+
'Content-Type': 'application/json',
|
| 19 |
+
...corsHeaders
|
| 20 |
+
}
|
| 21 |
+
});
|
| 22 |
+
|
| 23 |
+
} catch (error) {
|
| 24 |
+
console.error('Error cleaning up debug data:', error);
|
| 25 |
+
return new Response(JSON.stringify({
|
| 26 |
+
error: 'Failed to cleanup debug data',
|
| 27 |
+
details: error instanceof Error ? error.message : String(error)
|
| 28 |
+
}), {
|
| 29 |
+
status: 500,
|
| 30 |
+
headers: {
|
| 31 |
+
'Content-Type': 'application/json',
|
| 32 |
+
...corsHeaders
|
| 33 |
+
}
|
| 34 |
+
});
|
| 35 |
+
}
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
export async function onRequestOPTIONS() {
|
| 39 |
+
return new Response(null, {
|
| 40 |
+
status: 200,
|
| 41 |
+
headers: corsHeaders
|
| 42 |
+
});
|
| 43 |
+
}
|
functions/api/debug/list.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { CORS_HEADERS as corsHeaders } from '../../utils/cors.js';
|
| 2 |
+
import { getAllDebugInfo } from '../../utils/debugStorage.js';
|
| 3 |
+
|
| 4 |
+
export async function onRequestGET(context: any) {
|
| 5 |
+
const { request } = context;
|
| 6 |
+
const url = new URL(request.url);
|
| 7 |
+
const limit = parseInt(url.searchParams.get('limit') || '50');
|
| 8 |
+
|
| 9 |
+
try {
|
| 10 |
+
// 获取所有调试信息
|
| 11 |
+
const debugItems = await getAllDebugInfo();
|
| 12 |
+
|
| 13 |
+
// 限制返回数量
|
| 14 |
+
const limitedItems = debugItems.slice(0, limit);
|
| 15 |
+
|
| 16 |
+
return new Response(JSON.stringify({
|
| 17 |
+
success: true,
|
| 18 |
+
data: limitedItems,
|
| 19 |
+
total: debugItems.length
|
| 20 |
+
}), {
|
| 21 |
+
headers: {
|
| 22 |
+
'Content-Type': 'application/json',
|
| 23 |
+
...corsHeaders
|
| 24 |
+
}
|
| 25 |
+
});
|
| 26 |
+
|
| 27 |
+
} catch (error) {
|
| 28 |
+
console.error('Error retrieving debug list:', error);
|
| 29 |
+
return new Response(JSON.stringify({
|
| 30 |
+
error: 'Failed to retrieve debug list',
|
| 31 |
+
details: error instanceof Error ? error.message : String(error)
|
| 32 |
+
}), {
|
| 33 |
+
status: 500,
|
| 34 |
+
headers: {
|
| 35 |
+
'Content-Type': 'application/json',
|
| 36 |
+
...corsHeaders
|
| 37 |
+
}
|
| 38 |
+
});
|
| 39 |
+
}
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
export async function onRequestOPTIONS() {
|
| 43 |
+
return new Response(null, {
|
| 44 |
+
status: 200,
|
| 45 |
+
headers: corsHeaders
|
| 46 |
+
});
|
| 47 |
+
}
|
functions/api/debug/screenshot.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { CORS_HEADERS as corsHeaders } from '../../utils/cors.js';
|
| 2 |
+
import { getScreenshot } from '../../utils/debugStorage.js';
|
| 3 |
+
|
| 4 |
+
export async function onRequestGET(context: any) {
|
| 5 |
+
const { request } = context;
|
| 6 |
+
const url = new URL(request.url);
|
| 7 |
+
const debugId = url.searchParams.get('id');
|
| 8 |
+
|
| 9 |
+
if (!debugId) {
|
| 10 |
+
return new Response(JSON.stringify({ error: 'Missing debug ID' }), {
|
| 11 |
+
status: 400,
|
| 12 |
+
headers: {
|
| 13 |
+
'Content-Type': 'application/json',
|
| 14 |
+
...corsHeaders
|
| 15 |
+
}
|
| 16 |
+
});
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
try {
|
| 20 |
+
// 获取截图数据
|
| 21 |
+
const screenshotBuffer = await getScreenshot(debugId);
|
| 22 |
+
|
| 23 |
+
if (!screenshotBuffer) {
|
| 24 |
+
return new Response(JSON.stringify({ error: 'Screenshot not found' }), {
|
| 25 |
+
status: 404,
|
| 26 |
+
headers: {
|
| 27 |
+
'Content-Type': 'application/json',
|
| 28 |
+
...corsHeaders
|
| 29 |
+
}
|
| 30 |
+
});
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
return new Response(screenshotBuffer, {
|
| 34 |
+
headers: {
|
| 35 |
+
'Content-Type': 'image/png',
|
| 36 |
+
'Cache-Control': 'public, max-age=3600',
|
| 37 |
+
...corsHeaders
|
| 38 |
+
}
|
| 39 |
+
});
|
| 40 |
+
|
| 41 |
+
} catch (error) {
|
| 42 |
+
console.error('Error retrieving screenshot:', error);
|
| 43 |
+
return new Response(JSON.stringify({
|
| 44 |
+
error: 'Failed to retrieve screenshot',
|
| 45 |
+
details: error instanceof Error ? error.message : String(error)
|
| 46 |
+
}), {
|
| 47 |
+
status: 500,
|
| 48 |
+
headers: {
|
| 49 |
+
'Content-Type': 'application/json',
|
| 50 |
+
...corsHeaders
|
| 51 |
+
}
|
| 52 |
+
});
|
| 53 |
+
}
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
export async function onRequestOPTIONS() {
|
| 57 |
+
return new Response(null, {
|
| 58 |
+
status: 200,
|
| 59 |
+
headers: corsHeaders
|
| 60 |
+
});
|
| 61 |
+
}
|
functions/types.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ interface KVNamespace {
|
|
| 3 |
put: (key: string, value: string, options?: { expiration?: number, expirationTtl?: number, metadata?: object }) => Promise<void>;
|
| 4 |
get: (key: string) => Promise<string | null>;
|
| 5 |
delete: (key: string) => Promise<void>;
|
|
|
|
| 6 |
}
|
| 7 |
|
| 8 |
interface Env {
|
|
|
|
| 3 |
put: (key: string, value: string, options?: { expiration?: number, expirationTtl?: number, metadata?: object }) => Promise<void>;
|
| 4 |
get: (key: string) => Promise<string | null>;
|
| 5 |
delete: (key: string) => Promise<void>;
|
| 6 |
+
list: (options?: { prefix?: string }) => Promise<{ keys: { name: string }[] }>
|
| 7 |
}
|
| 8 |
|
| 9 |
interface Env {
|
functions/utils/authService.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
import { Page } from 'playwright';
|
| 2 |
import BrowserManager from './browser.js';
|
| 3 |
import { getVerificationCode } from './emailVerification.js';
|
|
|
|
| 4 |
|
| 5 |
interface Account {
|
| 6 |
email: string;
|
|
@@ -39,18 +40,61 @@ export class AuthService {
|
|
| 39 |
const redirectUri = this.env.AUTH_REDIRECT_URI;
|
| 40 |
let browser;
|
| 41 |
let context;
|
|
|
|
|
|
|
| 42 |
|
| 43 |
try {
|
| 44 |
browser = await BrowserManager.getInstance();
|
| 45 |
context = await browser.newContext();
|
| 46 |
-
|
| 47 |
|
| 48 |
const authUrl = this.buildAuthUrl(clientId, redirectUri, account.email);
|
| 49 |
await this.handleLoginProcess(page, account, authUrl);
|
| 50 |
await this.handleMultiFactorAuth(page, account);
|
| 51 |
await this.confirmLogin(page, account);
|
| 52 |
await this.handleConsent(page, redirectUri);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
} finally {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
if (context) await context.close();
|
| 55 |
}
|
| 56 |
}
|
|
@@ -67,6 +111,11 @@ export class AuthService {
|
|
| 67 |
}
|
| 68 |
|
| 69 |
public async loginMail(email: string): Promise<{ success: boolean; error?: string }> {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 70 |
try {
|
| 71 |
const accountsStr = await this.env.KV.get("accounts");
|
| 72 |
const accounts: Account[] = accountsStr ? JSON.parse(accountsStr) : [];
|
|
@@ -75,19 +124,62 @@ export class AuthService {
|
|
| 75 |
if (!account) {
|
| 76 |
throw new Error("Account not found");
|
| 77 |
}
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
|
|
|
| 81 |
await this.handleLoginProcess(page, account, "https://outlook.live.com/mail/0/?prompt=select_account");
|
| 82 |
await this.handleMultiFactorAuth(page, account);
|
| 83 |
await this.confirmLogin(page, account);
|
| 84 |
await page.waitForTimeout(5000);
|
| 85 |
return { success: true };
|
| 86 |
} catch (error: any) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
return {
|
| 88 |
success: false,
|
| 89 |
error: error.message
|
| 90 |
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 91 |
}
|
| 92 |
}
|
| 93 |
|
|
@@ -122,11 +214,17 @@ export class AuthService {
|
|
| 122 |
console.log(account.email, `没有新版切换到密码登录,继续执行: ${error}`);
|
| 123 |
}
|
| 124 |
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
|
| 131 |
|
| 132 |
const proofEmail = account.proofEmail;
|
|
@@ -288,13 +386,13 @@ export class AuthService {
|
|
| 288 |
}, { timeout: 3000 });
|
| 289 |
await page.click('button[type="submit"]#acceptButton', { timeout: 3000 });
|
| 290 |
} catch (error) {
|
| 291 |
-
console.log(account.email,
|
| 292 |
}
|
| 293 |
}
|
| 294 |
|
| 295 |
private async handleConsent(page: Page, redirectUri: string) {
|
| 296 |
try {
|
| 297 |
-
await page.waitForURL("https://account.live.com/Consent/**", { timeout:
|
| 298 |
await page.click('button[type="submit"][data-testid="appConsentPrimaryButton"]');
|
| 299 |
} catch (error) {
|
| 300 |
console.log("Consent page not found or timeout, skipping...");
|
|
|
|
| 1 |
import { Page } from 'playwright';
|
| 2 |
import BrowserManager from './browser.js';
|
| 3 |
import { getVerificationCode } from './emailVerification.js';
|
| 4 |
+
import { saveScreenshot, saveDebugInfo, saveErrorInfo } from './debugStorage.js';
|
| 5 |
|
| 6 |
interface Account {
|
| 7 |
email: string;
|
|
|
|
| 40 |
const redirectUri = this.env.AUTH_REDIRECT_URI;
|
| 41 |
let browser;
|
| 42 |
let context;
|
| 43 |
+
let page;
|
| 44 |
+
const debugId = `auth_${account.email}_${Date.now()}`;
|
| 45 |
|
| 46 |
try {
|
| 47 |
browser = await BrowserManager.getInstance();
|
| 48 |
context = await browser.newContext();
|
| 49 |
+
page = await context.newPage();
|
| 50 |
|
| 51 |
const authUrl = this.buildAuthUrl(clientId, redirectUri, account.email);
|
| 52 |
await this.handleLoginProcess(page, account, authUrl);
|
| 53 |
await this.handleMultiFactorAuth(page, account);
|
| 54 |
await this.confirmLogin(page, account);
|
| 55 |
await this.handleConsent(page, redirectUri);
|
| 56 |
+
} catch (error) {
|
| 57 |
+
// 记录错误信息
|
| 58 |
+
const errorInfo = {
|
| 59 |
+
email: account.email,
|
| 60 |
+
error: error instanceof Error ? error.message : String(error),
|
| 61 |
+
timestamp: new Date().toISOString(),
|
| 62 |
+
debugId: debugId
|
| 63 |
+
};
|
| 64 |
+
|
| 65 |
+
try {
|
| 66 |
+
await saveErrorInfo(debugId, errorInfo);
|
| 67 |
+
} catch (saveError) {
|
| 68 |
+
console.error('Failed to save error info:', saveError);
|
| 69 |
+
}
|
| 70 |
+
throw error;
|
| 71 |
} finally {
|
| 72 |
+
// 截取最后的页面截图
|
| 73 |
+
if (page) {
|
| 74 |
+
try {
|
| 75 |
+
const screenshot = await page.screenshot({
|
| 76 |
+
fullPage: true,
|
| 77 |
+
type: 'png'
|
| 78 |
+
});
|
| 79 |
+
|
| 80 |
+
const screenshotInfo = {
|
| 81 |
+
email: account.email,
|
| 82 |
+
timestamp: new Date().toISOString(),
|
| 83 |
+
debugId: debugId,
|
| 84 |
+
url: page.url(),
|
| 85 |
+
title: await page.title().catch(() => 'Unknown')
|
| 86 |
+
};
|
| 87 |
+
|
| 88 |
+
// 保存截图和调试信息到本地文件
|
| 89 |
+
await saveScreenshot(debugId, screenshot);
|
| 90 |
+
await saveDebugInfo(debugId, screenshotInfo);
|
| 91 |
+
|
| 92 |
+
console.log(`Screenshot saved for debug: ${debugId}`);
|
| 93 |
+
} catch (screenshotError) {
|
| 94 |
+
console.error('Failed to take screenshot:', screenshotError);
|
| 95 |
+
}
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
if (context) await context.close();
|
| 99 |
}
|
| 100 |
}
|
|
|
|
| 111 |
}
|
| 112 |
|
| 113 |
public async loginMail(email: string): Promise<{ success: boolean; error?: string }> {
|
| 114 |
+
let browser;
|
| 115 |
+
let context;
|
| 116 |
+
let page;
|
| 117 |
+
const debugId = `login_${email}_${Date.now()}`;
|
| 118 |
+
|
| 119 |
try {
|
| 120 |
const accountsStr = await this.env.KV.get("accounts");
|
| 121 |
const accounts: Account[] = accountsStr ? JSON.parse(accountsStr) : [];
|
|
|
|
| 124 |
if (!account) {
|
| 125 |
throw new Error("Account not found");
|
| 126 |
}
|
| 127 |
+
|
| 128 |
+
browser = await BrowserManager.getInstance();
|
| 129 |
+
context = await browser.newContext();
|
| 130 |
+
page = await context.newPage();
|
| 131 |
await this.handleLoginProcess(page, account, "https://outlook.live.com/mail/0/?prompt=select_account");
|
| 132 |
await this.handleMultiFactorAuth(page, account);
|
| 133 |
await this.confirmLogin(page, account);
|
| 134 |
await page.waitForTimeout(5000);
|
| 135 |
return { success: true };
|
| 136 |
} catch (error: any) {
|
| 137 |
+
// 记录错误信息
|
| 138 |
+
const errorInfo = {
|
| 139 |
+
email: email,
|
| 140 |
+
error: error instanceof Error ? error.message : String(error),
|
| 141 |
+
timestamp: new Date().toISOString(),
|
| 142 |
+
debugId: debugId
|
| 143 |
+
};
|
| 144 |
+
|
| 145 |
+
try {
|
| 146 |
+
await saveErrorInfo(debugId, errorInfo);
|
| 147 |
+
} catch (saveError) {
|
| 148 |
+
console.error('Failed to save error info:', saveError);
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
return {
|
| 152 |
success: false,
|
| 153 |
error: error.message
|
| 154 |
};
|
| 155 |
+
} finally {
|
| 156 |
+
// 截取最后的页面截图
|
| 157 |
+
if (page) {
|
| 158 |
+
try {
|
| 159 |
+
const screenshot = await page.screenshot({
|
| 160 |
+
fullPage: true,
|
| 161 |
+
type: 'png'
|
| 162 |
+
});
|
| 163 |
+
|
| 164 |
+
const screenshotInfo = {
|
| 165 |
+
email: email,
|
| 166 |
+
timestamp: new Date().toISOString(),
|
| 167 |
+
debugId: debugId,
|
| 168 |
+
url: page.url(),
|
| 169 |
+
title: await page.title().catch(() => 'Unknown')
|
| 170 |
+
};
|
| 171 |
+
|
| 172 |
+
// 保存截图和调试信息到本地文件
|
| 173 |
+
await saveScreenshot(debugId, screenshot);
|
| 174 |
+
await saveDebugInfo(debugId, screenshotInfo);
|
| 175 |
+
|
| 176 |
+
console.log(`Screenshot saved for debug: ${debugId}`);
|
| 177 |
+
} catch (screenshotError) {
|
| 178 |
+
console.error('Failed to take screenshot:', screenshotError);
|
| 179 |
+
}
|
| 180 |
+
}
|
| 181 |
+
|
| 182 |
+
if (context) await context.close();
|
| 183 |
}
|
| 184 |
}
|
| 185 |
|
|
|
|
| 214 |
console.log(account.email, `没有新版切换到密码登录,继续执行: ${error}`);
|
| 215 |
}
|
| 216 |
|
| 217 |
+
try {
|
| 218 |
+
await page.waitForURL("https://login.live.com/**", { timeout: 30000 });
|
| 219 |
+
// 填写密码 - 双重填写确保成功
|
| 220 |
+
await page.fill('input[type="password"]', account.password);
|
| 221 |
+
await page.waitForTimeout(500); // 等待页面稳定
|
| 222 |
+
await page.fill('input[type="password"]', account.password);
|
| 223 |
+
await page.click('button[type="submit"]');
|
| 224 |
+
await page.waitForTimeout(2000); // 等待提交处理
|
| 225 |
+
} catch (error) {
|
| 226 |
+
console.log(account.email, `填写密码失败: ${error}`);
|
| 227 |
+
}
|
| 228 |
|
| 229 |
|
| 230 |
const proofEmail = account.proofEmail;
|
|
|
|
| 386 |
}, { timeout: 3000 });
|
| 387 |
await page.click('button[type="submit"]#acceptButton', { timeout: 3000 });
|
| 388 |
} catch (error) {
|
| 389 |
+
console.log(account.email, `无旧版的登录确认,继续执行: ${error}`);
|
| 390 |
}
|
| 391 |
}
|
| 392 |
|
| 393 |
private async handleConsent(page: Page, redirectUri: string) {
|
| 394 |
try {
|
| 395 |
+
await page.waitForURL("https://account.live.com/Consent/**", { timeout: 20000 });
|
| 396 |
await page.click('button[type="submit"][data-testid="appConsentPrimaryButton"]');
|
| 397 |
} catch (error) {
|
| 398 |
console.log("Consent page not found or timeout, skipping...");
|
functions/utils/debugStorage.ts
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import fs from 'fs/promises';
|
| 2 |
+
import path from 'path';
|
| 3 |
+
|
| 4 |
+
const DEBUG_DIR = 'debug';
|
| 5 |
+
|
| 6 |
+
// 确保调试目录存在
|
| 7 |
+
async function ensureDebugDir() {
|
| 8 |
+
try {
|
| 9 |
+
await fs.access(DEBUG_DIR);
|
| 10 |
+
} catch {
|
| 11 |
+
await fs.mkdir(DEBUG_DIR, { recursive: true });
|
| 12 |
+
}
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
// 保存截图
|
| 16 |
+
export async function saveScreenshot(debugId: string, screenshot: Buffer): Promise<void> {
|
| 17 |
+
await ensureDebugDir();
|
| 18 |
+
const screenshotPath = path.join(DEBUG_DIR, `${debugId}.png`);
|
| 19 |
+
await fs.writeFile(screenshotPath, screenshot);
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
// 保存调试信息
|
| 23 |
+
export async function saveDebugInfo(debugId: string, info: any): Promise<void> {
|
| 24 |
+
await ensureDebugDir();
|
| 25 |
+
const infoPath = path.join(DEBUG_DIR, `${debugId}.json`);
|
| 26 |
+
await fs.writeFile(infoPath, JSON.stringify(info, null, 2));
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
// 保存错误信息
|
| 30 |
+
export async function saveErrorInfo(debugId: string, error: any): Promise<void> {
|
| 31 |
+
await ensureDebugDir();
|
| 32 |
+
const errorPath = path.join(DEBUG_DIR, `${debugId}_error.json`);
|
| 33 |
+
await fs.writeFile(errorPath, JSON.stringify(error, null, 2));
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
// 获取所有调试信息
|
| 37 |
+
export async function getAllDebugInfo(): Promise<any[]> {
|
| 38 |
+
try {
|
| 39 |
+
await ensureDebugDir();
|
| 40 |
+
const files = await fs.readdir(DEBUG_DIR);
|
| 41 |
+
const debugItems = [];
|
| 42 |
+
|
| 43 |
+
// 获取所有 .json 文件(排除 _error.json)
|
| 44 |
+
const infoFiles = files.filter(file => file.endsWith('.json') && !file.endsWith('_error.json'));
|
| 45 |
+
|
| 46 |
+
for (const file of infoFiles) {
|
| 47 |
+
try {
|
| 48 |
+
const debugId = file.replace('.json', '');
|
| 49 |
+
const infoPath = path.join(DEBUG_DIR, file);
|
| 50 |
+
const errorPath = path.join(DEBUG_DIR, `${debugId}_error.json`);
|
| 51 |
+
const screenshotPath = path.join(DEBUG_DIR, `${debugId}.png`);
|
| 52 |
+
|
| 53 |
+
// 读取基本信息
|
| 54 |
+
const infoData = await fs.readFile(infoPath, 'utf-8');
|
| 55 |
+
const info = JSON.parse(infoData);
|
| 56 |
+
|
| 57 |
+
// 检查是否有错误信息
|
| 58 |
+
let errorInfo = null;
|
| 59 |
+
try {
|
| 60 |
+
const errorData = await fs.readFile(errorPath, 'utf-8');
|
| 61 |
+
errorInfo = JSON.parse(errorData);
|
| 62 |
+
} catch {
|
| 63 |
+
// 没有错误文件,忽略
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
// 检查是否有截图
|
| 67 |
+
let hasScreenshot = false;
|
| 68 |
+
try {
|
| 69 |
+
await fs.access(screenshotPath);
|
| 70 |
+
hasScreenshot = true;
|
| 71 |
+
} catch {
|
| 72 |
+
// 没有截图文件,忽略
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
debugItems.push({
|
| 76 |
+
debugId,
|
| 77 |
+
...info,
|
| 78 |
+
hasError: !!errorInfo,
|
| 79 |
+
errorMessage: errorInfo?.error || null,
|
| 80 |
+
hasScreenshot,
|
| 81 |
+
screenshotUrl: hasScreenshot ? `/api/debug/screenshot?id=${debugId}` : null
|
| 82 |
+
});
|
| 83 |
+
} catch (error) {
|
| 84 |
+
console.error(`Error reading debug file ${file}:`, error);
|
| 85 |
+
}
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
// 按时间戳排序(最新的在前)
|
| 89 |
+
debugItems.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
| 90 |
+
|
| 91 |
+
return debugItems;
|
| 92 |
+
} catch (error) {
|
| 93 |
+
console.error('Error reading debug directory:', error);
|
| 94 |
+
return [];
|
| 95 |
+
}
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
// 获取截图
|
| 99 |
+
export async function getScreenshot(debugId: string): Promise<Buffer | null> {
|
| 100 |
+
try {
|
| 101 |
+
const screenshotPath = path.join(DEBUG_DIR, `${debugId}.png`);
|
| 102 |
+
return await fs.readFile(screenshotPath);
|
| 103 |
+
} catch {
|
| 104 |
+
return null;
|
| 105 |
+
}
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
// 清理旧的调试文件
|
| 109 |
+
export async function cleanupOldDebugFiles(daysToKeep: number = 7): Promise<number> {
|
| 110 |
+
try {
|
| 111 |
+
await ensureDebugDir();
|
| 112 |
+
const files = await fs.readdir(DEBUG_DIR);
|
| 113 |
+
const cutoffTime = Date.now() - (daysToKeep * 24 * 60 * 60 * 1000);
|
| 114 |
+
let deletedCount = 0;
|
| 115 |
+
|
| 116 |
+
for (const file of files) {
|
| 117 |
+
try {
|
| 118 |
+
const filePath = path.join(DEBUG_DIR, file);
|
| 119 |
+
const stats = await fs.stat(filePath);
|
| 120 |
+
|
| 121 |
+
if (stats.mtime.getTime() < cutoffTime) {
|
| 122 |
+
await fs.unlink(filePath);
|
| 123 |
+
deletedCount++;
|
| 124 |
+
}
|
| 125 |
+
} catch (error) {
|
| 126 |
+
console.error(`Error processing file ${file}:`, error);
|
| 127 |
+
}
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
return deletedCount;
|
| 131 |
+
} catch (error) {
|
| 132 |
+
console.error('Error cleaning up debug files:', error);
|
| 133 |
+
return 0;
|
| 134 |
+
}
|
| 135 |
+
}
|
index.ts
CHANGED
|
@@ -30,6 +30,9 @@ import { onRequest as handleMailSend } from './functions/api/mail/send.js'
|
|
| 30 |
import { onRequest as handleBatch } from './functions/api/mail/batch.js'
|
| 31 |
import { onRequest as handleMailStatus } from './functions/api/mail/status.js'
|
| 32 |
import { onRequest as handleMailActivate } from './functions/api/mail/activate.js'
|
|
|
|
|
|
|
|
|
|
| 33 |
dotenv.config({ path: ['.env', '.env.local'], override: true });
|
| 34 |
const isDev = process.env.NODE_ENV === 'development'
|
| 35 |
|
|
@@ -64,6 +67,13 @@ const kv: KVNamespace = {
|
|
| 64 |
},
|
| 65 |
delete: async (key: string) => {
|
| 66 |
await storage.removeItem(key);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
}
|
| 68 |
};
|
| 69 |
|
|
@@ -169,7 +179,18 @@ app.all('/api/*', async (c) => {
|
|
| 169 |
case '/api/mail/activate':
|
| 170 |
response = await handleMailActivate(context);
|
| 171 |
break;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 172 |
default:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 173 |
return c.json({ error: 'Route not found' }, 404);
|
| 174 |
}
|
| 175 |
return response;
|
|
|
|
| 30 |
import { onRequest as handleBatch } from './functions/api/mail/batch.js'
|
| 31 |
import { onRequest as handleMailStatus } from './functions/api/mail/status.js'
|
| 32 |
import { onRequest as handleMailActivate } from './functions/api/mail/activate.js'
|
| 33 |
+
import { onRequestGET as handleDebugList } from './functions/api/debug/list.js'
|
| 34 |
+
import { onRequestGET as handleDebugScreenshot } from './functions/api/debug/screenshot.js'
|
| 35 |
+
import { onRequestPOST as handleDebugCleanup } from './functions/api/debug/cleanup.js'
|
| 36 |
dotenv.config({ path: ['.env', '.env.local'], override: true });
|
| 37 |
const isDev = process.env.NODE_ENV === 'development'
|
| 38 |
|
|
|
|
| 67 |
},
|
| 68 |
delete: async (key: string) => {
|
| 69 |
await storage.removeItem(key);
|
| 70 |
+
},
|
| 71 |
+
list: async (options?: { prefix?: string }) => {
|
| 72 |
+
const keys = await storage.getKeys(options?.prefix);
|
| 73 |
+
console.log(options?.prefix,keys)
|
| 74 |
+
return {
|
| 75 |
+
keys: keys.map(key => ({ name: key }))
|
| 76 |
+
};
|
| 77 |
}
|
| 78 |
};
|
| 79 |
|
|
|
|
| 179 |
case '/api/mail/activate':
|
| 180 |
response = await handleMailActivate(context);
|
| 181 |
break;
|
| 182 |
+
case '/api/debug/list':
|
| 183 |
+
response = await handleDebugList(context);
|
| 184 |
+
break;
|
| 185 |
+
case '/api/debug/cleanup':
|
| 186 |
+
response = await handleDebugCleanup(context);
|
| 187 |
+
break;
|
| 188 |
default:
|
| 189 |
+
// 处理带参数的路由
|
| 190 |
+
if (path.startsWith('/api/debug/screenshot')) {
|
| 191 |
+
response = await handleDebugScreenshot(context);
|
| 192 |
+
break;
|
| 193 |
+
}
|
| 194 |
return c.json({ error: 'Route not found' }, 404);
|
| 195 |
}
|
| 196 |
return response;
|
src/App.vue
CHANGED
|
@@ -24,6 +24,11 @@ const menu = [
|
|
| 24 |
name: '设置',
|
| 25 |
path: '/setting',
|
| 26 |
icon: 'setting-1'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
}
|
| 28 |
];
|
| 29 |
|
|
|
|
| 24 |
name: '设置',
|
| 25 |
path: '/setting',
|
| 26 |
icon: 'setting-1'
|
| 27 |
+
},
|
| 28 |
+
{
|
| 29 |
+
name: '调试',
|
| 30 |
+
path: '/debug',
|
| 31 |
+
icon: 'bug'
|
| 32 |
}
|
| 33 |
];
|
| 34 |
|
src/router/index.ts
CHANGED
|
@@ -33,6 +33,12 @@ const router = createRouter({
|
|
| 33 |
component: () => import('../views/AccountView.vue'),
|
| 34 |
meta: { requiresAuth: true }
|
| 35 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
],
|
| 37 |
})
|
| 38 |
// 添加路由守卫
|
|
|
|
| 33 |
component: () => import('../views/AccountView.vue'),
|
| 34 |
meta: { requiresAuth: true }
|
| 35 |
},
|
| 36 |
+
{
|
| 37 |
+
path: '/debug',
|
| 38 |
+
name: 'Debug',
|
| 39 |
+
component: () => import('../views/DebugView.vue'),
|
| 40 |
+
meta: { requiresAuth: true }
|
| 41 |
+
},
|
| 42 |
],
|
| 43 |
})
|
| 44 |
// 添加路由守卫
|
src/views/DebugView.vue
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<template>
|
| 2 |
+
<div class="debug-view p-6">
|
| 3 |
+
<div class="mb-4">
|
| 4 |
+
<t-button theme="primary" @click="loadDebugData" :loading="loading">
|
| 5 |
+
<template #icon>
|
| 6 |
+
<t-icon name="refresh" />
|
| 7 |
+
</template>
|
| 8 |
+
刷新数据
|
| 9 |
+
</t-button>
|
| 10 |
+
</div>
|
| 11 |
+
|
| 12 |
+
<t-loading :loading="loading" text="正在加载调试数据...">
|
| 13 |
+
<div v-if="error" class="mb-4">
|
| 14 |
+
<t-alert theme="error" :message="error" />
|
| 15 |
+
</div>
|
| 16 |
+
|
| 17 |
+
<div v-if="!loading && debugList.length === 0" class="text-center py-12">
|
| 18 |
+
<t-icon name="inbox" size="48px" class="text-gray-400 mb-4" />
|
| 19 |
+
<p class="text-gray-500">暂无调试数据</p>
|
| 20 |
+
</div>
|
| 21 |
+
|
| 22 |
+
<div v-else class="space-y-4">
|
| 23 |
+
<t-card
|
| 24 |
+
v-for="item in debugList"
|
| 25 |
+
:key="item.debugId"
|
| 26 |
+
:class="{ 'border-red-200': item.hasError }"
|
| 27 |
+
class="cursor-pointer hover:shadow-md transition-shadow"
|
| 28 |
+
@click="toggleDebugContent(item.debugId)"
|
| 29 |
+
>
|
| 30 |
+
<template #header>
|
| 31 |
+
<div class="flex justify-between items-center">
|
| 32 |
+
<div class="flex items-center space-x-3">
|
| 33 |
+
<strong class="text-lg">{{ item.email }}</strong>
|
| 34 |
+
<t-tag
|
| 35 |
+
:theme="item.hasError ? 'danger' : 'success'"
|
| 36 |
+
variant="light"
|
| 37 |
+
>
|
| 38 |
+
{{ item.hasError ? '失败' : '成功' }}
|
| 39 |
+
</t-tag>
|
| 40 |
+
<span class="text-gray-500 text-sm">
|
| 41 |
+
{{ formatTime(item.timestamp) }}
|
| 42 |
+
</span>
|
| 43 |
+
</div>
|
| 44 |
+
<t-icon
|
| 45 |
+
:name="expandedItems.has(item.debugId) ? 'chevron-up' : 'chevron-down'"
|
| 46 |
+
class="text-gray-400"
|
| 47 |
+
/>
|
| 48 |
+
</div>
|
| 49 |
+
</template>
|
| 50 |
+
|
| 51 |
+
<div v-if="expandedItems.has(item.debugId)" class="space-y-4">
|
| 52 |
+
<div v-if="item.hasError" class="mb-4">
|
| 53 |
+
<t-alert theme="error" :message="`错误信息: ${item.errorMessage}`" />
|
| 54 |
+
</div>
|
| 55 |
+
|
| 56 |
+
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 text-sm">
|
| 57 |
+
<div>
|
| 58 |
+
<span class="font-medium text-gray-700">调试ID:</span>
|
| 59 |
+
<span class="ml-2 text-gray-600 font-mono">{{ item.debugId }}</span>
|
| 60 |
+
</div>
|
| 61 |
+
<div>
|
| 62 |
+
<span class="font-medium text-gray-700">邮箱:</span>
|
| 63 |
+
<span class="ml-2 text-gray-600">{{ item.email }}</span>
|
| 64 |
+
</div>
|
| 65 |
+
<div>
|
| 66 |
+
<span class="font-medium text-gray-700">时间:</span>
|
| 67 |
+
<span class="ml-2 text-gray-600">{{ formatTime(item.timestamp) }}</span>
|
| 68 |
+
</div>
|
| 69 |
+
<div>
|
| 70 |
+
<span class="font-medium text-gray-700">页面标题:</span>
|
| 71 |
+
<span class="ml-2 text-gray-600">{{ item.title }}</span>
|
| 72 |
+
</div>
|
| 73 |
+
<div class="md:col-span-2">
|
| 74 |
+
<span class="font-medium text-gray-700">页面URL:</span>
|
| 75 |
+
<span class="ml-2 text-gray-600 break-all">{{ item.url }}</span>
|
| 76 |
+
</div>
|
| 77 |
+
</div>
|
| 78 |
+
|
| 79 |
+
<div>
|
| 80 |
+
<h4 class="font-medium text-gray-700 mb-2">页面截图:</h4>
|
| 81 |
+
<div class="border rounded-lg overflow-hidden bg-gray-50">
|
| 82 |
+
<img
|
| 83 |
+
:src="item.screenshotUrl"
|
| 84 |
+
:alt="`${item.email} 的页面截图`"
|
| 85 |
+
class="w-full h-auto max-h-96 object-contain"
|
| 86 |
+
loading="lazy"
|
| 87 |
+
@error="handleImageError"
|
| 88 |
+
/>
|
| 89 |
+
</div>
|
| 90 |
+
</div>
|
| 91 |
+
</div>
|
| 92 |
+
</t-card>
|
| 93 |
+
</div>
|
| 94 |
+
</t-loading>
|
| 95 |
+
</div>
|
| 96 |
+
</template>
|
| 97 |
+
|
| 98 |
+
<script setup lang="ts">
|
| 99 |
+
import { ref, onMounted } from 'vue'
|
| 100 |
+
|
| 101 |
+
interface DebugItem {
|
| 102 |
+
debugId: string
|
| 103 |
+
email: string
|
| 104 |
+
timestamp: string
|
| 105 |
+
url: string
|
| 106 |
+
title: string
|
| 107 |
+
hasError: boolean
|
| 108 |
+
errorMessage?: string
|
| 109 |
+
screenshotUrl: string
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
const loading = ref(false)
|
| 113 |
+
const error = ref('')
|
| 114 |
+
const debugList = ref<DebugItem[]>([])
|
| 115 |
+
const expandedItems = ref(new Set<string>())
|
| 116 |
+
|
| 117 |
+
const loadDebugData = async () => {
|
| 118 |
+
loading.value = true
|
| 119 |
+
error.value = ''
|
| 120 |
+
|
| 121 |
+
try {
|
| 122 |
+
const response = await fetch('/api/debug/list')
|
| 123 |
+
const result = await response.json()
|
| 124 |
+
|
| 125 |
+
if (result.success) {
|
| 126 |
+
debugList.value = result.data
|
| 127 |
+
} else {
|
| 128 |
+
error.value = `加载失败: ${result.error}`
|
| 129 |
+
}
|
| 130 |
+
} catch (err) {
|
| 131 |
+
error.value = `网络错误: ${err instanceof Error ? err.message : String(err)}`
|
| 132 |
+
} finally {
|
| 133 |
+
loading.value = false
|
| 134 |
+
}
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
const toggleDebugContent = (debugId: string) => {
|
| 138 |
+
if (expandedItems.value.has(debugId)) {
|
| 139 |
+
expandedItems.value.delete(debugId)
|
| 140 |
+
} else {
|
| 141 |
+
expandedItems.value.add(debugId)
|
| 142 |
+
}
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
const formatTime = (timestamp: string) => {
|
| 146 |
+
return new Date(timestamp).toLocaleString('zh-CN')
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
const handleImageError = (event: Event) => {
|
| 150 |
+
const img = event.target as HTMLImageElement
|
| 151 |
+
img.style.display = 'none'
|
| 152 |
+
const parent = img.parentElement
|
| 153 |
+
if (parent) {
|
| 154 |
+
parent.innerHTML = '<div class="text-center py-8 text-gray-500">截图加载失败</div>'
|
| 155 |
+
}
|
| 156 |
+
}
|
| 157 |
+
|
| 158 |
+
onMounted(() => {
|
| 159 |
+
loadDebugData()
|
| 160 |
+
})
|
| 161 |
+
</script>
|