github-actions[bot] commited on
Commit
11a8eb2
·
1 Parent(s): 3ed7406

Update from GitHub Actions

Browse files
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
- const page = await context.newPage();
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
- const browser = await BrowserManager.getInstance();
79
- const context = await browser.newContext();
80
- const page = await context.newPage();
 
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
- await page.waitForURL("https://login.live.com/**");
127
- await page.fill('input[type="password"]', account.password);
128
- await page.fill('input[type="password"]', account.password);
129
- await page.click('button[type="submit"]');
 
 
 
 
 
 
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, `无其他登录确认,继续执行: ${error}`);
292
  }
293
  }
294
 
295
  private async handleConsent(page: Page, redirectUri: string) {
296
  try {
297
- await page.waitForURL("https://account.live.com/Consent/**", { timeout: 10000 });
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>