diff --git a/sau_backend.py b/sau_backend.py index c7529f7..3567068 100644 --- a/sau_backend.py +++ b/sau_backend.py @@ -48,14 +48,14 @@ def index(): # put application's code here def upload_file(): if 'file' not in request.files: return jsonify({ - "code": 200, + "code": 400, "data": None, "msg": "No file part in the request" }), 400 file = request.files['file'] if file.filename == '': return jsonify({ - "code": 200, + "code": 400, "data": None, "msg": "No selected file" }), 400 @@ -67,7 +67,7 @@ def upload_file(): file.save(filepath) return jsonify({"code":200,"msg": "File uploaded successfully", "data": f"{uuid_v1}_{file.filename}"}), 200 except Exception as e: - return jsonify({"code":200,"msg": str(e),"data":None}), 500 + return jsonify({"code":500,"msg": str(e),"data":None}), 500 @app.route('/getFile', methods=['GET']) def get_file(): @@ -75,11 +75,11 @@ def get_file(): filename = request.args.get('filename') if not filename: - return {"error": "filename is required"}, 400 + return jsonify({"code": 400, "msg": "filename is required", "data": None}), 400 # 防止路径穿越攻击 if '..' in filename or filename.startswith('/'): - return {"error": "Invalid filename"}, 400 + return jsonify({"code": 400, "msg": "Invalid filename", "data": None}), 400 # 拼接完整路径 file_path = str(Path(BASE_DIR / "videoFile")) @@ -316,7 +316,16 @@ def delete_file(): @app.route('/deleteAccount', methods=['GET']) def delete_account(): - account_id = int(request.args.get('id')) + account_id = request.args.get('id') + + if not account_id or not account_id.isdigit(): + return jsonify({ + "code": 400, + "msg": "Invalid or missing account ID", + "data": None + }), 400 + + account_id = int(account_id) try: # 获取数据库连接 @@ -337,6 +346,16 @@ def delete_account(): record = dict(record) + # 删除关联的cookie文件 + if record.get('filePath'): + cookie_file_path = Path(BASE_DIR / "cookiesFile" / record['filePath']) + if cookie_file_path.exists(): + try: + cookie_file_path.unlink() + print(f"✅ Cookie文件已删除: {cookie_file_path}") + except Exception as e: + print(f"⚠️ 删除Cookie文件失败: {e}") + # 删除数据库记录 cursor.execute("DELETE FROM user_info WHERE id = ?", (account_id,)) conn.commit() @@ -350,7 +369,7 @@ def delete_account(): except Exception as e: return jsonify({ "code": 500, - "msg": str("delete failed!"), + "msg": f"delete failed: {str(e)}", "data": None }), 500 @@ -385,6 +404,9 @@ def postVideo(): # 获取JSON数据 data = request.get_json() + if not data: + return jsonify({"code": 400, "msg": "请求数据不能为空", "data": None}), 400 + # 从JSON数据中提取fileList和accountList file_list = data.get('fileList', []) account_list = data.get('accountList', []) @@ -403,29 +425,52 @@ def postVideo(): videos_per_day = data.get('videosPerDay') daily_times = data.get('dailyTimes') start_days = data.get('startDays') + + # 参数校验 + if not file_list: + return jsonify({"code": 400, "msg": "文件列表不能为空", "data": None}), 400 + if not account_list: + return jsonify({"code": 400, "msg": "账号列表不能为空", "data": None}), 400 + if not type: + return jsonify({"code": 400, "msg": "平台类型不能为空", "data": None}), 400 + if not title: + return jsonify({"code": 400, "msg": "标题不能为空", "data": None}), 400 + # 打印获取到的数据(仅作为示例) print("File List:", file_list) print("Account List:", account_list) - match type: - case 1: - post_video_xhs(title, file_list, tags, account_list, category, enableTimer, videos_per_day, daily_times, - start_days) - case 2: - post_video_tencent(title, file_list, tags, account_list, category, enableTimer, videos_per_day, daily_times, - start_days, is_draft) - case 3: - post_video_DouYin(title, file_list, tags, account_list, category, enableTimer, videos_per_day, daily_times, - start_days, thumbnail_path, productLink, productTitle) - case 4: - post_video_ks(title, file_list, tags, account_list, category, enableTimer, videos_per_day, daily_times, - start_days) - # 返回响应给客户端 - return jsonify( - { - "code": 200, - "msg": None, + + try: + match type: + case 1: + post_video_xhs(title, file_list, tags, account_list, category, enableTimer, videos_per_day, daily_times, + start_days) + case 2: + post_video_tencent(title, file_list, tags, account_list, category, enableTimer, videos_per_day, daily_times, + start_days, is_draft) + case 3: + post_video_DouYin(title, file_list, tags, account_list, category, enableTimer, videos_per_day, daily_times, + start_days, thumbnail_path, productLink, productTitle) + case 4: + post_video_ks(title, file_list, tags, account_list, category, enableTimer, videos_per_day, daily_times, + start_days) + case _: + return jsonify({"code": 400, "msg": f"不支持的平台类型: {type}", "data": None}), 400 + + # 返回响应给客户端 + return jsonify( + { + "code": 200, + "msg": "发布任务已提交", + "data": None + }), 200 + except Exception as e: + print(f"发布视频时出错: {str(e)}") + return jsonify({ + "code": 500, + "msg": f"发布失败: {str(e)}", "data": None - }), 200 + }), 500 @app.route('/updateUserinfo', methods=['POST']) @@ -470,7 +515,7 @@ def postVideoBatch(): data_list = request.get_json() if not isinstance(data_list, list): - return jsonify({"error": "Expected a JSON array"}), 400 + return jsonify({"code": 400, "msg": "Expected a JSON array", "data": None}), 400 for data in data_list: # 从JSON数据中提取fileList和accountList file_list = data.get('fileList', []) @@ -484,6 +529,7 @@ def postVideoBatch(): category = None productLink = data.get('productLink', '') productTitle = data.get('productTitle', '') + is_draft = data.get('isDraft', False) videos_per_day = data.get('videosPerDay') daily_times = data.get('dailyTimes') @@ -493,10 +539,11 @@ def postVideoBatch(): print("Account List:", account_list) match type: case 1: - return + post_video_xhs(title, file_list, tags, account_list, category, enableTimer, videos_per_day, daily_times, + start_days) case 2: post_video_tencent(title, file_list, tags, account_list, category, enableTimer, videos_per_day, daily_times, - start_days) + start_days, is_draft) case 3: post_video_DouYin(title, file_list, tags, account_list, category, enableTimer, videos_per_day, daily_times, start_days, productLink, productTitle) @@ -517,7 +564,7 @@ def upload_cookie(): try: if 'file' not in request.files: return jsonify({ - "code": 500, + "code": 400, "msg": "没有找到Cookie文件", "data": None }), 400 @@ -525,14 +572,14 @@ def upload_cookie(): file = request.files['file'] if file.filename == '': return jsonify({ - "code": 500, + "code": 400, "msg": "Cookie文件名不能为空", "data": None }), 400 if not file.filename.endswith('.json'): return jsonify({ - "code": 500, + "code": 400, "msg": "Cookie文件必须是JSON格式", "data": None }), 400 @@ -543,7 +590,7 @@ def upload_cookie(): if not account_id or not platform: return jsonify({ - "code": 500, + "code": 400, "msg": "缺少账号ID或平台信息", "data": None }), 400 diff --git a/sau_frontend/src/App.vue b/sau_frontend/src/App.vue index 802b2e3..cca49d6 100644 --- a/sau_frontend/src/App.vue +++ b/sau_frontend/src/App.vue @@ -32,13 +32,9 @@ 发布中心 - - - 网站 - - + - 数据 + 关于 @@ -65,8 +61,8 @@ - - diff --git a/sau_frontend/src/stores/account.js b/sau_frontend/src/stores/account.js index 1c33d84..8d27aa1 100644 --- a/sau_frontend/src/stores/account.js +++ b/sau_frontend/src/stores/account.js @@ -22,9 +22,8 @@ export const useAccountStore = defineStore('account', () => { type: item[1], filePath: item[2], name: item[3], - status: item[4] === 1 ? '正常' : '异常', - platform: platformTypes[item[1]] || '未知', - avatar: '/vite.svg' // 默认使用vite.svg作为头像 + status: item[4] === -1 ? '验证中' : (item[4] === 1 ? '正常' : '异常'), + platform: platformTypes[item[1]] || '未知' } }) } diff --git a/sau_frontend/src/utils/request.js b/sau_frontend/src/utils/request.js index 73a25e6..bfb4411 100644 --- a/sau_frontend/src/utils/request.js +++ b/sau_frontend/src/utils/request.js @@ -34,8 +34,8 @@ request.interceptors.response.use( if (data.code === 200 || data.success) { return data } else { - ElMessage.error(data.message || '请求失败') - return Promise.reject(new Error(data.message || '请求失败')) + ElMessage.error(data.msg || data.message || '请求失败') + return Promise.reject(new Error(data.msg || data.message || '请求失败')) } }, (error) => { diff --git a/sau_frontend/src/views/About.vue b/sau_frontend/src/views/About.vue index 2a32954..b84bebf 100644 --- a/sau_frontend/src/views/About.vue +++ b/sau_frontend/src/views/About.vue @@ -1,7 +1,54 @@ @@ -13,16 +60,55 @@ @use '@/styles/variables.scss' as *; .about { - text-align: center; - padding: 2rem; - - h1 { - color: #2c3e50; - margin-bottom: 1rem; - } - - p { - color: #7f8c8d; + max-width: 700px; + margin: 0 auto; + + .about-card { + .about-header { + text-align: center; + + h1 { + color: $text-primary; + margin: 0 0 8px 0; + font-size: 24px; + } + + .version { + color: $text-secondary; + font-size: 14px; + margin: 0; + } + } + + .about-section { + margin-bottom: 24px; + + h3 { + font-size: 16px; + color: $text-primary; + margin: 0 0 12px 0; + } + + p { + color: $text-secondary; + line-height: 1.8; + margin: 0; + } + + .platform-tags, + .tech-tags { + display: flex; + flex-wrap: wrap; + gap: 10px; + } + + .feature-list { + margin: 0; + padding-left: 20px; + color: $text-secondary; + line-height: 2; + } + } } } - \ No newline at end of file + diff --git a/sau_frontend/src/views/AccountManagement.vue b/sau_frontend/src/views/AccountManagement.vue index dad1efc..045ed22 100644 --- a/sau_frontend/src/views/AccountManagement.vue +++ b/sau_frontend/src/views/AccountManagement.vue @@ -433,6 +433,7 @@ import { ElMessage, ElMessageBox } from 'element-plus' import { accountApi } from '@/api/account' import { useAccountStore } from '@/stores/account' import { useAppStore } from '@/stores/app' +import { http } from '@/utils/request' // 获取账号状态管理 const accountStore = useAccountStore() @@ -452,9 +453,8 @@ const fetchAccountsQuick = async () => { if (res.code === 200 && res.data) { // 将所有账号的状态暂时设为"验证中" const accountsWithPendingStatus = res.data.map(account => { - // account[4] 是状态字段,暂时设为"验证中" const updatedAccount = [...account]; - updatedAccount[4] = '验证中'; // 临时状态 + updatedAccount[4] = -1; // -1 表示验证中的临时状态 return updatedAccount; }); accountStore.setAccounts(accountsWithPendingStatus); @@ -713,24 +713,13 @@ const handleUploadCookie = (row) => { formData.append('id', row.id) formData.append('platform', row.platform) - // 发送上传请求 - const baseUrl = import.meta.env.VITE_API_BASE_URL || 'http://localhost:5409' - const response = await fetch(`${baseUrl}/uploadCookie`, { - method: 'POST', - body: formData - }) + // 使用统一的http封装发送上传请求 + const result = await http.upload('/uploadCookie', formData) - const result = await response.json() - - if (result.code === 200) { - ElMessage.success('Cookie文件上传成功') - // 刷新账号列表以显示更新 - fetchAccounts() - } else { - ElMessage.error(result.msg || 'Cookie文件上传失败') - } + ElMessage.success('Cookie文件上传成功') + // 刷新账号列表以显示更新 + fetchAccounts() } catch (error) { - console.error('上传Cookie文件失败:', error) ElMessage.error('Cookie文件上传失败') } finally { document.body.removeChild(input) @@ -811,22 +800,17 @@ const connectSSE = (platform, name) => { // 监听消息 eventSource.onmessage = (event) => { const data = event.data - console.log('SSE消息:', data) // 如果还没有二维码数据,且数据长度较长,认为是二维码 if (!qrCodeData.value && data.length > 100) { try { - // 确保数据是有效的base64编码 - // 如果数据已经包含了data:image前缀,直接使用 if (data.startsWith('data:image')) { qrCodeData.value = data } else { - // 否则添加前缀 qrCodeData.value = `data:image/png;base64,${data}` } - console.log('设置二维码数据,长度:', data.length) } catch (error) { - console.error('处理二维码数据出错:', error) + // 处理二维码数据出错 } } // 如果收到状态码 @@ -897,10 +881,10 @@ const submitAccountForm = () => { try { // 将平台名称转换为类型数字 const platformTypeMap = { - '快手': 1, - '抖音': 2, - '视频号': 3, - '小红书': 4 + '小红书': 1, + '视频号': 2, + '抖音': 3, + '快手': 4 }; const type = platformTypeMap[accountForm.platform] || 1; diff --git a/sau_frontend/src/views/Dashboard.vue b/sau_frontend/src/views/Dashboard.vue index da604ee..9a9fe83 100644 --- a/sau_frontend/src/views/Dashboard.vue +++ b/sau_frontend/src/views/Dashboard.vue @@ -3,11 +3,11 @@ - +
- +
@@ -26,9 +26,9 @@
- + - +
@@ -36,7 +36,7 @@
{{ platformStats.total }}
-
平台总数
+
已接入平台
- - - - -
-
- -
-
-
{{ taskStats.total }}
-
任务总数
-
-
- -
-
- - - + + +
@@ -89,19 +67,20 @@
{{ contentStats.total }}
-
内容总数
+
素材总数
- +

快捷操作

@@ -116,199 +95,144 @@ - +
-
内容上传
-
上传视频和图文内容
+
素材管理
+
上传和管理视频素材
- +
-
定时发布
-
设置内容发布时间
+
发布中心
+
发布内容到各平台
- +
-
数据分析
-
查看内容数据分析
+
关于系统
+
查看系统信息
- - + +
-

最近任务

- 查看全部 +

最近上传素材

+ 查看全部
- - - - + + + + - - - + + - - - + +
\ No newline at end of file + diff --git a/sau_frontend/src/views/Home.vue b/sau_frontend/src/views/Home.vue deleted file mode 100644 index 90373ab..0000000 --- a/sau_frontend/src/views/Home.vue +++ /dev/null @@ -1,106 +0,0 @@ - - - - - \ No newline at end of file diff --git a/sau_frontend/src/views/PublishCenter.vue b/sau_frontend/src/views/PublishCenter.vue index c2fde30..4167a12 100644 --- a/sau_frontend/src/views/PublishCenter.vue +++ b/sau_frontend/src/views/PublishCenter.vue @@ -296,6 +296,15 @@ + +
+ +
+
- -
-

商品链接

- - -
-

定时发布

@@ -509,6 +497,7 @@ import { ElMessage } from 'element-plus' import { useAccountStore } from '@/stores/account' import { useAppStore } from '@/stores/app' import { materialApi } from '@/api/material' +import { http } from '@/utils/request' // API base URL const apiBaseUrl = import.meta.env.VITE_API_BASE_URL || 'http://localhost:5409' @@ -565,7 +554,8 @@ const defaultTabInit = { startDays: 0, // 从今天开始计算的发布天数,0表示明天,1表示后天 publishStatus: null, // 发布状态,包含message和type publishing: false, // 发布状态,用于控制按钮loading效果 - isDraft: false // 是否保存为草稿,仅视频号平台可见 + isDraft: false, // 是否保存为草稿,仅视频号平台可见 + isOriginal: false // 是否标记为原创 } // helper to create a fresh deep-copied tab from defaultTabInit @@ -663,7 +653,6 @@ const handleUploadSuccess = (response, file, tab) => { }))] ElMessage.success('文件上传成功') - console.log('上传成功:', fileInfo) } else { ElMessage.error(response.msg || '上传失败') } @@ -672,7 +661,6 @@ const handleUploadSuccess = (response, file, tab) => { // 处理文件上传失败 const handleUploadError = (error) => { ElMessage.error('文件上传失败') - console.error('上传错误:', error) } // 删除已上传文件 @@ -774,102 +762,77 @@ const cancelPublish = (tab) => { const confirmPublish = async (tab) => { // 防止重复点击 if (tab.publishing) { - return Promise.reject(new Error('正在发布中,请稍候...')) + throw new Error('正在发布中,请稍候...') } tab.publishing = true // 设置发布状态为进行中 - return new Promise((resolve, reject) => { - // 数据验证 - if (tab.fileList.length === 0) { - ElMessage.error('请先上传视频文件') - tab.publishing = false // 重置发布状态 - reject(new Error('请先上传视频文件')) - return - } - if (!tab.title.trim()) { - ElMessage.error('请输入标题') - tab.publishing = false // 重置发布状态 - reject(new Error('请输入标题')) - return - } - if (!tab.selectedPlatform) { - ElMessage.error('请选择发布平台') - tab.publishing = false // 重置发布状态 - reject(new Error('请选择发布平台')) - return - } - if (tab.selectedAccounts.length === 0) { - ElMessage.error('请选择发布账号') - tab.publishing = false // 重置发布状态 - reject(new Error('请选择发布账号')) - return - } + // 数据验证 + if (tab.fileList.length === 0) { + ElMessage.error('请先上传视频文件') + tab.publishing = false + throw new Error('请先上传视频文件') + } + if (!tab.title.trim()) { + ElMessage.error('请输入标题') + tab.publishing = false + throw new Error('请输入标题') + } + if (!tab.selectedPlatform) { + ElMessage.error('请选择发布平台') + tab.publishing = false + throw new Error('请选择发布平台') + } + if (tab.selectedAccounts.length === 0) { + ElMessage.error('请选择发布账号') + tab.publishing = false + throw new Error('请选择发布账号') + } - // 构造发布数据,符合后端API格式 - const publishData = { - type: tab.selectedPlatform, - title: tab.title, - tags: tab.selectedTopics, // 不带#号的话题列表 - fileList: tab.fileList.map(file => file.path), // 只发送文件路径 - accountList: tab.selectedAccounts.map(accountId => { - const account = accountStore.accounts.find(acc => acc.id === accountId) - return account ? account.filePath : accountId - }), // 发送账号的文件路径 - enableTimer: tab.scheduleEnabled ? 1 : 0, // 是否启用定时发布,开启传1,不开启传0 - videosPerDay: tab.scheduleEnabled ? tab.videosPerDay || 1 : 1, // 每天发布视频数量,1-55 - dailyTimes: tab.scheduleEnabled ? tab.dailyTimes || ['10:00'] : ['10:00'], // 每天发布时间点 - startDays: tab.scheduleEnabled ? tab.startDays || 0 : 0, // 从今天开始计算的发布天数,0表示明天,1表示后天 - category: 0, //表示非原创 - productLink: tab.productLink.trim() || '', // 商品链接 - productTitle: tab.productTitle.trim() || '', // 商品名称 - isDraft: tab.isDraft // 是否保存为草稿,仅视频号平台使用 - } + // 构造发布数据,符合后端API格式 + const publishData = { + type: tab.selectedPlatform, + title: tab.title, + tags: tab.selectedTopics, // 不带#号的话题列表 + fileList: tab.fileList.map(file => file.path), // 只发送文件路径 + accountList: tab.selectedAccounts.map(accountId => { + const account = accountStore.accounts.find(acc => acc.id === accountId) + return account ? account.filePath : accountId + }), // 发送账号的文件路径 + enableTimer: tab.scheduleEnabled ? 1 : 0, + videosPerDay: tab.scheduleEnabled ? tab.videosPerDay || 1 : 1, + dailyTimes: tab.scheduleEnabled ? tab.dailyTimes || ['10:00'] : ['10:00'], + startDays: tab.scheduleEnabled ? tab.startDays || 0 : 0, + category: tab.isOriginal ? 1 : 0, // 1表示原创,0表示非原创 + productLink: tab.productLink.trim() || '', + productTitle: tab.productTitle.trim() || '', + isDraft: tab.isDraft + } - // 调用后端发布API - fetch(`${apiBaseUrl}/postVideo`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - ...authHeaders.value - }, - body: JSON.stringify(publishData) - }) - .then(response => response.json()) - .then(data => { - if (data.code === 200) { - tab.publishStatus = { - message: '发布成功', - type: 'success' - } - // 清空当前tab的数据 - tab.fileList = [] - tab.displayFileList = [] - tab.title = '' - tab.selectedTopics = [] - tab.selectedAccounts = [] - tab.scheduleEnabled = false - resolve() - } else { - tab.publishStatus = { - message: `发布失败:${data.msg || '发布失败'}`, - type: 'error' - } - reject(new Error(data.msg || '发布失败')) - } - }) - .catch(error => { - console.error('发布错误:', error) - tab.publishStatus = { - message: '发布失败,请检查网络连接', - type: 'error' - } - reject(error) - }) - .finally(() => { - tab.publishing = false // 重置发布状态 - }) - }) + // 调用后端发布API(使用统一的http封装) + try { + const data = await http.post('/postVideo', publishData) + tab.publishStatus = { + message: '发布成功', + type: 'success' + } + // 清空当前tab的数据 + tab.fileList = [] + tab.displayFileList = [] + tab.title = '' + tab.selectedTopics = [] + tab.selectedAccounts = [] + tab.scheduleEnabled = false + } catch (error) { + console.error('发布错误:', error) + tab.publishStatus = { + message: `发布失败:${error.message || '请检查网络连接'}`, + type: 'error' + } + throw error + } finally { + tab.publishing = false + } } // 显示上传选项 @@ -1324,6 +1287,15 @@ const batchPublish = async () => { margin: 10px 0; } } + + .original-section { + margin: 10px 0 20px; + + .original-checkbox { + display: block; + margin: 10px 0; + } + } } } }