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/reports/ReportList.vue | 1010 +++++++++++++++++++++++++++++++++++++++++------------------
1 files changed, 700 insertions(+), 310 deletions(-)
diff --git "a/\346\265\213\350\257\225\347\273\204/Test_platform/Interface_automation/frontend/src/pages/reports/ReportList.vue" "b/\346\265\213\350\257\225\347\273\204/Test_platform/Interface_automation/frontend/src/pages/reports/ReportList.vue"
index d43ff48..8ec7853 100644
--- "a/\346\265\213\350\257\225\347\273\204/Test_platform/Interface_automation/frontend/src/pages/reports/ReportList.vue"
+++ "b/\346\265\213\350\257\225\347\273\204/Test_platform/Interface_automation/frontend/src/pages/reports/ReportList.vue"
@@ -1,53 +1,42 @@
<template>
<el-container>
- <el-header style="padding: 10px 0; height: 50px">
+ <el-header style="padding: 20px 24px; height: auto; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);">
<div class="report__header">
<div class="report__header--item">
<el-input
clearable
- size="small"
placeholder="请输入报告名称"
v-model="search"
- style="width: 300px"
+ style="width: 320px"
+ prefix-icon="el-icon-search"
+ size="medium"
>
</el-input>
</div>
<div class="report__header--item">
<el-dropdown @command="reportTypeChangeHandle">
- <el-button type="primary" size="small">
+ <el-button type="primary" size="medium" style="background: rgba(255, 255, 255, 0.2); border-color: rgba(255, 255, 255, 0.3); color: white;">
类型
<i class="el-icon-arrow-down el-icon--right"></i>
</el-button>
<el-dropdown-menu slot="dropdown">
- <el-dropdown-item command="1"
- >调试</el-dropdown-item
- >
- <el-dropdown-item command="2"
- >异步</el-dropdown-item
- >
- <el-dropdown-item command="3"
- >定时</el-dropdown-item
- >
+ <el-dropdown-item command="1">调试</el-dropdown-item>
+ <el-dropdown-item command="2">异步</el-dropdown-item>
+ <el-dropdown-item command="3">定时</el-dropdown-item>
<el-dropdown-item command="">全部</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
<div class="report__header--item">
<el-dropdown @command="reportStatusChangeHandle">
- <el-button type="primary" size="small">
+ <el-button type="primary" size="medium" style="background: rgba(255, 255, 255, 0.2); border-color: rgba(255, 255, 255, 0.3); color: white;">
结果
<i class="el-icon-arrow-down el-icon--right"></i>
</el-button>
<el-dropdown-menu slot="dropdown">
- <el-dropdown-item command="0"
- >失败</el-dropdown-item
- >
- <el-dropdown-item command="1"
- >成功</el-dropdown-item
- >
- <el-dropdown-item command="0"
- >全部</el-dropdown-item
- >
+ <el-dropdown-item command="0">失败</el-dropdown-item>
+ <el-dropdown-item command="1">成功</el-dropdown-item>
+ <el-dropdown-item command="">全部</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
@@ -55,11 +44,14 @@
<div class="report__header--item">
<el-button
plain
- size="small"
+ size="medium"
icon="el-icon-refresh"
@click="resetSearch"
- >重置</el-button
+ type="info"
+ style="background: rgba(255, 255, 255, 0.2); border-color: rgba(255, 255, 255, 0.3); color: white;"
>
+ 重置
+ </el-button>
</div>
<div class="report__header--item">
@@ -69,24 +61,30 @@
v-if="isSuperuser"
type="danger"
icon="el-icon-delete"
- size="small"
+ size="medium"
@click="delSelectionReports"
- >批量删除</el-button
+ style="background: rgba(245, 108, 108, 0.8); border-color: rgba(245, 108, 108, 0.9);"
>
+ 批量删除
+ </el-button>
</div>
- <el-switch
- style="margin-left: 10px"
- v-model="onlyMe"
- active-color="#13ce66"
- inactive-color="#ff4949"
- active-text="只看自己"
- ></el-switch>
+ <div class="report__header--item">
+ <el-switch
+ v-model="onlyMe"
+ active-color="#67C23A"
+ inactive-color="#F56C6C"
+ active-text="只看自己"
+ inactive-text="查看全部"
+ size="medium"
+ style="color: white;"
+ ></el-switch>
+ </div>
</div>
</el-header>
<el-container>
- <el-main style="padding: 0; margin-left: 10px; margin-top: 10px">
+ <el-main style="padding: 24px; margin: 0; background: linear-gradient(180deg, #f5f7fa 0%, #e8ecf1 100%);">
<el-dialog
v-if="dialogTableVisible"
:visible.sync="dialogTableVisible"
@@ -94,224 +92,138 @@
>
<report :summary="summary"></report>
</el-dialog>
- <div class="report__body__table">
- <el-table
- :data="reportData.results"
- highlight-current-row
- stripe
- height="calc(100%)"
- @cell-mouse-enter="cellMouseEnter"
- @cell-mouse-leave="cellMouseLeave"
- @selection-change="handleSelectionChange"
- v-loading="loading"
- style="margin-top: -10px"
- :row-class-name="tableRowClassName"
+ <div class="report__body__list" v-loading="loading">
+ <div
+ v-for="(item, index) in reportData.results"
+ :key="item.id"
+ class="report-list-item"
+ :class="{ 'success-item': item.success, 'failure-item': !item.success }"
+ @mouseenter="handleCardHover(index, true)"
+ @mouseleave="handleCardHover(index, false)"
>
- <el-table-column type="selection" width="55">
- </el-table-column>
-
- <el-table-column label="报告类型" width="100">
- <template slot-scope="scope">
- <el-tag color="#2C3E50" style="color: white">{{
- scope.row.type
- }}</el-tag>
- </template>
- </el-table-column>
-
- <el-table-column label="报告名称" width="250">
- <template slot-scope="scope">
- <div
- :title="scope.row.name"
- style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"
- >
- {{ scope.row.name }}
+ <div class="item-left">
+ <div class="item-status">
+ <div class="status-dot" :class="item.success ? 'success' : 'failure'"></div>
+ </div>
+ <div class="item-info">
+ <div class="item-type-badge">
+ <i :class="getTypeIcon(item.type)"></i>
+ <span>{{ item.type }}</span>
</div>
- </template>
- </el-table-column>
-
- <el-table-column label="执行结果" width="100">
- <template slot-scope="scope">
- <el-button
- :type="
- scope.row.success ? 'success' : 'danger'
- "
- size="mini"
- >
- {{ scope.row.success ? "成功" : "失败" }}
- </el-button>
- </template>
- </el-table-column>
-
- <el-table-column label="耗时" width="100">
- <template slot-scope="scope">
- <div class="time-display">
- <el-tag
- :type="scope.row.time.duration < 5 ? 'success' : scope.row.time.duration < 10 ? 'warning' : 'danger'"
- size="small"
- effect="dark"
- >
- {{ scope.row.time.duration.toFixed(3) }}s
- </el-tag>
+ <div class="item-name" :title="item.name">
+ {{ item.name }}
</div>
- </template>
- </el-table-column>
-
- <el-table-column label="总计接口" width="80">
- <template slot-scope="scope">
- <div class="stat-badge">
- <el-badge :value="scope.row.stat.testsRun" class="item" type="info"></el-badge>
+ <div class="item-meta-inline">
+ <span class="meta-inline">
+ <i class="el-icon-user"></i>
+ {{ !item.creator ? "机器人" : item.creator }}
+ </span>
+ <span class="meta-inline">
+ <i class="el-icon-date"></i>
+ {{ item.time.start_at | timestampToTime }}
+ </span>
+ <span class="meta-inline">
+ <i class="el-icon-time"></i>
+ 总用时: {{ formatDuration(item.time.duration) }}
+ </span>
</div>
- </template>
- </el-table-column>
+ </div>
+ </div>
- <el-table-column label="通过" width="80">
- <template slot-scope="scope">
- <div class="stat-badge success">
- <div style="display: flex; align-items: center; gap: 4px;">
- <el-badge :value="scope.row.stat.successes" class="item"></el-badge>
- <i class="el-icon-success"></i>
- </div>
- <el-progress
- :percentage="scope.row.stat.testsRun > 0 ?
- Math.round((scope.row.stat.successes / scope.row.stat.testsRun) * 100) : 0"
- :stroke-width="8"
- :show-text="false"
- color="#67C23A"
- class="mini-progress"
- />
+ <div class="item-stats">
+ <div class="stat-group">
+ <div class="stat-item">
+ <span class="stat-label">总用例</span>
+ <span class="stat-value">{{ item.stat.testsRun }}</span>
</div>
- </template>
- </el-table-column>
-
- <el-table-column label="失败" width="80">
- <template slot-scope="scope">
- <div class="stat-badge danger">
- <div style="display: flex; align-items: center; gap: 4px;">
- <el-badge :value="scope.row.stat.failures" class="item"></el-badge>
- <i class="el-icon-error"></i>
- </div>
- <el-progress
- :percentage="scope.row.stat.testsRun > 0 ?
- Math.round((scope.row.stat.failures / scope.row.stat.testsRun) * 100) : 0"
- :stroke-width="8"
- :show-text="false"
- color="#F56C6C"
- class="mini-progress"
- />
+ <div class="stat-item success">
+ <span class="stat-label">通过</span>
+ <span class="stat-value">{{ item.stat.successes }}</span>
</div>
- </template>
- </el-table-column>
-
- <el-table-column label="异常" width="80">
- <template slot-scope="scope">
- <div class="stat-badge warning">
- <div style="display: flex; align-items: center; gap: 4px;">
- <el-badge :value="scope.row.stat.errors" class="item"></el-badge>
- <i class="el-icon-warning"></i>
- </div>
- <el-progress
- :percentage="scope.row.stat.testsRun > 0 ?
- Math.round((scope.row.stat.errors / scope.row.stat.testsRun) * 100) : 0"
- :stroke-width="8"
- :show-text="false"
- color="#E6A23C"
- class="mini-progress"
- />
+ <div class="stat-item failure">
+ <span class="stat-label">失败</span>
+ <span class="stat-value">{{ item.stat.failures }}</span>
</div>
- </template>
- </el-table-column>
-
- <el-table-column label="执行人" width="100">
- <template slot-scope="scope">
- <div
- :title="scope.row.creator"
- style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap;"
- >
- <svg class="icon" aria-hidden="true">
- <use
- :xlink:href="
- !scope.row.creator
- ? '#icon-jiqiren'
- : '#icon-sharpicons_user'
- "
- ></use>
- </svg>
- <span>{{
- !scope.row.creator
- ? "机器人"
- : scope.row.creator
- }}</span>
+ <div class="stat-item error">
+ <span class="stat-label">异常</span>
+ <span class="stat-value">{{ item.stat.errors }}</span>
</div>
- </template>
- </el-table-column>
-
- <el-table-column label="执行时间" width="180">
- <template slot-scope="scope">
- <div>
- {{
- scope.row.time.start_at
- | timestampToTime
- }}
+ <div class="stat-item skipped" v-if="item.stat.skipped !== undefined">
+ <span class="stat-label">跳过</span>
+ <span class="stat-value">{{ item.stat.skipped || 0 }}</span>
</div>
- </template>
- </el-table-column>
-
- <el-table-column label="报告操作">
- <template slot-scope="scope">
- <el-row v-show="currentRow === scope.row">
+ </div>
+ <div class="progress-bar-container">
+ <div class="progress-bar">
<div
- style="display: flex; align-items: center;"
- >
- <!-- v-show="handleShowReRun(scope.row)" -->
- <el-button
- type="info"
- icon="el-icon-refresh-right"
- circle
- size="mini"
- title="重新运行失败用例"
- v-show="false"
- @click="
- handleRunFailCase(scope.row)
- "
- ></el-button>
- <el-button
- type="success"
- icon="el-icon-view"
- circle
- size="mini"
- @click="
- handleWatchReports(scope.row.id)
- "
- ></el-button>
- <el-button
- type="danger"
- icon="el-icon-delete"
- title="删除"
- circle
- size="mini"
- @click="
- handleDelReports(scope.row.id)
- "
- ></el-button>
- </div>
- </el-row>
- </template>
- </el-table-column>
- </el-table>
- <div class="pagination-container">
- <el-pagination
- v-show="reportData.count !== 0"
- @size-change="handleSizeChange"
- @current-change="handleCurrentChange"
- :current-page.sync="currentPage"
- :page-sizes="[10, 20, 30, 40]"
- :page-size="pageSize"
- :pager-count="5"
- :total="reportData.count"
- layout="total, sizes, prev, pager, next, jumper"
- background
- ></el-pagination>
+ class="progress-fill success"
+ :style="{ width: calculatePercentage(item.stat.successes, item.stat.testsRun) + '%' }"
+ ></div>
+ <div
+ class="progress-fill failure"
+ :style="{ width: calculatePercentage(item.stat.failures, item.stat.testsRun) + '%' }"
+ ></div>
+ <div
+ class="progress-fill error"
+ :style="{ width: calculatePercentage(item.stat.errors, item.stat.testsRun) + '%' }"
+ ></div>
+ <div
+ class="progress-fill skipped"
+ v-if="item.stat.skipped !== undefined"
+ :style="{ width: calculatePercentage(item.stat.skipped || 0, item.stat.testsRun) + '%' }"
+ ></div>
+ </div>
+ <div class="success-rate">
+ 成功率: <span :class="getSuccessRateClass(item)">{{ calculateSuccessRate(item) }}%</span>
+ </div>
+ </div>
+ </div>
+
+ <div class="item-actions">
+ <div class="action-buttons">
+ <el-button
+ type="success"
+ icon="el-icon-view"
+ size="small"
+ @click.stop="handleWatchReports(item.id)"
+ >
+ 查看
+ </el-button>
+ <el-button
+ type="danger"
+ icon="el-icon-delete"
+ size="small"
+ plain
+ @click.stop="handleDelReports(item.id)"
+ >
+ 删除
+ </el-button>
+ </div>
+ </div>
</div>
+
+ <div v-if="reportData.results.length === 0 && !loading" class="empty-state">
+ <div class="empty-icon">
+ <i class="el-icon-document"></i>
+ </div>
+ <div class="empty-title">暂无测试报告</div>
+ <div class="empty-description">还没有执行过测试,快去运行你的第一个测试吧!</div>
+ </div>
+ </div>
+
+ <div class="pagination-container">
+ <el-pagination
+ v-show="reportData.count !== 0"
+ @size-change="handleSizeChange"
+ @current-change="handleCurrentChange"
+ :current-page.sync="currentPage"
+ :page-sizes="[10, 20, 30, 50]"
+ :page-size="pageSize"
+ :pager-count="5"
+ :total="reportData.count"
+ layout="total, sizes, prev, pager, next, jumper"
+ background
+ ></el-pagination>
</div>
</el-main>
</el-container>
@@ -330,6 +242,7 @@
search: "",
searchDebounce: null,
selectReports: [],
+ selectedReports: {},
currentRow: "",
currentPage: 1,
pageSize: 10,
@@ -348,21 +261,85 @@
};
},
methods: {
- cellMouseEnter(row) {
- this.currentRow = row;
- },
- cellMouseLeave() {
- this.currentRow = "";
+ handleCardHover(index, isHovering) {
+ this.currentRow = isHovering ? index : "";
},
handleWatchReports(index) {
let reportUrl =
this.$api.baseUrl + this.$store.state.report_path + index;
window.open(reportUrl);
},
- handleSelectionChange(val) {
- this.selectReports = val;
- // 更新是否已经选择Report, 依赖这个属性来判断是否禁用批量删除按钮
+ handleSelectionChange() {
+ this.selectReports = this.reportData.results.filter(item => this.selectedReports[item.id]);
this.isSelectReport = this.selectReports.length > 0;
+ },
+ getTypeIcon(type) {
+ const iconMap = {
+ '调试': 'el-icon-magic-stick',
+ '异步': 'el-icon-video-play',
+ '定时': 'el-icon-alarm-clock',
+ '部署': 'el-icon-upload'
+ };
+ return iconMap[type] || 'el-icon-document';
+ },
+ calculateSuccessRate(item) {
+ if (!item.stat.testsRun || item.stat.testsRun === 0) return 0;
+ return Math.round((item.stat.successes / item.stat.testsRun) * 100);
+ },
+ calculatePercentage(value, total) {
+ if (!total || total === 0) return 0;
+ return Math.round((value / total) * 100);
+ },
+ formatDuration(seconds) {
+ if (seconds < 1) {
+ return (seconds * 1000).toFixed(0) + 'ms';
+ } else if (seconds < 60) {
+ return seconds.toFixed(2) + 's';
+ } else {
+ const minutes = Math.floor(seconds / 60);
+ const remainingSeconds = (seconds % 60).toFixed(0);
+ return `${minutes}m ${remainingSeconds}s`;
+ }
+ },
+ formatPlatform(platform) {
+ if (!platform) return '未知';
+ if (typeof platform === 'string') {
+ return platform;
+ }
+ if (typeof platform === 'object') {
+ return platform.name || platform.type || JSON.stringify(platform);
+ }
+ return String(platform);
+ },
+ getPlatformName(platform) {
+ if (!platform) return '未知平台';
+ if (typeof platform === 'string') {
+ return platform;
+ }
+ if (typeof platform === 'object') {
+ return platform.name || platform.os_name || platform.platform || '未知';
+ }
+ return String(platform);
+ },
+ getPlatformDetail(platform) {
+ if (!platform) return '';
+ if (typeof platform === 'string') {
+ return '';
+ }
+ if (typeof platform === 'object') {
+ const details = [];
+ if (platform.os_version) details.push(platform.os_version);
+ if (platform.browser) details.push(platform.browser);
+ if (platform.python_version) details.push('Python ' + platform.python_version);
+ return details.length > 0 ? `(${details.join(', ')})` : '';
+ }
+ return '';
+ },
+ getSuccessRateClass(item) {
+ const rate = this.calculateSuccessRate(item);
+ if (rate >= 80) return 'rate-high';
+ if (rate >= 60) return 'rate-medium';
+ return 'rate-low';
},
reportTypeChangeHandle(command) {
this.reportType = command;
@@ -381,6 +358,7 @@
this.reportStatus = "";
this.currentPage = 1;
this.onlyMe = false;
+ this.selectedReports = {};
this.getReportList();
},
handleCurrentChange() {
@@ -398,14 +376,13 @@
})
.then(resp => {
this.reportData = resp;
+ this.selectedReports = {};
});
},
handleSizeChange(newSize) {
this.pageSize = newSize;
- // 计算新的最大页码
let maxPage = Math.ceil(this.reportData.count / newSize);
if (this.currentPage > maxPage) {
- // 如果当前页码超出了范围,请将其设置为最大页面
this.currentPage = maxPage;
}
this.$api
@@ -422,6 +399,7 @@
})
.then(resp => {
this.reportData = resp;
+ this.selectedReports = {};
});
},
handleRunFailCase(row) {
@@ -475,6 +453,7 @@
.delAllReports({ data: this.selectReports })
.then(resp => {
this.$message.success(resp.msg);
+ this.selectedReports = {};
this.getReportList();
});
});
@@ -498,6 +477,7 @@
.then(resp => {
this.reportData = resp;
this.loading = false;
+ this.selectedReports = {};
});
},
handleShowReRun(row) {
@@ -519,9 +499,6 @@
this.currentPage = 1;
this.getReportList();
}, 300);
- },
- tableRowClassName({ row }) {
- return row.success === false ? 'warning-row' : ''
}
},
watch: {
@@ -542,85 +519,498 @@
.report__header {
display: flex;
align-items: center;
+ flex-wrap: wrap;
+ gap: 12px;
+ padding: 0;
}
.report__header--item {
display: flex;
- margin-left: 10px;
+ align-items: center;
+ margin: 0;
}
-.report__body__table {
- position: fixed;
+.report__body__list {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+ padding: 0;
+ min-height: 400px;
+}
+
+.report-list-item {
+ display: grid;
+ grid-template-columns: 240px 1fr 180px;
+ gap: 14px;
+ align-items: center;
+ background: linear-gradient(145deg, #ffffff 0%, #f8f9fa 100%);
+ border-radius: 12px;
+ padding: 12px 16px;
+ box-shadow:
+ 0 2px 4px rgba(0, 0, 0, 0.04),
+ 0 4px 8px rgba(0, 0, 0, 0.06);
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+ cursor: pointer;
+ border: 1px solid rgba(0, 0, 0, 0.06);
+ position: relative;
+ overflow: hidden;
+}
+
+.report-list-item::before {
+ content: '';
+ position: absolute;
+ left: 0;
+ top: 0;
bottom: 0;
- right: 0;
- left: 220px;
- top: 120px;
- margin-left: -10px;
- padding-bottom: 60px;
+ width: 4px;
+ background: linear-gradient(180deg, #667eea 0%, #764ba2 100%);
+ opacity: 0;
+ transition: opacity 0.3s ease;
}
-</style>
-<style scoped>
-.mini-progress {
- width: 100%;
- margin-top: 2px;
- margin-bottom: -3px;
+.report-list-item:hover {
+ transform: translateX(4px);
+ box-shadow:
+ 0 4px 8px rgba(0, 0, 0, 0.08),
+ 0 8px 16px rgba(0, 0, 0, 0.1);
}
-</style>
-<style scoped>
-.stat-badge {
+.report-list-item:hover::before {
+ opacity: 1;
+}
+
+.success-item::before {
+ background: #67C23A;
+}
+
+.failure-item::before {
+ background: #F56C6C;
+}
+
+.item-left {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+
+.item-status {
+ flex-shrink: 0;
+}
+
+.status-dot {
+ width: 12px;
+ height: 12px;
+ border-radius: 50%;
+ animation: pulse 2s infinite;
+}
+
+.status-dot.success {
+ background: #67C23A;
+ box-shadow: 0 0 0 3px rgba(103, 194, 58, 0.2);
+}
+
+.status-dot.failure {
+ background: #F56C6C;
+ box-shadow: 0 0 0 3px rgba(245, 108, 108, 0.2);
+}
+
+@keyframes pulse {
+ 0%, 100% {
+ opacity: 1;
+ }
+ 50% {
+ opacity: 0.5;
+ }
+}
+
+.item-info {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ min-width: 0;
+}
+
+.item-type-badge {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ padding: 4px 10px;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ border-radius: 6px;
+ color: white;
+ font-size: 11px;
+ font-weight: 600;
+ width: fit-content;
+}
+
+.item-type-badge i {
+ font-size: 12px;
+}
+
+.item-name {
+ font-size: 15px;
+ font-weight: 600;
+ color: #1a1a2e;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ line-height: 1.4;
+}
+
+.item-meta-inline {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ margin-top: 8px;
+ padding-top: 8px;
+ border-top: 1px dashed #e4e7ed;
+}
+
+.meta-inline {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ font-size: 12px;
+ color: #909399;
+}
+
+.meta-inline i {
+ font-size: 13px;
+ color: #909399;
+}
+
+.item-stats {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.stat-group {
+ display: flex;
+ gap: 16px;
+ align-items: center;
+}
+
+.stat-item {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 4px;
+}
+
+.stat-label {
+ font-size: 11px;
+ color: #909399;
+ font-weight: 500;
+ text-transform: uppercase;
+ letter-spacing: 0.3px;
+}
+
+.stat-value {
+ font-size: 16px;
+ font-weight: 700;
+ color: #303133;
+}
+
+.stat-item.success .stat-value {
+ color: #67C23A;
+}
+
+.stat-item.failure .stat-value {
+ color: #F56C6C;
+}
+
+.stat-item.error .stat-value {
+ color: #E6A23C;
+}
+
+.stat-item.skipped .stat-value {
+ color: #909399;
+}
+
+.progress-bar-container {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+
+.progress-bar {
+ flex: 1;
+ height: 8px;
+ background: #e4e7ed;
+ border-radius: 4px;
+ overflow: hidden;
+ display: flex;
+}
+
+.progress-fill {
+ height: 100%;
+ transition: width 0.3s ease;
+}
+
+.progress-fill.success {
+ background: linear-gradient(90deg, #67C23A 0%, #85ce61 100%);
+}
+
+.progress-fill.failure {
+ background: linear-gradient(90deg, #F56C6C 0%, #f78989 100%);
+}
+
+.progress-fill.error {
+ background: linear-gradient(90deg, #E6A23C 0%, #ebb563 100%);
+}
+
+.progress-fill.skipped {
+ background: linear-gradient(90deg, #909399 0%, #a6a9ad 100%);
+}
+
+.success-rate {
+ font-size: 13px;
+ color: #606266;
+ font-weight: 500;
+ white-space: nowrap;
+}
+
+.success-rate .rate-high {
+ color: #67C23A;
+ font-weight: 700;
+}
+
+.success-rate .rate-medium {
+ color: #E6A23C;
+ font-weight: 700;
+}
+
+.success-rate .rate-low {
+ color: #F56C6C;
+ font-weight: 700;
+}
+
+.item-meta {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+}
+
+.meta-row {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ flex-wrap: nowrap;
+}
+
+.meta-item {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ font-size: 12px;
+ color: #606266;
+ background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
+ padding: 5px 10px;
+ border-radius: 6px;
+ border: 1px solid #e4e7ed;
+ transition: all 0.3s ease;
+ white-space: nowrap;
+ flex-shrink: 0;
+}
+
+.meta-item:hover {
+ border-color: #409EFF;
+ background: #f0f9ff;
+}
+
+.meta-item i {
+ font-size: 13px;
+ color: #909399;
+}
+
+.ci-link {
+ color: #409EFF;
+ text-decoration: none;
+ font-weight: 600;
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ transition: all 0.3s ease;
+}
+
+.ci-link:hover {
+ color: #66b1ff;
+}
+
+.item-actions {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ min-width: 0;
+}
+
+.action-buttons {
+ display: flex;
+ gap: 6px;
+ flex-shrink: 0;
+}
+
+.action-buttons .el-button {
+ padding: 6px 12px;
+ font-size: 12px;
+}
+
+.empty-state {
+ grid-column: 1 / -1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
- padding: 4px 0;
+ padding: 100px 20px;
+ color: #909399;
}
-.stat-badge i {
- margin: 2px 0;
- font-size: 16px;
-}
-
-.mini-progress {
- width: 80%;
- margin: 2px 0;
-}
-</style>
-
-<style scoped>
-.stat-badge.success {
- color: #67C23A;
-}
-
-.stat-badge.danger {
- color: #F56C6C;
-}
-
-.stat-badge.warning {
- color: #E6A23C;
-}
-
-.stat-badge .item {
- margin-top: -2px;
-}
-
-.time-display {
+.empty-icon {
+ width: 120px;
+ height: 120px;
+ border-radius: 50%;
+ background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
display: flex;
- justify-content: left;
align-items: center;
- height: 100%;
+ justify-content: center;
+ margin-bottom: 24px;
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
}
-.time-progress {
- width: 80%;
- margin-top: 2px;
+.empty-icon i {
+ font-size: 48px;
+ color: #909399;
+ opacity: 0.6;
}
-.el-table >>> .warning-row td {
- background-color: #fbbaba !important;
+.empty-title {
+ font-size: 20px;
+ font-weight: 700;
+ color: #303133;
+ margin-bottom: 8px;
}
-.el-table >>> .warning-row:hover td {
- background-color: #fba5a5 !important;
+
+.empty-description {
+ font-size: 14px;
+ color: #909399;
+ margin: 0;
+}
+
+.pagination-container {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ padding: 32px 0;
+ margin-top: 24px;
+}
+
+.el-button {
+ border-radius: 12px;
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+ font-weight: 600;
+ letter-spacing: 0.3px;
+}
+
+.el-button:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+}
+
+.el-tag {
+ border-radius: 8px;
+ font-weight: 600;
+ padding: 6px 14px;
+}
+
+.el-switch {
+ margin-left: 0;
+}
+
+.el-input {
+ border-radius: 12px;
+}
+
+.el-input >>> .el-input__inner {
+ border-radius: 12px;
+ border: 2px solid rgba(255, 255, 255, 0.3);
+ background: rgba(255, 255, 255, 0.15);
+ color: white;
+ transition: all 0.3s ease;
+ font-weight: 500;
+}
+
+.el-input >>> .el-input__inner:focus {
+ background: rgba(255, 255, 255, 0.25);
+ border-color: rgba(255, 255, 255, 0.5);
+ box-shadow: 0 0 0 3px rgba(255, 255, 255, 0.1);
+}
+
+.el-input >>> .el-input__inner::placeholder {
+ color: rgba(255, 255, 255, 0.7);
+}
+
+.el-input >>> .el-input__prefix {
+ color: rgba(255, 255, 255, 0.8);
+}
+
+.icon {
+ width: 14px;
+ height: 14px;
+}
+
+@media (max-width: 1600px) {
+ .report-list-item {
+ grid-template-columns: 200px 1fr 160px;
+ gap: 12px;
+ }
+}
+
+@media (max-width: 1200px) {
+ .report-list-item {
+ grid-template-columns: 180px 1fr 150px;
+ gap: 10px;
+ }
+}
+
+@media (max-width: 768px) {
+ .report__header {
+ flex-direction: column;
+ align-items: stretch;
+ }
+
+ .report__header--item {
+ width: 100%;
+ }
+
+ .report-list-item {
+ grid-template-columns: 1fr;
+ gap: 10px;
+ padding: 10px 12px;
+ }
+
+ .item-left {
+ grid-column: 1 / -1;
+ margin-bottom: 8px;
+ }
+
+ .item-stats {
+ grid-column: 1 / -1;
+ margin-bottom: 8px;
+ }
+
+ .stat-group {
+ flex-wrap: wrap;
+ justify-content: center;
+ gap: 8px;
+ }
+
+ .item-actions {
+ grid-column: 1 / -1;
+ justify-content: center;
+ padding-top: 8px;
+ border-top: 1px solid #e4e7ed;
+ }
}
</style>
--
Gitblit v1.9.1