dreammis commited on
Commit
89bc4ac
·
2 Parent(s): 988a36e75bfc25

Merge pull request #172 from wjj9868/feat/system-optimization

Browse files
myUtils/login.py CHANGED
@@ -7,7 +7,24 @@ from myUtils.auth import check_cookie
7
  from utils.base_social_media import set_init_script
8
  import uuid
9
  from pathlib import Path
10
- from conf import BASE_DIR, LOCAL_CHROME_HEADLESS
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
 
12
  # 抖音登录
13
  async def douyin_cookie_gen(id,status_queue):
@@ -17,9 +34,7 @@ async def douyin_cookie_gen(id,status_queue):
17
  if page.url != original_url:
18
  url_changed_event.set()
19
  async with async_playwright() as playwright:
20
- options = {
21
- 'headless': LOCAL_CHROME_HEADLESS
22
- }
23
  # Make sure to run headed.
24
  browser = await playwright.chromium.launch(**options)
25
  # Setup context however you like.
 
7
  from utils.base_social_media import set_init_script
8
  import uuid
9
  from pathlib import Path
10
+ from conf import BASE_DIR, LOCAL_CHROME_HEADLESS, LOCAL_CHROME_PATH
11
+
12
+ # 统一获取浏览器启动配置(防风控+引入本地浏览器)
13
+ def get_browser_options():
14
+ options = {
15
+ 'headless': LOCAL_CHROME_HEADLESS,
16
+ 'args': [
17
+ '--disable-blink-features=AutomationControlled', # 核心防爬屏蔽:去掉 window.navigator.webdriver 标签
18
+ '--lang=zh-CN',
19
+ '--disable-infobars',
20
+ '--start-maximized'
21
+ ]
22
+ }
23
+ # 如果用户在 conf.py 里配置了本地 Chrome,就用本地的,这样成功率极高
24
+ if LOCAL_CHROME_PATH:
25
+ options['executable_path'] = LOCAL_CHROME_PATH
26
+
27
+ return options
28
 
29
  # 抖音登录
30
  async def douyin_cookie_gen(id,status_queue):
 
34
  if page.url != original_url:
35
  url_changed_event.set()
36
  async with async_playwright() as playwright:
37
+ options = get_browser_options()
 
 
38
  # Make sure to run headed.
39
  browser = await playwright.chromium.launch(**options)
40
  # Setup context however you like.
sau_backend.py CHANGED
@@ -48,14 +48,14 @@ def index(): # put application's code here
48
  def upload_file():
49
  if 'file' not in request.files:
50
  return jsonify({
51
- "code": 200,
52
  "data": None,
53
  "msg": "No file part in the request"
54
  }), 400
55
  file = request.files['file']
56
  if file.filename == '':
57
  return jsonify({
58
- "code": 200,
59
  "data": None,
60
  "msg": "No selected file"
61
  }), 400
@@ -67,7 +67,7 @@ def upload_file():
67
  file.save(filepath)
68
  return jsonify({"code":200,"msg": "File uploaded successfully", "data": f"{uuid_v1}_{file.filename}"}), 200
69
  except Exception as e:
70
- return jsonify({"code":200,"msg": str(e),"data":None}), 500
71
 
72
  @app.route('/getFile', methods=['GET'])
73
  def get_file():
@@ -75,11 +75,11 @@ def get_file():
75
  filename = request.args.get('filename')
76
 
77
  if not filename:
78
- return {"error": "filename is required"}, 400
79
 
80
  # 防止路径穿越攻击
81
  if '..' in filename or filename.startswith('/'):
82
- return {"error": "Invalid filename"}, 400
83
 
84
  # 拼接完整路径
85
  file_path = str(Path(BASE_DIR / "videoFile"))
@@ -316,7 +316,16 @@ def delete_file():
316
 
317
  @app.route('/deleteAccount', methods=['GET'])
318
  def delete_account():
319
- account_id = int(request.args.get('id'))
 
 
 
 
 
 
 
 
 
320
 
321
  try:
322
  # 获取数据库连接
@@ -337,6 +346,16 @@ def delete_account():
337
 
338
  record = dict(record)
339
 
 
 
 
 
 
 
 
 
 
 
340
  # 删除数据库记录
341
  cursor.execute("DELETE FROM user_info WHERE id = ?", (account_id,))
342
  conn.commit()
@@ -350,7 +369,7 @@ def delete_account():
350
  except Exception as e:
351
  return jsonify({
352
  "code": 500,
353
- "msg": str("delete failed!"),
354
  "data": None
355
  }), 500
356
 
@@ -385,6 +404,9 @@ def postVideo():
385
  # 获取JSON数据
386
  data = request.get_json()
387
 
 
 
 
388
  # 从JSON数据中提取fileList和accountList
389
  file_list = data.get('fileList', [])
390
  account_list = data.get('accountList', [])
@@ -403,29 +425,52 @@ def postVideo():
403
  videos_per_day = data.get('videosPerDay')
404
  daily_times = data.get('dailyTimes')
405
  start_days = data.get('startDays')
 
 
 
 
 
 
 
 
 
 
 
406
  # 打印获取到的数据(仅作为示例)
407
  print("File List:", file_list)
408
  print("Account List:", account_list)
409
- match type:
410
- case 1:
411
- post_video_xhs(title, file_list, tags, account_list, category, enableTimer, videos_per_day, daily_times,
412
- start_days)
413
- case 2:
414
- post_video_tencent(title, file_list, tags, account_list, category, enableTimer, videos_per_day, daily_times,
415
- start_days, is_draft)
416
- case 3:
417
- post_video_DouYin(title, file_list, tags, account_list, category, enableTimer, videos_per_day, daily_times,
418
- start_days, thumbnail_path, productLink, productTitle)
419
- case 4:
420
- post_video_ks(title, file_list, tags, account_list, category, enableTimer, videos_per_day, daily_times,
421
- start_days)
422
- # 返回响应给客户端
423
- return jsonify(
424
- {
425
- "code": 200,
426
- "msg": None,
 
 
 
 
 
 
 
 
 
 
 
 
427
  "data": None
428
- }), 200
429
 
430
 
431
  @app.route('/updateUserinfo', methods=['POST'])
@@ -470,7 +515,7 @@ def postVideoBatch():
470
  data_list = request.get_json()
471
 
472
  if not isinstance(data_list, list):
473
- return jsonify({"error": "Expected a JSON array"}), 400
474
  for data in data_list:
475
  # 从JSON数据中提取fileList和accountList
476
  file_list = data.get('fileList', [])
@@ -484,6 +529,7 @@ def postVideoBatch():
484
  category = None
485
  productLink = data.get('productLink', '')
486
  productTitle = data.get('productTitle', '')
 
487
 
488
  videos_per_day = data.get('videosPerDay')
489
  daily_times = data.get('dailyTimes')
@@ -493,10 +539,11 @@ def postVideoBatch():
493
  print("Account List:", account_list)
494
  match type:
495
  case 1:
496
- return
 
497
  case 2:
498
  post_video_tencent(title, file_list, tags, account_list, category, enableTimer, videos_per_day, daily_times,
499
- start_days)
500
  case 3:
501
  post_video_DouYin(title, file_list, tags, account_list, category, enableTimer, videos_per_day, daily_times,
502
  start_days, productLink, productTitle)
@@ -517,7 +564,7 @@ def upload_cookie():
517
  try:
518
  if 'file' not in request.files:
519
  return jsonify({
520
- "code": 500,
521
  "msg": "没有找到Cookie文件",
522
  "data": None
523
  }), 400
@@ -525,14 +572,14 @@ def upload_cookie():
525
  file = request.files['file']
526
  if file.filename == '':
527
  return jsonify({
528
- "code": 500,
529
  "msg": "Cookie文件名不能为空",
530
  "data": None
531
  }), 400
532
 
533
  if not file.filename.endswith('.json'):
534
  return jsonify({
535
- "code": 500,
536
  "msg": "Cookie文件必须是JSON格式",
537
  "data": None
538
  }), 400
@@ -543,7 +590,7 @@ def upload_cookie():
543
 
544
  if not account_id or not platform:
545
  return jsonify({
546
- "code": 500,
547
  "msg": "缺少账号ID或平台信息",
548
  "data": None
549
  }), 400
 
48
  def upload_file():
49
  if 'file' not in request.files:
50
  return jsonify({
51
+ "code": 400,
52
  "data": None,
53
  "msg": "No file part in the request"
54
  }), 400
55
  file = request.files['file']
56
  if file.filename == '':
57
  return jsonify({
58
+ "code": 400,
59
  "data": None,
60
  "msg": "No selected file"
61
  }), 400
 
67
  file.save(filepath)
68
  return jsonify({"code":200,"msg": "File uploaded successfully", "data": f"{uuid_v1}_{file.filename}"}), 200
69
  except Exception as e:
70
+ return jsonify({"code":500,"msg": str(e),"data":None}), 500
71
 
72
  @app.route('/getFile', methods=['GET'])
73
  def get_file():
 
75
  filename = request.args.get('filename')
76
 
77
  if not filename:
78
+ return jsonify({"code": 400, "msg": "filename is required", "data": None}), 400
79
 
80
  # 防止路径穿越攻击
81
  if '..' in filename or filename.startswith('/'):
82
+ return jsonify({"code": 400, "msg": "Invalid filename", "data": None}), 400
83
 
84
  # 拼接完整路径
85
  file_path = str(Path(BASE_DIR / "videoFile"))
 
316
 
317
  @app.route('/deleteAccount', methods=['GET'])
318
  def delete_account():
319
+ account_id = request.args.get('id')
320
+
321
+ if not account_id or not account_id.isdigit():
322
+ return jsonify({
323
+ "code": 400,
324
+ "msg": "Invalid or missing account ID",
325
+ "data": None
326
+ }), 400
327
+
328
+ account_id = int(account_id)
329
 
330
  try:
331
  # 获取数据库连接
 
346
 
347
  record = dict(record)
348
 
349
+ # 删除关联的cookie文件
350
+ if record.get('filePath'):
351
+ cookie_file_path = Path(BASE_DIR / "cookiesFile" / record['filePath'])
352
+ if cookie_file_path.exists():
353
+ try:
354
+ cookie_file_path.unlink()
355
+ print(f"✅ Cookie文件已删除: {cookie_file_path}")
356
+ except Exception as e:
357
+ print(f"⚠️ 删除Cookie文件失败: {e}")
358
+
359
  # 删除数据库记录
360
  cursor.execute("DELETE FROM user_info WHERE id = ?", (account_id,))
361
  conn.commit()
 
369
  except Exception as e:
370
  return jsonify({
371
  "code": 500,
372
+ "msg": f"delete failed: {str(e)}",
373
  "data": None
374
  }), 500
375
 
 
404
  # 获取JSON数据
405
  data = request.get_json()
406
 
407
+ if not data:
408
+ return jsonify({"code": 400, "msg": "请求数据不能为空", "data": None}), 400
409
+
410
  # 从JSON数据中提取fileList和accountList
411
  file_list = data.get('fileList', [])
412
  account_list = data.get('accountList', [])
 
425
  videos_per_day = data.get('videosPerDay')
426
  daily_times = data.get('dailyTimes')
427
  start_days = data.get('startDays')
428
+
429
+ # 参数校验
430
+ if not file_list:
431
+ return jsonify({"code": 400, "msg": "文件列表不能为空", "data": None}), 400
432
+ if not account_list:
433
+ return jsonify({"code": 400, "msg": "账号列表不能为空", "data": None}), 400
434
+ if not type:
435
+ return jsonify({"code": 400, "msg": "平台类型不能为空", "data": None}), 400
436
+ if not title:
437
+ return jsonify({"code": 400, "msg": "标题不能为空", "data": None}), 400
438
+
439
  # 打印获取到的数据(仅作为示例)
440
  print("File List:", file_list)
441
  print("Account List:", account_list)
442
+
443
+ try:
444
+ match type:
445
+ case 1:
446
+ post_video_xhs(title, file_list, tags, account_list, category, enableTimer, videos_per_day, daily_times,
447
+ start_days)
448
+ case 2:
449
+ post_video_tencent(title, file_list, tags, account_list, category, enableTimer, videos_per_day, daily_times,
450
+ start_days, is_draft)
451
+ case 3:
452
+ post_video_DouYin(title, file_list, tags, account_list, category, enableTimer, videos_per_day, daily_times,
453
+ start_days, thumbnail_path, productLink, productTitle)
454
+ case 4:
455
+ post_video_ks(title, file_list, tags, account_list, category, enableTimer, videos_per_day, daily_times,
456
+ start_days)
457
+ case _:
458
+ return jsonify({"code": 400, "msg": f"不支持的平台类型: {type}", "data": None}), 400
459
+
460
+ # 返回响应给客户端
461
+ return jsonify(
462
+ {
463
+ "code": 200,
464
+ "msg": "发布任务已提交",
465
+ "data": None
466
+ }), 200
467
+ except Exception as e:
468
+ print(f"发布视频时出错: {str(e)}")
469
+ return jsonify({
470
+ "code": 500,
471
+ "msg": f"发布失败: {str(e)}",
472
  "data": None
473
+ }), 500
474
 
475
 
476
  @app.route('/updateUserinfo', methods=['POST'])
 
515
  data_list = request.get_json()
516
 
517
  if not isinstance(data_list, list):
518
+ return jsonify({"code": 400, "msg": "Expected a JSON array", "data": None}), 400
519
  for data in data_list:
520
  # 从JSON数据中提取fileList和accountList
521
  file_list = data.get('fileList', [])
 
529
  category = None
530
  productLink = data.get('productLink', '')
531
  productTitle = data.get('productTitle', '')
532
+ is_draft = data.get('isDraft', False)
533
 
534
  videos_per_day = data.get('videosPerDay')
535
  daily_times = data.get('dailyTimes')
 
539
  print("Account List:", account_list)
540
  match type:
541
  case 1:
542
+ post_video_xhs(title, file_list, tags, account_list, category, enableTimer, videos_per_day, daily_times,
543
+ start_days)
544
  case 2:
545
  post_video_tencent(title, file_list, tags, account_list, category, enableTimer, videos_per_day, daily_times,
546
+ start_days, is_draft)
547
  case 3:
548
  post_video_DouYin(title, file_list, tags, account_list, category, enableTimer, videos_per_day, daily_times,
549
  start_days, productLink, productTitle)
 
564
  try:
565
  if 'file' not in request.files:
566
  return jsonify({
567
+ "code": 400,
568
  "msg": "没有找到Cookie文件",
569
  "data": None
570
  }), 400
 
572
  file = request.files['file']
573
  if file.filename == '':
574
  return jsonify({
575
+ "code": 400,
576
  "msg": "Cookie文件名不能为空",
577
  "data": None
578
  }), 400
579
 
580
  if not file.filename.endswith('.json'):
581
  return jsonify({
582
+ "code": 400,
583
  "msg": "Cookie文件必须是JSON格式",
584
  "data": None
585
  }), 400
 
590
 
591
  if not account_id or not platform:
592
  return jsonify({
593
+ "code": 400,
594
  "msg": "缺少账号ID或平台信息",
595
  "data": None
596
  }), 400
sau_frontend/src/App.vue CHANGED
@@ -32,13 +32,9 @@
32
  <el-icon><Upload /></el-icon>
33
  <span>发布中心</span>
34
  </el-menu-item>
35
- <el-menu-item index="/website">
36
- <el-icon><Monitor /></el-icon>
37
- <span>网站</span>
38
- </el-menu-item>
39
- <el-menu-item index="/data">
40
  <el-icon><DataAnalysis /></el-icon>
41
- <span>数据</span>
42
  </el-menu-item>
43
  </el-menu>
44
  </div>
@@ -65,8 +61,8 @@
65
  <script setup>
66
  import { ref, computed } from 'vue'
67
  import { useRoute } from 'vue-router'
68
- import {
69
- HomeFilled, User, Monitor, DataAnalysis,
70
  Fold, Picture, Upload
71
  } from '@element-plus/icons-vue'
72
 
 
32
  <el-icon><Upload /></el-icon>
33
  <span>发布中心</span>
34
  </el-menu-item>
35
+ <el-menu-item index="/about">
 
 
 
 
36
  <el-icon><DataAnalysis /></el-icon>
37
+ <span>关于</span>
38
  </el-menu-item>
39
  </el-menu>
40
  </div>
 
61
  <script setup>
62
  import { ref, computed } from 'vue'
63
  import { useRoute } from 'vue-router'
64
+ import {
65
+ HomeFilled, User, DataAnalysis,
66
  Fold, Picture, Upload
67
  } from '@element-plus/icons-vue'
68
 
sau_frontend/src/api/user.js CHANGED
@@ -1,49 +1,5 @@
1
  import { http } from '@/utils/request'
2
 
3
- // 用户相关API
4
- export const userApi = {
5
- // 获取用户信息
6
- getUserInfo(id) {
7
- return http.get(`/user/${id}`)
8
- },
9
-
10
- // 获取用户列表
11
- getUserList(params) {
12
- return http.get('/user/list', params)
13
- },
14
-
15
- // 创建用户
16
- createUser(data) {
17
- return http.post('/user', data)
18
- },
19
-
20
- // 更新用户信息
21
- updateUser(id, data) {
22
- return http.put(`/user/${id}`, data)
23
- },
24
-
25
- // 删除用户
26
- deleteUser(id) {
27
- return http.delete(`/user/${id}`)
28
- },
29
-
30
- // 用户登录
31
- login(data) {
32
- return http.post('/auth/login', data)
33
- },
34
-
35
- // 用户注册
36
- register(data) {
37
- return http.post('/auth/register', data)
38
- },
39
-
40
- // 用户登出
41
- logout() {
42
- return http.post('/auth/logout')
43
- },
44
-
45
- // 刷新token
46
- refreshToken() {
47
- return http.post('/auth/refresh')
48
- }
49
- }
 
1
  import { http } from '@/utils/request'
2
 
3
+ // 用户相关API(预留)
4
+ // 注意:当前后端暂无用户认证接口,以下为预留定义
5
+ export const userApi = {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
sau_frontend/src/components/helloworld.vue DELETED
@@ -1,66 +0,0 @@
1
- <template>
2
- <div class="hello-world">
3
- <h1>{{ msg }}</h1>
4
- <div class="card">
5
- <el-button type="primary" @click="count++">
6
- 点击次数: {{ count }}
7
- </el-button>
8
- <p class="mt-3">
9
- 这是一个使用 Element Plus 的示例组件
10
- </p>
11
- </div>
12
- <div class="links mt-4">
13
- <el-link href="https://vuejs.org/" target="_blank" type="primary">
14
- Vue.js 官网
15
- </el-link>
16
- <el-link href="https://element-plus.org/" target="_blank" type="success">
17
- Element Plus 官网
18
- </el-link>
19
- </div>
20
- </div>
21
- </template>
22
-
23
- <script setup>
24
- import { ref } from 'vue'
25
-
26
- defineProps({
27
- msg: {
28
- type: String,
29
- default: 'Hello Vue 3 + Vite'
30
- }
31
- })
32
-
33
- const count = ref(0)
34
- </script>
35
-
36
- <style lang="scss" scoped>
37
- @use '@/styles/variables.scss' as *;
38
-
39
- .hello-world {
40
- text-align: center;
41
- padding: 2rem;
42
-
43
- h1 {
44
- color: #2c3e50;
45
- margin-bottom: 2rem;
46
- }
47
-
48
- .card {
49
- background: white;
50
- padding: 2rem;
51
- border-radius: 8px;
52
- box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
53
- margin-bottom: 2rem;
54
-
55
- p {
56
- color: #666;
57
- }
58
- }
59
-
60
- .links {
61
- display: flex;
62
- justify-content: center;
63
- gap: 1rem;
64
- }
65
- }
66
- </style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
sau_frontend/src/stores/account.js CHANGED
@@ -22,9 +22,8 @@ export const useAccountStore = defineStore('account', () => {
22
  type: item[1],
23
  filePath: item[2],
24
  name: item[3],
25
- status: item[4] === 1 ? '正常' : '异常',
26
- platform: platformTypes[item[1]] || '未知',
27
- avatar: '/vite.svg' // 默认使用vite.svg作为头像
28
  }
29
  })
30
  }
 
22
  type: item[1],
23
  filePath: item[2],
24
  name: item[3],
25
+ status: item[4] === -1 ? '验证中' : (item[4] === 1 ? '正常' : '异常'),
26
+ platform: platformTypes[item[1]] || '未知'
 
27
  }
28
  })
29
  }
sau_frontend/src/utils/request.js CHANGED
@@ -34,8 +34,8 @@ request.interceptors.response.use(
34
  if (data.code === 200 || data.success) {
35
  return data
36
  } else {
37
- ElMessage.error(data.message || '请求失败')
38
- return Promise.reject(new Error(data.message || '请求失败'))
39
  }
40
  },
41
  (error) => {
 
34
  if (data.code === 200 || data.success) {
35
  return data
36
  } else {
37
+ ElMessage.error(data.msg || data.message || '请求失败')
38
+ return Promise.reject(new Error(data.msg || data.message || '请求失败'))
39
  }
40
  },
41
  (error) => {
sau_frontend/src/views/About.vue CHANGED
@@ -1,7 +1,54 @@
1
  <template>
2
  <div class="about">
3
- <h1>关于我们</h1>
4
- <p>这是关于页面</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  </div>
6
  </template>
7
 
@@ -13,16 +60,55 @@
13
  @use '@/styles/variables.scss' as *;
14
 
15
  .about {
16
- text-align: center;
17
- padding: 2rem;
18
-
19
- h1 {
20
- color: #2c3e50;
21
- margin-bottom: 1rem;
22
- }
23
-
24
- p {
25
- color: #7f8c8d;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  }
27
  }
28
- </style>
 
1
  <template>
2
  <div class="about">
3
+ <el-card class="about-card">
4
+ <div class="about-header">
5
+ <h1>自媒体自动化运营系统</h1>
6
+ <p class="version">social-auto-upload</p>
7
+ </div>
8
+
9
+ <el-divider />
10
+
11
+ <div class="about-section">
12
+ <h3>系统简介</h3>
13
+ <p>
14
+ 本系统是一款强大的自动化工具,帮助内容创作者和运营人员一键将视频内容高效发布到多个国内外主流社交媒体平台。
15
+ 支持视频上传、定时发布等功能。
16
+ </p>
17
+ </div>
18
+
19
+ <div class="about-section">
20
+ <h3>支持平台</h3>
21
+ <div class="platform-tags">
22
+ <el-tag type="danger">抖音</el-tag>
23
+ <el-tag type="success">快手</el-tag>
24
+ <el-tag type="warning">视频号</el-tag>
25
+ <el-tag type="info">小红书</el-tag>
26
+ </div>
27
+ </div>
28
+
29
+ <div class="about-section">
30
+ <h3>核心功能</h3>
31
+ <ul class="feature-list">
32
+ <li>多平台账号管理与登录状态维护</li>
33
+ <li>视频素材上传与管理</li>
34
+ <li>一键多平台发布</li>
35
+ <li>定时发布与批量发布</li>
36
+ <li>Cookie 导入导出</li>
37
+ </ul>
38
+ </div>
39
+
40
+ <div class="about-section">
41
+ <h3>技术栈</h3>
42
+ <div class="tech-tags">
43
+ <el-tag effect="plain">Vue 3</el-tag>
44
+ <el-tag effect="plain">Element Plus</el-tag>
45
+ <el-tag effect="plain">Pinia</el-tag>
46
+ <el-tag effect="plain">Flask</el-tag>
47
+ <el-tag effect="plain">Playwright</el-tag>
48
+ <el-tag effect="plain">SQLite</el-tag>
49
+ </div>
50
+ </div>
51
+ </el-card>
52
  </div>
53
  </template>
54
 
 
60
  @use '@/styles/variables.scss' as *;
61
 
62
  .about {
63
+ max-width: 700px;
64
+ margin: 0 auto;
65
+
66
+ .about-card {
67
+ .about-header {
68
+ text-align: center;
69
+
70
+ h1 {
71
+ color: $text-primary;
72
+ margin: 0 0 8px 0;
73
+ font-size: 24px;
74
+ }
75
+
76
+ .version {
77
+ color: $text-secondary;
78
+ font-size: 14px;
79
+ margin: 0;
80
+ }
81
+ }
82
+
83
+ .about-section {
84
+ margin-bottom: 24px;
85
+
86
+ h3 {
87
+ font-size: 16px;
88
+ color: $text-primary;
89
+ margin: 0 0 12px 0;
90
+ }
91
+
92
+ p {
93
+ color: $text-secondary;
94
+ line-height: 1.8;
95
+ margin: 0;
96
+ }
97
+
98
+ .platform-tags,
99
+ .tech-tags {
100
+ display: flex;
101
+ flex-wrap: wrap;
102
+ gap: 10px;
103
+ }
104
+
105
+ .feature-list {
106
+ margin: 0;
107
+ padding-left: 20px;
108
+ color: $text-secondary;
109
+ line-height: 2;
110
+ }
111
+ }
112
  }
113
  }
114
+ </style>
sau_frontend/src/views/AccountManagement.vue CHANGED
@@ -433,6 +433,7 @@ import { ElMessage, ElMessageBox } from 'element-plus'
433
  import { accountApi } from '@/api/account'
434
  import { useAccountStore } from '@/stores/account'
435
  import { useAppStore } from '@/stores/app'
 
436
 
437
  // 获取账号状态管理
438
  const accountStore = useAccountStore()
@@ -452,9 +453,8 @@ const fetchAccountsQuick = async () => {
452
  if (res.code === 200 && res.data) {
453
  // 将所有账号的状态暂时设为"验证中"
454
  const accountsWithPendingStatus = res.data.map(account => {
455
- // account[4] 是状态字段,暂时设为"验证中"
456
  const updatedAccount = [...account];
457
- updatedAccount[4] = '验证中'; // 临时状态
458
  return updatedAccount;
459
  });
460
  accountStore.setAccounts(accountsWithPendingStatus);
@@ -713,24 +713,13 @@ const handleUploadCookie = (row) => {
713
  formData.append('id', row.id)
714
  formData.append('platform', row.platform)
715
 
716
- // 发送上传请求
717
- const baseUrl = import.meta.env.VITE_API_BASE_URL || 'http://localhost:5409'
718
- const response = await fetch(`${baseUrl}/uploadCookie`, {
719
- method: 'POST',
720
- body: formData
721
- })
722
 
723
- const result = await response.json()
724
-
725
- if (result.code === 200) {
726
- ElMessage.success('Cookie文件上传成功')
727
- // 刷新账号列表以显示更新
728
- fetchAccounts()
729
- } else {
730
- ElMessage.error(result.msg || 'Cookie文件上传失败')
731
- }
732
  } catch (error) {
733
- console.error('上传Cookie文件失败:', error)
734
  ElMessage.error('Cookie文件上传失败')
735
  } finally {
736
  document.body.removeChild(input)
@@ -811,22 +800,17 @@ const connectSSE = (platform, name) => {
811
  // 监听消息
812
  eventSource.onmessage = (event) => {
813
  const data = event.data
814
- console.log('SSE消息:', data)
815
 
816
  // 如果还没有二维码数据,且数据长度较长,认为是二维码
817
  if (!qrCodeData.value && data.length > 100) {
818
  try {
819
- // 确保数据是有效的base64编码
820
- // 如果数据已经包含了data:image前缀,直接使用
821
  if (data.startsWith('data:image')) {
822
  qrCodeData.value = data
823
  } else {
824
- // 否则添加前缀
825
  qrCodeData.value = `data:image/png;base64,${data}`
826
  }
827
- console.log('设置二维码数据,长度:', data.length)
828
  } catch (error) {
829
- console.error('处理二维码数据出错:', error)
830
  }
831
  }
832
  // 如果收到状态码
@@ -897,10 +881,10 @@ const submitAccountForm = () => {
897
  try {
898
  // 将平台名称转换为类型数字
899
  const platformTypeMap = {
900
- '快手': 1,
901
- '抖音': 2,
902
- '视频号': 3,
903
- '小红书': 4
904
  };
905
  const type = platformTypeMap[accountForm.platform] || 1;
906
 
 
433
  import { accountApi } from '@/api/account'
434
  import { useAccountStore } from '@/stores/account'
435
  import { useAppStore } from '@/stores/app'
436
+ import { http } from '@/utils/request'
437
 
438
  // 获取账号状态管理
439
  const accountStore = useAccountStore()
 
453
  if (res.code === 200 && res.data) {
454
  // 将所有账号的状态暂时设为"验证中"
455
  const accountsWithPendingStatus = res.data.map(account => {
 
456
  const updatedAccount = [...account];
457
+ updatedAccount[4] = -1; // -1 表示验证中的临时状态
458
  return updatedAccount;
459
  });
460
  accountStore.setAccounts(accountsWithPendingStatus);
 
713
  formData.append('id', row.id)
714
  formData.append('platform', row.platform)
715
 
716
+ // 使用统一的http封装发送上传请求
717
+ const result = await http.upload('/uploadCookie', formData)
 
 
 
 
718
 
719
+ ElMessage.success('Cookie文件上传成功')
720
+ // 刷新账号列表以显示更新
721
+ fetchAccounts()
 
 
 
 
 
 
722
  } catch (error) {
 
723
  ElMessage.error('Cookie文件上传失败')
724
  } finally {
725
  document.body.removeChild(input)
 
800
  // 监听消息
801
  eventSource.onmessage = (event) => {
802
  const data = event.data
 
803
 
804
  // 如果还没有二维码数据,且数据长度较长,认为是二维码
805
  if (!qrCodeData.value && data.length > 100) {
806
  try {
 
 
807
  if (data.startsWith('data:image')) {
808
  qrCodeData.value = data
809
  } else {
 
810
  qrCodeData.value = `data:image/png;base64,${data}`
811
  }
 
812
  } catch (error) {
813
+ // 处理二维码数据出错
814
  }
815
  }
816
  // 如果收到状态码
 
881
  try {
882
  // 将平台名称转换为类型数字
883
  const platformTypeMap = {
884
+ '小红书': 1,
885
+ '视频号': 2,
886
+ '抖音': 3,
887
+ '快手': 4
888
  };
889
  const type = platformTypeMap[accountForm.platform] || 1;
890
 
sau_frontend/src/views/Dashboard.vue CHANGED
@@ -3,11 +3,11 @@
3
  <div class="page-header">
4
  <h1>自媒体自动化运营系统</h1>
5
  </div>
6
-
7
  <div class="dashboard-content">
8
  <el-row :gutter="20">
9
  <!-- 账号统计卡片 -->
10
- <el-col :span="6">
11
  <el-card class="stat-card">
12
  <div class="stat-card-content">
13
  <div class="stat-icon">
@@ -26,9 +26,9 @@
26
  </div>
27
  </el-card>
28
  </el-col>
29
-
30
  <!-- 平台统计卡片 -->
31
- <el-col :span="6">
32
  <el-card class="stat-card">
33
  <div class="stat-card-content">
34
  <div class="stat-icon platform-icon">
@@ -36,7 +36,7 @@
36
  </div>
37
  <div class="stat-info">
38
  <div class="stat-value">{{ platformStats.total }}</div>
39
- <div class="stat-label">平台总数</div>
40
  </div>
41
  </div>
42
  <div class="stat-footer">
@@ -57,31 +57,9 @@
57
  </div>
58
  </el-card>
59
  </el-col>
60
-
61
- <!-- 任务统计卡片 -->
62
- <el-col :span="6">
63
- <el-card class="stat-card">
64
- <div class="stat-card-content">
65
- <div class="stat-icon task-icon">
66
- <el-icon><List /></el-icon>
67
- </div>
68
- <div class="stat-info">
69
- <div class="stat-value">{{ taskStats.total }}</div>
70
- <div class="stat-label">任务总数</div>
71
- </div>
72
- </div>
73
- <div class="stat-footer">
74
- <div class="stat-detail">
75
- <span>完成: {{ taskStats.completed }}</span>
76
- <span>进行中: {{ taskStats.inProgress }}</span>
77
- <span>失败: {{ taskStats.failed }}</span>
78
- </div>
79
- </div>
80
- </el-card>
81
- </el-col>
82
-
83
- <!-- 内容统计卡片 -->
84
- <el-col :span="6">
85
  <el-card class="stat-card">
86
  <div class="stat-card-content">
87
  <div class="stat-icon content-icon">
@@ -89,19 +67,20 @@
89
  </div>
90
  <div class="stat-info">
91
  <div class="stat-value">{{ contentStats.total }}</div>
92
- <div class="stat-label">内容总数</div>
93
  </div>
94
  </div>
95
  <div class="stat-footer">
96
  <div class="stat-detail">
97
- <span>已发布: {{ contentStats.published }}</span>
98
- <span>草稿: {{ contentStats.draft }}</span>
 
99
  </div>
100
  </div>
101
  </el-card>
102
  </el-col>
103
  </el-row>
104
-
105
  <!-- 快捷操作区域 -->
106
  <div class="quick-actions">
107
  <h2>快捷操作</h2>
@@ -116,199 +95,144 @@
116
  </el-card>
117
  </el-col>
118
  <el-col :span="6">
119
- <el-card class="action-card">
120
  <div class="action-icon">
121
  <el-icon><Upload /></el-icon>
122
  </div>
123
- <div class="action-title">内容上传</div>
124
- <div class="action-desc">上传视频和图文内容</div>
125
  </el-card>
126
  </el-col>
127
  <el-col :span="6">
128
- <el-card class="action-card">
129
  <div class="action-icon">
130
  <el-icon><Timer /></el-icon>
131
  </div>
132
- <div class="action-title">定时发布</div>
133
- <div class="action-desc">设置内容发布时间</div>
134
  </el-card>
135
  </el-col>
136
  <el-col :span="6">
137
- <el-card class="action-card">
138
  <div class="action-icon">
139
  <el-icon><DataAnalysis /></el-icon>
140
  </div>
141
- <div class="action-title">数据分析</div>
142
- <div class="action-desc">查看内容数据分析</div>
143
  </el-card>
144
  </el-col>
145
  </el-row>
146
  </div>
147
-
148
- <!-- 最近任务列表 -->
149
  <div class="recent-tasks">
150
  <div class="section-header">
151
- <h2>最近任务</h2>
152
- <el-button text>查看全部</el-button>
153
  </div>
154
-
155
- <el-table :data="recentTasks" style="width: 100%">
156
- <el-table-column prop="title" label="任务" width="250" />
157
- <el-table-column prop="platform" label="平台" width="120">
158
  <template #default="scope">
159
- <el-tag
160
- :type="getPlatformTagType(scope.row.platform)"
161
- effect="plain"
162
- >
163
- {{ scope.row.platform }}
164
- </el-tag>
165
  </template>
166
  </el-table-column>
167
- <el-table-column prop="account" label="账号" width="150" />
168
- <el-table-column prop="createTime" label="创建时间" width="180" />
169
- <el-table-column prop="status" label="状态" width="120">
170
  <template #default="scope">
171
  <el-tag
172
- :type="getStatusTagType(scope.row.status)"
173
  effect="plain"
 
174
  >
175
- {{ scope.row.status }}
176
  </el-tag>
177
  </template>
178
  </el-table-column>
179
- <el-table-column label="操作">
180
- <template #default="scope">
181
- <el-button size="small" @click="viewTaskDetail(scope.row)">查看</el-button>
182
- <el-button
183
- size="small"
184
- type="primary"
185
- v-if="scope.row.status === '待执行'"
186
- @click="executeTask(scope.row)"
187
- >
188
- 执行
189
- </el-button>
190
- <el-button
191
- size="small"
192
- type="danger"
193
- v-if="scope.row.status !== '已完成' && scope.row.status !== '已失败'"
194
- @click="cancelTask(scope.row)"
195
- >
196
- 取消
197
- </el-button>
198
- </template>
199
- </el-table-column>
200
  </el-table>
 
 
201
  </div>
202
  </div>
203
  </div>
204
  </template>
205
 
206
  <script setup>
207
- import { ref, reactive } from 'vue'
208
  import { useRouter } from 'vue-router'
209
- import {
210
- User, UserFilled, Platform, List, Document,
211
- Upload, Timer, DataAnalysis
212
  } from '@element-plus/icons-vue'
213
- import { ElMessage, ElMessageBox } from 'element-plus'
 
 
 
214
 
215
  const router = useRouter()
 
 
 
216
 
217
- // 账号统计数据
218
- const accountStats = reactive({
219
- total: 12,
220
- normal: 10,
221
- abnormal: 2
 
 
 
 
 
222
  })
223
 
224
- // 平台统计数据
225
- const platformStats = reactive({
226
- total: 4,
227
- kuaishou: 3,
228
- douyin: 4,
229
- channels: 2,
230
- xiaohongshu: 3
 
 
 
231
  })
232
 
233
- // 任务统计数据
234
- const taskStats = reactive({
235
- total: 24,
236
- completed: 18,
237
- inProgress: 5,
238
- failed: 1
 
 
 
 
 
 
 
 
239
  })
240
 
241
- // 内容统计数据
242
- const contentStats = reactive({
243
- total: 36,
244
- published: 30,
245
- draft: 6
246
  })
247
 
248
- // 最近任务数据
249
- const recentTasks = ref([
250
- {
251
- id: 1,
252
- title: '快手视频自动发布',
253
- platform: '快手',
254
- account: '快手账号1',
255
- createTime: '2024-05-01 10:30:00',
256
- status: '已完成'
257
- },
258
- {
259
- id: 2,
260
- title: '抖音视频定时发布',
261
- platform: '抖音',
262
- account: '抖音账号1',
263
- createTime: '2024-05-01 11:15:00',
264
- status: '进行中'
265
- },
266
- {
267
- id: 3,
268
- title: '视频号内容上传',
269
- platform: '视频号',
270
- account: '视频号账号1',
271
- createTime: '2024-05-01 14:20:00',
272
- status: '待执行'
273
- },
274
- {
275
- id: 4,
276
- title: '小红书图文发布',
277
- platform: '小红书',
278
- account: '小红书账号1',
279
- createTime: '2024-05-01 16:45:00',
280
- status: '已失败'
281
- },
282
- {
283
- id: 5,
284
- title: '快手短视频批量上传',
285
- platform: '快手',
286
- account: '快手账号2',
287
- createTime: '2024-05-02 09:10:00',
288
- status: '待执行'
289
- }
290
- ])
291
-
292
- // 根据平台获取标签类型
293
- const getPlatformTagType = (platform) => {
294
- const typeMap = {
295
- '快手': 'success',
296
- '抖音': 'danger',
297
- '视频号': 'warning',
298
- '小红书': 'info'
299
- }
300
- return typeMap[platform] || 'info'
301
  }
302
 
303
- // 根据状态获取标签类型
304
- const getStatusTagType = (status) => {
305
- const typeMap = {
306
- '已完成': 'success',
307
- '进行中': 'warning',
308
- '待执行': 'info',
309
- '已失败': 'danger'
310
- }
311
- return typeMap[status] || 'info'
312
  }
313
 
314
  // 导航到指定路由
@@ -316,65 +240,32 @@ const navigateTo = (path) => {
316
  router.push(path)
317
  }
318
 
319
- // 查看任务详情
320
- const viewTaskDetail = (task) => {
321
- ElMessage.info(`查看任务: ${task.title}`)
322
- // 实际应用中应该跳转到任务详情页面
323
- }
 
 
 
 
324
 
325
- // 执行任务
326
- const executeTask = (task) => {
327
- ElMessageBox.confirm(
328
- `确定要执行任务 ${task.title} 吗?`,
329
- '提示',
330
- {
331
- confirmButtonText: '确定',
332
- cancelButtonText: '取消',
333
- type: 'info',
334
  }
335
- )
336
- .then(() => {
337
- // 更新任务状态
338
- const index = recentTasks.value.findIndex(t => t.id === task.id)
339
- if (index !== -1) {
340
- recentTasks.value[index].status = '进行中'
341
- }
342
- ElMessage({
343
- type: 'success',
344
- message: '任务已开始执行',
345
- })
346
- })
347
- .catch(() => {
348
- // 取消执行
349
- })
350
- }
351
-
352
- // 取消任务
353
- const cancelTask = (task) => {
354
- ElMessageBox.confirm(
355
- `确定要取消任务 ${task.title} 吗?`,
356
- '警告',
357
- {
358
- confirmButtonText: '确定',
359
- cancelButtonText: '取消',
360
- type: 'warning',
361
  }
362
- )
363
- .then(() => {
364
- // 更新任务状态
365
- const index = recentTasks.value.findIndex(t => t.id === task.id)
366
- if (index !== -1) {
367
- recentTasks.value[index].status = '已取消'
368
- }
369
- ElMessage({
370
- type: 'success',
371
- message: '任务已取消',
372
- })
373
- })
374
- .catch(() => {
375
- // 取消操作
376
- })
377
  }
 
 
 
 
378
  </script>
379
 
380
  <style lang="scss" scoped>
@@ -383,24 +274,24 @@ const cancelTask = (task) => {
383
  .dashboard {
384
  .page-header {
385
  margin-bottom: 20px;
386
-
387
  h1 {
388
  font-size: 24px;
389
  color: $text-primary;
390
  margin: 0;
391
  }
392
  }
393
-
394
  .dashboard-content {
395
  .stat-card {
396
  height: 140px;
397
  margin-bottom: 20px;
398
-
399
  .stat-card-content {
400
  display: flex;
401
  align-items: center;
402
  margin-bottom: 15px;
403
-
404
  .stat-icon {
405
  width: 60px;
406
  height: 60px;
@@ -410,37 +301,29 @@ const cancelTask = (task) => {
410
  justify-content: center;
411
  align-items: center;
412
  margin-right: 15px;
413
-
414
  .el-icon {
415
  font-size: 30px;
416
  color: $primary-color;
417
  }
418
-
419
  &.platform-icon {
420
  background-color: rgba($success-color, 0.1);
421
-
422
  .el-icon {
423
  color: $success-color;
424
  }
425
  }
426
-
427
- &.task-icon {
428
- background-color: rgba($warning-color, 0.1);
429
-
430
- .el-icon {
431
- color: $warning-color;
432
- }
433
- }
434
-
435
  &.content-icon {
436
  background-color: rgba($info-color, 0.1);
437
-
438
  .el-icon {
439
  color: $info-color;
440
  }
441
  }
442
  }
443
-
444
  .stat-info {
445
  .stat-value {
446
  font-size: 24px;
@@ -448,40 +331,40 @@ const cancelTask = (task) => {
448
  color: $text-primary;
449
  line-height: 1.2;
450
  }
451
-
452
  .stat-label {
453
  font-size: 14px;
454
  color: $text-secondary;
455
  }
456
  }
457
  }
458
-
459
  .stat-footer {
460
  border-top: 1px solid $border-lighter;
461
  padding-top: 10px;
462
-
463
  .stat-detail {
464
  display: flex;
465
  justify-content: space-between;
466
  color: $text-secondary;
467
  font-size: 13px;
468
-
469
  .el-tag {
470
  margin-right: 5px;
471
  }
472
  }
473
  }
474
  }
475
-
476
  .quick-actions {
477
  margin: 20px 0 30px;
478
-
479
  h2 {
480
  font-size: 18px;
481
  margin-bottom: 15px;
482
  color: $text-primary;
483
  }
484
-
485
  .action-card {
486
  height: 160px;
487
  display: flex;
@@ -490,12 +373,12 @@ const cancelTask = (task) => {
490
  justify-content: center;
491
  cursor: pointer;
492
  transition: all 0.3s;
493
-
494
  &:hover {
495
  transform: translateY(-5px);
496
  box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
497
  }
498
-
499
  .action-icon {
500
  width: 50px;
501
  height: 50px;
@@ -505,20 +388,20 @@ const cancelTask = (task) => {
505
  justify-content: center;
506
  align-items: center;
507
  margin-bottom: 15px;
508
-
509
  .el-icon {
510
  font-size: 24px;
511
  color: $primary-color;
512
  }
513
  }
514
-
515
  .action-title {
516
  font-size: 16px;
517
  font-weight: bold;
518
  color: $text-primary;
519
  margin-bottom: 5px;
520
  }
521
-
522
  .action-desc {
523
  font-size: 13px;
524
  color: $text-secondary;
@@ -526,16 +409,16 @@ const cancelTask = (task) => {
526
  }
527
  }
528
  }
529
-
530
  .recent-tasks {
531
  margin-top: 30px;
532
-
533
  .section-header {
534
  display: flex;
535
  justify-content: space-between;
536
  align-items: center;
537
  margin-bottom: 15px;
538
-
539
  h2 {
540
  font-size: 18px;
541
  color: $text-primary;
@@ -545,4 +428,4 @@ const cancelTask = (task) => {
545
  }
546
  }
547
  }
548
- </style>
 
3
  <div class="page-header">
4
  <h1>自媒体自动化运营系统</h1>
5
  </div>
6
+
7
  <div class="dashboard-content">
8
  <el-row :gutter="20">
9
  <!-- 账号统计卡片 -->
10
+ <el-col :span="8">
11
  <el-card class="stat-card">
12
  <div class="stat-card-content">
13
  <div class="stat-icon">
 
26
  </div>
27
  </el-card>
28
  </el-col>
29
+
30
  <!-- 平台统计卡片 -->
31
+ <el-col :span="8">
32
  <el-card class="stat-card">
33
  <div class="stat-card-content">
34
  <div class="stat-icon platform-icon">
 
36
  </div>
37
  <div class="stat-info">
38
  <div class="stat-value">{{ platformStats.total }}</div>
39
+ <div class="stat-label">已接入平台</div>
40
  </div>
41
  </div>
42
  <div class="stat-footer">
 
57
  </div>
58
  </el-card>
59
  </el-col>
60
+
61
+ <!-- 素材统计卡片 -->
62
+ <el-col :span="8">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  <el-card class="stat-card">
64
  <div class="stat-card-content">
65
  <div class="stat-icon content-icon">
 
67
  </div>
68
  <div class="stat-info">
69
  <div class="stat-value">{{ contentStats.total }}</div>
70
+ <div class="stat-label">素材总数</div>
71
  </div>
72
  </div>
73
  <div class="stat-footer">
74
  <div class="stat-detail">
75
+ <span>视频: {{ contentStats.videos }}</span>
76
+ <span>图片: {{ contentStats.images }}</span>
77
+ <span>其他: {{ contentStats.others }}</span>
78
  </div>
79
  </div>
80
  </el-card>
81
  </el-col>
82
  </el-row>
83
+
84
  <!-- 快捷操作区域 -->
85
  <div class="quick-actions">
86
  <h2>快捷操作</h2>
 
95
  </el-card>
96
  </el-col>
97
  <el-col :span="6">
98
+ <el-card class="action-card" @click="navigateTo('/material-management')">
99
  <div class="action-icon">
100
  <el-icon><Upload /></el-icon>
101
  </div>
102
+ <div class="action-title">素材管理</div>
103
+ <div class="action-desc">上传和管理视频素材</div>
104
  </el-card>
105
  </el-col>
106
  <el-col :span="6">
107
+ <el-card class="action-card" @click="navigateTo('/publish-center')">
108
  <div class="action-icon">
109
  <el-icon><Timer /></el-icon>
110
  </div>
111
+ <div class="action-title">发布中心</div>
112
+ <div class="action-desc">发布内容到各平台</div>
113
  </el-card>
114
  </el-col>
115
  <el-col :span="6">
116
+ <el-card class="action-card" @click="navigateTo('/about')">
117
  <div class="action-icon">
118
  <el-icon><DataAnalysis /></el-icon>
119
  </div>
120
+ <div class="action-title">关于系统</div>
121
+ <div class="action-desc">查看系统信息</div>
122
  </el-card>
123
  </el-col>
124
  </el-row>
125
  </div>
126
+
127
+ <!-- 素材列表 -->
128
  <div class="recent-tasks">
129
  <div class="section-header">
130
+ <h2>最近上传素材</h2>
131
+ <el-button text @click="navigateTo('/material-management')">查看全部</el-button>
132
  </div>
133
+
134
+ <el-table :data="recentMaterials" style="width: 100%" v-loading="loading">
135
+ <el-table-column prop="filename" label="文件名" width="300" />
136
+ <el-table-column prop="filesize" label="文件大小" width="120">
137
  <template #default="scope">
138
+ {{ scope.row.filesize }} MB
 
 
 
 
 
139
  </template>
140
  </el-table-column>
141
+ <el-table-column prop="upload_time" label="上传时间" width="200" />
142
+ <el-table-column label="类型" width="100">
 
143
  <template #default="scope">
144
  <el-tag
145
+ :type="getFileTypeTag(scope.row.filename)"
146
  effect="plain"
147
+ size="small"
148
  >
149
+ {{ getFileType(scope.row.filename) }}
150
  </el-tag>
151
  </template>
152
  </el-table-column>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
  </el-table>
154
+
155
+ <el-empty v-if="!loading && recentMaterials.length === 0" description="暂无素材数据" />
156
  </div>
157
  </div>
158
  </div>
159
  </template>
160
 
161
  <script setup>
162
+ import { ref, reactive, computed, onMounted } from 'vue'
163
  import { useRouter } from 'vue-router'
164
+ import {
165
+ User, UserFilled, Platform, Document,
166
+ Upload, Timer, DataAnalysis
167
  } from '@element-plus/icons-vue'
168
+ import { accountApi } from '@/api/account'
169
+ import { materialApi } from '@/api/material'
170
+ import { useAccountStore } from '@/stores/account'
171
+ import { useAppStore } from '@/stores/app'
172
 
173
  const router = useRouter()
174
+ const accountStore = useAccountStore()
175
+ const appStore = useAppStore()
176
+ const loading = ref(false)
177
 
178
+ // 账号统计数据 - 从真实数据计算
179
+ const accountStats = computed(() => {
180
+ const accounts = accountStore.accounts
181
+ const normal = accounts.filter(a => a.status === '正常').length
182
+ const abnormal = accounts.filter(a => a.status !== '正常' && a.status !== '验证中').length
183
+ return {
184
+ total: accounts.length,
185
+ normal,
186
+ abnormal
187
+ }
188
  })
189
 
190
+ // 平台统计数据 - 从真实数据计算
191
+ const platformStats = computed(() => {
192
+ const accounts = accountStore.accounts
193
+ const kuaishou = accounts.filter(a => a.platform === '快手').length
194
+ const douyin = accounts.filter(a => a.platform === '抖音').length
195
+ const channels = accounts.filter(a => a.platform === '视频号').length
196
+ const xiaohongshu = accounts.filter(a => a.platform === '小红书').length
197
+ // 统计有账号的平台数量
198
+ const total = [kuaishou, douyin, channels, xiaohongshu].filter(n => n > 0).length
199
+ return { total, kuaishou, douyin, channels, xiaohongshu }
200
  })
201
 
202
+ // 素材统计数据 - 从真实数据计算
203
+ const videoExtensions = ['.mp4', '.avi', '.mov', '.wmv', '.flv', '.mkv']
204
+ const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp']
205
+
206
+ const contentStats = computed(() => {
207
+ const materials = appStore.materials
208
+ const videos = materials.filter(m => videoExtensions.some(ext => m.filename.toLowerCase().endsWith(ext))).length
209
+ const images = materials.filter(m => imageExtensions.some(ext => m.filename.toLowerCase().endsWith(ext))).length
210
+ return {
211
+ total: materials.length,
212
+ videos,
213
+ images,
214
+ others: materials.length - videos - images
215
+ }
216
  })
217
 
218
+ // 最近上传的素材(最多显示5条)
219
+ const recentMaterials = computed(() => {
220
+ return [...appStore.materials]
221
+ .sort((a, b) => new Date(b.upload_time) - new Date(a.upload_time))
222
+ .slice(0, 5)
223
  })
224
 
225
+ // 获取文件类型
226
+ const getFileType = (filename) => {
227
+ if (videoExtensions.some(ext => filename.toLowerCase().endsWith(ext))) return '视频'
228
+ if (imageExtensions.some(ext => filename.toLowerCase().endsWith(ext))) return '图片'
229
+ return '其他'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
230
  }
231
 
232
+ // 获取文件类型标签颜色
233
+ const getFileTypeTag = (filename) => {
234
+ const type = getFileType(filename)
235
+ return { '视频': 'success', '图片': 'warning', '其他': 'info' }[type] || 'info'
 
 
 
 
 
236
  }
237
 
238
  // 导航到指定路由
 
240
  router.push(path)
241
  }
242
 
243
+ // 加载数据
244
+ const fetchDashboardData = async () => {
245
+ loading.value = true
246
+ try {
247
+ // 并行获取账号和素材数据
248
+ const [accountRes, materialRes] = await Promise.allSettled([
249
+ accountApi.getAccounts(),
250
+ materialApi.getAllMaterials()
251
+ ])
252
 
253
+ if (accountRes.status === 'fulfilled' && accountRes.value.code === 200) {
254
+ accountStore.setAccounts(accountRes.value.data)
 
 
 
 
 
 
 
255
  }
256
+ if (materialRes.status === 'fulfilled' && materialRes.value.code === 200) {
257
+ appStore.setMaterials(materialRes.value.data)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
258
  }
259
+ } catch (error) {
260
+ console.error('获取仪表盘数据失败:', error)
261
+ } finally {
262
+ loading.value = false
263
+ }
 
 
 
 
 
 
 
 
 
 
264
  }
265
+
266
+ onMounted(() => {
267
+ fetchDashboardData()
268
+ })
269
  </script>
270
 
271
  <style lang="scss" scoped>
 
274
  .dashboard {
275
  .page-header {
276
  margin-bottom: 20px;
277
+
278
  h1 {
279
  font-size: 24px;
280
  color: $text-primary;
281
  margin: 0;
282
  }
283
  }
284
+
285
  .dashboard-content {
286
  .stat-card {
287
  height: 140px;
288
  margin-bottom: 20px;
289
+
290
  .stat-card-content {
291
  display: flex;
292
  align-items: center;
293
  margin-bottom: 15px;
294
+
295
  .stat-icon {
296
  width: 60px;
297
  height: 60px;
 
301
  justify-content: center;
302
  align-items: center;
303
  margin-right: 15px;
304
+
305
  .el-icon {
306
  font-size: 30px;
307
  color: $primary-color;
308
  }
309
+
310
  &.platform-icon {
311
  background-color: rgba($success-color, 0.1);
312
+
313
  .el-icon {
314
  color: $success-color;
315
  }
316
  }
317
+
 
 
 
 
 
 
 
 
318
  &.content-icon {
319
  background-color: rgba($info-color, 0.1);
320
+
321
  .el-icon {
322
  color: $info-color;
323
  }
324
  }
325
  }
326
+
327
  .stat-info {
328
  .stat-value {
329
  font-size: 24px;
 
331
  color: $text-primary;
332
  line-height: 1.2;
333
  }
334
+
335
  .stat-label {
336
  font-size: 14px;
337
  color: $text-secondary;
338
  }
339
  }
340
  }
341
+
342
  .stat-footer {
343
  border-top: 1px solid $border-lighter;
344
  padding-top: 10px;
345
+
346
  .stat-detail {
347
  display: flex;
348
  justify-content: space-between;
349
  color: $text-secondary;
350
  font-size: 13px;
351
+
352
  .el-tag {
353
  margin-right: 5px;
354
  }
355
  }
356
  }
357
  }
358
+
359
  .quick-actions {
360
  margin: 20px 0 30px;
361
+
362
  h2 {
363
  font-size: 18px;
364
  margin-bottom: 15px;
365
  color: $text-primary;
366
  }
367
+
368
  .action-card {
369
  height: 160px;
370
  display: flex;
 
373
  justify-content: center;
374
  cursor: pointer;
375
  transition: all 0.3s;
376
+
377
  &:hover {
378
  transform: translateY(-5px);
379
  box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
380
  }
381
+
382
  .action-icon {
383
  width: 50px;
384
  height: 50px;
 
388
  justify-content: center;
389
  align-items: center;
390
  margin-bottom: 15px;
391
+
392
  .el-icon {
393
  font-size: 24px;
394
  color: $primary-color;
395
  }
396
  }
397
+
398
  .action-title {
399
  font-size: 16px;
400
  font-weight: bold;
401
  color: $text-primary;
402
  margin-bottom: 5px;
403
  }
404
+
405
  .action-desc {
406
  font-size: 13px;
407
  color: $text-secondary;
 
409
  }
410
  }
411
  }
412
+
413
  .recent-tasks {
414
  margin-top: 30px;
415
+
416
  .section-header {
417
  display: flex;
418
  justify-content: space-between;
419
  align-items: center;
420
  margin-bottom: 15px;
421
+
422
  h2 {
423
  font-size: 18px;
424
  color: $text-primary;
 
428
  }
429
  }
430
  }
431
+ </style>
sau_frontend/src/views/Home.vue DELETED
@@ -1,106 +0,0 @@
1
- <template>
2
- <div class="home">
3
- <div class="welcome-section">
4
- <h1>欢迎使用 Vue3 + Vite 项目</h1>
5
- <p>这是一个集成了 Vue3、Vite、Element Plus、Pinia、Vue Router 和 Axios 的现代化前端项目</p>
6
- <div class="features">
7
- <el-row :gutter="20">
8
- <el-col :span="8">
9
- <el-card class="feature-card">
10
- <template #header>
11
- <div class="card-header">
12
- <el-icon><Lightning /></el-icon>
13
- <span>快速开发</span>
14
- </div>
15
- </template>
16
- <p>基于 Vite 构建,提供极速的开发体验</p>
17
- </el-card>
18
- </el-col>
19
- <el-col :span="8">
20
- <el-card class="feature-card">
21
- <template #header>
22
- <div class="card-header">
23
- <el-icon><Star /></el-icon>
24
- <span>现代化</span>
25
- </div>
26
- </template>
27
- <p>使用 Vue3 Composition API 和 setup 语法</p>
28
- </el-card>
29
- </el-col>
30
- <el-col :span="8">
31
- <el-card class="feature-card">
32
- <template #header>
33
- <div class="card-header">
34
- <el-icon><Setting /></el-icon>
35
- <span>完整配置</span>
36
- </div>
37
- </template>
38
- <p>集成路由、状态管理、HTTP请求等完整解决方案</p>
39
- </el-card>
40
- </el-col>
41
- </el-row>
42
- </div>
43
- </div>
44
-
45
- <div class="demo-section">
46
- <HelloWorld msg="Vue3 + Vite + Element Plus" />
47
- </div>
48
- </div>
49
- </template>
50
-
51
- <script setup>
52
- import HelloWorld from '../components/HelloWorld.vue'
53
- import { Lightning, Star, Setting } from '@element-plus/icons-vue'
54
- </script>
55
-
56
- <style lang="scss" scoped>
57
- @use '@/styles/variables.scss' as *;
58
-
59
- .home {
60
- .welcome-section {
61
- text-align: center;
62
- margin-bottom: 3rem;
63
-
64
- h1 {
65
- color: #2c3e50;
66
- margin-bottom: 1rem;
67
- font-size: 2.5rem;
68
- }
69
-
70
- p {
71
- color: #7f8c8d;
72
- font-size: 1.2rem;
73
- margin-bottom: 2rem;
74
- }
75
-
76
- .features {
77
- margin-top: 2rem;
78
-
79
- .feature-card {
80
- height: 200px;
81
-
82
- .card-header {
83
- display: flex;
84
- align-items: center;
85
- gap: 0.5rem;
86
- font-weight: bold;
87
-
88
- .el-icon {
89
- color: #409eff;
90
- }
91
- }
92
-
93
- p {
94
- color: #666;
95
- font-size: 1rem;
96
- line-height: 1.6;
97
- }
98
- }
99
- }
100
- }
101
-
102
- .demo-section {
103
- margin-top: 3rem;
104
- }
105
- }
106
- </style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
sau_frontend/src/views/PublishCenter.vue CHANGED
@@ -296,6 +296,15 @@
296
  </el-radio-group>
297
  </div>
298
 
 
 
 
 
 
 
 
 
 
299
  <!-- 草稿选项 (仅在视频号可见) -->
300
  <div v-if="tab.selectedPlatform === 2" class="draft-section">
301
  <el-checkbox
@@ -411,27 +420,6 @@
411
  </template>
412
  </el-dialog>
413
 
414
- <!-- 标签 (仅在抖音可见) -->
415
- <div v-if="tab.selectedPlatform === 3" class="product-section">
416
- <h3>商品链接</h3>
417
- <el-input
418
- v-model="tab.productTitle"
419
- type="text"
420
- :rows="1"
421
- placeholder="请输入商品名称"
422
- maxlength="200"
423
- class="product-name-input"
424
- />
425
- <el-input
426
- v-model="tab.productLink"
427
- type="text"
428
- :rows="1"
429
- placeholder="请输入商品链接"
430
- maxlength="200"
431
- class="product-link-input"
432
- />
433
- </div>
434
-
435
  <!-- 定时发布 -->
436
  <div class="schedule-section">
437
  <h3>定时发布</h3>
@@ -509,6 +497,7 @@ import { ElMessage } from 'element-plus'
509
  import { useAccountStore } from '@/stores/account'
510
  import { useAppStore } from '@/stores/app'
511
  import { materialApi } from '@/api/material'
 
512
 
513
  // API base URL
514
  const apiBaseUrl = import.meta.env.VITE_API_BASE_URL || 'http://localhost:5409'
@@ -565,7 +554,8 @@ const defaultTabInit = {
565
  startDays: 0, // 从今天开始计算的发布天数,0表示明天,1表示后天
566
  publishStatus: null, // 发布状态,包含message和type
567
  publishing: false, // 发布状态,用于控制按钮loading效果
568
- isDraft: false // 是否保存为草稿,仅视频号平台可见
 
569
  }
570
 
571
  // helper to create a fresh deep-copied tab from defaultTabInit
@@ -663,7 +653,6 @@ const handleUploadSuccess = (response, file, tab) => {
663
  }))]
664
 
665
  ElMessage.success('文件上传成功')
666
- console.log('上传成功:', fileInfo)
667
  } else {
668
  ElMessage.error(response.msg || '上传失败')
669
  }
@@ -672,7 +661,6 @@ const handleUploadSuccess = (response, file, tab) => {
672
  // 处理文件上传失败
673
  const handleUploadError = (error) => {
674
  ElMessage.error('文件上传失败')
675
- console.error('上传错误:', error)
676
  }
677
 
678
  // 删除已上传文件
@@ -774,102 +762,77 @@ const cancelPublish = (tab) => {
774
  const confirmPublish = async (tab) => {
775
  // 防止重复点击
776
  if (tab.publishing) {
777
- return Promise.reject(new Error('正在发布中,请稍候...'))
778
  }
779
 
780
  tab.publishing = true // 设置发布状态为进行中
781
 
782
- return new Promise((resolve, reject) => {
783
- // 数据验证
784
- if (tab.fileList.length === 0) {
785
- ElMessage.error('请先上传视频文件')
786
- tab.publishing = false // 重置发布状态
787
- reject(new Error('请先上传视频文件'))
788
- return
789
- }
790
- if (!tab.title.trim()) {
791
- ElMessage.error('请输入标题')
792
- tab.publishing = false // 重置发布状态
793
- reject(new Error('请输入标题'))
794
- return
795
- }
796
- if (!tab.selectedPlatform) {
797
- ElMessage.error('请选择发布平台')
798
- tab.publishing = false // 重置发布状态
799
- reject(new Error('请选择发布平台'))
800
- return
801
- }
802
- if (tab.selectedAccounts.length === 0) {
803
- ElMessage.error('请选择发布账号')
804
- tab.publishing = false // 重置发布状态
805
- reject(new Error('请选择发布账号'))
806
- return
807
- }
808
 
809
- // 构造发布数据,符合后端API格式
810
- const publishData = {
811
- type: tab.selectedPlatform,
812
- title: tab.title,
813
- tags: tab.selectedTopics, // 不带#号的话题列表
814
- fileList: tab.fileList.map(file => file.path), // 只发送文件路径
815
- accountList: tab.selectedAccounts.map(accountId => {
816
- const account = accountStore.accounts.find(acc => acc.id === accountId)
817
- return account ? account.filePath : accountId
818
- }), // 发送账��的文件路径
819
- enableTimer: tab.scheduleEnabled ? 1 : 0, // 是否启用定时发布,开启传1,不开启传0
820
- videosPerDay: tab.scheduleEnabled ? tab.videosPerDay || 1 : 1, // 每天发布视频数量,1-55
821
- dailyTimes: tab.scheduleEnabled ? tab.dailyTimes || ['10:00'] : ['10:00'], // 每天发布时间点
822
- startDays: tab.scheduleEnabled ? tab.startDays || 0 : 0, // 从今天开始计算的发布天数,0表示明天,1表示后天
823
- category: 0, //表示非原创
824
- productLink: tab.productLink.trim() || '', // 商品链接
825
- productTitle: tab.productTitle.trim() || '', // 商品名称
826
- isDraft: tab.isDraft // 是否保存为草稿,仅视频号平台使用
827
- }
828
 
829
- // 调用后端发布API
830
- fetch(`${apiBaseUrl}/postVideo`, {
831
- method: 'POST',
832
- headers: {
833
- 'Content-Type': 'application/json',
834
- ...authHeaders.value
835
- },
836
- body: JSON.stringify(publishData)
837
- })
838
- .then(response => response.json())
839
- .then(data => {
840
- if (data.code === 200) {
841
- tab.publishStatus = {
842
- message: '发布成功',
843
- type: 'success'
844
- }
845
- // 清空当前tab的数据
846
- tab.fileList = []
847
- tab.displayFileList = []
848
- tab.title = ''
849
- tab.selectedTopics = []
850
- tab.selectedAccounts = []
851
- tab.scheduleEnabled = false
852
- resolve()
853
- } else {
854
- tab.publishStatus = {
855
- message: `发布失败:${data.msg || '发布失败'}`,
856
- type: 'error'
857
- }
858
- reject(new Error(data.msg || '发布失败'))
859
- }
860
- })
861
- .catch(error => {
862
- console.error('发布错误:', error)
863
- tab.publishStatus = {
864
- message: '发布失败,请检查网络连接',
865
- type: 'error'
866
- }
867
- reject(error)
868
- })
869
- .finally(() => {
870
- tab.publishing = false // 重置发布状态
871
- })
872
- })
873
  }
874
 
875
  // 显示上传选项
@@ -1324,6 +1287,15 @@ const batchPublish = async () => {
1324
  margin: 10px 0;
1325
  }
1326
  }
 
 
 
 
 
 
 
 
 
1327
  }
1328
  }
1329
  }
 
296
  </el-radio-group>
297
  </div>
298
 
299
+ <!-- 原创声明 -->
300
+ <div class="original-section">
301
+ <el-checkbox
302
+ v-model="tab.isOriginal"
303
+ label="声明原创"
304
+ class="original-checkbox"
305
+ />
306
+ </div>
307
+
308
  <!-- 草稿选项 (仅在视频号可见) -->
309
  <div v-if="tab.selectedPlatform === 2" class="draft-section">
310
  <el-checkbox
 
420
  </template>
421
  </el-dialog>
422
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
423
  <!-- 定时发布 -->
424
  <div class="schedule-section">
425
  <h3>定时发布</h3>
 
497
  import { useAccountStore } from '@/stores/account'
498
  import { useAppStore } from '@/stores/app'
499
  import { materialApi } from '@/api/material'
500
+ import { http } from '@/utils/request'
501
 
502
  // API base URL
503
  const apiBaseUrl = import.meta.env.VITE_API_BASE_URL || 'http://localhost:5409'
 
554
  startDays: 0, // 从今天开始计算的发布天数,0表示明天,1表示后天
555
  publishStatus: null, // 发布状态,包含message和type
556
  publishing: false, // 发布状态,用于控制按钮loading效果
557
+ isDraft: false, // 是否保存为草稿,仅视频号平台可见
558
+ isOriginal: false // 是否标记为原创
559
  }
560
 
561
  // helper to create a fresh deep-copied tab from defaultTabInit
 
653
  }))]
654
 
655
  ElMessage.success('文件上传成功')
 
656
  } else {
657
  ElMessage.error(response.msg || '上传失败')
658
  }
 
661
  // 处理文件上传失败
662
  const handleUploadError = (error) => {
663
  ElMessage.error('文件上传失败')
 
664
  }
665
 
666
  // 删除已上传文件
 
762
  const confirmPublish = async (tab) => {
763
  // 防止重复点击
764
  if (tab.publishing) {
765
+ throw new Error('正在发布中,请稍候...')
766
  }
767
 
768
  tab.publishing = true // 设置发布状态为进行中
769
 
770
+ // 数据验证
771
+ if (tab.fileList.length === 0) {
772
+ ElMessage.error('请先上传视频文件')
773
+ tab.publishing = false
774
+ throw new Error('请先上传视频文件')
775
+ }
776
+ if (!tab.title.trim()) {
777
+ ElMessage.error('请输入标题')
778
+ tab.publishing = false
779
+ throw new Error('请输入标题')
780
+ }
781
+ if (!tab.selectedPlatform) {
782
+ ElMessage.error('请选择发布平台')
783
+ tab.publishing = false
784
+ throw new Error('请选择发布平台')
785
+ }
786
+ if (tab.selectedAccounts.length === 0) {
787
+ ElMessage.error('请选择发布账号')
788
+ tab.publishing = false
789
+ throw new Error('请选择发布账号')
790
+ }
 
 
 
 
 
791
 
792
+ // 构造发布数据,符合后端API格式
793
+ const publishData = {
794
+ type: tab.selectedPlatform,
795
+ title: tab.title,
796
+ tags: tab.selectedTopics, // 不带#号的话题列表
797
+ fileList: tab.fileList.map(file => file.path), // 只发送文件路径
798
+ accountList: tab.selectedAccounts.map(accountId => {
799
+ const account = accountStore.accounts.find(acc => acc.id === accountId)
800
+ return account ? account.filePath : accountId
801
+ }), // 发送账的文件路径
802
+ enableTimer: tab.scheduleEnabled ? 1 : 0,
803
+ videosPerDay: tab.scheduleEnabled ? tab.videosPerDay || 1 : 1,
804
+ dailyTimes: tab.scheduleEnabled ? tab.dailyTimes || ['10:00'] : ['10:00'],
805
+ startDays: tab.scheduleEnabled ? tab.startDays || 0 : 0,
806
+ category: tab.isOriginal ? 1 : 0, // 1表示原创,0表示非原创
807
+ productLink: tab.productLink.trim() || '',
808
+ productTitle: tab.productTitle.trim() || '',
809
+ isDraft: tab.isDraft
810
+ }
811
 
812
+ // 调用后端发布API(使用统一的http封装)
813
+ try {
814
+ const data = await http.post('/postVideo', publishData)
815
+ tab.publishStatus = {
816
+ message: '发布成功',
817
+ type: 'success'
818
+ }
819
+ // 清空当前tab的数据
820
+ tab.fileList = []
821
+ tab.displayFileList = []
822
+ tab.title = ''
823
+ tab.selectedTopics = []
824
+ tab.selectedAccounts = []
825
+ tab.scheduleEnabled = false
826
+ } catch (error) {
827
+ console.error('发布错误:', error)
828
+ tab.publishStatus = {
829
+ message: `发布失败:${error.message || '请检查网络连接'}`,
830
+ type: 'error'
831
+ }
832
+ throw error
833
+ } finally {
834
+ tab.publishing = false
835
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
836
  }
837
 
838
  // 显示上传选项
 
1287
  margin: 10px 0;
1288
  }
1289
  }
1290
+
1291
+ .original-section {
1292
+ margin: 10px 0 20px;
1293
+
1294
+ .original-checkbox {
1295
+ display: block;
1296
+ margin: 10px 0;
1297
+ }
1298
+ }
1299
  }
1300
  }
1301
  }
utils/browser_hook.py ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from conf import LOCAL_CHROME_HEADLESS, LOCAL_CHROME_PATH
2
+
3
+ def get_browser_options():
4
+ options = {
5
+ 'headless': LOCAL_CHROME_HEADLESS,
6
+ 'args': [
7
+ '--disable-blink-features=AutomationControlled',
8
+ '--lang=zh-CN',
9
+ '--disable-infobars',
10
+ '--start-maximized',
11
+ '--no-sandbox',
12
+ '--disable-web-security'
13
+ ]
14
+ }
15
+ if LOCAL_CHROME_PATH:
16
+ options['executable_path'] = LOCAL_CHROME_PATH
17
+ return options