hyb
2026-01-30 15bc7727b58bf9ca0c8f21702fa893daac232b8d
测试组/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,223 +92,142 @@
                >
                    <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>
                            </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>
                            </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>
                            </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>
                            </template>
                        </el-table-column>
                        <el-table-column label="执行时间" width="180">
                            <template slot-scope="scope">
                                <div>
                                    {{
                                        scope.row.time.start_at
                                            | timestampToTime
                                    }}
                                </div>
                            </template>
                        </el-table-column>
                        <el-table-column label="报告操作">
                            <template slot-scope="scope">
                                <el-row v-show="currentRow === scope.row">
                        <div class="item-stats">
                            <div class="stat-group">
                            <div class="stat-item">
                                <span class="stat-label">总用例</span>
                                <span class="stat-value">{{ item.stat.case_count }}</span>
                            </div>
                            <div class="stat-item">
                                <span class="stat-label">总接口</span>
                                <span class="stat-value">{{ item.stat.testsRun }}</span>
                            </div>
                            <div class="stat-item success">
                                <span class="stat-label">通过</span>
                                <span class="stat-value">{{ item.stat.successes }}</span>
                            </div>
                            <div class="stat-item failure">
                                <span class="stat-label">失败</span>
                                <span class="stat-value">{{ item.stat.failures }}</span>
                            </div>
                            <div class="stat-item error">
                                <span class="stat-label">异常</span>
                                <span class="stat-value">{{ item.stat.errors }}</span>
                            </div>
                            <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>
                        </div>
                            <div class="progress-bar-container">
                                <div class="progress-bar">
                                    <div
                                        style="display: flex; align-items: center;"
                                    >
                                        <el-button
                                            type="info"
                                            icon="el-icon-refresh-right"
                                            circle
                                            size="mini"
                                            title="重新运行失败用例"
                                            v-show="handleShowReRun(scope.row)"
                                            @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>
@@ -329,6 +246,7 @@
            search: "",
            searchDebounce: null,
            selectReports: [],
            selectedReports: {},
            currentRow: "",
            currentPage: 1,
            pageSize: 10,
@@ -347,21 +265,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;
@@ -380,6 +362,7 @@
            this.reportStatus = "";
            this.currentPage = 1;
            this.onlyMe = false;
            this.selectedReports = {};
            this.getReportList();
        },
        handleCurrentChange() {
@@ -397,14 +380,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
@@ -421,6 +403,7 @@
                })
                .then(resp => {
                    this.reportData = resp;
                    this.selectedReports = {};
                });
        },
        handleRunFailCase(row) {
@@ -474,6 +457,7 @@
                        .delAllReports({ data: this.selectReports })
                        .then(resp => {
                            this.$message.success(resp.msg);
                            this.selectedReports = {};
                            this.getReportList();
                        });
                });
@@ -497,6 +481,7 @@
                .then(resp => {
                    this.reportData = resp;
                    this.loading = false;
                    this.selectedReports = {};
                });
        },
        handleShowReRun(row) {
@@ -518,9 +503,6 @@
                this.currentPage = 1;
                this.getReportList();
            }, 300);
        },
        tableRowClassName({ row }) {
            return row.success === false ? 'warning-row' : ''
        }
    },
    watch: {
@@ -541,85 +523,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>