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