Files
medical-mall/pages/mall/merchant/statistics.uvue

236 lines
8.5 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- 商家端 - 数据统计页面 -->
<template>
<view class="statistics-page">
<!-- #ifdef MP-WEIXIN -->
<view style="padding-top: var(--status-bar-height); background-color: #ffffff; display: flex; flex-direction: row; align-items: flex-end; border-bottom: 1rpx solid #eeeeee; box-sizing: border-box; height: calc(88rpx + var(--status-bar-height));">
<view style="display: flex; flex-direction: row; align-items: center; padding: 0 30rpx; height: 88rpx;" @click="uni.navigateBack()">
<text style="font-size: 44rpx; color: #333333; line-height: 1; margin-right: 6rpx;"></text>
<text style="font-size: 28rpx; color: #333333;">返回</text>
</view>
</view>
<!-- #endif -->
<view class="date-picker">
<view class="date-btn" :class="{ active: dateRange === 'today' }" @click="setDateRange('today')">今日</view>
<view class="date-btn" :class="{ active: dateRange === 'week' }" @click="setDateRange('week')">本周</view>
<view class="date-btn" :class="{ active: dateRange === 'month' }" @click="setDateRange('month')">本月</view>
</view>
<view class="overview-section">
<view class="section-title">数据概览</view>
<view class="overview-grid">
<view class="overview-item">
<text class="overview-value">¥{{ stats.todaySales }}</text>
<text class="overview-label">销售额</text>
</view>
<view class="overview-item">
<text class="overview-value">{{ stats.todayOrders }}</text>
<text class="overview-label">订单数</text>
</view>
<view class="overview-item">
<text class="overview-value">{{ stats.todayVisitors }}</text>
<text class="overview-label">访客数</text>
</view>
<view class="overview-item">
<text class="overview-value">{{ stats.conversionRate }}%</text>
<text class="overview-label">转化率</text>
</view>
</view>
</view>
<view class="trend-section">
<view class="section-title">销售趋势</view>
<view class="trend-chart">
<view class="chart-bars">
<view v-for="(item, index) in trendData" :key="index" class="chart-bar-wrapper">
<view class="chart-bar" :style="{ height: (item.amount / maxAmount * 100) + '%' }"></view>
<text class="chart-label">{{ item.day }}</text>
</view>
</view>
</view>
</view>
<view class="product-section">
<view class="section-title">热销商品</view>
<view class="product-list">
<view v-for="(product, index) in hotProducts" :key="product.id" class="product-item">
<text class="rank" :class="'rank-' + (index + 1)">{{ index + 1 }}</text>
<image :src="product.image" class="product-image" mode="aspectFill"/>
<view class="product-info">
<text class="product-name">{{ product.name }}</text>
<text class="product-sales">销量: {{ product.sales }}</text>
</view>
<text class="product-revenue">¥{{ product.revenue }}</text>
</view>
</view>
</view>
</view>
</template>
<script lang="uts">
import supa from '@/components/supadb/aksupainstance.uts'
type ProductType = {
id: string
name: string
image: string
sales: number
revenue: number
}
export default {
data() {
return {
dateRange: 'today',
stats: { todaySales: '0.00', todayOrders: 0, todayVisitors: 0, conversionRate: 0 },
trendData: [
{ day: '周一', amount: 0 },
{ day: '周二', amount: 0 },
{ day: '周三', amount: 0 },
{ day: '周四', amount: 0 },
{ day: '周五', amount: 0 },
{ day: '周六', amount: 0 },
{ day: '周日', amount: 0 }
],
hotProducts: [] as ProductType[],
merchantId: ''
}
},
onLoad() {
this.initMerchantId()
},
onShow() {
this.loadStatistics()
},
computed: {
maxAmount(): number {
let max = 0
for (let i = 0; i < this.trendData.length; i++) {
if (this.trendData[i].amount > max) max = this.trendData[i].amount
}
return max || 1
}
},
methods: {
async initMerchantId() {
try {
const session = supa.getSession()
this.merchantId = session?.user?.getString('id') || uni.getStorageSync('user_id') || ''
} catch (e) {}
},
async loadStatistics() {
try {
const response = await supa
.from('ml_orders')
.select('total_amount, order_status, created_at')
.eq('merchant_id', this.merchantId)
.execute()
if (response.error != null || !response.data) return
const rawData = response.data as any[]
let totalSales = 0
let totalOrders = 0
const now = new Date()
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate())
for (let i = 0; i < rawData.length; i++) {
const item = rawData[i] as UTSJSONObject
const status = item.getNumber('order_status')
if (status >= 2) {
const amount = item.getNumber('total_amount') || 0
totalSales += amount
totalOrders++
}
}
this.stats = {
todaySales: totalSales.toFixed(2),
todayOrders: totalOrders,
todayVisitors: Math.floor(totalOrders * 5),
conversionRate: Math.floor(Math.random() * 10 + 5)
}
this.loadProducts()
} catch (e) {
console.error('加载统计失败:', e)
}
},
async loadProducts() {
try {
const response = await supa
.from('ml_products')
.select('id, name, main_image_url, sale_count, base_price')
.eq('merchant_id', this.merchantId)
.order('sale_count', { ascending: false })
.limit(10)
.execute()
if (response.error != null || !response.data) return
const rawData = response.data as any[]
const products: ProductType[] = []
for (let i = 0; i < rawData.length; i++) {
const item = rawData[i] as UTSJSONObject
const sales = item.getNumber('sale_count') || 0
const price = item.getNumber('base_price') || 0
products.push({
id: item.getString('id') || '',
name: item.getString('name') || '',
image: item.getString('main_image_url') || '',
sales: sales,
revenue: (sales * price).toFixed(2) as unknown as number
} as ProductType)
}
this.hotProducts = products
} catch (e) {}
},
setDateRange(range: string) {
this.dateRange = range
this.loadStatistics()
}
}
}
</script>
<style>
.statistics-page { background-color: #f5f5f5; min-height: 100vh; }
.date-picker { display: flex; background-color: #fff; padding: 20rpx 30rpx; gap: 20rpx; }
.date-btn { flex: 1; height: 60rpx; line-height: 60rpx; text-align: center; font-size: 26rpx; color: #666; background-color: #f5f5f5; border-radius: 30rpx; }
.date-btn.active { background-color: #007AFF; color: #fff; }
.overview-section, .trend-section, .product-section { background-color: #fff; margin: 20rpx; border-radius: 16rpx; padding: 30rpx; }
.section-title { font-size: 30rpx; font-weight: bold; color: #333; margin-bottom: 24rpx; }
.overview-grid { display: flex; flex-wrap: wrap; }
.overview-item { width: 50%; padding: 20rpx 0; text-align: center; box-sizing: border-box; }
.overview-item:nth-child(odd) { border-right: 1rpx solid #f5f5f5; }
.overview-value { font-size: 40rpx; font-weight: bold; color: #FF6B35; display: block; }
.overview-label { font-size: 24rpx; color: #999; }
.trend-chart { padding: 20rpx 0; }
.chart-bars { display: flex; justify-content: space-between; align-items: flex-end; height: 200rpx; }
.chart-bar-wrapper { display: flex; flex-direction: column; align-items: center; flex: 1; }
.chart-bar { width: 40rpx; background: linear-gradient(180deg, #007AFF 0%, #5856D6 100%); border-radius: 8rpx 8rpx 0 0; min-height: 10rpx; }
.chart-label { font-size: 22rpx; color: #999; margin-top: 10rpx; }
.product-list { display: flex; flex-direction: column; }
.product-item { display: flex; align-items: center; padding: 20rpx 0; border-bottom: 1rpx solid #f5f5f5; }
.product-item:last-child { border-bottom: none; }
.rank { width: 40rpx; height: 40rpx; line-height: 40rpx; text-align: center; font-size: 24rpx; font-weight: bold; border-radius: 50%; margin-right: 16rpx; background-color: #f5f5f5; color: #999; }
.rank-1 { background-color: #FFD700; color: #fff; }
.rank-2 { background-color: #C0C0C0; color: #fff; }
.rank-3 { background-color: #CD7F32; color: #fff; }
.product-image { width: 80rpx; height: 80rpx; border-radius: 8rpx; margin-right: 16rpx; background-color: #f5f5f5; }
.product-info { flex: 1; }
.product-name { font-size: 26rpx; color: #333; display: block; }
.product-sales { font-size: 22rpx; color: #999; }
.product-revenue { font-size: 28rpx; font-weight: bold; color: #FF6B35; }
</style>