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

@@ -168,10 +168,12 @@
</template>
<script lang="uts">
import supa from '@/components/supadb/aksupainstance.uts'
import AnalyticsSidebarMenu from '@/components/analytics/AnalyticsSidebarMenu.uvue'
import AnalyticsTopBar from '@/components/analytics/AnalyticsTopBar.uvue'
import EChartsView from '@/uni_modules/charts/EChartsView.vue'
import { fetchProductOverview, fetchTopProducts, fetchProductTrend, fetchCategorySales, fetchStockInsights, fetchPriceTrend, fetchReviewInsights } from '@/services/analytics/productInsightsService.uts'
import { computeDateRange, toDateOnly } from '@/services/analytics/dateRange.uts'
import { mapAnalyticsError } from '@/services/analytics/errorMapper.uts'
type TimePeriod = { value: string; label: string }
type ProductData = {
@@ -244,20 +246,15 @@ export default {
},
methods: {
async loadSelectedProductTrend(startDate: Date, endDate: Date) {
async loadSelectedProductTrend() {
try {
if (this.selectedProductId == null || this.selectedProductId === '') {
this.salesChartOption = {}
return
}
const pTrend = new UTSJSONObject()
pTrend.set('p_start_date', startDate.toISOString().slice(0, 10))
pTrend.set('p_end_date', endDate.toISOString().slice(0, 10))
pTrend.set('p_product_id', this.selectedProductId)
const res: any = await supa.rpc('rpc_analytics_product_trend', pTrend)
const rows: Array<any> = Array.isArray(res.data) ? (res.data as Array<any>) : []
const trend = await fetchProductTrend(this.selectedPeriod, this.selectedProductId)
const rows: Array<any> = trend as any
const x: Array<string> = []
const gmv: Array<number> = []
@@ -306,53 +303,38 @@ export default {
} catch (e) {
console.error('loadSelectedProductTrend failed', e)
this.salesChartOption = {}
uni.showToast({ title: mapAnalyticsError(e, { fallbackMessage: '加载商品趋势失败' }), icon: 'none' })
}
},
handleProductChange() {
const { startDate, endDate } = this.calcDateRange()
this.loadSelectedProductTrend(startDate, endDate)
this.loadSelectedProductTrend()
},
calcDateRange() {
const now = new Date()
const endDate = new Date(now.getFullYear(), now.getMonth(), now.getDate())
const days = this.selectedPeriod === '7d' ? 7 : this.selectedPeriod === '30d' ? 30 : this.selectedPeriod === '90d' ? 90 : 365
const startDate = new Date(endDate.getTime() - (days - 1) * 24 * 60 * 60 * 1000)
return { startDate, endDate }
},
async loadProductData() {
this.loading = true
try {
this.updateTime()
const { startDate, endDate } = this.calcDateRange()
// 1) 热销商品 TOP复用 top_products按 GMV 口径)
const pTop = new UTSJSONObject()
pTop.set('p_start_date', startDate.toISOString().slice(0, 10))
pTop.set('p_end_date', endDate.toISOString().slice(0, 10))
pTop.set('p_limit', 10)
pTop.set('p_merchant_id', null)
const topRes: any = await supa.rpc('rpc_analytics_top_products', pTop)
const topRows: Array<any> = Array.isArray(topRes.data) ? (topRes.data as Array<any>) : []
const topList: Array<ProductRank> = []
for (let i = 0; i < topRows.length; i++) {
topList.push({
id: `${topRows[i].id}`,
rank: i + 1,
name: `${topRows[i].name}`,
sales: Number(topRows[i].sales) || 0,
growth: Math.round((Math.random() * 20 - 10) * 10) / 10
})
}
const [overview, topList, catRows, stockRows, priceRows, reviewRows] = await Promise.all([
fetchProductOverview(this.selectedPeriod),
fetchTopProducts(this.selectedPeriod, 10),
fetchCategorySales(this.selectedPeriod),
fetchStockInsights(this.selectedPeriod),
fetchPriceTrend(this.selectedPeriod),
fetchReviewInsights()
])
this.productData = overview
// 不足 10 条时补齐虚拟数据(真实数据优先,虚拟数据追加)
if (topList.length < 10) {
const need = 10 - topList.length
const top = topList.slice()
if (top.length < 10) {
const need = 10 - top.length
for (let i = 0; i < need; i++) {
const n = topList.length + 1
topList.push({
const n = top.length + 1
top.push({
id: `fake-product-${n}`,
rank: n,
name: `示例商品${n}`,
@@ -361,47 +343,31 @@ export default {
})
}
} else {
topList.splice(10)
top.splice(10)
}
for (let i = 0; i < topList.length; i++) topList[i].rank = i + 1
for (let i = 0; i < top.length; i++) top[i].rank = i + 1
this.topProducts = top
this.topProducts = topList
// 2) 商品维度销售趋势A2按商品 + 日期聚合)
// 默认选中 TOP1 商品;如用户手动切换,则使用选择的商品
if ((this.selectedProductId == null || this.selectedProductId === '') && topList.length > 0) {
const real = topList.find((it) => !String(it.id).startsWith('fake-product-'))
if ((this.selectedProductId == null || this.selectedProductId === '') && top.length > 0) {
const real = top.find((it) => !String(it.id).startsWith('fake-product-'))
this.selectedProductId = real ? real.id : ''
}
// 如果仍然没有可选商品,则清空图表
if (this.selectedProductId == null || this.selectedProductId === '') {
this.salesChartOption = {}
} else {
await this.loadSelectedProductTrend(startDate, endDate)
await this.loadSelectedProductTrend()
}
// 3) KPI以 products 表为基础口径:总商品数/热销商品/库存均值)
this.buildCategoryChart(catRows)
this.buildStockChart(stockRows)
// priceChartOption 在 loadSelectedProductTrend 里会生成均价趋势;这里仍保留整体价格趋势图(如果你有对应图表函数可以接入)
this.buildReviewChart(reviewRows)
// 3) KPI以 products 表为基础口径:总商品数/热销商品/库存均值)
// 注:当前 analytics schema 没有商品 KPI RPC这里用简单查询占位后续可补 RPC
this.productData = {
total_products: 0,
product_growth: 0,
hot_products: topList.filter((p) => p.sales >= 100).length,
turnover_rate: 0,
turnover_growth: 0,
avg_stock: 0,
stock_growth: 0
}
// 其余图表先占位(后续补 RPC分类/库存/价格/评价)
this.categoryChartOption = { title: { text: '分类分析(待接入)', left: 'center', top: 10, textStyle: { fontSize: 12, color: 'rgba(0,0,0,0.55)' } }, series: [] }
this.stockChartOption = { title: { text: '库存分析(待接入)', left: 'center', top: 10, textStyle: { fontSize: 12, color: 'rgba(0,0,0,0.55)' } }, series: [] }
this.priceChartOption = { title: { text: '价格趋势(待接入)', left: 'center', top: 10, textStyle: { fontSize: 12, color: 'rgba(0,0,0,0.55)' } }, series: [] }
this.reviewChartOption = { title: { text: '评价分析(待接入)', left: 'center', top: 10, textStyle: { fontSize: 12, color: 'rgba(0,0,0,0.55)' } }, series: [] }
this.updateTime()
} catch (e) {
console.error('loadProductData failed', e)
uni.showToast({ title: '数据加载失败', icon: 'none', duration: 2000 })
uni.showToast({ title: mapAnalyticsError(e, { fallbackMessage: '商品洞察数据加载失败' }), icon: 'none', duration: 2000 })
} finally {
this.loading = false
this.updateTime()