From f6f5a8af7e6c5359e13d7c75172adb0bc6cac555 Mon Sep 17 00:00:00 2001
From: hyb <kk_huangyangbo@163.com>
Date: Fri, 23 Jan 2026 01:53:11 +0000
Subject: [PATCH] feat: 添加用户登录功能、添加注册功能、注册审批功能,参考:注册审批功能测试指南.md、驱动代码模块增加插入其他项目代码功能,驱动代码模块增加插入其他项目代码功能,优化测试报告模块前端布局和美化 - 实现超级用户管理系统用户,可进行增删改查,其他用户只可查看本人的数据,不可进行增删改 - 将Django后台管理中的用户配置相关功能迁移到系统中进行适配、可配置禁用、是否管理员、是否超级用户 - 增加用户管理的分组字段、可直接进行分组操作,实现用户管理即可通过分组配置实现各项目的访问权限 - 实现登录页面注册 - 实现管理员首页点击注册用户审批可实现通过不通过 - 实现未审核用户的登录提示与审核未通过的用户登录提示 - 实现一键插入其他本人可访问项目的脚本代码

---
 测试组/Test_platform/Interface_automation/frontend/src/pages/project/ProjectList.vue |  489 ++++++++++++++++++++++++++++++++++++++++-------------
 1 files changed, 365 insertions(+), 124 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..7c5a597 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"
@@ -24,6 +24,13 @@
                         >项目看板</el-button
                     >
                     <el-button
+                        type="warning"
+                        size="small"
+                        icon="el-icon-user"
+                        @click="handleUserManagement"
+                        >用户管理</el-button
+                    >
+                    <el-button
                         type="info"
                         size="small"
                         icon="el-icon-arrow-left"
@@ -126,6 +133,24 @@
                                     v-model.trim="projectForm.jira_project_key"
                                     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>
 
@@ -234,6 +259,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 +297,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
@@ -331,6 +381,18 @@
             :visible.sync="dashBoardVisible"
         >
             <ProjectDashBoard></ProjectDashBoard>
+        </el-drawer>
+
+        <el-drawer
+            title="用户管理"
+            :visible.sync="userManagementVisible"
+            direction="rtl"
+            size="80%"
+            :before-close="handleUserManagementClose"
+            :modal="false"
+            class="user-management-drawer-wrapper"
+        >
+            <user-management-drawer v-if="userManagementVisible"></user-management-drawer>
         </el-drawer>
         <el-container>
             <el-main style="padding: 0; margin-left: 10px">
@@ -507,9 +569,10 @@
 
 <script>
 import ProjectDashBoard from "@/pages/project/ProjectDashBoard.vue";
+import UserManagementDrawer from "@/pages/user/UserManagementDrawer.vue";
 export default {
     name: "ProjectList",
-    components: { ProjectDashBoard },
+    components: { ProjectDashBoard, UserManagementDrawer },
     data() {
         return {
             recentEnabledProjects: [], // 存储有启动任务的项目
@@ -520,6 +583,7 @@
             task_count: 0,
             dialogVisible: false,
             dashBoardVisible: false,
+            userManagementVisible: false,
             editVisible: false,
             hoveringSuccess: false,
             hoveringError: false,
@@ -534,9 +598,11 @@
                 yapi_base_url: "",
                 yapi_openapi_token: "",
                 jira_bearer_token: "",
-                jira_project_key: ""
+                jira_project_key: "",
+                groups: []
             },
             responsibleOptions: [],
+            groupOptions: [],
             rules: {
                 name: [
                     {
@@ -630,6 +696,12 @@
             this.dialogVisible = true;
             this.resetProjectForm();
         },
+        handleUserManagement() {
+            this.userManagementVisible = true;
+        },
+        handleUserManagementClose() {
+            this.userManagementVisible = false;
+        },
         handleEdit(index, row) {
             this.editVisible = true;
             this.projectForm.name = row["name"];
@@ -640,6 +712,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 +879,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 +899,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 +922,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