数据分析ui补充完善,接入数据库
This commit is contained in:
@@ -266,14 +266,7 @@ import supa from '@/components/supadb/aksupainstance.uts'
|
||||
import AnalyticsSidebarMenu from '@/components/analytics/AnalyticsSidebarMenu.uvue'
|
||||
import type { UserType } from '@/types/mall-types'
|
||||
|
||||
// 报表类型定义
|
||||
type ReportType = {
|
||||
id: string
|
||||
title: string
|
||||
description: string
|
||||
status: string
|
||||
created_at: string
|
||||
}
|
||||
import type { RecentReport, OverviewData, ReportCounts, TodayInsights, TrendDatum } from '@/types/analytics/profile.uts'
|
||||
|
||||
// 响应式数据
|
||||
const showSidebarMenu = ref(false)
|
||||
@@ -293,7 +286,7 @@ const analystInfo = ref({
|
||||
const workExperience = ref(5)
|
||||
const expertise = ref('电商数据')
|
||||
|
||||
const overviewData = ref({
|
||||
const overviewData = ref<OverviewData>({
|
||||
totalSales: '0',
|
||||
salesGrowth: 0,
|
||||
totalUsers: '0',
|
||||
@@ -304,24 +297,24 @@ const overviewData = ref({
|
||||
conversionGrowth: 0
|
||||
})
|
||||
|
||||
const reportCounts = ref({
|
||||
const reportCounts = ref<ReportCounts>({
|
||||
total: 0,
|
||||
pending: 0,
|
||||
scheduled: 0,
|
||||
shared: 0
|
||||
})
|
||||
|
||||
const todayInsights = ref({
|
||||
const todayInsights = ref<TodayInsights>({
|
||||
hotProduct: '-',
|
||||
peakTraffic: '0',
|
||||
conversionAnomaly: '-',
|
||||
mobileRatio: 0
|
||||
})
|
||||
|
||||
const recentReports = ref([] as Array<ReportType>)
|
||||
const recentReports = ref<Array<RecentReport>>([])
|
||||
|
||||
const trendPeriod = ref('week')
|
||||
const trendData = ref([
|
||||
const trendData = ref<Array<TrendDatum>>([
|
||||
{ label: '周一', sales: 0, orders: 0 },
|
||||
{ label: '周二', sales: 0, orders: 0 },
|
||||
{ label: '周三', sales: 0, orders: 0 },
|
||||
@@ -457,85 +450,30 @@ async function loadRecentReports() {
|
||||
async function loadOverview() {
|
||||
try {
|
||||
const now = new Date()
|
||||
const end = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1) // < end
|
||||
const start = new Date(end.getTime() - 30 * 24 * 60 * 60 * 1000)
|
||||
const prevEnd = start
|
||||
const prevStart = new Date(prevEnd.getTime() - 30 * 24 * 60 * 60 * 1000)
|
||||
const end = new Date(now.getFullYear(), now.getMonth(), now.getDate())
|
||||
const start = new Date(end.getTime() - 29 * 24 * 60 * 60 * 1000) // Last 30 days
|
||||
|
||||
const curRes: any = await supa
|
||||
.from('orders')
|
||||
.select('total_amount, user_id, created_at')
|
||||
.gte('created_at', start.toISOString())
|
||||
.lt('created_at', end.toISOString())
|
||||
.eq('status', 2)
|
||||
const prevRes: any = await supa
|
||||
.from('orders')
|
||||
.select('total_amount, user_id, created_at')
|
||||
.gte('created_at', prevStart.toISOString())
|
||||
.lt('created_at', prevEnd.toISOString())
|
||||
.eq('status', 2)
|
||||
const p = new UTSJSONObject()
|
||||
p.set('p_start_date', dateISO(start))
|
||||
p.set('p_end_date', dateISO(end))
|
||||
|
||||
const curOrders: Array<any> = Array.isArray(curRes.data) ? (curRes.data as Array<any>) : []
|
||||
const prevOrders: Array<any> = Array.isArray(prevRes.data) ? (prevRes.data as Array<any>) : []
|
||||
const [salesKpisRes, userKpisRes] = await Promise.all([
|
||||
supa.rpc('rpc_analytics_sales_kpis', p),
|
||||
supa.rpc('rpc_analytics_user_kpis', p)
|
||||
])
|
||||
|
||||
let curSales = 0
|
||||
let prevSales = 0
|
||||
const curUsers: Record<string, boolean> = {}
|
||||
const prevUsers: Record<string, boolean> = {}
|
||||
for (let i = 0; i < curOrders.length; i++) {
|
||||
curSales += safeNumber(curOrders[i].total_amount)
|
||||
const uid = `${curOrders[i].user_id || ''}`
|
||||
if (uid) curUsers[uid] = true
|
||||
}
|
||||
for (let i = 0; i < prevOrders.length; i++) {
|
||||
prevSales += safeNumber(prevOrders[i].total_amount)
|
||||
const uid = `${prevOrders[i].user_id || ''}`
|
||||
if (uid) prevUsers[uid] = true
|
||||
}
|
||||
|
||||
const curOrderCnt = curOrders.length
|
||||
const prevOrderCnt = prevOrders.length
|
||||
const curUserCnt = Object.keys(curUsers).length
|
||||
const prevUserCnt = Object.keys(prevUsers).length
|
||||
|
||||
// 转化率:下单用户 / 访问用户(用 user_sessions 近30天会话去重近似)
|
||||
const curSessRes: any = await supa
|
||||
.from('user_sessions')
|
||||
.select('user_id, created_at')
|
||||
.gte('created_at', start.toISOString())
|
||||
.lt('created_at', end.toISOString())
|
||||
const prevSessRes: any = await supa
|
||||
.from('user_sessions')
|
||||
.select('user_id, created_at')
|
||||
.gte('created_at', prevStart.toISOString())
|
||||
.lt('created_at', prevEnd.toISOString())
|
||||
|
||||
const curSess: Array<any> = Array.isArray(curSessRes.data) ? (curSessRes.data as Array<any>) : []
|
||||
const prevSess: Array<any> = Array.isArray(prevSessRes.data) ? (prevSessRes.data as Array<any>) : []
|
||||
const curVisitUsers: Record<string, boolean> = {}
|
||||
const prevVisitUsers: Record<string, boolean> = {}
|
||||
for (let i = 0; i < curSess.length; i++) {
|
||||
const uid = `${curSess[i].user_id || ''}`
|
||||
if (uid) curVisitUsers[uid] = true
|
||||
}
|
||||
for (let i = 0; i < prevSess.length; i++) {
|
||||
const uid = `${prevSess[i].user_id || ''}`
|
||||
if (uid) prevVisitUsers[uid] = true
|
||||
}
|
||||
const curVisitCnt = Object.keys(curVisitUsers).length
|
||||
const prevVisitCnt = Object.keys(prevVisitUsers).length
|
||||
const curConv = curVisitCnt > 0 ? (curUserCnt / curVisitCnt) * 100 : 0
|
||||
const prevConv = prevVisitCnt > 0 ? (prevUserCnt / prevVisitCnt) * 100 : 0
|
||||
const salesKpis = Array.isArray(salesKpisRes.data) && salesKpisRes.data.length > 0 ? salesKpisRes.data[0] : {}
|
||||
const userKpis = Array.isArray(userKpisRes.data) && userKpisRes.data.length > 0 ? userKpisRes.data[0] : {}
|
||||
|
||||
overviewData.value = {
|
||||
totalSales: fmtMoney(curSales),
|
||||
salesGrowth: safeNumber(pctGrowth(curSales, prevSales)),
|
||||
totalUsers: fmtInt(curUserCnt),
|
||||
userGrowth: safeNumber(pctGrowth(curUserCnt, prevUserCnt)),
|
||||
totalOrders: fmtInt(curOrderCnt),
|
||||
orderGrowth: safeNumber(pctGrowth(curOrderCnt, prevOrderCnt)),
|
||||
conversionRate: safeNumber(curConv),
|
||||
conversionGrowth: safeNumber(pctGrowth(curConv, prevConv))
|
||||
totalSales: fmtMoney(safeNumber(salesKpis.gmv)),
|
||||
salesGrowth: safeNumber(salesKpis.gmv_growth),
|
||||
totalUsers: fmtInt(safeNumber(userKpis.new_users)), // Use new_users for the period
|
||||
userGrowth: safeNumber(userKpis.new_user_growth),
|
||||
totalOrders: fmtInt(safeNumber(salesKpis.orders)),
|
||||
orderGrowth: safeNumber(salesKpis.order_growth),
|
||||
conversionRate: safeNumber(salesKpis.conversion_rate),
|
||||
conversionGrowth: safeNumber(salesKpis.conversion_growth)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('loadOverview failed', e)
|
||||
@@ -551,8 +489,7 @@ async function loadTrend() {
|
||||
const p = new UTSJSONObject()
|
||||
p.set('p_start_date', dateISO(start))
|
||||
p.set('p_end_date', dateISO(end))
|
||||
p.set('p_merchant_id', null)
|
||||
const res: any = await supa.rpc('rpc_analytics_trend_data', p)
|
||||
const res: any = await supa.rpc('rpc_analytics_sales_trend', p)
|
||||
const rows: Array<any> = Array.isArray(res.data) ? (res.data as Array<any>) : []
|
||||
|
||||
const weekLabels = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
|
||||
@@ -571,8 +508,7 @@ async function loadTrend() {
|
||||
const p = new UTSJSONObject()
|
||||
p.set('p_start_date', dateISO(start))
|
||||
p.set('p_end_date', dateISO(new Date(now.getFullYear(), now.getMonth(), now.getDate())))
|
||||
p.set('p_merchant_id', null)
|
||||
const res: any = await supa.rpc('rpc_analytics_trend_data', p)
|
||||
const res: any = await supa.rpc('rpc_analytics_sales_trend', p)
|
||||
const rows: Array<any> = Array.isArray(res.data) ? (res.data as Array<any>) : []
|
||||
|
||||
const buckets: Record<string, { sales: number; orders: number }> = {}
|
||||
@@ -595,8 +531,7 @@ async function loadTrend() {
|
||||
const p = new UTSJSONObject()
|
||||
p.set('p_start_date', dateISO(start))
|
||||
p.set('p_end_date', dateISO(end))
|
||||
p.set('p_merchant_id', null)
|
||||
const res: any = await supa.rpc('rpc_analytics_trend_data', p)
|
||||
const res: any = await supa.rpc('rpc_analytics_sales_trend', p)
|
||||
const rows: Array<any> = Array.isArray(res.data) ? (res.data as Array<any>) : []
|
||||
|
||||
const buckets: Record<string, { sales: number; orders: number }> = {}
|
||||
@@ -628,32 +563,23 @@ async function loadTodayInsights() {
|
||||
p.set('p_start_date', dateISO(today0))
|
||||
p.set('p_end_date', dateISO(today0))
|
||||
p.set('p_limit', 1)
|
||||
p.set('p_merchant_id', null)
|
||||
const prodRes: any = await supa.rpc('rpc_analytics_top_products', p)
|
||||
const prodRows: Array<any> = Array.isArray(prodRes.data) ? (prodRes.data as Array<any>) : []
|
||||
if (prodRows.length > 0) {
|
||||
todayInsights.value.hotProduct = `${prodRows[0].name}`
|
||||
}
|
||||
|
||||
// 访问量峰值(简化:今日总访问量)
|
||||
// 访问量峰值:用今日浏览行为数近似(主库口径:ml_browse_history)
|
||||
const pvRes: any = await supa
|
||||
.from('page_views')
|
||||
.from('ml_browse_history')
|
||||
.select('id, created_at')
|
||||
.gte('created_at', today0.toISOString())
|
||||
.lt('created_at', new Date(today0.getTime() + 24 * 60 * 60 * 1000).toISOString())
|
||||
const pvRows: Array<any> = Array.isArray(pvRes.data) ? (pvRes.data as Array<any>) : []
|
||||
todayInsights.value.peakTraffic = fmtInt(pvRows.length)
|
||||
|
||||
// 转化异常:取今日 KPI 增长(简化,负数提示“下降xx%”)
|
||||
const kpiP = new UTSJSONObject()
|
||||
kpiP.set('p_start', today0.toISOString())
|
||||
kpiP.set('p_end', now.toISOString())
|
||||
const ySame = new Date(now.getTime() - 24 * 60 * 60 * 1000)
|
||||
const y0 = new Date(ySame.getFullYear(), ySame.getMonth(), ySame.getDate())
|
||||
kpiP.set('p_compare_start', y0.toISOString())
|
||||
kpiP.set('p_compare_end', ySame.toISOString())
|
||||
kpiP.set('p_merchant_id', null)
|
||||
const kpiRes: any = await supa.rpc('rpc_analytics_realtime_kpis', kpiP)
|
||||
// 转化异常:使用无参版 rpc_analytics_realtime_kpis
|
||||
const kpiRes: any = await supa.rpc('rpc_analytics_realtime_kpis', {} as any)
|
||||
const row = Array.isArray(kpiRes.data) && kpiRes.data.length > 0 ? kpiRes.data[0] : (kpiRes.data || {})
|
||||
const cg = safeNumber(row.conversion_growth)
|
||||
todayInsights.value.conversionAnomaly = cg < 0 ? `下降${Math.abs(cg).toFixed(1)}%` : `上升${cg.toFixed(1)}%`
|
||||
|
||||
Reference in New Issue
Block a user