数据分析页面骨架

This commit is contained in:
comlibmb
2026-01-23 16:33:11 +08:00
parent fdbee0fa32
commit c14f67cfc8
24 changed files with 9986 additions and 986 deletions

View File

@@ -0,0 +1,651 @@
<template>
<view class="page" @click="closeMoreMenu">
<!-- 固定顶部导航栏 -->
<AnalyticsTopBar
:title="'数据分析详情'"
:lastUpdateTime="lastUpdateTime"
:sidebarVisible="showSidebarMenu"
@menu-click="handleMenu"
@refresh="refreshData"
@search="handleSearch"
@notification="handleNotification"
@fullscreen="handleFullscreen"
@mobile="handleMobile"
@dropdown="handleDropdown"
@settings="handleSettings"
/>
<view class="page-layout">
<!-- 侧边栏菜单组件 -->
<AnalyticsSidebarMenu
:visible="showSidebarMenu"
:currentPath="currentPath"
@visible-change="handleSidebarUpdate"
/>
<!-- 主内容区域 -->
<view class="main-content">
<view class="container">
<!-- 数据筛选器 -->
<view class="filter-bar">
<view class="filter-item">
<text class="filter-label">时间范围:</text>
<view class="filter-value" @click="selectTimeRange">
{{ timeRangeText }}
</view>
</view>
<view class="filter-item">
<text class="filter-label">数据维度:</text>
<view class="filter-value" @click="selectDimension">
{{ dimensionText }}
</view>
</view>
<view class="filter-item">
<text class="filter-label">对比模式:</text>
<view class="filter-value" @click="toggleCompare">
{{ compareMode ? '开启' : '关闭' }}
</view>
</view>
</view>
<!-- 详细数据表格 -->
<view class="card card-full">
<view class="card-head">
<text class="card-title">详细数据</text>
<text class="card-desc">支持排序、筛选、导出</text>
</view>
<view class="data-table">
<view class="table-header">
<view class="table-cell" v-for="col in tableColumns" :key="col.key">
<text>{{ col.label }}</text>
<text class="sort-icon" v-if="col.sortable" @click="sortBy(col.key)">⇅</text>
</view>
</view>
<view class="table-body">
<view class="table-row" v-for="row in tableData" :key="row.id">
<view class="table-cell" v-for="col in tableColumns" :key="col.key">
<text>{{ formatCellValue(row[col.key], col.type) }}</text>
</view>
</view>
</view>
</view>
</view>
<!-- 数据对比图表 -->
<view class="card card-full" v-if="compareMode">
<view class="card-head">
<text class="card-title">数据对比</text>
<text class="card-desc">当前周期 vs 对比周期</text>
</view>
<EChartsView class="chart-box" :option="compareChartOption" />
</view>
<!-- 数据钻取 -->
<view class="card">
<view class="card-head">
<text class="card-title">数据钻取</text>
<text class="card-desc">点击数据项查看详情</text>
</view>
<view class="drill-down-list">
<view v-for="item in drillDownItems" :key="item.id" class="drill-item" @click="drillDown(item)">
<text class="drill-label">{{ item.label }}</text>
<text class="drill-value">{{ item.value }}</text>
<text class="drill-arrow">→</text>
</view>
</view>
</view>
<!-- 留白 -->
<view style="height: 24px;"></view>
</view>
</view>
</view>
</view>
</template>
<script lang="uts">
import supa from '@/components/supadb/aksupainstance.uts'
import AnalyticsSidebarMenu from '@/components/analytics/AnalyticsSidebarMenu.uvue'
import EChartsView from '@/uni_modules/charts/EChartsView.vue'
type TableColumn = { key: string; label: string; type: string; sortable: boolean }
type DrillDownItem = { id: string; label: string; value: string; type: string }
export default {
components: {
AnalyticsSidebarMenu,
AnalyticsTopBar,
EChartsView
},
data() {
return {
lastUpdateTime: '',
showMoreMenu: false,
timeRangeText: '最近7天',
dimensionText: '全部',
compareMode: false,
sortKey: '',
sortOrder: 'asc',
tableColumns: [
{ key: 'date', label: '日期', type: 'date', sortable: true },
{ key: 'gmv', label: 'GMV', type: 'money', sortable: true },
{ key: 'orders', label: '订单数', type: 'number', sortable: true },
{ key: 'users', label: '用户数', type: 'number', sortable: true }
] as Array<TableColumn>,
tableData: [] as Array<any>,
drillDownItems: [] as Array<DrillDownItem>,
compareChartOption: {} as any
}
},
onLoad(options: any) {
this.currentPath = '/pages/mall/analytics/data-detail'
// 接收参数dataType, timeRange, dimension
if (options.dataType) {
// 根据数据类型加载不同的数据
}
this.updateTime()
this.loadDetailData()
},
onShow() {
this.currentPath = '/pages/mall/analytics/data-detail'
},
methods: {
async loadDetailData() {
// TODO: 实现详细数据加载
this.updateTime()
this.buildChartOptions()
},
selectTimeRange() {
uni.showActionSheet({
itemList: ['最近7天', '最近30天', '最近90天', '自定义'],
success: (res) => {
const ranges = ['最近7天', '最近30天', '最近90天', '自定义']
this.timeRangeText = ranges[res.tapIndex]
this.loadDetailData()
}
})
},
selectDimension() {
uni.showActionSheet({
itemList: ['全部', '按商家', '按分类', '按地域'],
success: (res) => {
const dims = ['全部', '按商家', '按分类', '按地域']
this.dimensionText = dims[res.tapIndex]
this.loadDetailData()
}
})
},
toggleCompare() {
this.compareMode = !this.compareMode
if (this.compareMode) {
this.buildChartOptions()
}
},
sortBy(key: string) {
if (this.sortKey === key) {
this.sortOrder = this.sortOrder === 'asc' ? 'desc' : 'asc'
} else {
this.sortKey = key
this.sortOrder = 'asc'
}
// TODO: 实现排序逻辑
},
formatCellValue(value: any, type: string): string {
if (value == null) return '-'
if (type === 'money') {
const v = Number(value)
if (v >= 10000) return (v / 10000).toFixed(1) + '万'
return v.toFixed(2)
}
if (type === 'number') {
return String(Math.round(Number(value)))
}
if (type === 'date') {
return String(value)
}
return String(value)
},
drillDown(item: DrillDownItem) {
// TODO: 实现数据钻取
uni.showToast({ title: `查看 ${item.label} 详情`, icon: 'none' })
},
refreshData() {
this.loadDetailData()
uni.showToast({ title: '已刷新', icon: 'success' })
},
exportReport() {
uni.showActionSheet({
itemList: ['导出Excel', '导出PDF', '导出CSV'],
success: () => uni.showToast({ title: '导出成功', icon: 'success' })
})
},
updateTime() {
const now = new Date()
const hh = now.getHours().toString().padStart(2, '0')
const mm = now.getMinutes().toString().padStart(2, '0')
this.lastUpdateTime = `${hh}:${mm}`
},
buildChartOptions() {
// TODO: 构建图表配置
this.compareChartOption = {}
},
handleMenu() {
this.showSidebarMenu = true
},
handleSidebarUpdate(visible: boolean) {
this.showSidebarMenu = visible
},
toggleMoreMenu() {
this.showMoreMenu = !this.showMoreMenu
},
closeMoreMenu() {
this.showMoreMenu = false
},
handleSearch() {
uni.showToast({ title: '搜索', icon: 'none' })
},
handleNotification() {
uni.showToast({ title: '通知', icon: 'none' })
},
handleFullscreen() {
uni.showToast({ title: '全屏', icon: 'none' })
},
handleMobile() {
uni.showToast({ title: '移动端', icon: 'none' })
},
handleDropdown() {
uni.showToast({ title: '下拉菜单', icon: 'none' })
},
handleSettings() {
uni.showToast({ title: '设置', icon: 'none' })
}
}
}
</script>
<style>
.page {
min-height: 100vh;
background: #f6f7fb;
}
/* 页面布局:宽屏时侧边栏+内容,窄屏时全屏内容 */
.page-layout {
display: flex;
flex-direction: row !important;
min-height: 100vh;
}
.main-content {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
padding-top: 64px; /* 为固定顶部导航栏留出空间 */
}
.container {
width: 100%;
max-width: 1280px;
margin: 0 auto;
padding: 16px 16px 28px;
box-sizing: border-box;
}
/* 顶部栏 */
.topbar {
display: flex;
flex-direction: row !important;
justify-content: space-between;
align-items: center;
gap: 12px;
padding: 14px 16px;
background: #fff;
border-radius: 14px;
border: 1px solid rgba(0,0,0,0.06);
width: 100%;
box-sizing: border-box;
overflow: hidden;
}
.topbar-left {
display: flex;
flex-direction: row !important;
align-items: center;
gap: 12px;
flex: 1;
min-width: 0;
}
.menu-icon {
width: 32px;
height: 32px;
border-radius: 8px;
background: #f3f4f6;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s;
}
.menu-icon:active {
background: #e5e7eb;
transform: scale(0.95);
}
.menu-icon .icon {
font-size: 18px;
color: #111;
line-height: 1;
}
.title-group {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 4px;
min-width: 0;
}
.title {
font-size: 18px;
font-weight: 700;
color: #111;
max-width: 420px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.subtitle {
font-size: 12px;
color: rgba(0,0,0,0.55);
max-width: 420px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.topbar-right {
display: flex;
flex-direction: row !important;
gap: 8px;
align-items: center;
flex-wrap: nowrap;
flex-shrink: 0;
position: relative;
white-space: nowrap;
}
.icon-btn-icon {
width: 32px;
height: 32px;
border-radius: 8px;
background: #f3f4f6;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s;
flex-shrink: 0;
}
.icon-btn-icon:active {
background: #e5e7eb;
transform: scale(0.95);
}
.icon-btn-icon .icon {
font-size: 16px;
line-height: 1;
}
.more-btn {
display: none;
width: 32px;
height: 32px;
border-radius: 8px;
background: #f3f4f6;
align-items: center;
justify-content: center;
cursor: pointer;
position: relative;
transition: all 0.2s;
flex-shrink: 0;
}
.more-btn.active {
background: #e5e7eb;
}
.more-btn .icon {
font-size: 18px;
line-height: 1;
color: #111;
}
/* 筛选栏 */
.filter-bar {
margin-top: 12px;
padding: 12px 16px;
background: #fff;
border-radius: 14px;
border: 1px solid rgba(0,0,0,0.06);
display: flex;
flex-direction: row !important;
gap: 16px;
flex-wrap: wrap;
align-items: center;
}
.filter-item {
display: flex;
flex-direction: row !important;
align-items: center;
gap: 8px;
}
.filter-label {
font-size: 13px;
color: rgba(0,0,0,0.65);
}
.filter-value {
padding: 6px 12px;
background: #f3f4f6;
border-radius: 6px;
font-size: 13px;
color: #111;
cursor: pointer;
transition: all 0.2s;
}
.filter-value:active {
background: #e5e7eb;
}
/* 卡片 */
.card {
margin-top: 12px;
background: #fff;
border-radius: 16px;
border: 1px solid rgba(0,0,0,0.06);
box-shadow: 0 2px 10px rgba(0,0,0,0.04);
padding: 14px;
box-sizing: border-box;
}
.card-full {
width: 100%;
}
.card-head {
display: flex;
flex-direction: row !important;
align-items: baseline;
justify-content: space-between;
margin-bottom: 16px;
}
.card-title {
font-size: 14px;
font-weight: 600;
color: #111;
}
.card-desc {
font-size: 12px;
color: rgba(0,0,0,0.55);
}
.chart-box {
width: 100%;
height: 360px;
}
/* 数据表格 */
.data-table {
width: 100%;
overflow-x: auto;
}
.table-header {
display: flex;
flex-direction: row !important;
background: #f9fafb;
border-radius: 8px;
padding: 10px 0;
}
.table-row {
display: flex;
flex-direction: row !important;
border-bottom: 1px solid rgba(0,0,0,0.06);
padding: 10px 0;
}
.table-row:last-child {
border-bottom: none;
}
.table-cell {
flex: 1;
padding: 0 12px;
font-size: 13px;
color: #111;
display: flex;
flex-direction: row !important;
align-items: center;
gap: 4px;
min-width: 100px;
}
.table-header .table-cell {
font-weight: 600;
color: rgba(0,0,0,0.65);
}
.sort-icon {
font-size: 12px;
color: rgba(0,0,0,0.45);
cursor: pointer;
}
/* 数据钻取 */
.drill-down-list {
display: flex;
flex-direction: column;
gap: 10px;
}
.drill-item {
display: flex;
flex-direction: row !important;
align-items: center;
gap: 12px;
padding: 12px;
background: #f9fafb;
border-radius: 8px;
cursor: pointer;
transition: all 0.2s;
}
.drill-item:active {
background: #f3f4f6;
}
.drill-label {
flex: 1;
font-size: 13px;
color: #111;
}
.drill-value {
font-size: 13px;
font-weight: 600;
color: #111;
}
.drill-arrow {
font-size: 14px;
color: rgba(0,0,0,0.45);
}
/* 响应式:窄屏时全屏显示 */
@media screen and (max-width: 959px) {
.page-layout {
flex-direction: column !important;
}
.main-content {
width: 100%;
}
}
/* 响应式 */
@media screen and (max-width: 960px) {
.title,
.subtitle {
max-width: 200px;
}
.topbar-right .btn-hidden {
display: none !important;
}
.more-btn {
display: flex !important;
}
.filter-bar {
flex-direction: column;
align-items: flex-start;
}
.data-table {
overflow-x: scroll;
}
}
</style>