数据分析ui补充完善,接入数据库
This commit is contained in:
@@ -159,7 +159,7 @@
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="uts">
|
||||
<script setup lang="uts">
|
||||
import supa, { ensureSupabaseReady } from '@/components/supadb/aksupainstance.uts'
|
||||
import AnalyticsSidebarMenu from '@/components/analytics/AnalyticsSidebarMenu.uvue'
|
||||
import AnalyticsTopBar from '@/components/analytics/AnalyticsTopBar.uvue'
|
||||
@@ -168,38 +168,358 @@ import { getUserIdOrNull } from '@/services/analytics/auth.uts'
|
||||
import { listCustomReports, createCustomReport, updateCustomReport, deleteCustomReport } from '@/services/analytics/customReportService.uts'
|
||||
import { mapAnalyticsError } from '@/services/analytics/errorMapper.uts'
|
||||
|
||||
type Report = {
|
||||
id: string
|
||||
name: string
|
||||
description: string
|
||||
metrics: Array<string>
|
||||
charts: Array<string>
|
||||
updated_at: string
|
||||
}
|
||||
type Metric = { key: string; label: string }
|
||||
type TimePeriod = { value: string; label: string }
|
||||
type ChartType = { value: string; label: string }
|
||||
type ReportForm = {
|
||||
name: string
|
||||
description: string
|
||||
metrics: Array<string>
|
||||
period: string
|
||||
chartType: string
|
||||
}
|
||||
type ReportFormErrors = {
|
||||
name: string
|
||||
description: string
|
||||
metrics: string
|
||||
period: string
|
||||
chartType: string
|
||||
import { onLoad, onShow, reactive, ref } from 'vue'
|
||||
|
||||
import type { CustomReport, ReportForm, ReportFormErrors } from '@/types/analytics/custom-report.uts'
|
||||
import type { Metric, TimePeriod, ChartType } from '@/types/analytics/common.uts'
|
||||
|
||||
const showMoreMenu = ref(false)
|
||||
const showSidebarMenu = ref(false)
|
||||
const currentPath = ref('/pages/mall/analytics/custom-report')
|
||||
const showCreateModal = ref(false)
|
||||
const editingReport = ref<CustomReport | null>(null)
|
||||
|
||||
const reports = reactive<Array<CustomReport>>([])
|
||||
const isLoggedIn = ref(false)
|
||||
|
||||
const reportForm = reactive<ReportForm>({
|
||||
name: '',
|
||||
description: '',
|
||||
metrics: [] as Array<string>,
|
||||
period: '7d',
|
||||
chartType: 'line'
|
||||
})
|
||||
|
||||
const formErrors = reactive<ReportFormErrors>({
|
||||
name: '',
|
||||
description: '',
|
||||
metrics: '',
|
||||
period: '',
|
||||
chartType: ''
|
||||
})
|
||||
|
||||
const availableMetrics = ref<Array<Metric>>([
|
||||
{ key: 'gmv', label: 'GMV' },
|
||||
{ key: 'orders', label: '订单数' },
|
||||
{ key: 'users', label: '用户数' },
|
||||
{ key: 'conversion', label: '转化率' },
|
||||
{ key: 'avg_order', label: '客单价' },
|
||||
{ key: 'repurchase', label: '复购率' }
|
||||
])
|
||||
|
||||
const timePeriods = ref<Array<TimePeriod>>([
|
||||
{ value: '7d', label: '7天' },
|
||||
{ value: '30d', label: '30天' },
|
||||
{ value: '90d', label: '90天' },
|
||||
{ value: '1y', label: '1年' }
|
||||
])
|
||||
|
||||
const chartTypes = ref<Array<ChartType>>([
|
||||
{ value: 'line', label: '折线图' },
|
||||
{ value: 'bar', label: '柱状图' },
|
||||
{ value: 'pie', label: '饼图' },
|
||||
{ value: 'area', label: '面积图' },
|
||||
{ value: 'combo', label: '组合图' }
|
||||
])
|
||||
|
||||
onLoad(() => {
|
||||
currentPath.value = '/pages/mall/analytics/custom-report'
|
||||
loadReports()
|
||||
})
|
||||
|
||||
onShow(() => {
|
||||
currentPath.value = '/pages/mall/analytics/custom-report'
|
||||
})
|
||||
|
||||
async function loadReports() {
|
||||
try {
|
||||
await ensureSupabaseReady()
|
||||
|
||||
const uid = getUserIdOrNull()
|
||||
if (!uid || uid.length === 0) {
|
||||
isLoggedIn.value = false
|
||||
reports.splice(0, reports.length)
|
||||
return
|
||||
}
|
||||
|
||||
isLoggedIn.value = true
|
||||
|
||||
const items = await listCustomReports(uid)
|
||||
const list: Array<CustomReport> = []
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
const r = items[i]
|
||||
list.push({
|
||||
id: `${r.id}`,
|
||||
name: `${r.title}`,
|
||||
description: `${r.description || ''}`,
|
||||
metrics: [] as Array<string>,
|
||||
charts: [] as Array<string>,
|
||||
updated_at: `${r.updated_at || ''}`
|
||||
} as CustomReport)
|
||||
}
|
||||
|
||||
reports.splice(0, reports.length, ...list)
|
||||
} catch (e) {
|
||||
console.error('loadReports failed', e)
|
||||
uni.showToast({ title: '报表加载失败', icon: 'none' })
|
||||
}
|
||||
}
|
||||
|
||||
function createReport() {
|
||||
editingReport.value = null
|
||||
|
||||
reportForm.name = ''
|
||||
reportForm.description = ''
|
||||
reportForm.metrics = [] as Array<string>
|
||||
reportForm.period = '7d'
|
||||
reportForm.chartType = 'line'
|
||||
|
||||
formErrors.name = ''
|
||||
formErrors.description = ''
|
||||
formErrors.metrics = ''
|
||||
formErrors.period = ''
|
||||
formErrors.chartType = ''
|
||||
|
||||
showCreateModal.value = true
|
||||
}
|
||||
|
||||
function editReport(report: CustomReport) {
|
||||
editingReport.value = report
|
||||
|
||||
reportForm.name = report.name
|
||||
reportForm.description = report.description
|
||||
reportForm.metrics = report.metrics
|
||||
reportForm.period = '7d'
|
||||
reportForm.chartType = 'line'
|
||||
|
||||
formErrors.name = ''
|
||||
formErrors.description = ''
|
||||
formErrors.metrics = ''
|
||||
formErrors.period = ''
|
||||
formErrors.chartType = ''
|
||||
|
||||
showCreateModal.value = true
|
||||
}
|
||||
|
||||
function deleteReport(report: CustomReport) {
|
||||
uni.showModal({
|
||||
title: '确认删除',
|
||||
content: `确定要删除报表"${report.name}"吗?`,
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
doDeleteReport(report)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function doDeleteReport(report: CustomReport) {
|
||||
try {
|
||||
await ensureSupabaseReady()
|
||||
|
||||
await deleteCustomReport(report.id)
|
||||
uni.showToast({ title: '删除成功', icon: 'success' })
|
||||
loadReports()
|
||||
} catch (e: any) {
|
||||
console.error('doDeleteReport failed', e)
|
||||
const errorMsg = e?.message || '删除失败'
|
||||
uni.showToast({ title: errorMsg, icon: 'none' })
|
||||
}
|
||||
}
|
||||
|
||||
function toggleMetric(key: string) {
|
||||
const index = reportForm.metrics.indexOf(key)
|
||||
if (index >= 0) {
|
||||
reportForm.metrics.splice(index, 1)
|
||||
} else {
|
||||
reportForm.metrics.push(key)
|
||||
}
|
||||
if (reportForm.metrics.length > 0) {
|
||||
formErrors.metrics = ''
|
||||
}
|
||||
}
|
||||
|
||||
function onNameInput() {
|
||||
const name = reportForm.name.trim()
|
||||
if (name.length === 0) {
|
||||
formErrors.name = '报表名称不能为空'
|
||||
} else if (name.length > 50) {
|
||||
formErrors.name = '报表名称不能超过50个字符'
|
||||
} else {
|
||||
formErrors.name = ''
|
||||
}
|
||||
}
|
||||
|
||||
function onDescriptionInput() {
|
||||
const desc = reportForm.description
|
||||
if (desc.length > 200) {
|
||||
formErrors.description = '报表描述不能超过200个字符'
|
||||
} else {
|
||||
formErrors.description = ''
|
||||
}
|
||||
}
|
||||
|
||||
function selectPeriod(value: string) {
|
||||
reportForm.period = value
|
||||
formErrors.period = ''
|
||||
}
|
||||
|
||||
function selectChartType(value: string) {
|
||||
reportForm.chartType = value
|
||||
formErrors.chartType = ''
|
||||
}
|
||||
|
||||
function validateReportForm(): boolean {
|
||||
onNameInput()
|
||||
onDescriptionInput()
|
||||
|
||||
if (reportForm.metrics.length === 0) {
|
||||
formErrors.metrics = '请至少选择一个指标'
|
||||
} else {
|
||||
formErrors.metrics = ''
|
||||
}
|
||||
|
||||
if (!reportForm.period) {
|
||||
formErrors.period = '请选择时间维度'
|
||||
}
|
||||
if (!reportForm.chartType) {
|
||||
formErrors.chartType = '请选择图表类型'
|
||||
}
|
||||
|
||||
if (formErrors.name || formErrors.description || formErrors.metrics || formErrors.period || formErrors.chartType) {
|
||||
uni.showToast({ title: '请先修正表单中的错误提示', icon: 'none' })
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
async function saveReport() {
|
||||
if (!validateReportForm()) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
uni.showLoading({ title: '保存中...' })
|
||||
await ensureSupabaseReady()
|
||||
|
||||
const uid = getUserIdOrNull()
|
||||
if (!uid || uid.length === 0) {
|
||||
uni.hideLoading()
|
||||
uni.showModal({
|
||||
title: '需要登录',
|
||||
content: '创建自定义报表需要先登录,是否前往登录页面?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
goToLogin('/pages/mall/analytics/custom-report')
|
||||
}
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
let newReportId = ''
|
||||
|
||||
if (editingReport.value == null) {
|
||||
newReportId = await createCustomReport({
|
||||
title: reportForm.name,
|
||||
description: reportForm.description || '',
|
||||
period: reportForm.period,
|
||||
metrics: reportForm.metrics,
|
||||
chartType: reportForm.chartType || 'line'
|
||||
})
|
||||
} else {
|
||||
await updateCustomReport({
|
||||
reportId: editingReport.value.id,
|
||||
title: reportForm.name,
|
||||
description: reportForm.description || null,
|
||||
period: reportForm.period || null
|
||||
})
|
||||
newReportId = editingReport.value.id
|
||||
}
|
||||
|
||||
uni.hideLoading()
|
||||
uni.showToast({ title: '保存成功', icon: 'success' })
|
||||
closeModal()
|
||||
loadReports()
|
||||
|
||||
if (newReportId.length > 0) {
|
||||
setTimeout(() => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/mall/analytics/report-detail?reportId=${newReportId}`
|
||||
})
|
||||
}, 400)
|
||||
}
|
||||
} catch (e: any) {
|
||||
uni.hideLoading()
|
||||
console.error('saveReport exception:', e)
|
||||
uni.showToast({
|
||||
title: mapAnalyticsError(e, { fallbackMessage: '保存失败' }),
|
||||
icon: 'none',
|
||||
duration: 3000
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function openReport(report: CustomReport) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/mall/analytics/report-detail?reportId=${report.id}`
|
||||
})
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
showCreateModal.value = false
|
||||
editingReport.value = null
|
||||
}
|
||||
|
||||
function refreshData() {
|
||||
loadReports()
|
||||
uni.showToast({ title: '已刷新', icon: 'success' })
|
||||
}
|
||||
|
||||
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' })
|
||||
}
|
||||
|
||||
function goToLogin() {
|
||||
goToLogin('/pages/mall/analytics/custom-report')
|
||||
}
|
||||
|
||||
export default {
|
||||
components: {
|
||||
AnalyticsSidebarMenu,
|
||||
AnalyticsTopBar
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showMoreMenu: false,
|
||||
showSidebarMenu: false,
|
||||
@@ -498,7 +818,7 @@ export default {
|
||||
|
||||
openReport(report: Report) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/mall/analytics/report-detail?id=${report.id}`
|
||||
url: `/pages/mall/analytics/report-detail?reportId=${report.id}`
|
||||
})
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user