数据分析ui补充完善,接入数据库
This commit is contained in:
@@ -200,203 +200,160 @@
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="uts">
|
||||
<script setup lang="uts">
|
||||
import { onLoad, onShow, reactive, ref } from 'vue'
|
||||
|
||||
import AnalyticsSidebarMenu from '@/components/analytics/AnalyticsSidebarMenu.uvue'
|
||||
import AnalyticsTopBar from '@/components/analytics/AnalyticsTopBar.uvue'
|
||||
import { fetchReport, fetchReportMetrics, fetchReportRows, fetchReportInsights, fetchRelatedReports } from '@/services/analytics/reportDetailService.uts'
|
||||
import { mapAnalyticsError } from '@/services/analytics/errorMapper.uts'
|
||||
|
||||
type ReportType = {
|
||||
id: string
|
||||
title: string
|
||||
type: string
|
||||
period: string
|
||||
generated_at: string
|
||||
description: string
|
||||
}
|
||||
import type { ReportType, MetricType, ChartTabType, ChartLegendType, TableColumnType, InsightType } from '@/types/analytics/report-detail.uts'
|
||||
|
||||
type MetricType = {
|
||||
key: string
|
||||
label: string
|
||||
value: number
|
||||
format: string
|
||||
icon: string
|
||||
color: string
|
||||
change: number
|
||||
}
|
||||
const showSidebarMenu = ref(false)
|
||||
const currentPath = ref('/pages/mall/analytics/report-detail')
|
||||
|
||||
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 {
|
||||
components: {
|
||||
AnalyticsSidebarMenu,
|
||||
AnalyticsTopBar
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showSidebarMenu: false,
|
||||
currentPath: '/pages/mall/analytics/report-detail',
|
||||
report: {
|
||||
const report = reactive<ReportType>({
|
||||
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>,
|
||||
allRows: [] as Array<any>,
|
||||
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) {
|
||||
// 兼容两种参数名:reportId 和 id
|
||||
})
|
||||
|
||||
const coreMetrics = reactive<Array<MetricType>>([])
|
||||
const chartTabs = reactive<Array<ChartTabType>>([])
|
||||
const activeChartTab = ref('')
|
||||
const chartLegends = reactive<Array<ChartLegendType>>([])
|
||||
const tableColumns = reactive<Array<TableColumnType>>([])
|
||||
const allRows = reactive<Array<any>>([])
|
||||
const tableData = reactive<Array<any>>([])
|
||||
const dataInsights = reactive<Array<InsightType>>([])
|
||||
const relatedReports = reactive<Array<ReportType>>([])
|
||||
|
||||
const sortIndex = ref(0)
|
||||
const sortOptions = ref<Array<string>>([])
|
||||
const limitIndex = ref(1)
|
||||
const limitOptions = ref<Array<string>>(['10条', '20条', '50条', '100条'])
|
||||
const currentPage = ref(1)
|
||||
const totalPages = ref(1)
|
||||
const autoRefresh = ref(false)
|
||||
const intervalIndex = ref(1)
|
||||
const intervalOptions = ref<Array<string>>(['1分钟', '5分钟', '10分钟', '30分钟', '1小时'])
|
||||
const emailNotify = ref(false)
|
||||
|
||||
onLoad((options: any) => {
|
||||
const reportId = (options.reportId || options.id) as string
|
||||
if (reportId) {
|
||||
this.loadReportDetail(reportId)
|
||||
void loadReportDetail(reportId)
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '缺少报表ID',
|
||||
icon: 'none'
|
||||
})
|
||||
uni.showToast({ title: '缺少报表ID', icon: 'none' })
|
||||
setTimeout(() => {
|
||||
uni.navigateBack()
|
||||
}, 1500)
|
||||
}
|
||||
this.currentPath = '/pages/mall/analytics/report-detail'
|
||||
},
|
||||
currentPath.value = '/pages/mall/analytics/report-detail'
|
||||
})
|
||||
|
||||
onShow() {
|
||||
this.currentPath = '/pages/mall/analytics/report-detail'
|
||||
},
|
||||
onShow(() => {
|
||||
currentPath.value = '/pages/mall/analytics/report-detail'
|
||||
})
|
||||
|
||||
methods: {
|
||||
handleMenu() {
|
||||
this.showSidebarMenu = true
|
||||
},
|
||||
handleSidebarUpdate(visible: boolean) {
|
||||
this.showSidebarMenu = visible
|
||||
},
|
||||
safeNumber(v: any): number {
|
||||
function handleMenu() {
|
||||
showSidebarMenu.value = true
|
||||
}
|
||||
|
||||
function handleSidebarUpdate(visible: boolean) {
|
||||
showSidebarMenu.value = visible
|
||||
}
|
||||
|
||||
function safeNumber(v: any): number {
|
||||
const n = Number(v)
|
||||
return isFinite(n) ? n : 0
|
||||
},
|
||||
}
|
||||
|
||||
async loadReportDetail(reportId: string) {
|
||||
async function loadReportDetail(reportId: string) {
|
||||
try {
|
||||
uni.showLoading({ title: '加载中...' })
|
||||
|
||||
// 1. 加载报表主体
|
||||
const report = await fetchReport(reportId)
|
||||
if (report == null) {
|
||||
const rep = await fetchReport(reportId)
|
||||
if (rep == null) {
|
||||
uni.showToast({ title: '报表不存在', icon: 'none' })
|
||||
return
|
||||
}
|
||||
this.report = report
|
||||
|
||||
// 2. 加载核心指标
|
||||
this.coreMetrics = await fetchReportMetrics(reportId)
|
||||
report.id = rep.id
|
||||
report.title = rep.title
|
||||
report.type = rep.type
|
||||
report.period = rep.period
|
||||
report.generated_at = rep.generated_at
|
||||
report.description = rep.description
|
||||
|
||||
// 3. 配置表头与排序选项(固定结构)
|
||||
this.tableColumns = [
|
||||
const metrics = await fetchReportMetrics(reportId)
|
||||
coreMetrics.splice(0, coreMetrics.length, ...metrics)
|
||||
|
||||
tableColumns.splice(0, tableColumns.length,
|
||||
{ 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 = ['按日期降序', '按销售额降序', '按订单数降序', '按转化率降序']
|
||||
)
|
||||
|
||||
// 4. 加载明细行(趋势/表格)
|
||||
this.allRows = await fetchReportRows(reportId)
|
||||
this.currentPage = 1
|
||||
this.updateTotalPages()
|
||||
this.generateTableData()
|
||||
sortOptions.value = ['按日期降序', '按销售额降序', '按订单数降序', '按转化率降序']
|
||||
|
||||
// 5. 加载洞察
|
||||
this.dataInsights = await fetchReportInsights(reportId)
|
||||
const rows = await fetchReportRows(reportId)
|
||||
allRows.splice(0, allRows.length, ...rows)
|
||||
|
||||
// 6. 相关报表(同类型最近报表)
|
||||
this.relatedReports = await fetchRelatedReports(this.report.type, reportId)
|
||||
currentPage.value = 1
|
||||
updateTotalPages()
|
||||
generateTableData()
|
||||
|
||||
const insights = await fetchReportInsights(reportId)
|
||||
dataInsights.splice(0, dataInsights.length, ...insights)
|
||||
|
||||
const rel = await fetchRelatedReports(report.type, reportId)
|
||||
relatedReports.splice(0, relatedReports.length, ...rel)
|
||||
} catch (e) {
|
||||
console.error('loadReportDetail failed', e)
|
||||
uni.showToast({ title: mapAnalyticsError(e, { fallbackMessage: '报表加载失败' }), icon: 'none' })
|
||||
} finally {
|
||||
uni.hideLoading()
|
||||
}
|
||||
},
|
||||
|
||||
updateTotalPages() {
|
||||
const total = this.allRows.length
|
||||
const limit = parseInt(this.limitOptions[this.limitIndex])
|
||||
this.totalPages = total > 0 ? Math.ceil(total / limit) : 1
|
||||
},
|
||||
}
|
||||
|
||||
generateTableData() {
|
||||
this.tableData = []
|
||||
const total = this.allRows.length
|
||||
function updateTotalPages() {
|
||||
const total = allRows.length
|
||||
const limit = parseInt(limitOptions.value[limitIndex.value])
|
||||
totalPages.value = total > 0 ? Math.ceil(total / limit) : 1
|
||||
}
|
||||
|
||||
function generateTableData() {
|
||||
tableData.splice(0, tableData.length)
|
||||
const total = allRows.length
|
||||
if (total === 0) {
|
||||
return
|
||||
}
|
||||
const limit = parseInt(this.limitOptions[this.limitIndex])
|
||||
const start = (this.currentPage - 1) * limit
|
||||
const limit = parseInt(limitOptions.value[limitIndex.value])
|
||||
const start = (currentPage.value - 1) * limit
|
||||
const end = Math.min(start + limit, total)
|
||||
|
||||
for (let i = start; i < end; i++) {
|
||||
const row = this.allRows[i]
|
||||
this.tableData.push({
|
||||
const row = allRows[i]
|
||||
tableData.push({
|
||||
date: `${row.row_date}`,
|
||||
sales: this.safeNumber(row.gmv),
|
||||
orders: this.safeNumber(row.orders),
|
||||
users: this.safeNumber(row.users),
|
||||
conversion: this.safeNumber(row.conversion).toFixed(1),
|
||||
avg_value: this.safeNumber(row.avg_order_amount).toFixed(2)
|
||||
sales: safeNumber(row.gmv),
|
||||
orders: safeNumber(row.orders),
|
||||
users: safeNumber(row.users),
|
||||
conversion: safeNumber(row.conversion).toFixed(1),
|
||||
avg_value: safeNumber(row.avg_order_amount).toFixed(2)
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
getReportTypeText(): string {
|
||||
}
|
||||
|
||||
function getReportTypeText(): string {
|
||||
const types: Record<string, string> = {
|
||||
sales: '销售报表',
|
||||
user: '用户报表',
|
||||
@@ -404,10 +361,10 @@ export default {
|
||||
financial: '财务报表',
|
||||
marketing: '营销报表'
|
||||
}
|
||||
return types[this.report.type] || '其他报表'
|
||||
},
|
||||
return types[report.type] || '其他报表'
|
||||
}
|
||||
|
||||
formatMetricValue(value: number, format: string): string {
|
||||
function formatMetricValue(value: number, format: string): string {
|
||||
switch (format) {
|
||||
case 'currency':
|
||||
return `¥${(value / 10000).toFixed(1)}万`
|
||||
@@ -418,13 +375,13 @@ export default {
|
||||
default:
|
||||
return value.toString()
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
formatTime(timeStr: string): string {
|
||||
function formatTime(timeStr: string): string {
|
||||
return timeStr.replace('T', ' ').split('.')[0]
|
||||
},
|
||||
}
|
||||
|
||||
getInsightIcon(type: string): string {
|
||||
function getInsightIcon(type: string): string {
|
||||
const icons: Record<string, string> = {
|
||||
positive: '✅',
|
||||
warning: '⚠️',
|
||||
@@ -432,18 +389,18 @@ export default {
|
||||
info: 'ℹ️'
|
||||
}
|
||||
return icons[type] || 'ℹ️'
|
||||
},
|
||||
}
|
||||
|
||||
getImpactText(impact: string): string {
|
||||
function getImpactText(impact: string): string {
|
||||
const impacts: Record<string, string> = {
|
||||
high: '高影响',
|
||||
medium: '中影响',
|
||||
low: '低影响'
|
||||
}
|
||||
return impacts[impact] || '未知影响'
|
||||
},
|
||||
}
|
||||
|
||||
formatCellValue(value: any, column: TableColumnType): string {
|
||||
function formatCellValue(value: any, column: TableColumnType): string {
|
||||
switch (column.type) {
|
||||
case 'currency':
|
||||
return `¥${parseFloat(value).toLocaleString()}`
|
||||
@@ -454,144 +411,131 @@ export default {
|
||||
default:
|
||||
return value.toString()
|
||||
}
|
||||
},
|
||||
|
||||
switchChartTab(tabKey: string) {
|
||||
this.activeChartTab = tabKey
|
||||
// 这里可以重新绘制图表
|
||||
},
|
||||
|
||||
onChartTouch(e: any) {
|
||||
}
|
||||
|
||||
function switchChartTab(tabKey: string) {
|
||||
activeChartTab.value = tabKey
|
||||
}
|
||||
|
||||
function 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.updateTotalPages()
|
||||
this.generateTableData()
|
||||
},
|
||||
|
||||
previousPage() {
|
||||
if (this.currentPage > 1) {
|
||||
this.currentPage--
|
||||
this.generateTableData()
|
||||
}
|
||||
|
||||
function onSortChange(e: any) {
|
||||
sortIndex.value = e.detail.value
|
||||
generateTableData()
|
||||
}
|
||||
|
||||
function onLimitChange(e: any) {
|
||||
limitIndex.value = e.detail.value
|
||||
currentPage.value = 1
|
||||
updateTotalPages()
|
||||
generateTableData()
|
||||
}
|
||||
|
||||
function previousPage() {
|
||||
if (currentPage.value > 1) {
|
||||
currentPage.value--
|
||||
generateTableData()
|
||||
}
|
||||
},
|
||||
|
||||
nextPage() {
|
||||
if (this.currentPage < this.totalPages) {
|
||||
this.currentPage++
|
||||
this.generateTableData()
|
||||
}
|
||||
|
||||
function nextPage() {
|
||||
if (currentPage.value < totalPages.value) {
|
||||
currentPage.value++
|
||||
generateTableData()
|
||||
}
|
||||
},
|
||||
|
||||
exportReport() {
|
||||
}
|
||||
|
||||
function exportReport() {
|
||||
uni.showActionSheet({
|
||||
itemList: ['导出Excel', '导出PDF', '导出图片'],
|
||||
success: (res) => {
|
||||
const formats = ['Excel', 'PDF', '图片']
|
||||
uni.showToast({
|
||||
title: `正在导出${formats[res.tapIndex]}`,
|
||||
icon: 'loading'
|
||||
})
|
||||
|
||||
uni.showToast({ title: `正在导出${formats[res.tapIndex]}`, icon: 'loading' })
|
||||
setTimeout(() => {
|
||||
uni.showToast({
|
||||
title: '导出成功',
|
||||
icon: 'success'
|
||||
})
|
||||
uni.showToast({ title: '导出成功', icon: 'success' })
|
||||
}, 2000)
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
refreshReport() {
|
||||
function refreshReport() {
|
||||
uni.showLoading({ title: '刷新中...' })
|
||||
setTimeout(() => {
|
||||
uni.hideLoading()
|
||||
this.loadReportDetail(this.report.id)
|
||||
uni.showToast({
|
||||
title: '刷新成功',
|
||||
icon: 'success'
|
||||
})
|
||||
void loadReportDetail(report.id)
|
||||
uni.showToast({ title: '刷新成功', icon: 'success' })
|
||||
}, 1500)
|
||||
},
|
||||
}
|
||||
|
||||
goToDataDetail() {
|
||||
if (!this.report.id || this.report.id.length === 0) {
|
||||
function goToDataDetail() {
|
||||
if (!report.id || report.id.length === 0) {
|
||||
uni.showToast({ title: '报表未加载完成', icon: 'none' })
|
||||
return
|
||||
}
|
||||
uni.navigateTo({
|
||||
url: `/pages/mall/analytics/data-detail?reportId=${this.report.id}`
|
||||
url: `/pages/mall/analytics/data-detail?reportId=${report.id}`
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
viewInsightDetail(insight: InsightType) {
|
||||
function viewInsightDetail(insight: InsightType) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/mall/analytics/insight-detail?insightId=${insight.id}`
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
viewRelatedReport(report: ReportType) {
|
||||
function viewRelatedReport(rep: ReportType) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/mall/analytics/report-detail?reportId=${report.id}`
|
||||
url: `/pages/mall/analytics/report-detail?reportId=${rep.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'
|
||||
})
|
||||
},
|
||||
handleSearch() {
|
||||
}
|
||||
|
||||
function toggleAutoRefresh(e: any) {
|
||||
autoRefresh.value = e.detail.value
|
||||
}
|
||||
|
||||
function onIntervalChange(e: any) {
|
||||
intervalIndex.value = e.detail.value
|
||||
}
|
||||
|
||||
function toggleEmailNotify(e: any) {
|
||||
emailNotify.value = e.detail.value
|
||||
}
|
||||
|
||||
function saveConfig() {
|
||||
uni.showToast({ title: '配置已保存', icon: 'success' })
|
||||
}
|
||||
|
||||
function resetConfig() {
|
||||
autoRefresh.value = false
|
||||
intervalIndex.value = 1
|
||||
emailNotify.value = false
|
||||
uni.showToast({ title: '配置已重置', icon: 'success' })
|
||||
}
|
||||
|
||||
function handleSearch() {
|
||||
uni.showToast({ title: '搜索', icon: 'none' })
|
||||
},
|
||||
handleNotification() {
|
||||
}
|
||||
|
||||
function handleNotification() {
|
||||
uni.showToast({ title: '通知', icon: 'none' })
|
||||
},
|
||||
handleFullscreen() {
|
||||
}
|
||||
|
||||
function handleFullscreen() {
|
||||
uni.showToast({ title: '全屏', icon: 'none' })
|
||||
},
|
||||
handleMobile() {
|
||||
}
|
||||
|
||||
function handleMobile() {
|
||||
uni.showToast({ title: '移动端', icon: 'none' })
|
||||
},
|
||||
handleDropdown() {
|
||||
}
|
||||
|
||||
function handleDropdown() {
|
||||
uni.showToast({ title: '下拉菜单', icon: 'none' })
|
||||
},
|
||||
handleSettings() {
|
||||
}
|
||||
|
||||
function handleSettings() {
|
||||
uni.showToast({ title: '设置', icon: 'none' })
|
||||
},
|
||||
|
||||
resetConfig() {
|
||||
this.autoRefresh = false
|
||||
this.intervalIndex = 1
|
||||
this.emailNotify = false
|
||||
uni.showToast({
|
||||
title: '配置已重置',
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -757,12 +701,12 @@ export default {
|
||||
.metric-label {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.metric-change {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 8rpx;
|
||||
}
|
||||
|
||||
.metric-change.positive {
|
||||
@@ -770,17 +714,15 @@ export default {
|
||||
}
|
||||
|
||||
.metric-change.negative {
|
||||
color: #ff4444;
|
||||
color: #f44336;
|
||||
}
|
||||
|
||||
.change-icon {
|
||||
font-size: 20rpx;
|
||||
margin-right: 5rpx;
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
.change-value {
|
||||
font-size: 22rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.chart-tabs {
|
||||
@@ -789,111 +731,104 @@ export default {
|
||||
}
|
||||
|
||||
.chart-tab {
|
||||
padding: 12rpx 24rpx;
|
||||
padding: 8rpx 16rpx;
|
||||
border-radius: 20rpx;
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
background-color: #f0f0f0;
|
||||
color: #666;
|
||||
font-size: 22rpx;
|
||||
}
|
||||
|
||||
.chart-tab.active {
|
||||
background-color: #2196f3;
|
||||
background-color: #111827;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
height: 500rpx;
|
||||
margin: 30rpx 0;
|
||||
border: 1rpx solid #eee;
|
||||
border-radius: 8rpx;
|
||||
height: 400rpx;
|
||||
margin: 20rpx 0;
|
||||
}
|
||||
|
||||
.chart-canvas {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
height: 400rpx;
|
||||
}
|
||||
|
||||
.chart-legend {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 40rpx;
|
||||
gap: 30rpx;
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.legend-color {
|
||||
width: 20rpx;
|
||||
height: 20rpx;
|
||||
border-radius: 10rpx;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
|
||||
.legend-label {
|
||||
font-size: 24rpx;
|
||||
font-size: 22rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.table-filters {
|
||||
display: flex;
|
||||
gap: 40rpx;
|
||||
margin-bottom: 25rpx;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.filter-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10rpx;
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
|
||||
.filter-value, .config-value {
|
||||
.filter-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;
|
||||
margin: 20rpx 0;
|
||||
}
|
||||
|
||||
.table-scroll {
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.table {
|
||||
min-width: 100%;
|
||||
min-width: 600rpx;
|
||||
}
|
||||
|
||||
.table-header, .table-row {
|
||||
.table-header {
|
||||
display: flex;
|
||||
background-color: #f8f9fa;
|
||||
padding: 15rpx;
|
||||
}
|
||||
|
||||
.table-row {
|
||||
display: flex;
|
||||
padding: 15rpx;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
}
|
||||
|
||||
.table-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.table-cell {
|
||||
padding: 20rpx 15rpx;
|
||||
font-size: 24rpx;
|
||||
flex-shrink: 0;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 22rpx;
|
||||
}
|
||||
|
||||
.header-cell {
|
||||
background-color: #f8f9fa;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
@@ -902,24 +837,29 @@ export default {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.data-cell.number, .data-cell.currency {
|
||||
text-align: right;
|
||||
.data-cell.number {
|
||||
color: #2196f3;
|
||||
}
|
||||
|
||||
.data-cell.currency {
|
||||
color: #4caf50;
|
||||
}
|
||||
|
||||
.table-pagination {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 30rpx;
|
||||
gap: 20rpx;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.page-btn {
|
||||
padding: 15rpx 30rpx;
|
||||
background-color: #2196f3;
|
||||
color: #fff;
|
||||
border: none;
|
||||
padding: 10rpx 20rpx;
|
||||
border-radius: 6rpx;
|
||||
font-size: 24rpx;
|
||||
background-color: #111827;
|
||||
color: #fff;
|
||||
font-size: 22rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.page-btn:disabled {
|
||||
@@ -932,16 +872,17 @@ export default {
|
||||
}
|
||||
|
||||
.insight-card {
|
||||
padding: 25rpx;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 10rpx;
|
||||
margin-bottom: 20rpx;
|
||||
padding: 20rpx;
|
||||
border-radius: 12rpx;
|
||||
margin-bottom: 15rpx;
|
||||
}
|
||||
|
||||
.insight-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 15rpx;
|
||||
gap: 15rpx;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.insight-icon {
|
||||
@@ -951,24 +892,8 @@ export default {
|
||||
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;
|
||||
font-size: 24rpx;
|
||||
background-color: #e5e7eb;
|
||||
}
|
||||
|
||||
.insight-title {
|
||||
@@ -980,33 +905,35 @@ export default {
|
||||
.insight-content {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 15rpx;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.insight-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 15rpx;
|
||||
}
|
||||
|
||||
.insight-impact {
|
||||
font-size: 22rpx;
|
||||
padding: 6rpx 12rpx;
|
||||
border-radius: 8rpx;
|
||||
color: #fff;
|
||||
padding: 4rpx 12rpx;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
|
||||
.insight-impact.high {
|
||||
background-color: #ff4444;
|
||||
background-color: #ffebee;
|
||||
color: #f44336;
|
||||
}
|
||||
|
||||
.insight-impact.medium {
|
||||
background-color: #ffa726;
|
||||
background-color: #fff3e0;
|
||||
color: #ff9800;
|
||||
}
|
||||
|
||||
.insight-impact.low {
|
||||
background-color: #4caf50;
|
||||
background-color: #e8f5e8;
|
||||
color: #4caf50;
|
||||
}
|
||||
|
||||
.insight-action {
|
||||
@@ -1018,30 +945,31 @@ export default {
|
||||
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;
|
||||
padding: 20rpx 0;
|
||||
border-bottom: 1rpx solid #eee;
|
||||
}
|
||||
|
||||
.config-label {
|
||||
font-size: 26rpx;
|
||||
font-size: 24rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.config-value {
|
||||
font-size: 24rpx;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.config-actions {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
margin-top: 30rpx;
|
||||
gap: 15rpx;
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.config-btn {
|
||||
flex: 1;
|
||||
height: 70rpx;
|
||||
padding: 15rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 26rpx;
|
||||
font-size: 24rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
@@ -1051,28 +979,22 @@ export default {
|
||||
}
|
||||
|
||||
.config-btn.reset {
|
||||
background-color: #f0f0f0;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.report-list {
|
||||
margin-top: 25rpx;
|
||||
background-color: #f44336;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.report-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 25rpx 0;
|
||||
border-bottom: 1rpx solid #f5f5f5;
|
||||
}
|
||||
|
||||
.report-item:last-child {
|
||||
border-bottom: none;
|
||||
padding: 20rpx;
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 12rpx;
|
||||
margin-bottom: 15rpx;
|
||||
}
|
||||
|
||||
.report-icon {
|
||||
font-size: 32rpx;
|
||||
margin-right: 20rpx;
|
||||
margin-right: 15rpx;
|
||||
}
|
||||
|
||||
.report-info {
|
||||
@@ -1081,35 +1003,24 @@ export default {
|
||||
|
||||
.report-name {
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
font-weight: bold;
|
||||
margin-bottom: 5rpx;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.report-desc {
|
||||
font-size: 22rpx;
|
||||
color: #666;
|
||||
margin-bottom: 5rpx;
|
||||
margin-top: 5rpx;
|
||||
}
|
||||
|
||||
.report-time {
|
||||
font-size: 20rpx;
|
||||
color: #999;
|
||||
margin-top: 5rpx;
|
||||
}
|
||||
|
||||
.report-arrow {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* 响应式:窄屏时全屏显示 */
|
||||
@media screen and (max-width: 959px) {
|
||||
.page-layout {
|
||||
flex-direction: column !important;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user