mall数据库文件

This commit is contained in:
comlibmb
2026-01-30 16:11:23 +08:00
parent b53d2376ff
commit cfec4a16c0
71 changed files with 11786 additions and 1009 deletions

View File

@@ -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 = []