1035 lines
24 KiB
Plaintext
1035 lines
24 KiB
Plaintext
<!-- 数据分析端 - 报表详情页 -->
|
||
<template>
|
||
<view class="report-detail-page">
|
||
<!-- 报表头部 -->
|
||
<view class="report-header">
|
||
<view class="header-info">
|
||
<text class="report-title">{{ report.title }}</text>
|
||
<view class="report-meta">
|
||
<text class="meta-item">{{ getReportTypeText() }}</text>
|
||
<text class="meta-item">{{ report.period }}</text>
|
||
<text class="meta-item">{{ formatTime(report.generated_at) }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="header-actions">
|
||
<button class="action-btn export" @click="exportReport">📊 导出</button>
|
||
<button class="action-btn refresh" @click="refreshReport">🔄 刷新</button>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 核心指标概览 -->
|
||
<view class="metrics-overview">
|
||
<view class="section-title">核心指标</view>
|
||
<view class="metrics-grid">
|
||
<view v-for="metric in coreMetrics" :key="metric.key" class="metric-card">
|
||
<view class="metric-icon" :style="{ backgroundColor: metric.color }">{{ metric.icon }}</view>
|
||
<view class="metric-content">
|
||
<text class="metric-value">{{ formatMetricValue(metric.value, metric.format) }}</text>
|
||
<text class="metric-label">{{ metric.label }}</text>
|
||
<view class="metric-change" :class="{ positive: metric.change > 0, negative: metric.change < 0 }">
|
||
<text class="change-icon">{{ metric.change > 0 ? '↗' : metric.change < 0 ? '↘' : '→' }}</text>
|
||
<text class="change-value">{{ Math.abs(metric.change) }}%</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 趋势图表 -->
|
||
<view class="chart-section">
|
||
<view class="section-header">
|
||
<text class="section-title">趋势分析</text>
|
||
<view class="chart-tabs">
|
||
<text v-for="tab in chartTabs" :key="tab.key"
|
||
class="chart-tab"
|
||
:class="{ active: activeChartTab === tab.key }"
|
||
@click="switchChartTab(tab.key)">{{ tab.label }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="chart-container">
|
||
<canvas class="chart-canvas" canvas-id="trendChart" @touchstart="onChartTouch" @touchmove="onChartTouch" @touchend="onChartTouch"></canvas>
|
||
</view>
|
||
|
||
<view class="chart-legend">
|
||
<view v-for="legend in chartLegends" :key="legend.key" class="legend-item">
|
||
<view class="legend-color" :style="{ backgroundColor: legend.color }"></view>
|
||
<text class="legend-label">{{ legend.label }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 数据表格 -->
|
||
<view class="data-table">
|
||
<view class="section-title">详细数据</view>
|
||
|
||
<view class="table-filters">
|
||
<view class="filter-item">
|
||
<text class="filter-label">排序方式:</text>
|
||
<picker :value="sortIndex" :range="sortOptions" @change="onSortChange">
|
||
<text class="filter-value">{{ sortOptions[sortIndex] }}</text>
|
||
</picker>
|
||
</view>
|
||
<view class="filter-item">
|
||
<text class="filter-label">显示条数:</text>
|
||
<picker :value="limitIndex" :range="limitOptions" @change="onLimitChange">
|
||
<text class="filter-value">{{ limitOptions[limitIndex] }}</text>
|
||
</picker>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="table-container">
|
||
<scroll-view scroll-x="true" class="table-scroll">
|
||
<view class="table">
|
||
<view class="table-header">
|
||
<text v-for="column in tableColumns" :key="column.key"
|
||
class="table-cell header-cell"
|
||
:style="{ width: column.width }">{{ column.title }}</text>
|
||
</view>
|
||
|
||
<view v-for="(row, index) in tableData" :key="index" class="table-row">
|
||
<text v-for="column in tableColumns" :key="column.key"
|
||
class="table-cell data-cell"
|
||
:style="{ width: column.width }"
|
||
:class="{ number: column.type === 'number', currency: column.type === 'currency' }">
|
||
{{ formatCellValue(row[column.key], column) }}
|
||
</text>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
|
||
<view class="table-pagination">
|
||
<button class="page-btn" :disabled="currentPage <= 1" @click="previousPage">上一页</button>
|
||
<text class="page-info">{{ currentPage }} / {{ totalPages }}</text>
|
||
<button class="page-btn" :disabled="currentPage >= totalPages" @click="nextPage">下一页</button>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 数据洞察 -->
|
||
<view class="data-insights">
|
||
<view class="section-title">数据洞察</view>
|
||
|
||
<view v-for="insight in dataInsights" :key="insight.id" class="insight-card">
|
||
<view class="insight-header">
|
||
<view class="insight-icon" :class="insight.type">{{ getInsightIcon(insight.type) }}</view>
|
||
<text class="insight-title">{{ insight.title }}</text>
|
||
</view>
|
||
<text class="insight-content">{{ insight.content }}</text>
|
||
<view class="insight-actions">
|
||
<text class="insight-impact" :class="insight.impact">{{ getImpactText(insight.impact) }}</text>
|
||
<text class="insight-action" @click="viewInsightDetail(insight)">查看详情 ></text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 报表配置 -->
|
||
<view class="report-config">
|
||
<view class="section-title">报表配置</view>
|
||
|
||
<view class="config-item">
|
||
<text class="config-label">自动刷新</text>
|
||
<switch :checked="autoRefresh" @change="toggleAutoRefresh" />
|
||
</view>
|
||
|
||
<view class="config-item">
|
||
<text class="config-label">刷新间隔</text>
|
||
<picker :value="intervalIndex" :range="intervalOptions" @change="onIntervalChange">
|
||
<text class="config-value">{{ intervalOptions[intervalIndex] }}</text>
|
||
</picker>
|
||
</view>
|
||
|
||
<view class="config-item">
|
||
<text class="config-label">邮件通知</text>
|
||
<switch :checked="emailNotify" @change="toggleEmailNotify" />
|
||
</view>
|
||
|
||
<view class="config-actions">
|
||
<button class="config-btn save" @click="saveConfig">保存配置</button>
|
||
<button class="config-btn reset" @click="resetConfig">重置配置</button>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 相关报表 -->
|
||
<view class="related-reports">
|
||
<view class="section-title">相关报表</view>
|
||
|
||
<view class="report-list">
|
||
<view v-for="relatedReport in relatedReports" :key="relatedReport.id"
|
||
class="report-item" @click="viewRelatedReport(relatedReport)">
|
||
<view class="report-icon">📊</view>
|
||
<view class="report-info">
|
||
<text class="report-name">{{ relatedReport.title }}</text>
|
||
<text class="report-desc">{{ relatedReport.description }}</text>
|
||
<text class="report-time">{{ formatTime(relatedReport.generated_at) }}</text>
|
||
</view>
|
||
<text class="report-arrow">></text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
type ReportType = {
|
||
id: string
|
||
title: string
|
||
type: string
|
||
period: string
|
||
generated_at: string
|
||
description: string
|
||
}
|
||
|
||
type MetricType = {
|
||
key: string
|
||
label: string
|
||
value: number
|
||
format: string
|
||
icon: string
|
||
color: string
|
||
change: number
|
||
}
|
||
|
||
type ChartTabType = {
|
||
key: string
|
||
label: string
|
||
}
|
||
|
||
type ChartLegendType = {
|
||
key: string
|
||
label: string
|
||
color: string
|
||
}
|
||
|
||
type TableColumnType = {
|
||
key: string
|
||
title: string
|
||
width: string
|
||
type: string
|
||
}
|
||
|
||
type InsightType = {
|
||
id: string
|
||
type: string
|
||
title: string
|
||
content: string
|
||
impact: string
|
||
}
|
||
|
||
export default {
|
||
data() {
|
||
return {
|
||
report: {
|
||
id: '',
|
||
title: '',
|
||
type: '',
|
||
period: '',
|
||
generated_at: '',
|
||
description: ''
|
||
} as ReportType,
|
||
coreMetrics: [] as Array<MetricType>,
|
||
chartTabs: [] as Array<ChartTabType>,
|
||
activeChartTab: '',
|
||
chartLegends: [] as Array<ChartLegendType>,
|
||
tableColumns: [] as Array<TableColumnType>,
|
||
tableData: [] as Array<any>,
|
||
dataInsights: [] as Array<InsightType>,
|
||
relatedReports: [] as Array<ReportType>,
|
||
sortIndex: 0,
|
||
sortOptions: [] as Array<string>,
|
||
limitIndex: 1,
|
||
limitOptions: ['10条', '20条', '50条', '100条'],
|
||
currentPage: 1,
|
||
totalPages: 1,
|
||
autoRefresh: false,
|
||
intervalIndex: 1,
|
||
intervalOptions: ['1分钟', '5分钟', '10分钟', '30分钟', '1小时'],
|
||
emailNotify: false
|
||
}
|
||
},
|
||
onLoad(options: any) {
|
||
const reportId = options.reportId as string
|
||
if (reportId) {
|
||
this.loadReportDetail(reportId)
|
||
}
|
||
},
|
||
methods: {
|
||
loadReportDetail(reportId: string) {
|
||
// 模拟加载报表详情数据
|
||
this.report = {
|
||
id: reportId,
|
||
title: '销售业绩分析报表',
|
||
type: 'sales',
|
||
period: '2024年1月',
|
||
generated_at: '2024-01-15T14:30:00',
|
||
description: '详细分析1月份的销售业绩情况'
|
||
}
|
||
|
||
this.coreMetrics = [
|
||
{
|
||
key: 'total_sales',
|
||
label: '总销售额',
|
||
value: 1250000,
|
||
format: 'currency',
|
||
icon: '💰',
|
||
color: '#4caf50',
|
||
change: 15.6
|
||
},
|
||
{
|
||
key: 'order_count',
|
||
label: '订单数量',
|
||
value: 8650,
|
||
format: 'number',
|
||
icon: '📦',
|
||
color: '#2196f3',
|
||
change: 8.3
|
||
},
|
||
{
|
||
key: 'avg_order_value',
|
||
label: '客单价',
|
||
value: 144.5,
|
||
format: 'currency',
|
||
icon: '🛍️',
|
||
color: '#ff9800',
|
||
change: 6.8
|
||
},
|
||
{
|
||
key: 'conversion_rate',
|
||
label: '转化率',
|
||
value: 3.2,
|
||
format: 'percent',
|
||
icon: '📈',
|
||
color: '#9c27b0',
|
||
change: -2.1
|
||
}
|
||
]
|
||
|
||
this.chartTabs = [
|
||
{ key: 'sales', label: '销售额' },
|
||
{ key: 'orders', label: '订单量' },
|
||
{ key: 'users', label: '用户数' }
|
||
]
|
||
this.activeChartTab = 'sales'
|
||
|
||
this.chartLegends = [
|
||
{ key: 'current', label: '当期', color: '#2196f3' },
|
||
{ key: 'previous', label: '上期', color: '#ff9800' },
|
||
{ key: 'target', label: '目标', color: '#4caf50' }
|
||
]
|
||
|
||
this.tableColumns = [
|
||
{ key: 'date', title: '日期', width: '120rpx', type: 'text' },
|
||
{ key: 'sales', title: '销售额', width: '120rpx', type: 'currency' },
|
||
{ key: 'orders', title: '订单数', width: '100rpx', type: 'number' },
|
||
{ key: 'users', title: '用户数', width: '100rpx', type: 'number' },
|
||
{ key: 'conversion', title: '转化率', width: '100rpx', type: 'percent' },
|
||
{ key: 'avg_value', title: '客单价', width: '120rpx', type: 'currency' }
|
||
]
|
||
|
||
this.sortOptions = ['按日期降序', '按销售额降序', '按订单数降序', '按转化率降序']
|
||
|
||
// 模拟表格数据
|
||
this.generateTableData()
|
||
|
||
this.dataInsights = [
|
||
{
|
||
id: 'insight_001',
|
||
type: 'positive',
|
||
title: '销售额显著增长',
|
||
content: '相比上月,本月销售额增长15.6%,主要得益于新产品上线和营销活动效果显著。',
|
||
impact: 'high'
|
||
},
|
||
{
|
||
id: 'insight_002',
|
||
type: 'warning',
|
||
title: '转化率轻微下降',
|
||
content: '转化率较上月下降2.1%,建议优化商品页面和购买流程,提升用户体验。',
|
||
impact: 'medium'
|
||
},
|
||
{
|
||
id: 'insight_003',
|
||
title: '周末销售高峰',
|
||
content: '数据显示周末(周六、周日)的销售额占总销售额的35%,建议加强周末营销投入。',
|
||
impact: 'medium',
|
||
type: 'info'
|
||
}
|
||
]
|
||
|
||
this.relatedReports = [
|
||
{
|
||
id: 'report_002',
|
||
title: '用户行为分析报表',
|
||
type: 'user',
|
||
period: '2024年1月',
|
||
generated_at: '2024-01-15T10:00:00',
|
||
description: '分析用户浏览、搜索、购买行为'
|
||
},
|
||
{
|
||
id: 'report_003',
|
||
title: '商品销售排行报表',
|
||
type: 'product',
|
||
period: '2024年1月',
|
||
generated_at: '2024-01-15T09:30:00',
|
||
description: '商品销售排行和库存分析'
|
||
}
|
||
]
|
||
|
||
this.totalPages = Math.ceil(31 / parseInt(this.limitOptions[this.limitIndex]))
|
||
},
|
||
|
||
generateTableData() {
|
||
this.tableData = []
|
||
const days = 31
|
||
const limit = parseInt(this.limitOptions[this.limitIndex])
|
||
const start = (this.currentPage - 1) * limit
|
||
const end = Math.min(start + limit, days)
|
||
|
||
for (let i = start; i < end; i++) {
|
||
const day = i + 1
|
||
this.tableData.push({
|
||
date: `2024-01-${day.toString().padStart(2, '0')}`,
|
||
sales: Math.floor(Math.random() * 50000) + 20000,
|
||
orders: Math.floor(Math.random() * 300) + 200,
|
||
users: Math.floor(Math.random() * 1000) + 500,
|
||
conversion: (Math.random() * 5 + 1).toFixed(1),
|
||
avg_value: (Math.random() * 100 + 50).toFixed(2)
|
||
})
|
||
}
|
||
},
|
||
|
||
getReportTypeText(): string {
|
||
const types: Record<string, string> = {
|
||
sales: '销售报表',
|
||
user: '用户报表',
|
||
product: '商品报表',
|
||
financial: '财务报表',
|
||
marketing: '营销报表'
|
||
}
|
||
return types[this.report.type] || '其他报表'
|
||
},
|
||
|
||
formatMetricValue(value: number, format: string): string {
|
||
switch (format) {
|
||
case 'currency':
|
||
return `¥${(value / 10000).toFixed(1)}万`
|
||
case 'percent':
|
||
return `${value}%`
|
||
case 'number':
|
||
return value.toLocaleString()
|
||
default:
|
||
return value.toString()
|
||
}
|
||
},
|
||
|
||
formatTime(timeStr: string): string {
|
||
return timeStr.replace('T', ' ').split('.')[0]
|
||
},
|
||
|
||
getInsightIcon(type: string): string {
|
||
const icons: Record<string, string> = {
|
||
positive: '✅',
|
||
warning: '⚠️',
|
||
negative: '❌',
|
||
info: 'ℹ️'
|
||
}
|
||
return icons[type] || 'ℹ️'
|
||
},
|
||
|
||
getImpactText(impact: string): string {
|
||
const impacts: Record<string, string> = {
|
||
high: '高影响',
|
||
medium: '中影响',
|
||
low: '低影响'
|
||
}
|
||
return impacts[impact] || '未知影响'
|
||
},
|
||
|
||
formatCellValue(value: any, column: TableColumnType): string {
|
||
switch (column.type) {
|
||
case 'currency':
|
||
return `¥${parseFloat(value).toLocaleString()}`
|
||
case 'percent':
|
||
return `${value}%`
|
||
case 'number':
|
||
return parseInt(value).toLocaleString()
|
||
default:
|
||
return value.toString()
|
||
}
|
||
},
|
||
|
||
switchChartTab(tabKey: string) {
|
||
this.activeChartTab = tabKey
|
||
// 这里可以重新绘制图表
|
||
},
|
||
|
||
onChartTouch(e: any) {
|
||
// 处理图表触摸事件
|
||
},
|
||
|
||
onSortChange(e: any) {
|
||
this.sortIndex = e.detail.value
|
||
this.generateTableData()
|
||
},
|
||
|
||
onLimitChange(e: any) {
|
||
this.limitIndex = e.detail.value
|
||
this.currentPage = 1
|
||
this.totalPages = Math.ceil(31 / parseInt(this.limitOptions[this.limitIndex]))
|
||
this.generateTableData()
|
||
},
|
||
|
||
previousPage() {
|
||
if (this.currentPage > 1) {
|
||
this.currentPage--
|
||
this.generateTableData()
|
||
}
|
||
},
|
||
|
||
nextPage() {
|
||
if (this.currentPage < this.totalPages) {
|
||
this.currentPage++
|
||
this.generateTableData()
|
||
}
|
||
},
|
||
|
||
exportReport() {
|
||
uni.showActionSheet({
|
||
itemList: ['导出Excel', '导出PDF', '导出图片'],
|
||
success: (res) => {
|
||
const formats = ['Excel', 'PDF', '图片']
|
||
uni.showToast({
|
||
title: `正在导出${formats[res.tapIndex]}`,
|
||
icon: 'loading'
|
||
})
|
||
|
||
setTimeout(() => {
|
||
uni.showToast({
|
||
title: '导出成功',
|
||
icon: 'success'
|
||
})
|
||
}, 2000)
|
||
}
|
||
})
|
||
},
|
||
|
||
refreshReport() {
|
||
uni.showLoading({ title: '刷新中...' })
|
||
setTimeout(() => {
|
||
uni.hideLoading()
|
||
this.loadReportDetail(this.report.id)
|
||
uni.showToast({
|
||
title: '刷新成功',
|
||
icon: 'success'
|
||
})
|
||
}, 1500)
|
||
},
|
||
|
||
viewInsightDetail(insight: InsightType) {
|
||
uni.navigateTo({
|
||
url: `/pages/mall/analytics/insight-detail?insightId=${insight.id}`
|
||
})
|
||
},
|
||
|
||
viewRelatedReport(report: ReportType) {
|
||
uni.navigateTo({
|
||
url: `/pages/mall/analytics/report-detail?reportId=${report.id}`
|
||
})
|
||
},
|
||
|
||
toggleAutoRefresh(e: any) {
|
||
this.autoRefresh = e.detail.value
|
||
},
|
||
|
||
onIntervalChange(e: any) {
|
||
this.intervalIndex = e.detail.value
|
||
},
|
||
|
||
toggleEmailNotify(e: any) {
|
||
this.emailNotify = e.detail.value
|
||
},
|
||
|
||
saveConfig() {
|
||
uni.showToast({
|
||
title: '配置已保存',
|
||
icon: 'success'
|
||
})
|
||
},
|
||
|
||
resetConfig() {
|
||
this.autoRefresh = false
|
||
this.intervalIndex = 1
|
||
this.emailNotify = false
|
||
uni.showToast({
|
||
title: '配置已重置',
|
||
icon: 'success'
|
||
})
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style>
|
||
.report-detail-page {
|
||
background-color: #f5f5f5;
|
||
min-height: 100vh;
|
||
}
|
||
|
||
.report-header, .metrics-overview, .chart-section, .data-table, .data-insights, .report-config, .related-reports {
|
||
background-color: #fff;
|
||
margin-bottom: 20rpx;
|
||
padding: 30rpx;
|
||
}
|
||
|
||
.report-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: flex-start;
|
||
}
|
||
|
||
.header-info {
|
||
flex: 1;
|
||
}
|
||
|
||
.report-title {
|
||
font-size: 36rpx;
|
||
font-weight: bold;
|
||
color: #333;
|
||
margin-bottom: 15rpx;
|
||
}
|
||
|
||
.report-meta {
|
||
display: flex;
|
||
gap: 20rpx;
|
||
}
|
||
|
||
.meta-item {
|
||
font-size: 24rpx;
|
||
color: #666;
|
||
background-color: #f0f0f0;
|
||
padding: 6rpx 12rpx;
|
||
border-radius: 8rpx;
|
||
}
|
||
|
||
.header-actions {
|
||
display: flex;
|
||
gap: 15rpx;
|
||
}
|
||
|
||
.action-btn.export, .action-btn.refresh {
|
||
padding: 15rpx 25rpx;
|
||
border-radius: 8rpx;
|
||
font-size: 24rpx;
|
||
border: none;
|
||
}
|
||
|
||
.action-btn.export {
|
||
background-color: #4caf50;
|
||
color: #fff;
|
||
}
|
||
|
||
.action-btn.refresh {
|
||
background-color: #2196f3;
|
||
color: #fff;
|
||
}
|
||
|
||
.section-title {
|
||
font-size: 30rpx;
|
||
font-weight: bold;
|
||
color: #333;
|
||
margin-bottom: 25rpx;
|
||
}
|
||
|
||
.section-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 25rpx;
|
||
}
|
||
|
||
.metrics-grid {
|
||
display: flex;
|
||
gap: 20rpx;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.metric-card {
|
||
flex: 1;
|
||
min-width: 300rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 25rpx;
|
||
background-color: #f8f9fa;
|
||
border-radius: 12rpx;
|
||
}
|
||
|
||
.metric-icon {
|
||
width: 80rpx;
|
||
height: 80rpx;
|
||
border-radius: 40rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 32rpx;
|
||
margin-right: 20rpx;
|
||
}
|
||
|
||
.metric-content {
|
||
flex: 1;
|
||
}
|
||
|
||
.metric-value {
|
||
font-size: 32rpx;
|
||
font-weight: bold;
|
||
color: #333;
|
||
margin-bottom: 5rpx;
|
||
}
|
||
|
||
.metric-label {
|
||
font-size: 24rpx;
|
||
color: #666;
|
||
margin-bottom: 8rpx;
|
||
}
|
||
|
||
.metric-change {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.metric-change.positive {
|
||
color: #4caf50;
|
||
}
|
||
|
||
.metric-change.negative {
|
||
color: #ff4444;
|
||
}
|
||
|
||
.change-icon {
|
||
font-size: 20rpx;
|
||
margin-right: 5rpx;
|
||
}
|
||
|
||
.change-value {
|
||
font-size: 22rpx;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.chart-tabs {
|
||
display: flex;
|
||
gap: 15rpx;
|
||
}
|
||
|
||
.chart-tab {
|
||
padding: 12rpx 24rpx;
|
||
border-radius: 20rpx;
|
||
font-size: 24rpx;
|
||
color: #666;
|
||
background-color: #f0f0f0;
|
||
}
|
||
|
||
.chart-tab.active {
|
||
background-color: #2196f3;
|
||
color: #fff;
|
||
}
|
||
|
||
.chart-container {
|
||
height: 500rpx;
|
||
margin: 30rpx 0;
|
||
border: 1rpx solid #eee;
|
||
border-radius: 8rpx;
|
||
}
|
||
|
||
.chart-canvas {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
.chart-legend {
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: 40rpx;
|
||
}
|
||
|
||
.legend-item {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.legend-color {
|
||
width: 20rpx;
|
||
height: 20rpx;
|
||
border-radius: 10rpx;
|
||
margin-right: 10rpx;
|
||
}
|
||
|
||
.legend-label {
|
||
font-size: 24rpx;
|
||
color: #666;
|
||
}
|
||
|
||
.table-filters {
|
||
display: flex;
|
||
gap: 40rpx;
|
||
margin-bottom: 25rpx;
|
||
}
|
||
|
||
.filter-item {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.filter-label {
|
||
font-size: 24rpx;
|
||
color: #666;
|
||
margin-right: 10rpx;
|
||
}
|
||
|
||
.filter-value, .config-value {
|
||
font-size: 24rpx;
|
||
color: #333;
|
||
padding: 10rpx 20rpx;
|
||
background-color: #f0f0f0;
|
||
border-radius: 6rpx;
|
||
}
|
||
|
||
.table-container {
|
||
border: 1rpx solid #eee;
|
||
border-radius: 8rpx;
|
||
margin-bottom: 25rpx;
|
||
}
|
||
|
||
.table-scroll {
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.table {
|
||
min-width: 100%;
|
||
}
|
||
|
||
.table-header, .table-row {
|
||
display: flex;
|
||
border-bottom: 1rpx solid #eee;
|
||
}
|
||
|
||
.table-row:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.table-cell {
|
||
padding: 20rpx 15rpx;
|
||
font-size: 24rpx;
|
||
text-align: center;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.header-cell {
|
||
background-color: #f8f9fa;
|
||
font-weight: bold;
|
||
color: #333;
|
||
}
|
||
|
||
.data-cell {
|
||
color: #666;
|
||
}
|
||
|
||
.data-cell.number, .data-cell.currency {
|
||
text-align: right;
|
||
}
|
||
|
||
.table-pagination {
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
gap: 30rpx;
|
||
}
|
||
|
||
.page-btn {
|
||
padding: 15rpx 30rpx;
|
||
background-color: #2196f3;
|
||
color: #fff;
|
||
border: none;
|
||
border-radius: 6rpx;
|
||
font-size: 24rpx;
|
||
}
|
||
|
||
.page-btn:disabled {
|
||
background-color: #ccc;
|
||
}
|
||
|
||
.page-info {
|
||
font-size: 24rpx;
|
||
color: #666;
|
||
}
|
||
|
||
.insight-card {
|
||
padding: 25rpx;
|
||
background-color: #f8f9fa;
|
||
border-radius: 10rpx;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.insight-header {
|
||
display: flex;
|
||
align-items: center;
|
||
margin-bottom: 15rpx;
|
||
}
|
||
|
||
.insight-icon {
|
||
width: 40rpx;
|
||
height: 40rpx;
|
||
border-radius: 20rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 20rpx;
|
||
margin-right: 15rpx;
|
||
}
|
||
|
||
.insight-icon.positive {
|
||
background-color: #e8f5e8;
|
||
}
|
||
|
||
.insight-icon.warning {
|
||
background-color: #fff8e1;
|
||
}
|
||
|
||
.insight-icon.negative {
|
||
background-color: #ffebee;
|
||
}
|
||
|
||
.insight-icon.info {
|
||
background-color: #e3f2fd;
|
||
}
|
||
|
||
.insight-title {
|
||
font-size: 26rpx;
|
||
font-weight: bold;
|
||
color: #333;
|
||
}
|
||
|
||
.insight-content {
|
||
font-size: 24rpx;
|
||
color: #666;
|
||
line-height: 1.6;
|
||
margin-bottom: 15rpx;
|
||
}
|
||
|
||
.insight-actions {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.insight-impact {
|
||
font-size: 22rpx;
|
||
padding: 6rpx 12rpx;
|
||
border-radius: 8rpx;
|
||
color: #fff;
|
||
}
|
||
|
||
.insight-impact.high {
|
||
background-color: #ff4444;
|
||
}
|
||
|
||
.insight-impact.medium {
|
||
background-color: #ffa726;
|
||
}
|
||
|
||
.insight-impact.low {
|
||
background-color: #4caf50;
|
||
}
|
||
|
||
.insight-action {
|
||
font-size: 22rpx;
|
||
color: #2196f3;
|
||
}
|
||
|
||
.config-item {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 25rpx 0;
|
||
border-bottom: 1rpx solid #f5f5f5;
|
||
}
|
||
|
||
.config-item:last-of-type {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.config-label {
|
||
font-size: 26rpx;
|
||
color: #333;
|
||
}
|
||
|
||
.config-actions {
|
||
display: flex;
|
||
gap: 20rpx;
|
||
margin-top: 30rpx;
|
||
}
|
||
|
||
.config-btn {
|
||
flex: 1;
|
||
height: 70rpx;
|
||
border-radius: 8rpx;
|
||
font-size: 26rpx;
|
||
border: none;
|
||
}
|
||
|
||
.config-btn.save {
|
||
background-color: #4caf50;
|
||
color: #fff;
|
||
}
|
||
|
||
.config-btn.reset {
|
||
background-color: #f0f0f0;
|
||
color: #666;
|
||
}
|
||
|
||
.report-list {
|
||
margin-top: 25rpx;
|
||
}
|
||
|
||
.report-item {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 25rpx 0;
|
||
border-bottom: 1rpx solid #f5f5f5;
|
||
}
|
||
|
||
.report-item:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
.report-icon {
|
||
font-size: 32rpx;
|
||
margin-right: 20rpx;
|
||
}
|
||
|
||
.report-info {
|
||
flex: 1;
|
||
}
|
||
|
||
.report-name {
|
||
font-size: 26rpx;
|
||
color: #333;
|
||
font-weight: bold;
|
||
margin-bottom: 5rpx;
|
||
}
|
||
|
||
.report-desc {
|
||
font-size: 22rpx;
|
||
color: #666;
|
||
margin-bottom: 5rpx;
|
||
}
|
||
|
||
.report-time {
|
||
font-size: 20rpx;
|
||
color: #999;
|
||
}
|
||
|
||
.report-arrow {
|
||
font-size: 24rpx;
|
||
color: #999;
|
||
}
|
||
</style>
|