From 536b18a7c5d53d72d78ffab579ff24ac9146d5ab Mon Sep 17 00:00:00 2001
From: hyb <kk_huangyangbo@163.com>
Date: Tue, 20 Jan 2026 09:39:42 +0000
Subject: [PATCH] 接口自动化平台优化登录页面和首页; 项目看板增加多个统计数据和详细数据信息,看板布局和样式优化

---
 测试组/Test_platform/Interface_automation/frontend/src/pages/project/ProjectList.vue |  460 ++++++++++++++++++++++++++++++++++++++++++---------------
 1 files changed, 337 insertions(+), 123 deletions(-)

diff --git "a/\346\265\213\350\257\225\347\273\204/Test_platform/Interface_automation/frontend/src/pages/project/ProjectList.vue" "b/\346\265\213\350\257\225\347\273\204/Test_platform/Interface_automation/frontend/src/pages/project/ProjectList.vue"
index 3c5786f..62d34ea 100644
--- "a/\346\265\213\350\257\225\347\273\204/Test_platform/Interface_automation/frontend/src/pages/project/ProjectList.vue"
+++ "b/\346\265\213\350\257\225\347\273\204/Test_platform/Interface_automation/frontend/src/pages/project/ProjectList.vue"
@@ -127,6 +127,24 @@
                                     clearable
                                 ></el-input>
                             </el-form-item>
+
+                            <el-form-item label="项目分组" prop="groups">
+                                <el-select
+                                    v-model="projectForm.groups"
+                                    placeholder="请选择项目分组(可多选)"
+                                    filterable
+                                    clearable
+                                    multiple
+                                    :style="{ width: '100%' }"
+                                >
+                                    <el-option
+                                        v-for="(item, index) in groupOptions"
+                                        :key="index"
+                                        :label="item.name"
+                                        :value="item.id"
+                                    ></el-option>
+                                </el-select>
+                            </el-form-item>
                         </el-form>
 
                         <span
@@ -234,6 +252,24 @@
                                 >
                                 </el-input>
                             </el-form-item>
+
+                            <el-form-item label="项目分组" prop="groups">
+                                <el-select
+                                    v-model="projectForm.groups"
+                                    placeholder="请选择项目分组(可多选)"
+                                    filterable
+                                    clearable
+                                    multiple
+                                    :style="{ width: '100%' }"
+                                >
+                                    <el-option
+                                        v-for="(item, index) in groupOptions"
+                                        :key="index"
+                                        :label="item.name"
+                                        :value="item.id"
+                                    ></el-option>
+                                </el-select>
+                            </el-form-item>
                         </el-form>
                         <span
                             slot="footer"
@@ -254,73 +290,80 @@
             </div>
         </el-header>
 
-        <div style="display: flex; align-items: center; margin: 10px">
-            <!-- 新增的启动任务提示牌 -->
-            <el-alert
-                v-if="recentEnabledProjects.length > 0"
-                title="正在启动的定时任务数"
-                type="success"
-                :closable="false"
-                style="flex: 1; margin-right: 10px; border: 1px solid #d9f7be; border-radius: 8px; background: linear-gradient(135deg, #f6ffed, #e6ffd3);">
-                <div class="scroll-wrapper" style="min-width: 180%">
+        <!-- 现代化警示牌区域 -->
+        <div class="dashboard-alerts">
+            <!-- 运行状态监控牌 -->
+            <div v-if="recentEnabledProjects.length > 0" class="alert-card success-card">
+                <div class="alert-header">
+                    <div class="alert-icon">
+                        <i class="el-icon-success"></i>
+                    </div>
+                    <div class="alert-title">
+                        <span class="title-text">运行状态监控</span>
+                        <span class="title-sub">正在执行的定时任务</span>
+                    </div>
+                    <div class="alert-badge">{{ recentEnabledProjects.length }}</div>
+                </div>
+                <div class="alert-content">
                     <div class="scroll-container">
-                        <div class="scroll-content" :style="{ animationDuration: animationDuration, animationPlayState: hoveringSuccess ? 'paused' : 'running' }"
-                        @mouseenter="hoveringSuccess = true"
-                        @mouseleave="hoveringSuccess = false">
-                            <div v-for="(item, index) in recentEnabledProjects"
-                                :key="'enabled'+index"
-                                class="scroll-item"
-                                @click="$router.push(`/lunarlink/tasks/${item.id}`)"
-                                style="cursor: pointer;">
-                                <el-icon name="el-icon-success"
-                                        style="color: #52c41a; margin-right: 8px;"></el-icon>
-                                <span class="alert-text">
-                                    【{{ item.name }}】启动任务数:
-                                    <strong class="highlight">{{ item.enabled_task_count }}</strong>
-                                </span>
-                                <div class="separator" v-if="index !== recentEnabledProjects.length -1">
-                                    <el-icon name="el-icon-caret-right"
-                                            style="color: #b7eb8f; margin: 0 20px;"></el-icon>
+                        <div class="scroll-content" 
+                             :style="{ animationDuration: animationDuration, animationPlayState: hoveringSuccess ? 'paused' : 'running' }"
+                             @mouseenter="hoveringSuccess = true"
+                             @mouseleave="hoveringSuccess = false">
+                            <div v-for="(item, index) in recentEnabledProjects" 
+                                 :key="'enabled'+index"
+                                 class="alert-item"
+                                 @click="$router.push(`/lunarlink/tasks/${item.id}`)">
+                                <div class="item-content">
+                                    <div class="project-name">{{ item.name }}</div>
+                                    <div class="task-count">
+                                        <span class="count-number">{{ item.enabled_task_count }}</span>
+                                        <span class="count-label">个任务运行中</span>
+                                    </div>
                                 </div>
+                                <div class="item-divider" v-if="index !== recentEnabledProjects.length - 1"></div>
                             </div>
                         </div>
                     </div>
                 </div>
-            </el-alert>
+            </div>
 
-            <!-- 原有失败巡检预警牌 -->
-            <el-alert
-                v-if="recentFailedProjects.length > 0"
-                title="近24小时失败巡检预警"
-                type="error"
-                :closable="false"
-                style="flex: 1; border: 1px solid #ffd3d4; border-radius: 8px; background: linear-gradient(135deg, #fff6f6, #ffecec);">
-                <div class="scroll-wrapper" style="min-width: 210%">
+            <!-- 风险预警监控牌 -->
+            <div v-if="recentFailedProjects.length > 0" class="alert-card error-card">
+                <div class="alert-header">
+                    <div class="alert-icon">
+                        <i class="el-icon-warning"></i>
+                    </div>
+                    <div class="alert-title">
+                        <span class="title-text">风险预警监控</span>
+                        <span class="title-sub">近24小时失败巡检</span>
+                    </div>
+                    <div class="alert-badge">{{ recentFailedProjects.length }}</div>
+                </div>
+                <div class="alert-content">
                     <div class="scroll-container">
-                        <div class="scroll-content" :style="{ animationDuration: '25s', animationPlayState: hoveringError ? 'paused' : 'running' }"
-                        @mouseenter="hoveringError = true"
-                        @mouseleave="hoveringError = false">
-                            <div v-for="(item, index) in recentFailedProjects"
-                                :key="index"
-                                class="scroll-item"
-                                @click="$router.push(`/lunarlink/reports/${item.id}`)"
-                                style="cursor: pointer;">
-                                <el-icon name="el-icon-warning"
-                                        style="color: #ff4d4f; margin-right: 8px;"></el-icon>
-                                <span class="alert-text">
-                                    【{{ item.name }}】在最近24小时中有
-                                    <strong class="highlight">{{ item.recent_failed_count }}</strong>
-                                    条失败报告,请及时处理!【最新一条失败巡检报告产生时间为:{{ formatDateTime(item.report_time) }}】
-                                </span>
-                                <div class="separator" v-if="index !== recentFailedProjects.length -1">
-                                    <el-icon name="el-icon-caret-right"
-                                            style="color: #ffa39e; margin: 0 20px;"></el-icon>
+                        <div class="scroll-content" 
+                             :style="{ animationDuration: '30s', animationPlayState: hoveringError ? 'paused' : 'running' }"
+                             @mouseenter="hoveringError = true"
+                             @mouseleave="hoveringError = false">
+                            <div v-for="(item, index) in recentFailedProjects" 
+                                 :key="index"
+                                 class="alert-item"
+                                 @click="$router.push(`/lunarlink/reports/${item.id}`)">
+                                <div class="item-content">
+                                    <div class="project-name">{{ item.name }}</div>
+                                    <div class="failure-info">
+                                        <span class="failure-count">{{ item.recent_failed_count }}</span>
+                                        <span class="failure-label">次失败</span>
+                                        <span class="failure-time">{{ formatDateTime(item.report_time) }}</span>
+                                    </div>
                                 </div>
+                                <div class="item-divider" v-if="index !== recentFailedProjects.length - 1"></div>
                             </div>
                         </div>
                     </div>
                 </div>
-            </el-alert>
+            </div>
         </div>
 
         <el-drawer
@@ -534,9 +577,11 @@
                 yapi_base_url: "",
                 yapi_openapi_token: "",
                 jira_bearer_token: "",
-                jira_project_key: ""
+                jira_project_key: "",
+                groups: []
             },
             responsibleOptions: [],
+            groupOptions: [],
             rules: {
                 name: [
                     {
@@ -640,6 +685,7 @@
             this.projectForm.yapi_openapi_token = row["yapi_openapi_token"];
             this.projectForm.jira_project_key = row["jira_project_key"];
             this.projectForm.jira_bearer_token = row["jira_bearer_token"];
+            this.projectForm.groups = row["groups"] || [];
         },
         handleDelete(index, row) {
             this.$confirm("此操作将永久删除该项目, 是否继续?", "提示", {
@@ -806,13 +852,13 @@
                 this.loading = false;
                 this.$nextTick(() => {
                     if (this.recentFailedProjects.length > 0 || this.recentEnabledProjects.length > 0) {
-                        const contentWidth = Math.max(
-                            this.recentFailedProjects.length * 400,
-                            this.recentEnabledProjects.length * 300
-                        );
+                        // 根据项目数量和内容长度计算动画持续时间
+                        const enabledWidth = this.recentEnabledProjects.length * 280;
+                        const failedWidth = this.recentFailedProjects.length * 350;
+                        const contentWidth = Math.max(enabledWidth, failedWidth);
                         const container = document.querySelector('.scroll-container');
-                        const viewportWidth = container ? container.offsetWidth : 1200;
-                        this.animationDuration = `${Math.max(10, (contentWidth / viewportWidth) * 20)}s`;
+                        const viewportWidth = container ? container.offsetWidth : 600;
+                        this.animationDuration = `${Math.max(15, (contentWidth / viewportWidth) * 25)}s`;
                     }
                 });
             }
@@ -826,6 +872,7 @@
             this.projectForm.yapi_openapi_token = "";
             this.projectForm.jira_bearer_token = "";
             this.projectForm.jira_project_key = "";
+            this.projectForm.groups = [];
         },
         closeEditDialog(formName) {
             this.editVisible = false;
@@ -848,104 +895,271 @@
                     });
                 }
             });
+        },
+        getGroupList() {
+            this.$api.getGroupList().then(resp => {
+                this.groupOptions = resp;
+            });
         }
     },
     created() {
         this.getProjectList();
         this.getUserList();
+        this.getGroupList();
     }
 };
 </script>
 
 <style scoped>
-/* 公共标题样式 */
-:deep(.el-alert__title) {
-    font-size: 16px;
-    font-weight: 600;
-    padding-left: 8px;
-    border-left: 3px solid;
-    line-height: 1.5;
+/* 现代化警示牌样式 */
+.dashboard-alerts {
+    display: flex;
+    gap: 16px;
+    margin: 16px;
+    flex-wrap: wrap;
 }
 
-/* 失败预警样式 */
-:deep(.el-alert[type="error"]) .el-alert__title {
-    color: #ff4d4f !important;
-    border-left-color: #ff4d4f;
-}
-:deep(.el-alert[type="error"]) .scroll-item {
-    background: rgba(255, 77, 79, 0.08);
-}
-:deep(.el-alert[type="error"]) .alert-text {
-    color: #ff4d4f;
-}
-:deep(.el-alert[type="error"]) .separator i {
-    color: #ffa39e !important;
+.alert-card {
+    flex: 1;
+    min-width: 300px;
+    background: #ffffff;
+    border-radius: 12px;
+    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
+    border: 1px solid #f0f0f0;
+    overflow: hidden;
+    transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
 }
 
-/* 成功预警样式 */
-:deep(.el-alert[type="success"]) .el-alert__title {
-    color: #52c41a !important;
-    border-left-color: #52c41a;
+.alert-card:hover {
+    transform: translateY(-2px);
+    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
 }
-:deep(.el-alert[type="success"]) .scroll-item {
-    background: rgba(82, 196, 26, 0.08);
+
+/* 成功状态卡片 */
+.success-card {
+    border-left: 4px solid #52c41a;
 }
-:deep(.el-alert[type="success"]) .alert-text {
+
+.success-card .alert-header {
+    background: linear-gradient(135deg, #f6ffed 0%, #e6ffd3 100%);
+}
+
+.success-card .alert-icon {
+    background: linear-gradient(135deg, #52c41a 0%, #73d13d 100%);
+}
+
+.success-card .alert-badge {
+    background: #52c41a;
+}
+
+.success-card .count-number {
     color: #52c41a;
 }
-:deep(.el-alert[type="success"]) .separator i {
-    color: #b7eb8f !important;
+
+/* 错误状态卡片 */
+.error-card {
+    border-left: 4px solid #ff4d4f;
 }
 
-/* 公共滚动样式 */
-.scroll-wrapper {
-    display: inline-block;
-    min-width: 100%;
-    margin: 0 -20px;
-    overflow: hidden;
-    position: relative;
-    padding: 8px 0;
+.error-card .alert-header {
+    background: linear-gradient(135deg, #fff6f6 0%, #ffecec 100%);
 }
+
+.error-card .alert-icon {
+    background: linear-gradient(135deg, #ff4d4f 0%, #ff7a7a 100%);
+}
+
+.error-card .alert-badge {
+    background: #ff4d4f;
+}
+
+.error-card .failure-count {
+    color: #ff4d4f;
+}
+
+/* 卡片头部样式 */
+.alert-header {
+    display: flex;
+    align-items: center;
+    padding: 16px 20px;
+    border-bottom: 1px solid #f5f5f5;
+}
+
+.alert-icon {
+    width: 40px;
+    height: 40px;
+    border-radius: 10px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    margin-right: 12px;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
+}
+
+.alert-icon i {
+    font-size: 20px;
+    color: white;
+}
+
+.alert-title {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+}
+
+.title-text {
+    font-size: 16px;
+    font-weight: 600;
+    color: #262626;
+    line-height: 1.4;
+}
+
+.title-sub {
+    font-size: 12px;
+    color: #8c8c8c;
+    margin-top: 2px;
+}
+
+.alert-badge {
+    background: #1890ff;
+    color: white;
+    padding: 4px 8px;
+    border-radius: 12px;
+    font-size: 12px;
+    font-weight: 600;
+    min-width: 24px;
+    text-align: center;
+}
+
+/* 内容区域样式 */
+.alert-content {
+    padding: 0;
+    height: 80px;
+    overflow: hidden;
+}
+
 .scroll-container {
+    height: 100%;
     overflow: hidden;
     position: relative;
-    width: 100%;
 }
+
 .scroll-content {
-    display: inline-block;
-    white-space: nowrap;
-    animation: project-list-scroll linear infinite;
+    display: flex;
+    align-items: center;
+    height: 100%;
+    animation: modern-scroll linear infinite;
     animation-play-state: running;
+    white-space: nowrap;
 }
-.scroll-item {
-    display: inline-flex;
+
+.alert-item {
+    display: flex;
     align-items: center;
-    padding: 6px 12px;
-    border-radius: 20px;
-    margin: 0 10px;
-    transition: all 0.3s;
-    vertical-align: middle;
+    padding: 0 24px;
+    height: 100%;
+    cursor: pointer;
+    transition: background-color 0.2s;
 }
-.alert-text {
+
+.alert-item:hover {
+    background: rgba(0, 0, 0, 0.02);
+}
+
+.item-content {
+    display: flex;
+    align-items: center;
+    gap: 16px;
+}
+
+.project-name {
     font-size: 14px;
-    font-family: 'Microsoft YaHei', sans-serif;
+    font-weight: 500;
+    color: #262626;
+    min-width: 120px;
 }
-.highlight {
-    font-weight: 700;
-    text-shadow: 0 1px 1px rgba(0,0,0,0.1);
-}
-.separator {
-    display: inline-flex;
+
+.task-count {
+    display: flex;
     align-items: center;
+    gap: 4px;
 }
-@keyframes project-list-scroll {
-    0% { transform: translateX(180%); }
-    100% { transform: translateX(-100%); }
+
+.count-number {
+    font-size: 20px;
+    font-weight: 700;
+    line-height: 1;
 }
-.scroll-item:hover {
-    transform: translateY(-2px);
-    box-shadow: 0 3px 6px rgba(0,0,0,0.15);
+
+.count-label {
+    font-size: 12px;
+    color: #8c8c8c;
 }
+
+.failure-info {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+}
+
+.failure-count {
+    font-size: 20px;
+    font-weight: 700;
+    line-height: 1;
+}
+
+.failure-label {
+    font-size: 12px;
+    color: #8c8c8c;
+}
+
+.failure-time {
+    font-size: 11px;
+    color: #bfbfbf;
+    background: #f5f5f5;
+    padding: 2px 6px;
+    border-radius: 4px;
+}
+
+.item-divider {
+    width: 1px;
+    height: 24px;
+    background: #f0f0f0;
+    margin: 0 0 0 24px;
+}
+
+/* 滚动动画 */
+@keyframes modern-scroll {
+    0% {
+        transform: translateX(100%);
+    }
+    100% {
+        transform: translateX(-100%);
+    }
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+    .dashboard-alerts {
+        flex-direction: column;
+    }
+    
+    .alert-card {
+        min-width: auto;
+    }
+    
+    .item-content {
+        flex-direction: column;
+        gap: 8px;
+        text-align: center;
+    }
+    
+    .project-name {
+        min-width: auto;
+    }
+}
+
+/* 表格相关样式保持不变 */
 .metric-card {
     padding: 12px;
     border-radius: 8px;

--
Gitblit v1.9.1