<template>
|
<el-container>
|
<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
|
placeholder="请输入报告名称"
|
v-model="search"
|
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="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="">全部</el-dropdown-item>
|
</el-dropdown-menu>
|
</el-dropdown>
|
</div>
|
<div class="report__header--item">
|
<el-dropdown @command="reportStatusChangeHandle">
|
<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="">全部</el-dropdown-item>
|
</el-dropdown-menu>
|
</el-dropdown>
|
</div>
|
|
<div class="report__header--item">
|
<el-button
|
plain
|
size="medium"
|
icon="el-icon-refresh"
|
@click="resetSearch"
|
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">
|
<el-button
|
:title="'删除'"
|
:disabled="!isSelectReport"
|
v-if="isSuperuser"
|
type="danger"
|
icon="el-icon-delete"
|
size="medium"
|
@click="delSelectionReports"
|
style="background: rgba(245, 108, 108, 0.8); border-color: rgba(245, 108, 108, 0.9);"
|
>
|
批量删除
|
</el-button>
|
</div>
|
|
<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: 24px; margin: 0; background: linear-gradient(180deg, #f5f7fa 0%, #e8ecf1 100%);">
|
<el-dialog
|
v-if="dialogTableVisible"
|
:visible.sync="dialogTableVisible"
|
width="70%"
|
>
|
<report :summary="summary"></report>
|
</el-dialog>
|
<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)"
|
>
|
<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>
|
<div class="item-name" :title="item.name">
|
{{ item.name }}
|
</div>
|
<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>
|
</div>
|
</div>
|
|
<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
|
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>
|
</el-container>
|
</template>
|
|
<script>
|
import Report from "@/pages/reports/DebugReport";
|
export default {
|
name: "ReportList",
|
components: {
|
Report
|
},
|
data() {
|
return {
|
search: "",
|
searchDebounce: null,
|
selectReports: [],
|
selectedReports: {},
|
currentRow: "",
|
currentPage: 1,
|
pageSize: 10,
|
onlyMe: false,
|
isSuperuser: this.$store.state.is_superuser,
|
reportType: "",
|
reportStatus: "",
|
reportData: {
|
count: 0,
|
results: []
|
},
|
dialogTableVisible: false,
|
summary: {},
|
loading: true,
|
isSelectReport: false
|
};
|
},
|
methods: {
|
handleCardHover(index, isHovering) {
|
this.currentRow = isHovering ? index : "";
|
},
|
handleWatchReports(index) {
|
let reportUrl =
|
this.$api.baseUrl + this.$store.state.report_path + index;
|
window.open(reportUrl);
|
},
|
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;
|
this.currentPage = 1;
|
this.getReportList();
|
},
|
reportStatusChangeHandle(command) {
|
this.reportStatus = command;
|
this.currentPage = 1;
|
this.getReportList();
|
},
|
resetSearch() {
|
this.pageSize = 10;
|
this.search = "";
|
this.reportType = "";
|
this.reportStatus = "";
|
this.currentPage = 1;
|
this.onlyMe = false;
|
this.selectedReports = {};
|
this.getReportList();
|
},
|
handleCurrentChange() {
|
this.$api
|
.getReportPaginationByPage({
|
params: {
|
page: this.currentPage,
|
size: this.pageSize,
|
project: this.$route.params.id,
|
search: this.search,
|
reportType: this.reportType,
|
reportStatus: this.reportStatus,
|
onlyMe: this.onlyMe
|
}
|
})
|
.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
|
.getReportPaginationByPage({
|
params: {
|
page: this.currentPage,
|
size: newSize,
|
project: this.$route.params.id,
|
search: this.search,
|
reportType: this.reportType,
|
reportStatus: this.reportStatus,
|
onlyMe: this.onlyMe
|
}
|
})
|
.then(resp => {
|
this.reportData = resp;
|
this.selectedReports = {};
|
});
|
},
|
handleRunFailCase(row) {
|
this.loading = true;
|
this.$api
|
.runMultiTest({
|
name: row.name,
|
project: this.$route.params.id,
|
case_config_mapping_list:
|
row.stat.failure_case_config_mapping_list
|
})
|
.then(resp => {
|
this.getReportList();
|
this.loading = false;
|
this.dialogTableVisible = true;
|
this.summary = resp;
|
})
|
.catch(err => {
|
this.$message.error(err);
|
this.loading = false;
|
});
|
},
|
handleDelReports(index) {
|
this.$confirm("此操作将永久删除该测试报告,是否继续?", "提示", {
|
confirmButtonText: "确定",
|
cancelButtonText: "取消",
|
type: "warning"
|
}).then(() => {
|
this.$api.delReports(index).then(resp => {
|
if (resp.success) {
|
this.$message.success(resp.msg);
|
this.getReportList();
|
} else {
|
this.$message.error(resp.msg);
|
}
|
});
|
});
|
},
|
delSelectionReports() {
|
if (this.selectReports.length !== 0) {
|
this.$confirm(
|
"此操作将永久删除该测试报告,是否继续?",
|
"提示",
|
{
|
confirmButtonText: "确定",
|
cancelButtonText: "取消",
|
type: "warning"
|
}
|
).then(() => {
|
this.$api
|
.delAllReports({ data: this.selectReports })
|
.then(resp => {
|
this.$message.success(resp.msg);
|
this.selectedReports = {};
|
this.getReportList();
|
});
|
});
|
} else {
|
this.$message.warning("请至少勾选一个测试报告");
|
}
|
},
|
getReportList() {
|
this.$api
|
.reportList({
|
params: {
|
project: this.$route.params.id,
|
search: this.search,
|
reportType: this.reportType,
|
reportStatus: this.reportStatus,
|
page: this.currentPage,
|
size: this.pageSize,
|
onlyMe: this.onlyMe
|
}
|
})
|
.then(resp => {
|
this.reportData = resp;
|
this.loading = false;
|
this.selectedReports = {};
|
});
|
},
|
handleShowReRun(row) {
|
try {
|
if (
|
row.stat.failure_case_config_mapping_list.length > 0 &&
|
row.stat.failure_case_config_mapping_list[0].config_name !==
|
undefined
|
) {
|
return true;
|
}
|
} catch (e) {
|
return false;
|
}
|
},
|
debouncedGetReportList() {
|
clearTimeout(this.searchDebounce);
|
this.searchDebounce = setTimeout(() => {
|
this.currentPage = 1;
|
this.getReportList();
|
}, 300);
|
}
|
},
|
watch: {
|
search() {
|
this.debouncedGetReportList();
|
},
|
onlyMe() {
|
this.debouncedGetReportList();
|
}
|
},
|
mounted() {
|
this.debouncedGetReportList();
|
}
|
};
|
</script>
|
|
<style scoped>
|
.report__header {
|
display: flex;
|
align-items: center;
|
flex-wrap: wrap;
|
gap: 12px;
|
padding: 0;
|
}
|
|
.report__header--item {
|
display: flex;
|
align-items: center;
|
margin: 0;
|
}
|
|
.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;
|
width: 4px;
|
background: linear-gradient(180deg, #667eea 0%, #764ba2 100%);
|
opacity: 0;
|
transition: opacity 0.3s ease;
|
}
|
|
.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);
|
}
|
|
.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: 100px 20px;
|
color: #909399;
|
}
|
|
.empty-icon {
|
width: 120px;
|
height: 120px;
|
border-radius: 50%;
|
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
margin-bottom: 24px;
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
|
}
|
|
.empty-icon i {
|
font-size: 48px;
|
color: #909399;
|
opacity: 0.6;
|
}
|
|
.empty-title {
|
font-size: 20px;
|
font-weight: 700;
|
color: #303133;
|
margin-bottom: 8px;
|
}
|
|
.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>
|