Files
medical-mall/pages/mall/analytics/data-detail.uvue
2026-01-26 21:34:17 +08:00

653 lines
14 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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 AnalyticsTopBar from '@/components/analytics/AnalyticsTopBar.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>