Files
medical-mall/pages/mall/analytics/insight-detail.uvue
2026-02-01 20:17:37 +08:00

294 lines
7.7 KiB
Plaintext

<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="card card-full">
<view class="card-head">
<text class="card-title">{{ insight.title || '洞察详情' }}</text>
<view class="meta-row">
<text class="badge" :class="'badge-' + (insight.type || 'info')">{{ getInsightTypeText(insight.type) }}</text>
<text class="badge badge-impact" :class="'impact-' + (insight.impact || 'medium')">{{ getImpactText(insight.impact) }}</text>
<text class="meta-time" v-if="insight.created_at">{{ formatTime(insight.created_at) }}</text>
</view>
</view>
<view v-if="loading" class="state">
<text class="state-text">加载中...</text>
</view>
<view v-else-if="errorMsg" class="state">
<text class="state-text">{{ errorMsg }}</text>
</view>
<view v-else class="content">
<text class="content-text">{{ insight.content }}</text>
</view>
</view>
<view class="card" v-if="relatedReport.id">
<view class="card-head">
<text class="card-title">关联报表</text>
<text class="card-desc">{{ relatedReport.type }} · {{ relatedReport.period }}</text>
</view>
<view class="report-row" @click="goToReportDetail">
<view class="report-icon">📄</view>
<view class="report-info">
<text class="report-title">{{ relatedReport.title }}</text>
<text class="report-time">{{ relatedReport.generated_at ? formatTime(relatedReport.generated_at) : '' }}</text>
</view>
<text class="report-arrow">></text>
</view>
</view>
<view style="height: 24px;"></view>
</view>
</view>
</view>
</view>
</template>
<script setup lang="uts">
import { reactive, ref } from 'vue'
import { onLoad, onShow } from '@dcloudio/uni-app'
import AnalyticsSidebarMenu from '@/components/analytics/AnalyticsSidebarMenu.uvue'
import AnalyticsTopBar from '@/components/analytics/AnalyticsTopBar.uvue'
import { fetchInsightDetail, fetchRelatedReport } from '@/services/analytics/insightDetailService.uts'
import { mapAnalyticsError } from '@/services/analytics/errorMapper.uts'
import type { InsightDetail, RelatedReport } from '@/types/analytics/insight.uts'
const lastUpdateTime = ref('')
const showMoreMenu = ref(false)
const showSidebarMenu = ref(false)
const currentPath = ref('/pages/mall/analytics/insight-detail')
const insightId = ref('')
const loading = ref(false)
const errorMsg = ref('')
const insight = reactive<InsightDetail>({
id: '',
report_id: '',
type: 'info',
impact: 'medium',
title: '',
content: '',
created_at: ''
})
const relatedReport = reactive<RelatedReport>({
id: '',
title: '',
type: '',
period: '',
generated_at: ''
})
onLoad((options: any) => {
currentPath.value = '/pages/mall/analytics/insight-detail'
updateTime()
const iid = (options.insightId || options.id) as string
if (!iid) {
uni.showToast({ title: '缺少洞察ID', icon: 'none' })
setTimeout(() => uni.navigateBack(), 1500)
return
}
insightId.value = iid
void loadInsightDetail()
})
onShow(() => {
currentPath.value = '/pages/mall/analytics/insight-detail'
})
async function loadInsightDetail() {
try {
loading.value = true
errorMsg.value = ''
updateTime()
const data = await fetchInsightDetail(insightId.value)
if (data == null) {
errorMsg.value = '洞察不存在或无权限访问'
return
}
insight.id = data.id
insight.report_id = data.report_id
insight.type = data.type
insight.impact = data.impact
insight.title = data.title
insight.content = data.content
insight.created_at = data.created_at
relatedReport.id = ''
relatedReport.title = ''
relatedReport.type = ''
relatedReport.period = ''
relatedReport.generated_at = ''
if (insight.report_id) {
try {
const related = await fetchRelatedReport(insight.report_id)
if (related != null) {
relatedReport.id = related.id
relatedReport.title = related.title
relatedReport.type = related.type
relatedReport.period = related.period
relatedReport.generated_at = related.generated_at
}
} catch (e) {
console.error('loadInsightDetail related report error', e)
}
}
} catch (e) {
console.error('loadInsightDetail failed', e)
errorMsg.value = mapAnalyticsError(e, { fallbackMessage: '加载失败,请稍后重试' })
} finally {
loading.value = false
}
}
function refreshData() {
void loadInsightDetail()
uni.showToast({ title: '已刷新', icon: 'success' })
}
function exportReport() {
uni.showActionSheet({
itemList: ['导出Excel', '导出PDF', '导出图片'],
success: () => uni.showToast({ title: '导出成功', icon: 'success' })
})
}
function updateTime() {
const now = new Date()
const hh = now.getHours().toString().padStart(2, '0')
const mm = now.getMinutes().toString().padStart(2, '0')
lastUpdateTime.value = `${hh}:${mm}`
}
function formatTime(timeStr: string): string {
if (!timeStr) return ''
return `${timeStr}`.replace('T', ' ').split('.')[0]
}
function getInsightTypeText(type: string): string {
const t = `${type || 'info'}`
const map: Record<string, string> = {
positive: '正向',
warning: '预警',
negative: '风险',
info: '信息'
}
return map[t] || '信息'
}
function getImpactText(impact: string): string {
const impacts: Record<string, string> = {
high: '高影响',
medium: '中影响',
low: '低影响'
}
return impacts[impact || 'medium'] || '中影响'
}
function goToReportDetail() {
if (!relatedReport.id) return
uni.navigateTo({
url: `/pages/mall/analytics/report-detail?reportId=${relatedReport.id}`
})
}
function handleMenu() {
showSidebarMenu.value = true
}
function handleSidebarUpdate(visible: boolean) {
showSidebarMenu.value = visible
}
function toggleMoreMenu() {
showMoreMenu.value = !showMoreMenu.value
}
function closeMoreMenu() {
showMoreMenu.value = false
}
function handleSearch() {
uni.showToast({ title: '搜索', icon: 'none' })
}
function handleNotification() {
uni.showToast({ title: '通知', icon: 'none' })
}
function handleFullscreen() {
uni.showToast({ title: '全屏', icon: 'none' })
}
function handleMobile() {
uni.showToast({ title: '移动端', icon: 'none' })
}
function handleDropdown() {
uni.showToast({ title: '下拉菜单', icon: 'none' })
}
function 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;
}
</style>