mall数据库文件
This commit is contained in:
@@ -259,11 +259,12 @@
|
||||
</template>
|
||||
|
||||
<script lang="uts">
|
||||
import supa from '@/components/supadb/aksupainstance.uts'
|
||||
import AnalyticsComboChart from '@/components/analytics/AnalyticsComboChart.uvue'
|
||||
import AnalyticsSidebarMenu from '@/components/analytics/AnalyticsSidebarMenu.uvue'
|
||||
import AnalyticsTopBar from '@/components/analytics/AnalyticsTopBar.uvue'
|
||||
import EChartsView from '@/uni_modules/charts/EChartsView.vue'
|
||||
import { fetchDashboardRealtime, fetchDashboardTrend, fetchDashboardUserSegments, fetchDashboardTrafficSources, fetchDashboardTopProducts, fetchDashboardTopMerchants } from '@/services/analytics/dashboardService.uts'
|
||||
import { mapAnalyticsError } from '@/services/analytics/errorMapper.uts'
|
||||
|
||||
type TrendData = { x: Array<string>; gmv: Array<number>; orders: Array<number> }
|
||||
type SegmentItem = { name: string; value: number }
|
||||
@@ -424,7 +425,7 @@ type TopMerchantItem = { id: string; rank: number; name: string; sales: number;
|
||||
})
|
||||
} catch (e) {
|
||||
console.error('❌ refreshAll failed', e)
|
||||
uni.showToast({ title: '数据加载失败', icon: 'none' })
|
||||
uni.showToast({ title: mapAnalyticsError(e, { fallbackMessage: '数据加载失败' }), icon: 'none' })
|
||||
}
|
||||
},
|
||||
|
||||
@@ -506,54 +507,9 @@ type TopMerchantItem = { id: string; rank: number; name: string; sales: number;
|
||||
|
||||
async loadTrend() {
|
||||
try {
|
||||
const { startDate, endDate } = this.calcDateRange()
|
||||
const p = new UTSJSONObject()
|
||||
p.set('p_start_date', startDate.toISOString().slice(0, 10))
|
||||
p.set('p_end_date', endDate.toISOString().slice(0, 10))
|
||||
p.set('p_merchant_id', null)
|
||||
|
||||
console.log('📊 loadTrend: 请求参数', {
|
||||
start_date: startDate.toISOString().slice(0, 10),
|
||||
end_date: endDate.toISOString().slice(0, 10)
|
||||
})
|
||||
|
||||
const res: any = await supa.rpc('rpc_analytics_trend_data', p)
|
||||
console.log('📊 loadTrend: RPC 返回结果', res)
|
||||
|
||||
// 检查返回结构:可能是 res.data 或 res 本身
|
||||
let rows: Array<any> = []
|
||||
if (Array.isArray(res.data)) {
|
||||
rows = res.data as Array<any>
|
||||
} else if (Array.isArray(res)) {
|
||||
rows = res as Array<any>
|
||||
} else if (res && typeof res === 'object') {
|
||||
// 可能是 { data: [...] } 或其他结构
|
||||
const data = res.data || res.rows || res.result || []
|
||||
rows = Array.isArray(data) ? data : []
|
||||
}
|
||||
|
||||
console.log('📊 loadTrend: 解析后的 rows', rows, '数量:', rows.length)
|
||||
|
||||
const x: Array<string> = []
|
||||
const gmv: Array<number> = []
|
||||
const orders: Array<number> = []
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
const row = rows[i]
|
||||
const d = `${row.date || row.day || row.date_key}` // 兼容不同字段名
|
||||
if (d && d.length >= 10) {
|
||||
x.push(d.slice(5)) // MM-DD
|
||||
} else {
|
||||
x.push(`${i + 1}`)
|
||||
}
|
||||
gmv.push(Number(row.gmv || row.total_amount || 0) || 0)
|
||||
orders.push(Number(row.orders || row.order_count || 0) || 0)
|
||||
}
|
||||
|
||||
console.log('📊 loadTrend: 最终数据', { x: x.length, gmv: gmv.length, orders: orders.length })
|
||||
this.trend = { x, gmv, orders }
|
||||
this.trend = await fetchDashboardTrend(this.selectedPeriod)
|
||||
} catch (e) {
|
||||
console.error('❌ loadTrend failed', e)
|
||||
// 即使失败也设置空数据,避免图表报错
|
||||
this.trend = { x: [], gmv: [], orders: [] }
|
||||
}
|
||||
},
|
||||
@@ -561,58 +517,7 @@ type TopMerchantItem = { id: string; rank: number; name: string; sales: number;
|
||||
// 实时指标:核心是"强制数值化 + 兜底",避免对象直接渲染
|
||||
async loadRealTime() {
|
||||
try {
|
||||
const now = new Date()
|
||||
const today0 = new Date(now.getFullYear(), now.getMonth(), now.getDate())
|
||||
const todayISO = today0.toISOString()
|
||||
|
||||
const ySame = new Date(now.getTime() - 24 * 60 * 60 * 1000)
|
||||
const y0 = new Date(ySame.getFullYear(), ySame.getMonth(), ySame.getDate())
|
||||
|
||||
const p = new UTSJSONObject()
|
||||
p.set('p_start', todayISO)
|
||||
p.set('p_end', now.toISOString())
|
||||
p.set('p_compare_start', y0.toISOString())
|
||||
p.set('p_compare_end', ySame.toISOString())
|
||||
p.set('p_merchant_id', null)
|
||||
|
||||
console.log('⚡ loadRealTime: 请求参数', {
|
||||
p_start: todayISO,
|
||||
p_end: now.toISOString(),
|
||||
p_compare_start: y0.toISOString(),
|
||||
p_compare_end: ySame.toISOString()
|
||||
})
|
||||
|
||||
const res: any = await supa.rpc('rpc_analytics_realtime_kpis', p)
|
||||
console.log('⚡ loadRealTime: RPC 返回结果', res)
|
||||
|
||||
// 检查返回结构
|
||||
let row: any = {}
|
||||
if (Array.isArray(res.data) && res.data.length > 0) {
|
||||
row = res.data[0]
|
||||
} else if (Array.isArray(res) && res.length > 0) {
|
||||
row = res[0]
|
||||
} else if (res && typeof res === 'object' && !Array.isArray(res)) {
|
||||
// 可能是直接返回对象,或者 { data: {...} }
|
||||
row = res.data || res.result || res
|
||||
}
|
||||
|
||||
console.log('⚡ loadRealTime: 解析后的 row', row)
|
||||
|
||||
const safe = (v: any): number => {
|
||||
const n = Number(v)
|
||||
return isFinite(n) ? n : 0
|
||||
}
|
||||
this.realTime = {
|
||||
gmv: Math.round(safe(row.gmv || row.total_gmv || row.revenue)),
|
||||
gmv_growth: safe(row.gmv_growth || row.gmv_growth_rate || row.revenue_growth),
|
||||
orders: Math.round(safe(row.orders || row.order_count || row.total_orders)),
|
||||
order_growth: safe(row.order_growth || row.order_growth_rate),
|
||||
online_users: Math.round(safe(row.online_users || row.active_users || row.current_users)),
|
||||
conversion_rate: safe(row.conversion_rate || row.conversion),
|
||||
conversion_growth: safe(row.conversion_growth || row.conversion_growth_rate)
|
||||
}
|
||||
|
||||
console.log('⚡ loadRealTime: 最终数据', this.realTime)
|
||||
this.realTime = await fetchDashboardRealtime()
|
||||
} catch (e) {
|
||||
console.error('❌ loadRealTime failed', e)
|
||||
}
|
||||
@@ -620,52 +525,7 @@ type TopMerchantItem = { id: string; rank: number; name: string; sales: number;
|
||||
|
||||
async loadTopProducts() {
|
||||
try {
|
||||
const { startDate, endDate } = this.calcDateRange()
|
||||
const p = new UTSJSONObject()
|
||||
p.set('p_start_date', startDate.toISOString().slice(0, 10))
|
||||
p.set('p_end_date', endDate.toISOString().slice(0, 10))
|
||||
p.set('p_limit', 50)
|
||||
p.set('p_merchant_id', null)
|
||||
const res: any = await supa.rpc('rpc_analytics_top_products', p)
|
||||
console.log('📦 loadTopProducts: RPC 返回结果', res)
|
||||
|
||||
// 检查返回结构:即使 status 400,如果 data 有数据也使用
|
||||
let rows: Array<any> = []
|
||||
if (res.data) {
|
||||
if (Array.isArray(res.data)) {
|
||||
rows = res.data as Array<any>
|
||||
} else if (typeof res.data === 'object' && res.data.constructor && res.data.constructor.name === 'Array') {
|
||||
rows = res.data as Array<any>
|
||||
} else if (typeof res.data === 'object') {
|
||||
const dataObj = res.data as any
|
||||
if (typeof dataObj.length === 'number' && dataObj.length >= 0) {
|
||||
rows = []
|
||||
for (let i = 0; i < dataObj.length; i++) {
|
||||
const item = dataObj[i]
|
||||
if (item) rows.push(item)
|
||||
}
|
||||
} else {
|
||||
rows = [dataObj]
|
||||
}
|
||||
}
|
||||
} else if (Array.isArray(res)) {
|
||||
rows = res as Array<any>
|
||||
}
|
||||
|
||||
console.log('📦 loadTopProducts: 解析后的 rows', rows, '数量:', rows.length)
|
||||
|
||||
const list: Array<TopProductItem> = []
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
const row = rows[i]
|
||||
list.push({
|
||||
id: `${row.id}`,
|
||||
rank: i + 1,
|
||||
name: `${row.name || '未知商品'}`,
|
||||
sales: Number(row.sales || row.total_amount || 0) || 0
|
||||
})
|
||||
}
|
||||
|
||||
console.log('📦 loadTopProducts: 最终数据', list)
|
||||
const list = await fetchDashboardTopProducts(this.selectedPeriod, 50)
|
||||
|
||||
// 如果数据少于6条,添加假数据以达到滚动效果
|
||||
if (list.length < 6) {
|
||||
@@ -676,7 +536,6 @@ type TopMerchantItem = { id: string; rank: number; name: string; sales: number;
|
||||
{ id: 'fake-4', rank: list.length + 4, name: '示例商品D', sales: Math.floor(Math.random() * 100) + 10 },
|
||||
{ id: 'fake-5', rank: list.length + 5, name: '示例商品E', sales: Math.floor(Math.random() * 100) + 5 }
|
||||
]
|
||||
// 填充到至少6条
|
||||
const needCount = 6 - list.length
|
||||
for (let i = 0; i < needCount; i++) {
|
||||
list.push(fakeProducts[i % fakeProducts.length])
|
||||
@@ -686,7 +545,6 @@ type TopMerchantItem = { id: string; rank: number; name: string; sales: number;
|
||||
this.topProducts = list
|
||||
} catch (e) {
|
||||
console.error('❌ loadTopProducts failed', e)
|
||||
// 即使失败也添加假数据
|
||||
const fakeProducts = [
|
||||
{ id: 'fake-1', rank: 1, name: '示例商品A', sales: 88 },
|
||||
{ id: 'fake-2', rank: 2, name: '示例商品B', sales: 76 },
|
||||
@@ -701,59 +559,7 @@ type TopMerchantItem = { id: string; rank: number; name: string; sales: number;
|
||||
|
||||
async loadTopMerchants() {
|
||||
try {
|
||||
const { startDate, endDate } = this.calcDateRange()
|
||||
const p = new UTSJSONObject()
|
||||
p.set('p_start_date', startDate.toISOString().slice(0, 10))
|
||||
p.set('p_end_date', endDate.toISOString().slice(0, 10))
|
||||
p.set('p_limit', 50)
|
||||
|
||||
console.log('🏪 loadTopMerchants: 请求参数', {
|
||||
p_start_date: startDate.toISOString().slice(0, 10),
|
||||
p_end_date: endDate.toISOString().slice(0, 10),
|
||||
p_limit: 5
|
||||
})
|
||||
|
||||
const res: any = await supa.rpc('rpc_analytics_top_merchants', p)
|
||||
console.log('🏪 loadTopMerchants: RPC 返回结果', res)
|
||||
|
||||
// 检查返回结构:即使 status 400,如果 data 有数据也使用
|
||||
let rows: Array<any> = []
|
||||
if (res.data) {
|
||||
if (Array.isArray(res.data)) {
|
||||
rows = res.data as Array<any>
|
||||
} else if (typeof res.data === 'object' && res.data.constructor && res.data.constructor.name === 'Array') {
|
||||
rows = res.data as Array<any>
|
||||
} else if (typeof res.data === 'object') {
|
||||
const dataObj = res.data as any
|
||||
if (typeof dataObj.length === 'number' && dataObj.length >= 0) {
|
||||
rows = []
|
||||
for (let i = 0; i < dataObj.length; i++) {
|
||||
const item = dataObj[i]
|
||||
if (item) rows.push(item)
|
||||
}
|
||||
} else {
|
||||
rows = [dataObj]
|
||||
}
|
||||
}
|
||||
} else if (Array.isArray(res)) {
|
||||
rows = res as Array<any>
|
||||
}
|
||||
|
||||
console.log('🏪 loadTopMerchants: 解析后的 rows', rows, '数量:', rows.length)
|
||||
|
||||
const list: Array<TopMerchantItem> = []
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
const row = rows[i]
|
||||
list.push({
|
||||
id: `${row.id}`,
|
||||
rank: i + 1,
|
||||
name: `${row.name || row.shop_name || '未知商家'}`,
|
||||
sales: Number(row.sales || row.total_amount || 0) || 0,
|
||||
growth: Number(row.growth || row.growth_rate || 0) || 0
|
||||
})
|
||||
}
|
||||
|
||||
console.log('🏪 loadTopMerchants: 最终数据', list)
|
||||
const list = await fetchDashboardTopMerchants(this.selectedPeriod, 50)
|
||||
|
||||
// 如果数据少于6条,添加假数据以达到滚动效果
|
||||
if (list.length < 6) {
|
||||
@@ -764,7 +570,6 @@ type TopMerchantItem = { id: string; rank: number; name: string; sales: number;
|
||||
{ id: 'fake-4', rank: list.length + 4, name: '示例商家D', sales: Math.floor(Math.random() * 5000) + 1000, growth: Math.floor(Math.random() * 20) - 10 },
|
||||
{ id: 'fake-5', rank: list.length + 5, name: '示例商家E', sales: Math.floor(Math.random() * 4000) + 500, growth: Math.floor(Math.random() * 20) - 10 }
|
||||
]
|
||||
// 填充到至少6条
|
||||
const needCount = 6 - list.length
|
||||
for (let i = 0; i < needCount; i++) {
|
||||
list.push(fakeMerchants[i % fakeMerchants.length])
|
||||
@@ -774,7 +579,6 @@ type TopMerchantItem = { id: string; rank: number; name: string; sales: number;
|
||||
this.topMerchants = list
|
||||
} catch (e) {
|
||||
console.error('❌ loadTopMerchants failed', e)
|
||||
// 即使失败也添加假数据
|
||||
const fakeMerchants = [
|
||||
{ id: 'fake-1', rank: 1, name: '示例商家A', sales: 8888, growth: 12.5 },
|
||||
{ id: 'fake-2', rank: 2, name: '示例商家B', sales: 7654, growth: 8.3 },
|
||||
@@ -789,64 +593,7 @@ type TopMerchantItem = { id: string; rank: number; name: string; sales: number;
|
||||
|
||||
async loadUserSegments() {
|
||||
try {
|
||||
const { startDate, endDate } = this.calcDateRange()
|
||||
const p = new UTSJSONObject()
|
||||
p.set('p_start_date', startDate.toISOString().slice(0, 10))
|
||||
p.set('p_end_date', endDate.toISOString().slice(0, 10))
|
||||
|
||||
console.log('👥 loadUserSegments: 请求参数', {
|
||||
start_date: startDate.toISOString().slice(0, 10),
|
||||
end_date: endDate.toISOString().slice(0, 10)
|
||||
})
|
||||
|
||||
const res: any = await supa.rpc('rpc_analytics_user_segments', p)
|
||||
console.log('👥 loadUserSegments: RPC 返回结果', res)
|
||||
|
||||
// 检查返回结构:即使 status 400,如果 data 有数据也使用
|
||||
let rows: Array<any> = []
|
||||
// 先检查 res.data(可能是 UTSJSONObject,需要检查是否有数组数据)
|
||||
if (res.data) {
|
||||
// 如果 res.data 是数组,直接使用
|
||||
if (Array.isArray(res.data)) {
|
||||
rows = res.data as Array<any>
|
||||
} else if (typeof res.data === 'object' && res.data.constructor && res.data.constructor.name === 'Array') {
|
||||
// UTS 的数组对象
|
||||
rows = res.data as Array<any>
|
||||
} else if (typeof res.data === 'object') {
|
||||
// 可能是 UTSJSONObject,尝试获取内部数组
|
||||
const dataObj = res.data as any
|
||||
// 检查是否有 length 属性(UTS 数组)
|
||||
if (typeof dataObj.length === 'number' && dataObj.length >= 0) {
|
||||
rows = []
|
||||
for (let i = 0; i < dataObj.length; i++) {
|
||||
const item = dataObj[i]
|
||||
if (item) rows.push(item)
|
||||
}
|
||||
} else {
|
||||
// 可能是单个对象,包装成数组
|
||||
rows = [dataObj]
|
||||
}
|
||||
}
|
||||
} else if (Array.isArray(res)) {
|
||||
rows = res as Array<any>
|
||||
} else if (res && typeof res === 'object') {
|
||||
const data = res.data || res.rows || res.result || []
|
||||
rows = Array.isArray(data) ? data : []
|
||||
}
|
||||
|
||||
console.log('👥 loadUserSegments: 解析后的 rows', rows, '数量:', rows.length)
|
||||
|
||||
const list: Array<SegmentItem> = []
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
const row = rows[i]
|
||||
const name = `${row.name || row.segment_name || row.label || '未知'}`
|
||||
const value = Number(row.value || row.count || row.amount || 0) || 0
|
||||
list.push({ name, value })
|
||||
}
|
||||
|
||||
console.log('👥 loadUserSegments: 最终数据', list)
|
||||
// 即使为空也更新,确保图表能正确显示空状态
|
||||
this.userSegments = list
|
||||
this.userSegments = await fetchDashboardUserSegments(this.selectedPeriod)
|
||||
} catch (e) {
|
||||
console.error('❌ loadUserSegments failed', e)
|
||||
this.userSegments = []
|
||||
@@ -855,58 +602,7 @@ type TopMerchantItem = { id: string; rank: number; name: string; sales: number;
|
||||
|
||||
async loadTrafficSources() {
|
||||
try {
|
||||
const { startDate, endDate } = this.calcDateRange()
|
||||
const p = new UTSJSONObject()
|
||||
p.set('p_start_date', startDate.toISOString().slice(0, 10))
|
||||
p.set('p_end_date', endDate.toISOString().slice(0, 10))
|
||||
|
||||
console.log('🌐 loadTrafficSources: 请求参数', {
|
||||
start_date: startDate.toISOString().slice(0, 10),
|
||||
end_date: endDate.toISOString().slice(0, 10)
|
||||
})
|
||||
|
||||
const res: any = await supa.rpc('rpc_analytics_traffic_sources', p)
|
||||
console.log('🌐 loadTrafficSources: RPC 返回结果', res)
|
||||
|
||||
// 检查返回结构:即使 status 400,如果 data 有数据也使用
|
||||
let rows: Array<any> = []
|
||||
if (res.data) {
|
||||
if (Array.isArray(res.data)) {
|
||||
rows = res.data as Array<any>
|
||||
} else if (typeof res.data === 'object' && res.data.constructor && res.data.constructor.name === 'Array') {
|
||||
rows = res.data as Array<any>
|
||||
} else if (typeof res.data === 'object') {
|
||||
const dataObj = res.data as any
|
||||
if (typeof dataObj.length === 'number' && dataObj.length >= 0) {
|
||||
rows = []
|
||||
for (let i = 0; i < dataObj.length; i++) {
|
||||
const item = dataObj[i]
|
||||
if (item) rows.push(item)
|
||||
}
|
||||
} else {
|
||||
rows = [dataObj]
|
||||
}
|
||||
}
|
||||
} else if (Array.isArray(res)) {
|
||||
rows = res as Array<any>
|
||||
} else if (res && typeof res === 'object') {
|
||||
const data = res.data || res.rows || res.result || []
|
||||
rows = Array.isArray(data) ? data : []
|
||||
}
|
||||
|
||||
console.log('🌐 loadTrafficSources: 解析后的 rows', rows, '数量:', rows.length)
|
||||
|
||||
const list: Array<TrafficItem> = []
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
const row = rows[i]
|
||||
const name = `${row.name || row.source_name || row.label || '未知'}`
|
||||
const value = Number(row.value || row.count || row.amount || 0) || 0
|
||||
list.push({ name, value })
|
||||
}
|
||||
|
||||
console.log('🌐 loadTrafficSources: 最终数据', list)
|
||||
// 即使为空也更新
|
||||
this.trafficSources = list
|
||||
this.trafficSources = await fetchDashboardTrafficSources(this.selectedPeriod)
|
||||
} catch (e) {
|
||||
console.error('❌ loadTrafficSources failed', e)
|
||||
this.trafficSources = []
|
||||
|
||||
Reference in New Issue
Block a user