优化细节
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<template>
|
||||
<view class="page" @click.self="closeMoreMenu">
|
||||
<!-- 固定顶部导航栏 -->
|
||||
<!-- 鍥哄畾椤堕儴瀵艰埅鏍?-->
|
||||
<AnalyticsTopBar
|
||||
:title="'销售报表'"
|
||||
:title="'閿€鍞姤琛?"
|
||||
:lastUpdateTime="lastUpdateTime"
|
||||
:sidebarVisible="showSidebarMenu"
|
||||
@menu-click="handleMenu"
|
||||
@@ -16,18 +16,18 @@
|
||||
/>
|
||||
|
||||
<view class="page-layout">
|
||||
<!-- 侧边栏菜单组件 -->
|
||||
<!-- 渚ц竟鏍忚彍鍗曠粍浠?-->
|
||||
<AnalyticsSidebarMenu
|
||||
:visible="showSidebarMenu"
|
||||
:currentPath="currentPath"
|
||||
@visible-change="handleSidebarUpdate"
|
||||
/>
|
||||
|
||||
<!-- 主内容区域 -->
|
||||
<!-- 涓诲唴瀹瑰尯鍩?-->
|
||||
<view class="main-content">
|
||||
<view class="container">
|
||||
|
||||
<!-- 时间维度筛选(快捷 + 自定义) -->
|
||||
<!-- 鏃堕棿缁村害绛涢€夛紙蹇嵎 + 鑷畾涔夛級 -->
|
||||
<view class="tabs">
|
||||
<view
|
||||
v-for="p in timePeriods"
|
||||
@@ -43,8 +43,7 @@
|
||||
:class="{ active: customRangeEnabled }"
|
||||
@click="toggleCustomRange"
|
||||
>
|
||||
自定义
|
||||
</view>
|
||||
鑷畾涔? </view>
|
||||
</view>
|
||||
|
||||
<AnalyticsDateRangePicker
|
||||
@@ -55,38 +54,38 @@
|
||||
@clear="onDateRangeClear"
|
||||
/>
|
||||
|
||||
<!-- KPI 指标卡片 -->
|
||||
<!-- KPI 鎸囨爣鍗$墖 -->
|
||||
<view class="kpi-grid">
|
||||
<view class="kpi-card">
|
||||
<text class="kpi-label">GMV(成交总额)</text>
|
||||
<text class="kpi-value">¥{{ formatMoney(salesData.gmv) }}</text>
|
||||
<text class="kpi-meta">较上期:{{ formatPct(salesData.gmv_growth) }}</text>
|
||||
<text class="kpi-label">GMV锛堟垚浜ゆ€婚锛?/text>
|
||||
<text class="kpi-value">楼{{ formatMoney(salesData.gmv) }}</text>
|
||||
<text class="kpi-meta">杈冧笂鏈燂細{{ formatPct(salesData.gmv_growth) }}</text>
|
||||
</view>
|
||||
<view class="kpi-card">
|
||||
<text class="kpi-label">订单量</text>
|
||||
<text class="kpi-label">璁㈠崟閲?/text>
|
||||
<text class="kpi-value">{{ formatInt(salesData.orders) }}</text>
|
||||
<text class="kpi-meta">较上期:{{ formatPct(salesData.order_growth) }}</text>
|
||||
<text class="kpi-meta">杈冧笂鏈燂細{{ formatPct(salesData.order_growth) }}</text>
|
||||
</view>
|
||||
<view class="kpi-card">
|
||||
<text class="kpi-label">转化率</text>
|
||||
<text class="kpi-label">杞寲鐜?/text>
|
||||
<text class="kpi-value">{{ formatPct(salesData.conversion_rate) }}</text>
|
||||
<text class="kpi-meta">较上期:{{ formatPct(salesData.conversion_growth) }}</text>
|
||||
<text class="kpi-meta">杈冧笂鏈燂細{{ formatPct(salesData.conversion_growth) }}</text>
|
||||
</view>
|
||||
<view class="kpi-card">
|
||||
<text class="kpi-label">客单价</text>
|
||||
<text class="kpi-value">¥{{ formatMoney(salesData.avg_order_amount) }}</text>
|
||||
<text class="kpi-meta">较上期:{{ formatPct(salesData.avg_order_growth) }}</text>
|
||||
<text class="kpi-label">瀹㈠崟浠?/text>
|
||||
<text class="kpi-value">楼{{ formatMoney(salesData.avg_order_amount) }}</text>
|
||||
<text class="kpi-meta">杈冧笂鏈燂細{{ formatPct(salesData.avg_order_growth) }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 销售趋势图表 -->
|
||||
<!-- 閿€鍞秼鍔垮浘琛?-->
|
||||
<view class="card card-full">
|
||||
<view class="card-head">
|
||||
<text class="card-title">销售趋势分析</text>
|
||||
<text class="card-desc">{{ selectedPeriodText }} · 柱:GMV(元) · 线:订单数</text>
|
||||
<text class="card-title">閿€鍞秼鍔垮垎鏋?/text>
|
||||
<text class="card-desc">{{ selectedPeriodText }} 路 鏌憋細GMV锛堝厓锛?路 绾匡細璁㈠崟鏁?/text>
|
||||
</view>
|
||||
<view v-if="loading || !trend.x || trend.x.length === 0" class="chart-loading">
|
||||
<text>{{ loading ? '加载中...' : '暂无数据' }}</text>
|
||||
<text>{{ loading ? '鍔犺浇涓?..' : '鏆傛棤鏁版嵁' }}</text>
|
||||
</view>
|
||||
<AnalyticsComboChart
|
||||
v-else
|
||||
@@ -97,7 +96,7 @@
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 销售地域分布(左地图 + 右双列表,同一块) -->
|
||||
<!-- 閿€鍞湴鍩熷垎甯冿紙宸﹀湴鍥?+ 鍙冲弻鍒楄〃锛屽悓涓€鍧楋級 -->
|
||||
<view class="card card-full sales-overview-card">
|
||||
<view class="sales-split">
|
||||
<view class="sales-split-left">
|
||||
@@ -111,18 +110,18 @@
|
||||
<view class="sales-split-right">
|
||||
<view class="sales-split-list">
|
||||
<view class="list-head">
|
||||
<text class="list-title">商品销售排行 TOP 10</text>
|
||||
<text class="list-desc">按销量排序</text>
|
||||
<text class="list-title">鍟嗗搧閿€鍞帓琛?TOP 10</text>
|
||||
<text class="list-desc">鎸夐攢閲忔帓搴?/text>
|
||||
</view>
|
||||
<view v-if="loading || topProducts.length === 0" class="chart-loading chart-loading-compact">
|
||||
<text>{{ loading ? '加载中...' : '暂无数据' }}</text>
|
||||
<text>{{ loading ? '鍔犺浇涓?..' : '鏆傛棤鏁版嵁' }}</text>
|
||||
</view>
|
||||
<view v-else class="rank-scroll">
|
||||
<view class="rank-list">
|
||||
<view v-for="p in topProducts" :key="p.id" class="rank-item">
|
||||
<text class="rank-no">{{ p.rank }}</text>
|
||||
<text class="rank-name">{{ p.name }}</text>
|
||||
<text class="rank-val">{{ p.sales }} 件</text>
|
||||
<text class="rank-val">{{ p.sales }} 浠?/text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -130,11 +129,11 @@
|
||||
|
||||
<view class="sales-split-list">
|
||||
<view class="list-head">
|
||||
<text class="list-title">商家销售排行 TOP 10</text>
|
||||
<text class="list-desc">按 GMV 排序</text>
|
||||
<text class="list-title">鍟嗗閿€鍞帓琛?TOP 10</text>
|
||||
<text class="list-desc">鎸?GMV 鎺掑簭</text>
|
||||
</view>
|
||||
<view v-if="loading || topMerchants.length === 0" class="chart-loading chart-loading-compact">
|
||||
<text>{{ loading ? '加载中...' : '暂无数据' }}</text>
|
||||
<text>{{ loading ? '鍔犺浇涓?..' : '鏆傛棤鏁版嵁' }}</text>
|
||||
</view>
|
||||
<view v-else class="rank-scroll">
|
||||
<view class="rank-list">
|
||||
@@ -142,7 +141,7 @@
|
||||
<text class="rank-no">{{ m.rank }}</text>
|
||||
<text class="rank-name">{{ m.name }}</text>
|
||||
<view class="rank-right">
|
||||
<text class="rank-val">¥{{ formatMoney(m.sales) }}</text>
|
||||
<text class="rank-val">楼{{ formatMoney(m.sales) }}</text>
|
||||
<text class="chip" :class="m.growth >= 0 ? 'pos' : 'neg'">
|
||||
{{ m.growth >= 0 ? '+' : '' }}{{ m.growth }}%
|
||||
</text>
|
||||
@@ -155,7 +154,7 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 留白 -->
|
||||
<!-- 鐣欑櫧 -->
|
||||
<view style="height: 24px;"></view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -189,10 +188,10 @@ const currentPath = ref('/pages/mall/analytics/sales-report')
|
||||
const loading = ref(false)
|
||||
|
||||
const timePeriods = ref<Array<TimePeriod>>([
|
||||
{ value: '7d', label: '7天' },
|
||||
{ value: '30d', label: '30天' },
|
||||
{ value: '90d', label: '90天' },
|
||||
{ value: '1y', label: '1年' }
|
||||
{ value: '7d', label: '7澶? },
|
||||
{ value: '30d', label: '30澶? },
|
||||
{ value: '90d', label: '90澶? },
|
||||
{ value: '1y', label: '1骞? }
|
||||
])
|
||||
|
||||
const salesData = reactive<SalesData>({
|
||||
@@ -212,7 +211,7 @@ const topMerchants = reactive<Array<MerchantRank>>([])
|
||||
|
||||
const selectedPeriodText = computed((): string => {
|
||||
const p = timePeriods.value.find((t) => t.value === selectedPeriod.value)
|
||||
return p ? p.label : '7天'
|
||||
return p ? p.label : '7澶?
|
||||
})
|
||||
|
||||
onLoad(() => {
|
||||
@@ -248,13 +247,13 @@ async function loadSalesData() {
|
||||
salesData.avg_order_amount = kpi.avg_order_amount
|
||||
salesData.avg_order_growth = kpi.avg_order_growth
|
||||
|
||||
// 趋势
|
||||
// 瓒嬪娍
|
||||
const t = await fetchSalesTrend(selectedPeriod.value, range)
|
||||
trend.x = t.x
|
||||
trend.gmv = t.gmv
|
||||
trend.orders = t.orders
|
||||
|
||||
// TOP 商品/商家
|
||||
// TOP 鍟嗗搧/鍟嗗
|
||||
const pList = await fetchSalesTopProducts(selectedPeriod.value, 50, range)
|
||||
for (let i = 0; i < pList.length; i++) {
|
||||
pList[i].rank = i + 1
|
||||
@@ -267,8 +266,8 @@ async function loadSalesData() {
|
||||
}
|
||||
topMerchants.splice(0, topMerchants.length, ...mList)
|
||||
} catch (e) {
|
||||
console.error('❌ loadSalesData failed', e)
|
||||
uni.showToast({ title: mapAnalyticsError(e, { fallbackMessage: '数据加载失败' }), icon: 'none', duration: 2000 })
|
||||
console.error('鉂?loadSalesData failed', e)
|
||||
uni.showToast({ title: mapAnalyticsError(e, { fallbackMessage: '鏁版嵁鍔犺浇澶辫触' }), icon: 'none', duration: 2000 })
|
||||
} finally {
|
||||
loading.value = false
|
||||
updateTime()
|
||||
@@ -285,13 +284,13 @@ function selectPeriod(p: string) {
|
||||
|
||||
function refreshData() {
|
||||
loadSalesData()
|
||||
uni.showToast({ title: '已刷新', icon: 'success' })
|
||||
uni.showToast({ title: '宸插埛鏂?, icon: 'success' })
|
||||
}
|
||||
|
||||
function exportReport() {
|
||||
uni.showActionSheet({
|
||||
itemList: ['导出Excel', '导出PDF', '导出图片'],
|
||||
success: () => uni.showToast({ title: '导出成功', icon: 'success' })
|
||||
itemList: ['瀵煎嚭Excel', '瀵煎嚭PDF', '瀵煎嚭鍥剧墖'],
|
||||
success: () => uni.showToast({ title: '瀵煎嚭鎴愬姛', icon: 'success' })
|
||||
})
|
||||
}
|
||||
|
||||
@@ -304,13 +303,13 @@ function updateTime() {
|
||||
|
||||
function formatInt(n: number): string {
|
||||
const v = isFinite(n) ? Math.round(n) : 0
|
||||
if (v >= 10000) return (v / 10000).toFixed(1) + '万'
|
||||
if (v >= 10000) return (v / 10000).toFixed(1) + '涓?
|
||||
return v.toString()
|
||||
}
|
||||
|
||||
function formatMoney(n: number): string {
|
||||
const v = isFinite(n) ? n : 0
|
||||
if (v >= 10000) return (v / 10000).toFixed(1) + '万'
|
||||
if (v >= 10000) return (v / 10000).toFixed(1) + '涓?
|
||||
return v.toFixed(0)
|
||||
}
|
||||
|
||||
@@ -337,27 +336,27 @@ function closeMoreMenu() {
|
||||
}
|
||||
|
||||
function handleSearch() {
|
||||
uni.showToast({ title: '搜索', icon: 'none' })
|
||||
uni.showToast({ title: '鎼滅储', icon: 'none' })
|
||||
}
|
||||
|
||||
function handleNotification() {
|
||||
uni.showToast({ title: '通知', icon: 'none' })
|
||||
uni.showToast({ title: '閫氱煡', icon: 'none' })
|
||||
}
|
||||
|
||||
function handleFullscreen() {
|
||||
uni.showToast({ title: '全屏', icon: 'none' })
|
||||
uni.showToast({ title: '鍏ㄥ睆', icon: 'none' })
|
||||
}
|
||||
|
||||
function handleMobile() {
|
||||
uni.showToast({ title: '移动端', icon: 'none' })
|
||||
uni.showToast({ title: '绉诲姩绔?, icon: 'none' })
|
||||
}
|
||||
|
||||
function handleDropdown() {
|
||||
uni.showToast({ title: '下拉菜单', icon: 'none' })
|
||||
uni.showToast({ title: '涓嬫媺鑿滃崟', icon: 'none' })
|
||||
}
|
||||
|
||||
function handleSettings() {
|
||||
uni.showToast({ title: '设置', icon: 'none' })
|
||||
uni.showToast({ title: '璁剧疆', icon: 'none' })
|
||||
}
|
||||
|
||||
function toggleCustomRange() {
|
||||
@@ -385,7 +384,7 @@ function onDateRangeClear() {
|
||||
background: #f6f7fb;
|
||||
}
|
||||
|
||||
/* 页面布局:宽屏时侧边栏+内容,窄屏时全屏内容 */
|
||||
/* 椤甸潰甯冨眬锛氬灞忔椂渚ц竟鏍?鍐呭锛岀獎灞忔椂鍏ㄥ睆鍐呭 */
|
||||
.page-layout {
|
||||
display: flex;
|
||||
flex-direction: row !important;
|
||||
@@ -397,18 +396,18 @@ function onDateRangeClear() {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding-top: 64px; /* 为固定顶部导航栏留出空间 */
|
||||
padding-top: 64px; /* 涓哄浐瀹氶《閮ㄥ鑸爮鐣欏嚭绌洪棿 */
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 16px 16px 28px;
|
||||
/* padding removed */ 16px 28px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 顶部栏 */
|
||||
/* 椤堕儴鏍?*/
|
||||
.topbar {
|
||||
display: flex;
|
||||
flex-direction: row !important;
|
||||
@@ -542,7 +541,7 @@ function onDateRangeClear() {
|
||||
color: #111;
|
||||
}
|
||||
|
||||
/* 时间维度 tabs */
|
||||
/* 鏃堕棿缁村害 tabs */
|
||||
.tabs {
|
||||
margin-top: 12px;
|
||||
display: flex;
|
||||
@@ -572,7 +571,7 @@ function onDateRangeClear() {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* KPI 网格 */
|
||||
/* KPI 缃戞牸 */
|
||||
.kpi-grid {
|
||||
margin-top: 12px;
|
||||
display: flex;
|
||||
@@ -609,7 +608,7 @@ function onDateRangeClear() {
|
||||
color: rgba(0,0,0,0.55);
|
||||
}
|
||||
|
||||
/* 卡片 */
|
||||
/* 鍗$墖 */
|
||||
.card {
|
||||
margin-top: 12px;
|
||||
background: #fff;
|
||||
@@ -729,7 +728,7 @@ function onDateRangeClear() {
|
||||
}
|
||||
}
|
||||
|
||||
/* 排行列表 */
|
||||
/* 鎺掕鍒楄〃 */
|
||||
.rank-scroll {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
@@ -738,14 +737,14 @@ function onDateRangeClear() {
|
||||
padding-right: 6px;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
|
||||
/* 防止滚动链把滚轮事件传给页面 */
|
||||
/* 闃叉婊氬姩閾炬妸婊氳疆浜嬩欢浼犵粰椤甸潰 */
|
||||
overscroll-behavior: contain;
|
||||
scroll-behavior: auto;
|
||||
|
||||
/* 默认隐藏滚动条(Firefox) */
|
||||
/* 榛樿闅愯棌婊氬姩鏉★紙Firefox锛?*/
|
||||
scrollbar-width: none;
|
||||
|
||||
/* 默认隐藏滚动条(WebKit) */
|
||||
/* 榛樿闅愯棌婊氬姩鏉★紙WebKit锛?*/
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
@@ -754,7 +753,7 @@ function onDateRangeClear() {
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/* 鼠标悬停在方块内时显示滚动条,并允许拖动 */
|
||||
/* 榧犳爣鎮仠鍦ㄦ柟鍧楀唴鏃舵樉绀烘粴鍔ㄦ潯锛屽苟鍏佽鎷栧姩 */
|
||||
.sales-split-list:hover .rank-scroll {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(0,0,0,0.35) rgba(0,0,0,0.06);
|
||||
@@ -842,7 +841,7 @@ function onDateRangeClear() {
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
/* 响应式 */
|
||||
/* 鍝嶅簲寮?*/
|
||||
@media screen and (min-width: 960px) {
|
||||
.kpi-card {
|
||||
flex: 1 1 calc(25% - 9px);
|
||||
@@ -866,3 +865,4 @@ function onDateRangeClear() {
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user