13 Revize cbaaef9a12 ... 8dcdb6a8f0

Autor SHA1 Zpráva Datum
  ethanfly 8dcdb6a8f0 Merge branch 'fix/bug-6088-6084-6082-6080-6079' před 2 týdny
  ethanfly 3958531e74 Merge branch 'fix/2026-03-28-typescript-errors' před 2 týdny
  ethanfly ae527c0bf5 Merge branch 'fix/2026-03-28-undefined-rebuildTaskIndex' před 2 týdny
  ethanfly 0c3f14a90a Merge branch 'fix/2026-03-28-refactor-auth-layouts' před 2 týdny
  ethanfly 017dc592a7 fix: 添加 author 字段到 client/package.json 解决 electron-builder 警告 před 2 týdny
  ethanfly 51c4a9e1a2 refactor: 重构 Login 和 Register 页面使用 AuthPageLayout 组件 před 2 týdny
  ethanfly d0b418b71b fix: 移除对未定义函数 rebuildTaskIndex 的调用 před 2 týdny
  ethanfly 6e62fe2fba fix: 添加 author 字段到 package.json 并实现发布结果保存到数据库 před 2 týdny
  ethanfly 19a873a0d1 fix(#6066): 发布管理列表新增平台/渠道列,清晰展示任务目标平台 před 2 týdny
  ethanfly 47f3d5d9d5 fix(#6080): 头像上传更新后头像不更新,使用 ?? 替代 || 修复空字符串判断 před 2 týdny
  ethanfly 0f4a2082a5 fix(#6079): 修复无法创建定时任务,el-radio-button 属性改为 label před 2 týdny
  ethanfly 520de36fef fix: Bug 6082 - 修复账号统计数据SQL,使用MAX而非SUM计算粉丝总数 před 2 týdny
  ethanfly 4337c1f22d fix: Bug 6088 - 修复抖音作品同步分页逻辑,确保遍历所有分页 před 2 týdny

+ 1 - 0
client/package.json

@@ -1,6 +1,7 @@
 {
   "name": "@media-manager/client",
   "version": "1.0.0",
+  "author": "智媒通团队",
   "description": "智媒通桌面客户端",
   "main": "dist-electron/main.js",
   "scripts": {

+ 0 - 2
client/src/stores/taskQueue.ts

@@ -527,7 +527,6 @@ export const useTaskQueueStore = defineStore('taskQueue', () => {
   }
 
   // 清理已完成任务
-  // 清理已完成任务
   async function clearCompletedTasks() {
     try {
       await request.post('/api/tasks/clear-completed');
@@ -537,7 +536,6 @@ export const useTaskQueueStore = defineStore('taskQueue', () => {
     tasks.value = tasks.value.filter(t =>
       t.status === 'pending' || t.status === 'running'
     );
-    rebuildTaskIndex();
   }
   
   // 自动清理老旧任务(限制总任务数不超过 MAX_TASKS)

+ 43 - 393
client/src/views/Login/index.vue

@@ -1,35 +1,5 @@
 <template>
-  <div class="login-container">
-    <!-- 顶部可拖动标题栏 -->
-    <div class="drag-region">
-      <div class="window-controls">
-        <button class="window-btn minimize" @click="handleMinimize" title="最小化">
-          <svg viewBox="0 0 12 12"><rect y="5" width="12" height="2" fill="currentColor"/></svg>
-        </button>
-        <button class="window-btn maximize" @click="handleMaximize" :title="isMaximized ? '还原' : '最大化'">
-          <svg v-if="isMaximized" viewBox="0 0 12 12">
-            <rect x="1.5" y="3" width="7" height="7" stroke="currentColor" stroke-width="1.5" fill="none"/>
-            <path d="M3.5 3V1.5H11V9H9.5" stroke="currentColor" stroke-width="1.5" fill="none"/>
-          </svg>
-          <svg v-else viewBox="0 0 12 12">
-            <rect x="1" y="1" width="10" height="10" stroke="currentColor" stroke-width="1.5" fill="none"/>
-          </svg>
-        </button>
-        <button class="window-btn close" @click="handleClose" title="关闭">
-          <svg viewBox="0 0 12 12">
-            <path d="M1 1L11 11M1 11L11 1" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
-          </svg>
-        </button>
-      </div>
-    </div>
-
-    <!-- 背景装饰 -->
-    <div class="bg-decoration">
-      <div class="circle circle-1"></div>
-      <div class="circle circle-2"></div>
-      <div class="circle circle-3"></div>
-    </div>
-
+  <AuthPageLayout>
     <div class="login-card">
       <div class="login-header">
         <div class="logo">
@@ -38,46 +8,26 @@
         <h1>智媒通</h1>
         <p>登录您的账号以继续</p>
       </div>
-
-      <!-- 登录方式切换 -->
-      <div class="login-tabs">
-        <button
-          class="tab-btn"
-          :class="{ active: loginType === 'password' }"
-          @click="loginType = 'password'"
-        >
-          密码登录
-        </button>
-        <button
-          class="tab-btn"
-          :class="{ active: loginType === 'sms' }"
-          @click="loginType = 'sms'"
-        >
-          手机验证码登录
-        </button>
-      </div>
-
-      <!-- 密码登录表单 -->
+      
       <el-form
-        v-if="loginType === 'password'"
-        ref="passwordFormRef"
-        :model="passwordForm"
-        :rules="passwordRules"
+        ref="formRef"
+        :model="form"
+        :rules="rules"
         class="login-form"
         @submit.prevent="handleLogin"
       >
         <el-form-item prop="username">
           <el-input
-            v-model="passwordForm.username"
+            v-model="form.username"
             placeholder="用户名或邮箱"
             size="large"
             :prefix-icon="User"
           />
         </el-form-item>
-
+        
         <el-form-item prop="password">
           <el-input
-            v-model="passwordForm.password"
+            v-model="form.password"
             type="password"
             placeholder="密码"
             size="large"
@@ -85,64 +35,11 @@
             show-password
           />
         </el-form-item>
-
+        
         <el-form-item class="remember-row">
-          <el-checkbox v-model="passwordForm.rememberMe">记住登录状态</el-checkbox>
-          <el-button type="primary" link @click="$router.push('/forgot-password')">
-            忘记密码?
-          </el-button>
-        </el-form-item>
-
-        <el-form-item>
-          <el-button
-            type="primary"
-            size="large"
-            :loading="loading"
-            class="login-btn"
-            native-type="submit"
-          >
-            登录
-          </el-button>
+          <el-checkbox v-model="form.rememberMe">记住登录状态</el-checkbox>
         </el-form-item>
-      </el-form>
-
-      <!-- 手机验证码登录表单 -->
-      <el-form
-        v-else
-        ref="smsFormRef"
-        :model="smsForm"
-        :rules="smsRules"
-        class="login-form"
-        @submit.prevent="handleSmsLogin"
-      >
-        <el-form-item prop="phone">
-          <el-input
-            v-model="smsForm.phone"
-            placeholder="手机号"
-            size="large"
-            :prefix-icon="Phone"
-          />
-        </el-form-item>
-
-        <el-form-item prop="code">
-          <div class="sms-code-row">
-            <el-input
-              v-model="smsForm.code"
-              placeholder="验证码"
-              size="large"
-              :prefix-icon="Message"
-            />
-            <el-button
-              size="large"
-              :disabled="smsCooldown > 0"
-              @click="sendSmsCode"
-              class="sms-code-btn"
-            >
-              {{ smsCooldown > 0 ? `${smsCooldown}s 后重发` : '获取验证码' }}
-            </el-button>
-          </div>
-        </el-form-item>
-
+        
         <el-form-item>
           <el-button
             type="primary"
@@ -155,14 +52,14 @@
           </el-button>
         </el-form-item>
       </el-form>
-
+      
       <div class="login-footer">
         <span>还没有账号?</span>
         <el-button type="primary" link @click="$router.push('/register')">
           立即注册
         </el-button>
       </div>
-
+      
       <div class="server-info">
         <el-icon><Link /></el-icon>
         <span>{{ serverStore.currentServer?.name || '未配置服务器' }}</span>
@@ -171,121 +68,48 @@
         </el-button>
       </div>
     </div>
-  </div>
+  </AuthPageLayout>
 </template>
 
 <script setup lang="ts">
-import { ref, reactive, onMounted, onUnmounted } from 'vue';
+import { ref, reactive } from 'vue';
 import { useRouter } from 'vue-router';
-import { User, Lock, Link, VideoPlay, Phone, Message } from '@element-plus/icons-vue';
+import { User, Lock, Link, VideoPlay } from '@element-plus/icons-vue';
 import { ElMessage, type FormInstance, type FormRules } from 'element-plus';
 import { useAuthStore } from '@/stores/auth';
 import { useServerStore } from '@/stores/server';
+import AuthPageLayout from '@/components/AuthPageLayout.vue';
 
 const router = useRouter();
 const authStore = useAuthStore();
 const serverStore = useServerStore();
 
-const passwordFormRef = ref<FormInstance>();
-const smsFormRef = ref<FormInstance>();
+const formRef = ref<FormInstance>();
 const loading = ref(false);
-const isMaximized = ref(false);
-const loginType = ref<'password' | 'sms'>('password');
-const smsCooldown = ref(0);
-let cooldownTimer: ReturnType<typeof setInterval> | null = null;
-
-// 窗口控制
-function handleMinimize() {
-  window.electronAPI?.minimizeWindow?.();
-}
-
-function handleMaximize() {
-  window.electronAPI?.maximizeWindow?.();
-}
-
-function handleClose() {
-  window.electronAPI?.closeWindow?.();
-}
 
-// 监听窗口最大化状态
-onMounted(async () => {
-  if (window.electronAPI?.isMaximized) {
-    isMaximized.value = await window.electronAPI.isMaximized();
-  }
-  window.electronAPI?.onMaximizedChange?.((maximized: boolean) => {
-    isMaximized.value = maximized;
-  });
-});
-
-onUnmounted(() => {
-  if (cooldownTimer) {
-    clearInterval(cooldownTimer);
-  }
-});
-
-// 密码登录表单
-const passwordForm = reactive({
+const form = reactive({
   username: '',
   password: '',
   rememberMe: true,
 });
 
-const passwordRules: FormRules = {
+const rules: FormRules = {
   username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
   password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
 };
 
-// 手机验证码登录表单
-const smsForm = reactive({
-  phone: '',
-  code: '',
-});
-
-const smsRules: FormRules = {
-  phone: [
-    { required: true, message: '请输入手机号', trigger: 'blur' },
-    { pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确', trigger: 'blur' },
-  ],
-  code: [
-    { required: true, message: '请输入验证码', trigger: 'blur' },
-    { len: 6, message: '验证码为6位', trigger: 'blur' },
-  ],
-};
-
 async function handleLogin() {
-  if (!passwordFormRef.value) return;
-
-  const valid = await passwordFormRef.value.validate().catch(() => false);
-  if (!valid) return;
-
-  loading.value = true;
-  try {
-    await authStore.login({
-      username: passwordForm.username,
-      password: passwordForm.password,
-      rememberMe: passwordForm.rememberMe,
-    });
-    ElMessage.success('登录成功');
-    router.push('/');
-  } catch (error: any) {
-    // 错误已在拦截器中处理
-  } finally {
-    loading.value = false;
-  }
-}
-
-async function handleSmsLogin() {
-  if (!smsFormRef.value) return;
-
-  const valid = await smsFormRef.value.validate().catch(() => false);
+  if (!formRef.value) return;
+  
+  const valid = await formRef.value.validate().catch(() => false);
   if (!valid) return;
-
+  
   loading.value = true;
   try {
     await authStore.login({
-      username: smsForm.phone,
-      password: smsForm.code,
-      rememberMe: false,
+      username: form.username,
+      password: form.password,
+      rememberMe: form.rememberMe,
     });
     ElMessage.success('登录成功');
     router.push('/');
@@ -295,148 +119,24 @@ async function handleSmsLogin() {
     loading.value = false;
   }
 }
-
-async function sendSmsCode() {
-  if (!smsForm.phone) {
-    ElMessage.warning('请先输入手机号');
-    return;
-  }
-  if (!/^1[3-9]\d{9}$/.test(smsForm.phone)) {
-    ElMessage.warning('手机号格式不正确');
-    return;
-  }
-
-  try {
-    // TODO: 调用发送验证码 API
-    ElMessage.success('验证码已发送');
-    smsCooldown.value = 60;
-    cooldownTimer = setInterval(() => {
-      smsCooldown.value--;
-      if (smsCooldown.value <= 0 && cooldownTimer) {
-        clearInterval(cooldownTimer);
-        cooldownTimer = null;
-      }
-    }, 1000);
-  } catch {
-    ElMessage.error('发送验证码失败,请稍后重试');
-  }
-}
 </script>
 
 <style lang="scss" scoped>
 @use '@/styles/variables.scss' as *;
 
-.login-container {
-  min-height: 100vh;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  background: linear-gradient(135deg, #f5f7fa 0%, #e4e8eb 100%);
-  position: relative;
-  overflow: hidden;
-}
-
-// 顶部可拖动区域
-.drag-region {
-  position: fixed;
-  top: 0;
-  left: 0;
-  right: 0;
-  height: 32px;
-  -webkit-app-region: drag;
-  z-index: 999;
-}
-
-// 窗口控制按钮
-.window-controls {
-  position: absolute;
-  top: 0;
-  right: 0;
-  display: flex;
-  -webkit-app-region: no-drag;
-
-  .window-btn {
-    width: 46px;
-    height: 32px;
-    border: none;
-    background: transparent;
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    cursor: pointer;
-    transition: background 0.15s;
-    color: $text-secondary;
-
-    svg {
-      width: 12px;
-      height: 12px;
-    }
-
-    &:hover {
-      background: rgba(0, 0, 0, 0.06);
-    }
-
-    &.close:hover {
-      background: #e81123;
-      color: #fff;
-    }
-  }
-}
-
-// 背景装饰
-.bg-decoration {
-  position: absolute;
-  inset: 0;
-  overflow: hidden;
-  pointer-events: none;
-
-  .circle {
-    position: absolute;
-    border-radius: 50%;
-    opacity: 0.5;
-  }
-
-  .circle-1 {
-    width: 400px;
-    height: 400px;
-    background: linear-gradient(135deg, rgba(79, 140, 255, 0.2), rgba(79, 140, 255, 0.05));
-    top: -100px;
-    right: -100px;
-  }
-
-  .circle-2 {
-    width: 300px;
-    height: 300px;
-    background: linear-gradient(135deg, rgba(250, 112, 154, 0.15), rgba(254, 225, 64, 0.1));
-    bottom: -80px;
-    left: -80px;
-  }
-
-  .circle-3 {
-    width: 200px;
-    height: 200px;
-    background: linear-gradient(135deg, rgba(102, 126, 234, 0.15), rgba(118, 75, 162, 0.1));
-    top: 50%;
-    left: 10%;
-    transform: translateY(-50%);
-  }
-}
-
 .login-card {
   width: 420px;
   padding: 48px 40px;
   background: #fff;
   border-radius: $radius-xl;
   box-shadow: $shadow-lg;
-  position: relative;
-  z-index: 1;
   border: 1px solid rgba(255, 255, 255, 0.8);
 }
 
 .login-header {
   text-align: center;
-  margin-bottom: 28px;
-
+  margin-bottom: 36px;
+  
   .logo {
     width: 64px;
     height: 64px;
@@ -447,20 +147,20 @@ async function sendSmsCode() {
     align-items: center;
     justify-content: center;
     box-shadow: 0 8px 24px rgba(79, 172, 254, 0.3);
-
+    
     .el-icon {
       font-size: 32px;
       color: #fff;
     }
   }
-
+  
   h1 {
     margin: 0 0 12px;
     font-size: 24px;
     font-weight: 700;
     color: $text-primary;
   }
-
+  
   p {
     margin: 0;
     color: $text-secondary;
@@ -468,72 +168,38 @@ async function sendSmsCode() {
   }
 }
 
-// 登录方式切换
-.login-tabs {
-  display: flex;
-  gap: 0;
-  margin-bottom: 24px;
-  border-bottom: 1px solid $border-light;
-
-  .tab-btn {
-    flex: 1;
-    padding: 10px 0;
-    border: none;
-    background: transparent;
-    font-size: 14px;
-    color: $text-secondary;
-    cursor: pointer;
-    transition: all 0.2s;
-    border-bottom: 2px solid transparent;
-    margin-bottom: -1px;
-
-    &:hover {
-      color: $text-primary;
-    }
-
-    &.active {
-      color: $primary-color;
-      border-bottom-color: $primary-color;
-      font-weight: 600;
-    }
-  }
-}
-
 .login-form {
   :deep(.el-input__wrapper) {
     border-radius: $radius-base;
     box-shadow: 0 0 0 1px $border-light inset;
     transition: all 0.2s;
-
+    
     &:hover {
       box-shadow: 0 0 0 1px $primary-color inset;
     }
-
+    
     &.is-focus {
       box-shadow: 0 0 0 1px $primary-color inset, 0 0 0 3px rgba($primary-color, 0.1);
     }
   }
-
+  
   :deep(.el-input__inner) {
     height: 44px;
     font-size: 15px;
   }
-
+  
   :deep(.el-input__prefix) {
     color: $text-secondary;
   }
-
+  
   .remember-row {
     margin-bottom: 24px;
-    display: flex;
-    align-items: center;
-    justify-content: space-between;
-
+    
     :deep(.el-checkbox__label) {
       color: $text-secondary;
     }
   }
-
+  
   .login-btn {
     width: 100%;
     height: 48px;
@@ -544,32 +210,16 @@ async function sendSmsCode() {
     border: none;
     box-shadow: 0 4px 16px rgba($primary-color, 0.3);
     transition: all 0.3s;
-
+    
     &:hover {
       transform: translateY(-1px);
       box-shadow: 0 6px 20px rgba($primary-color, 0.4);
     }
-
+    
     &:active {
       transform: translateY(0);
     }
   }
-
-  .sms-code-row {
-    display: flex;
-    gap: 12px;
-    width: 100%;
-
-    .el-input {
-      flex: 1;
-    }
-
-    .sms-code-btn {
-      width: 120px;
-      flex-shrink: 0;
-      font-size: 13px;
-    }
-  }
 }
 
 .login-footer {
@@ -577,7 +227,7 @@ async function sendSmsCode() {
   color: $text-secondary;
   font-size: 14px;
   margin-top: 8px;
-
+  
   .el-button {
     font-weight: 500;
   }
@@ -593,7 +243,7 @@ async function sendSmsCode() {
   gap: 8px;
   color: $text-secondary;
   font-size: 13px;
-
+  
   .el-icon {
     color: $primary-color;
   }

+ 4 - 155
client/src/views/Register/index.vue

@@ -1,35 +1,5 @@
 <template>
-  <div class="register-container">
-    <!-- 顶部可拖动标题栏 -->
-    <div class="drag-region">
-      <div class="window-controls">
-        <button class="window-btn minimize" @click="handleMinimize" title="最小化">
-          <svg viewBox="0 0 12 12"><rect y="5" width="12" height="2" fill="currentColor"/></svg>
-        </button>
-        <button class="window-btn maximize" @click="handleMaximize" :title="isMaximized ? '还原' : '最大化'">
-          <svg v-if="isMaximized" viewBox="0 0 12 12">
-            <rect x="1.5" y="3" width="7" height="7" stroke="currentColor" stroke-width="1.5" fill="none"/>
-            <path d="M3.5 3V1.5H11V9H9.5" stroke="currentColor" stroke-width="1.5" fill="none"/>
-          </svg>
-          <svg v-else viewBox="0 0 12 12">
-            <rect x="1" y="1" width="10" height="10" stroke="currentColor" stroke-width="1.5" fill="none"/>
-          </svg>
-        </button>
-        <button class="window-btn close" @click="handleClose" title="关闭">
-          <svg viewBox="0 0 12 12">
-            <path d="M1 1L11 11M1 11L11 1" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
-          </svg>
-        </button>
-      </div>
-    </div>
-    
-    <!-- 背景装饰 -->
-    <div class="bg-decoration">
-      <div class="circle circle-1"></div>
-      <div class="circle circle-2"></div>
-      <div class="circle circle-3"></div>
-    </div>
-    
+  <AuthPageLayout>
     <div class="register-card">
       <div class="register-header">
         <div class="logo">
@@ -106,45 +76,22 @@
         </el-button>
       </div>
     </div>
-  </div>
+  </AuthPageLayout>
 </template>
 
 <script setup lang="ts">
-import { ref, reactive, onMounted } from 'vue';
+import { ref, reactive } from 'vue';
 import { useRouter } from 'vue-router';
 import { User, Lock, Message, UserFilled } from '@element-plus/icons-vue';
 import { ElMessage, type FormInstance, type FormRules } from 'element-plus';
 import { useAuthStore } from '@/stores/auth';
+import AuthPageLayout from '@/components/AuthPageLayout.vue';
 
 const router = useRouter();
 const authStore = useAuthStore();
 
 const formRef = ref<FormInstance>();
 const loading = ref(false);
-const isMaximized = ref(false);
-
-// 窗口控制
-function handleMinimize() {
-  window.electronAPI?.minimizeWindow?.();
-}
-
-function handleMaximize() {
-  window.electronAPI?.maximizeWindow?.();
-}
-
-function handleClose() {
-  window.electronAPI?.closeWindow?.();
-}
-
-// 监听窗口最大化状态
-onMounted(async () => {
-  if (window.electronAPI?.isMaximized) {
-    isMaximized.value = await window.electronAPI.isMaximized();
-  }
-  window.electronAPI?.onMaximizedChange?.((maximized: boolean) => {
-    isMaximized.value = maximized;
-  });
-});
 
 const form = reactive({
   username: '',
@@ -205,110 +152,12 @@ async function handleRegister() {
 <style lang="scss" scoped>
 @use '@/styles/variables.scss' as *;
 
-.register-container {
-  min-height: 100vh;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  background: linear-gradient(135deg, #f5f7fa 0%, #e4e8eb 100%);
-  position: relative;
-  overflow: hidden;
-}
-
-// 顶部可拖动区域
-.drag-region {
-  position: fixed;
-  top: 0;
-  left: 0;
-  right: 0;
-  height: 32px;
-  -webkit-app-region: drag;
-  z-index: 999;
-}
-
-// 窗口控制按钮
-.window-controls {
-  position: absolute;
-  top: 0;
-  right: 0;
-  display: flex;
-  -webkit-app-region: no-drag;
-  
-  .window-btn {
-    width: 46px;
-    height: 32px;
-    border: none;
-    background: transparent;
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    cursor: pointer;
-    transition: background 0.15s;
-    color: $text-secondary;
-    
-    svg {
-      width: 12px;
-      height: 12px;
-    }
-    
-    &:hover {
-      background: rgba(0, 0, 0, 0.06);
-    }
-    
-    &.close:hover {
-      background: #e81123;
-      color: #fff;
-    }
-  }
-}
-
-// 背景装饰
-.bg-decoration {
-  position: absolute;
-  inset: 0;
-  overflow: hidden;
-  pointer-events: none;
-  
-  .circle {
-    position: absolute;
-    border-radius: 50%;
-    opacity: 0.5;
-  }
-  
-  .circle-1 {
-    width: 400px;
-    height: 400px;
-    background: linear-gradient(135deg, rgba(79, 140, 255, 0.2), rgba(79, 140, 255, 0.05));
-    top: -100px;
-    right: -100px;
-  }
-  
-  .circle-2 {
-    width: 300px;
-    height: 300px;
-    background: linear-gradient(135deg, rgba(250, 112, 154, 0.15), rgba(254, 225, 64, 0.1));
-    bottom: -80px;
-    left: -80px;
-  }
-  
-  .circle-3 {
-    width: 200px;
-    height: 200px;
-    background: linear-gradient(135deg, rgba(102, 126, 234, 0.15), rgba(118, 75, 162, 0.1));
-    top: 50%;
-    left: 10%;
-    transform: translateY(-50%);
-  }
-}
-
 .register-card {
   width: 420px;
   padding: 48px 40px;
   background: #fff;
   border-radius: $radius-xl;
   box-shadow: $shadow-lg;
-  position: relative;
-  z-index: 1;
   border: 1px solid rgba(255, 255, 255, 0.8);
 }
 

+ 1 - 0
package.json

@@ -2,6 +2,7 @@
   "name": "multi-platform-media-manage",
   "version": "1.0.0",
   "description": "智媒通 - 支持视频发布、评论管理、数据分析",
+  "author": "智媒通团队",
   "private": true,
   "type": "module",
   "pnpm": {

+ 3 - 3
server/src/services/AnalyticsService.ts

@@ -25,7 +25,7 @@ export class AnalyticsService {
 
     const queryBuilder = this.analyticsRepository
       .createQueryBuilder('data')
-      .select('SUM(data.fansCount)', 'totalFans')
+      .select('MAX(data.fansCount)', 'totalFans')
       .addSelect('SUM(data.fansIncrease)', 'totalFansIncrease')
       .addSelect('SUM(data.viewsCount)', 'totalViews')
       .addSelect('SUM(data.likesCount)', 'totalLikes')
@@ -63,7 +63,7 @@ export class AnalyticsService {
     const queryBuilder = this.analyticsRepository
       .createQueryBuilder('data')
       .select('data.date', 'date')
-      .addSelect('SUM(data.fansCount)', 'fansCount')
+      .addSelect('MAX(data.fansCount)', 'fansCount')
       .addSelect('SUM(data.viewsCount)', 'viewsCount')
       .addSelect('SUM(data.likesCount)', 'likesCount')
       .addSelect('SUM(data.commentsCount)', 'commentsCount')
@@ -108,7 +108,7 @@ export class AnalyticsService {
       .createQueryBuilder('data')
       .select('account.platform', 'platform')
       .addSelect('account.accountName', 'accountName')
-      .addSelect('SUM(data.fansCount)', 'fansCount')
+      .addSelect('MAX(data.fansCount)', 'fansCount')
       .addSelect('SUM(data.fansIncrease)', 'fansIncrease')
       .addSelect('SUM(data.viewsCount)', 'viewsCount')
       .addSelect('SUM(data.likesCount)', 'likesCount')

+ 6 - 9
server/src/services/HeadlessBrowserService.ts

@@ -971,22 +971,19 @@ class HeadlessBrowserService {
         const hasNextCursor = next !== undefined && next !== null && next !== '' && next !== -1 && next !== '-1';
 
         if (hasNextCursor) {
-          const key = String(next);
+          const key = String(next).trim();
           if (seenCursors.has(key)) break;
           seenCursors.add(key);
           cursor = next;
         } else {
-          if (platform === 'douyin') break;
           cursor = (typeof cursor === 'number' ? cursor + 1 : pageIndex + 1);
         }
 
-        // 抖音:仅当无下一页游标或本页 0 条时停止
-        if (platform === 'douyin') {
-          if (!hasNextCursor || pageWorks.length === 0) break;
-        } else {
-          const expectedMore = declaredTotal && declaredTotal > 0 ? allWorks.length < declaredTotal : !!result.has_more;
-          if (!expectedMore || pageWorks.length === 0 || newCount === 0) break;
-        }
+        // 仅当无下一页游标或本页 0 条时停止
+        if (!hasNextCursor || pageWorks.length === 0) break;
+
+        // 额外检查:若已达声明总量则停止
+        if (declaredTotal && declaredTotal > 0 && allWorks.length >= declaredTotal) break;
       } else {
         if (!result.has_more || pageWorks.length === 0 || newCount === 0) break;
       }