数据分析ui补充完善,接入数据库
This commit is contained in:
@@ -106,7 +106,6 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
|
||||
<!-- 留白 -->
|
||||
<view style="height: 24px;"></view>
|
||||
</view>
|
||||
@@ -115,338 +114,326 @@
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script lang="uts">
|
||||
<script setup lang="uts">
|
||||
import { computed, onLoad, reactive, ref } from 'vue'
|
||||
|
||||
import AnalyticsSidebarMenu from '@/components/analytics/AnalyticsSidebarMenu.uvue'
|
||||
import AnalyticsTopBar from '@/components/analytics/AnalyticsTopBar.uvue'
|
||||
import EChartsView from '@/uni_modules/charts/EChartsView.vue'
|
||||
|
||||
import { fetchDeliveryAnalysis } from '@/services/analytics/deliveryAnalysisService.uts'
|
||||
import { mapAnalyticsError } from '@/services/analytics/errorMapper.uts'
|
||||
|
||||
type DeliveryData = {
|
||||
avg_delivery_time: number
|
||||
time_growth: number
|
||||
total_fee: number
|
||||
avg_fee: number
|
||||
avg_orders_per_driver: number
|
||||
satisfaction_rate: number
|
||||
satisfaction_growth: number
|
||||
}
|
||||
type DriverRank = { id: string; rank: number; name: string; orders: number; rating: number }
|
||||
import type { TimePeriod } from '@/types/analytics/common.uts'
|
||||
import type { DeliveryData, DriverRank } from '@/types/analytics/delivery.uts'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
AnalyticsSidebarMenu,
|
||||
AnalyticsTopBar,
|
||||
EChartsView
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
lastUpdateTime: '',
|
||||
selectedPeriod: '7d',
|
||||
showMoreMenu: false,
|
||||
showSidebarMenu: false,
|
||||
currentPath: '/pages/mall/analytics/delivery-analysis',
|
||||
timePeriods: [
|
||||
{ value: '7d', label: '7天' },
|
||||
{ value: '30d', label: '30天' },
|
||||
{ value: '90d', label: '90天' },
|
||||
{ value: '1y', label: '1年' }
|
||||
],
|
||||
const lastUpdateTime = ref('')
|
||||
const selectedPeriod = ref('7d')
|
||||
const showMoreMenu = ref(false)
|
||||
const showSidebarMenu = ref(false)
|
||||
const currentPath = ref('/pages/mall/analytics/delivery-analysis')
|
||||
|
||||
deliveryData: {
|
||||
avg_delivery_time: 0,
|
||||
time_growth: 0,
|
||||
total_fee: 0,
|
||||
avg_fee: 0,
|
||||
avg_orders_per_driver: 0,
|
||||
satisfaction_rate: 0,
|
||||
satisfaction_growth: 0
|
||||
} as DeliveryData,
|
||||
const timePeriods = ref<Array<TimePeriod>>([
|
||||
{ value: '7d', label: '7天' },
|
||||
{ value: '30d', label: '30天' },
|
||||
{ value: '90d', label: '90天' },
|
||||
{ value: '1y', label: '1年' }
|
||||
])
|
||||
|
||||
topDrivers: [] as Array<DriverRank>,
|
||||
const deliveryData = reactive<DeliveryData>({
|
||||
avg_delivery_time: 0,
|
||||
time_growth: 0,
|
||||
total_fee: 0,
|
||||
avg_fee: 0,
|
||||
avg_orders_per_driver: 0,
|
||||
satisfaction_rate: 0,
|
||||
satisfaction_growth: 0
|
||||
})
|
||||
|
||||
timeChartOption: {} as any,
|
||||
feeChartOption: {} as any,
|
||||
isRankHover: false
|
||||
const topDrivers = reactive<Array<DriverRank>>([])
|
||||
|
||||
const timeChartOption = ref<any>({})
|
||||
const feeChartOption = ref<any>({})
|
||||
const isRankHover = ref(false)
|
||||
|
||||
const _trendRows = ref<Array<UTSJSONObject>>([])
|
||||
|
||||
const selectedPeriodText = computed((): string => {
|
||||
const p = timePeriods.value.find((t) => t.value === selectedPeriod.value)
|
||||
return p ? p.label : '7天'
|
||||
})
|
||||
|
||||
onLoad(() => {
|
||||
updateTime()
|
||||
loadDeliveryData()
|
||||
})
|
||||
|
||||
async function loadDeliveryData() {
|
||||
try {
|
||||
const data: any = await fetchDeliveryAnalysis(selectedPeriod.value)
|
||||
const trendList = data.trendList
|
||||
const topList = data.topList
|
||||
|
||||
const trendRows: Array<UTSJSONObject> = []
|
||||
let totalFee = 0
|
||||
let totalOrders = 0
|
||||
|
||||
for (let i = 0; i < trendList.length; i++) {
|
||||
const r = trendList[i]
|
||||
const dayStr = r.getString('day') ?? ''
|
||||
const orders = r.getNumber('completed_orders') ?? 0
|
||||
const avgMin = r.getNumber('avg_delivery_minutes') ?? 0
|
||||
const avgFee = r.getNumber('avg_fee') ?? 0
|
||||
const tFee = r.getNumber('total_fee') ?? 0
|
||||
|
||||
totalOrders += orders
|
||||
totalFee += tFee
|
||||
|
||||
const obj = new UTSJSONObject()
|
||||
obj.set('day', dayStr)
|
||||
obj.set('avg_delivery_time', avgMin)
|
||||
obj.set('avg_fee', avgFee)
|
||||
obj.set('satisfaction_rate', 0)
|
||||
trendRows.push(obj)
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
selectedPeriodText(): string {
|
||||
const p = this.timePeriods.find((t) => t.value === this.selectedPeriod)
|
||||
return p ? p.label : '7天'
|
||||
}
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.updateTime()
|
||||
this.loadDeliveryData()
|
||||
},
|
||||
|
||||
methods: {
|
||||
async loadDeliveryData() {
|
||||
try {
|
||||
const data = await fetchDeliveryAnalysis(this.selectedPeriod)
|
||||
const trendList = data.trendList
|
||||
const topList = data.topList
|
||||
|
||||
// 3) 转成页面内部 trendRows 格式
|
||||
const trendRows: Array<UTSJSONObject> = []
|
||||
let totalFee = 0
|
||||
let totalOrders = 0
|
||||
|
||||
for (let i = 0; i < trendList.length; i++) {
|
||||
const r = trendList[i]
|
||||
const dayStr = r.getString('day') ?? ''
|
||||
const orders = r.getNumber('completed_orders') ?? 0
|
||||
const avgMin = r.getNumber('avg_delivery_minutes') ?? 0
|
||||
const avgFee = r.getNumber('avg_fee') ?? 0
|
||||
const tFee = r.getNumber('total_fee') ?? 0
|
||||
|
||||
totalOrders += orders
|
||||
totalFee += tFee
|
||||
|
||||
const obj = new UTSJSONObject()
|
||||
obj.set('day', dayStr)
|
||||
obj.set('avg_delivery_time', avgMin)
|
||||
obj.set('avg_fee', avgFee)
|
||||
// 满意度趋势:目前来源为配送员表 rating_avg,后续如有配送评价表可替换
|
||||
obj.set('satisfaction_rate', 0)
|
||||
trendRows.push(obj)
|
||||
}
|
||||
|
||||
// 4) 满意度:用 TOP10 里的 rating_avg 做平均(简单可用;也可以后续改为全量司机或配送评价表)
|
||||
let satisSum = 0
|
||||
let satisCnt = 0
|
||||
for (let i = 0; i < topList.length; i++) {
|
||||
const r = topList[i]
|
||||
const rating = r.getNumber('rating_avg')
|
||||
if (rating != null) {
|
||||
satisSum += rating
|
||||
satisCnt += 1
|
||||
}
|
||||
}
|
||||
const satisAvg = satisCnt > 0 ? (satisSum / satisCnt) : 0
|
||||
for (let i = 0; i < trendRows.length; i++) {
|
||||
trendRows[i].set('satisfaction_rate', satisAvg)
|
||||
}
|
||||
|
||||
// 5) KPI:最后一天 vs 前一天环比
|
||||
const last = trendRows.length > 0 ? trendRows[trendRows.length - 1] : null
|
||||
const prev = trendRows.length > 1 ? trendRows[trendRows.length - 2] : null
|
||||
|
||||
const lastAvgTime = last != null ? (last.getNumber('avg_delivery_time') ?? 0) : 0
|
||||
const prevAvgTime = prev != null ? (prev.getNumber('avg_delivery_time') ?? 0) : 0
|
||||
const timeGrowth = prevAvgTime > 0 ? ((lastAvgTime - prevAvgTime) / prevAvgTime) * 100 : 0
|
||||
|
||||
const lastSatis = last != null ? (last.getNumber('satisfaction_rate') ?? 0) : 0
|
||||
const prevSatis = prev != null ? (prev.getNumber('satisfaction_rate') ?? 0) : 0
|
||||
const satisGrowth = prevSatis > 0 ? ((lastSatis - prevSatis) / prevSatis) * 100 : 0
|
||||
|
||||
// 配送员效率:单/人/天(按 TOP10 近似人数 + 趋势天数)
|
||||
const dayCount = Math.max(1, trendRows.length)
|
||||
const driverCount = Math.max(1, topList.length)
|
||||
const avgOrdersPerDriverPerDay = (totalOrders / driverCount) / dayCount
|
||||
|
||||
this.deliveryData = {
|
||||
avg_delivery_time: Math.round(lastAvgTime),
|
||||
time_growth: timeGrowth,
|
||||
total_fee: totalFee,
|
||||
avg_fee: totalOrders > 0 ? (totalFee / totalOrders) : 0,
|
||||
avg_orders_per_driver: avgOrdersPerDriverPerDay,
|
||||
satisfaction_rate: lastSatis,
|
||||
satisfaction_growth: satisGrowth
|
||||
} as DeliveryData
|
||||
|
||||
;(this as any)._trendRows = trendRows
|
||||
|
||||
// 6) TOP10 映射
|
||||
const list: DriverRank[] = []
|
||||
for (let i = 0; i < topList.length; i++) {
|
||||
const r = topList[i]
|
||||
list.push({
|
||||
id: r.getString('driver_id') ?? String(i),
|
||||
rank: i + 1,
|
||||
name: r.getString('driver_name') ?? '未知',
|
||||
orders: r.getNumber('orders') ?? 0,
|
||||
rating: r.getNumber('rating_avg') ?? 0
|
||||
} as DriverRank)
|
||||
}
|
||||
this.topDrivers = list
|
||||
|
||||
this.updateTime()
|
||||
this.buildChartOptions()
|
||||
} catch (e) {
|
||||
console.error('loadDeliveryData failed:', e)
|
||||
this.updateTime()
|
||||
this.buildChartOptions()
|
||||
uni.showToast({ title: mapAnalyticsError(e, { fallbackMessage: '配送分析数据加载失败' }), icon: 'none' })
|
||||
let satisSum = 0
|
||||
let satisCnt = 0
|
||||
for (let i = 0; i < topList.length; i++) {
|
||||
const r = topList[i]
|
||||
const rating = r.getNumber('rating_avg')
|
||||
if (rating != null) {
|
||||
satisSum += rating
|
||||
satisCnt += 1
|
||||
}
|
||||
},
|
||||
}
|
||||
const satisAvg = satisCnt > 0 ? satisSum / satisCnt : 0
|
||||
for (let i = 0; i < trendRows.length; i++) {
|
||||
trendRows[i].set('satisfaction_rate', satisAvg)
|
||||
}
|
||||
|
||||
selectPeriod(p: string) {
|
||||
this.selectedPeriod = p
|
||||
this.loadDeliveryData()
|
||||
},
|
||||
const last = trendRows.length > 0 ? trendRows[trendRows.length - 1] : null
|
||||
const prev = trendRows.length > 1 ? trendRows[trendRows.length - 2] : null
|
||||
|
||||
refreshData() {
|
||||
this.loadDeliveryData()
|
||||
uni.showToast({ title: '已刷新', icon: 'success' })
|
||||
},
|
||||
const lastAvgTime = last != null ? last.getNumber('avg_delivery_time') ?? 0 : 0
|
||||
const prevAvgTime = prev != null ? prev.getNumber('avg_delivery_time') ?? 0 : 0
|
||||
const timeGrowth = prevAvgTime > 0 ? ((lastAvgTime - prevAvgTime) / prevAvgTime) * 100 : 0
|
||||
|
||||
exportReport() {
|
||||
uni.showActionSheet({
|
||||
itemList: ['导出Excel', '导出PDF', '导出图片'],
|
||||
success: () => uni.showToast({ title: '导出成功', icon: 'success' })
|
||||
const lastSatis = last != null ? last.getNumber('satisfaction_rate') ?? 0 : 0
|
||||
const prevSatis = prev != null ? prev.getNumber('satisfaction_rate') ?? 0 : 0
|
||||
const satisGrowth = prevSatis > 0 ? ((lastSatis - prevSatis) / prevSatis) * 100 : 0
|
||||
|
||||
const dayCount = Math.max(1, trendRows.length)
|
||||
const driverCount = Math.max(1, topList.length)
|
||||
const avgOrdersPerDriverPerDay = (totalOrders / driverCount) / dayCount
|
||||
|
||||
deliveryData.avg_delivery_time = Math.round(lastAvgTime)
|
||||
deliveryData.time_growth = timeGrowth
|
||||
deliveryData.total_fee = totalFee
|
||||
deliveryData.avg_fee = totalOrders > 0 ? totalFee / totalOrders : 0
|
||||
deliveryData.avg_orders_per_driver = avgOrdersPerDriverPerDay
|
||||
deliveryData.satisfaction_rate = lastSatis
|
||||
deliveryData.satisfaction_growth = satisGrowth
|
||||
|
||||
_trendRows.value = trendRows
|
||||
|
||||
const list: Array<DriverRank> = []
|
||||
for (let i = 0; i < topList.length; i++) {
|
||||
const r = topList[i]
|
||||
list.push({
|
||||
id: r.getString('driver_id') ?? String(i),
|
||||
rank: i + 1,
|
||||
name: r.getString('driver_name') ?? '未知',
|
||||
orders: r.getNumber('orders') ?? 0,
|
||||
rating: r.getNumber('rating_avg') ?? 0
|
||||
})
|
||||
},
|
||||
|
||||
updateTime() {
|
||||
const now = new Date()
|
||||
const hh = now.getHours().toString().padStart(2, '0')
|
||||
const mm = now.getMinutes().toString().padStart(2, '0')
|
||||
this.lastUpdateTime = `${hh}:${mm}`
|
||||
},
|
||||
|
||||
formatInt(n: number): string {
|
||||
const v = isFinite(n) ? Math.round(n) : 0
|
||||
if (v >= 10000) return (v / 10000).toFixed(1) + '万'
|
||||
return v.toString()
|
||||
},
|
||||
|
||||
formatMoney(n: number): string {
|
||||
const v = isFinite(n) ? n : 0
|
||||
if (v >= 10000) return (v / 10000).toFixed(1) + '万'
|
||||
return v.toFixed(2)
|
||||
},
|
||||
|
||||
formatPct(n: number): string {
|
||||
const v = isFinite(n) ? n : 0
|
||||
const sign = v > 0 ? '+' : ''
|
||||
return `${sign}${v.toFixed(1)}%`
|
||||
},
|
||||
|
||||
formatScore(n: number): string {
|
||||
const v = isFinite(n) ? n : 0
|
||||
return v.toFixed(1)
|
||||
},
|
||||
|
||||
buildChartOptions() {
|
||||
const rowsAny = (this as any)._trendRows as any
|
||||
const rows = Array.isArray(rowsAny) ? rowsAny as Array<UTSJSONObject> : []
|
||||
|
||||
const xAxis: string[] = []
|
||||
const timeSeries: number[] = []
|
||||
const feeSeries: number[] = []
|
||||
const satisSeries: number[] = []
|
||||
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
const r = rows[i]
|
||||
const day = r.getString('day') ?? ''
|
||||
xAxis.push(day.length >= 10 ? day.substring(5, 10) : day)
|
||||
timeSeries.push(r.getNumber('avg_delivery_time') ?? 0)
|
||||
feeSeries.push(r.getNumber('avg_fee') ?? 0)
|
||||
satisSeries.push(r.getNumber('satisfaction_rate') ?? 0)
|
||||
}
|
||||
|
||||
this.timeChartOption = {
|
||||
tooltip: { trigger: 'axis' },
|
||||
legend: {
|
||||
data: ['平均配送时间(分钟)', '满意度(评分)'],
|
||||
top: 'bottom',
|
||||
itemGap: 30,
|
||||
itemWidth: 16,
|
||||
itemHeight: 16
|
||||
},
|
||||
grid: { left: 40, right: 50, top: 30, bottom: 60 },
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: xAxis,
|
||||
axisTick: { alignWithLabel: true }
|
||||
},
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
name: '配送时间',
|
||||
min: 0,
|
||||
splitLine: { lineStyle: { color: '#e5e7eb' } }
|
||||
},
|
||||
{
|
||||
type: 'value',
|
||||
name: '满意度',
|
||||
min: 0,
|
||||
max: 5,
|
||||
position: 'right',
|
||||
splitLine: { show: false }
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: '平均配送时间(分钟)',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
symbolSize: 6,
|
||||
data: timeSeries,
|
||||
yAxisIndex: 0
|
||||
},
|
||||
{
|
||||
name: '满意度(评分)',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
symbol: 'circle',
|
||||
symbolSize: 6,
|
||||
data: satisSeries,
|
||||
yAxisIndex: 1
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
this.feeChartOption = {
|
||||
tooltip: { trigger: 'axis' },
|
||||
grid: { left: 40, right: 20, top: 20, bottom: 30 },
|
||||
xAxis: { type: 'category', data: xAxis },
|
||||
yAxis: { type: 'value' },
|
||||
series: [
|
||||
{
|
||||
name: '平均配送费(元)',
|
||||
type: 'bar',
|
||||
data: feeSeries
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
this.satisfactionChartOption = {
|
||||
tooltip: { trigger: 'axis' },
|
||||
grid: { left: 40, right: 20, top: 20, bottom: 30 },
|
||||
xAxis: { type: 'category', data: xAxis },
|
||||
yAxis: { type: 'value', min: 0, max: 5 },
|
||||
series: [
|
||||
{
|
||||
name: '满意度(评分)',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
data: satisSeries
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
handleMenu() {
|
||||
this.showSidebarMenu = true
|
||||
},
|
||||
handleSidebarUpdate(visible: boolean) {
|
||||
this.showSidebarMenu = visible
|
||||
},
|
||||
|
||||
toggleMoreMenu() {
|
||||
this.showMoreMenu = !this.showMoreMenu
|
||||
},
|
||||
|
||||
closeMoreMenu() {
|
||||
this.showMoreMenu = false
|
||||
}
|
||||
topDrivers.splice(0, topDrivers.length, ...list)
|
||||
|
||||
updateTime()
|
||||
buildChartOptions()
|
||||
} catch (e) {
|
||||
console.error('loadDeliveryData failed:', e)
|
||||
updateTime()
|
||||
buildChartOptions()
|
||||
uni.showToast({ title: mapAnalyticsError(e, { fallbackMessage: '配送分析数据加载失败' }), icon: 'none' })
|
||||
}
|
||||
}
|
||||
|
||||
function selectPeriod(p: string) {
|
||||
selectedPeriod.value = p
|
||||
loadDeliveryData()
|
||||
}
|
||||
|
||||
function refreshData() {
|
||||
loadDeliveryData()
|
||||
uni.showToast({ title: '已刷新', icon: 'success' })
|
||||
}
|
||||
|
||||
function exportReport() {
|
||||
uni.showActionSheet({
|
||||
itemList: ['导出Excel', '导出PDF', '导出图片'],
|
||||
success: () => uni.showToast({ title: '导出成功', icon: 'success' })
|
||||
})
|
||||
}
|
||||
|
||||
function updateTime() {
|
||||
const now = new Date()
|
||||
const hh = now.getHours().toString().padStart(2, '0')
|
||||
const mm = now.getMinutes().toString().padStart(2, '0')
|
||||
lastUpdateTime.value = `${hh}:${mm}`
|
||||
}
|
||||
|
||||
function formatInt(n: number): string {
|
||||
const v = isFinite(n) ? Math.round(n) : 0
|
||||
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) + '万'
|
||||
return v.toFixed(2)
|
||||
}
|
||||
|
||||
function formatPct(n: number): string {
|
||||
const v = isFinite(n) ? n : 0
|
||||
const sign = v > 0 ? '+' : ''
|
||||
return `${sign}${v.toFixed(1)}%`
|
||||
}
|
||||
|
||||
function formatScore(n: number): string {
|
||||
const v = isFinite(n) ? n : 0
|
||||
return v.toFixed(1)
|
||||
}
|
||||
|
||||
function buildChartOptions() {
|
||||
const rows = Array.isArray(_trendRows.value) ? _trendRows.value : []
|
||||
|
||||
const xAxis: string[] = []
|
||||
const timeSeries: number[] = []
|
||||
const feeSeries: number[] = []
|
||||
const satisSeries: number[] = []
|
||||
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
const r = rows[i]
|
||||
const day = r.getString('day') ?? ''
|
||||
xAxis.push(day.length >= 10 ? day.substring(5, 10) : day)
|
||||
timeSeries.push(r.getNumber('avg_delivery_time') ?? 0)
|
||||
feeSeries.push(r.getNumber('avg_fee') ?? 0)
|
||||
satisSeries.push(r.getNumber('satisfaction_rate') ?? 0)
|
||||
}
|
||||
|
||||
timeChartOption.value = {
|
||||
tooltip: { trigger: 'axis' },
|
||||
legend: {
|
||||
data: ['平均配送时间(分钟)', '满意度(评分)'],
|
||||
top: 'bottom',
|
||||
itemGap: 30,
|
||||
itemWidth: 16,
|
||||
itemHeight: 16
|
||||
},
|
||||
grid: { left: 40, right: 50, top: 30, bottom: 60 },
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: xAxis,
|
||||
axisTick: { alignWithLabel: true }
|
||||
},
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value',
|
||||
name: '配送时间',
|
||||
min: 0,
|
||||
splitLine: { lineStyle: { color: '#e5e7eb' } }
|
||||
},
|
||||
{
|
||||
type: 'value',
|
||||
name: '满意度',
|
||||
min: 0,
|
||||
max: 5,
|
||||
position: 'right',
|
||||
splitLine: { show: false }
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: '平均配送时间(分钟)',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
symbolSize: 6,
|
||||
data: timeSeries,
|
||||
yAxisIndex: 0
|
||||
},
|
||||
{
|
||||
name: '满意度(评分)',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
symbol: 'circle',
|
||||
symbolSize: 6,
|
||||
data: satisSeries,
|
||||
yAxisIndex: 1
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
feeChartOption.value = {
|
||||
tooltip: { trigger: 'axis' },
|
||||
grid: { left: 40, right: 20, top: 20, bottom: 30 },
|
||||
xAxis: { type: 'category', data: xAxis },
|
||||
yAxis: { type: 'value' },
|
||||
series: [
|
||||
{
|
||||
name: '平均配送费(元)',
|
||||
type: 'bar',
|
||||
data: feeSeries
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
function handleMenu() {
|
||||
showSidebarMenu.value = true
|
||||
}
|
||||
|
||||
function handleSidebarUpdate(visible: boolean) {
|
||||
showSidebarMenu.value = visible
|
||||
}
|
||||
|
||||
function toggleMoreMenu() {
|
||||
showMoreMenu.value = !showMoreMenu.value
|
||||
}
|
||||
|
||||
function closeMoreMenu() {
|
||||
showMoreMenu.value = false
|
||||
}
|
||||
|
||||
function handleSearch() {
|
||||
uni.showToast({ title: '搜索', icon: 'none' })
|
||||
}
|
||||
|
||||
function handleNotification() {
|
||||
uni.showToast({ title: '通知', icon: 'none' })
|
||||
}
|
||||
|
||||
function handleFullscreen() {
|
||||
uni.showToast({ title: '全屏', icon: 'none' })
|
||||
}
|
||||
|
||||
function handleMobile() {
|
||||
uni.showToast({ title: '移动端', icon: 'none' })
|
||||
}
|
||||
|
||||
function handleDropdown() {
|
||||
uni.showToast({ title: '下拉菜单', icon: 'none' })
|
||||
}
|
||||
|
||||
function handleSettings() {
|
||||
uni.showToast({ title: '设置', icon: 'none' })
|
||||
}
|
||||
|
||||
function onRankHover(hover: boolean) {
|
||||
isRankHover.value = hover
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@@ -717,9 +704,7 @@ export default {
|
||||
height: 360px;
|
||||
}
|
||||
|
||||
/* 排行滚动容器:固定高度(约 5 条) */
|
||||
.rank-scroll {
|
||||
/* 5条左右的可视高度:5*(10px上下padding + 28px内容 + 10px gap) 约 300 */
|
||||
height: 320px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
@@ -728,7 +713,6 @@ export default {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* H5:默认隐藏滚动条,悬停时显示 */
|
||||
.rank-scroll-inner::-webkit-scrollbar {
|
||||
width: 0;
|
||||
height: 0;
|
||||
@@ -747,7 +731,6 @@ export default {
|
||||
background: rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
/* 排行列表 */
|
||||
.rank-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -812,7 +795,6 @@ export default {
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
/* 响应式 */
|
||||
@media screen and (min-width: 960px) {
|
||||
.kpi-card {
|
||||
flex: 1 1 calc(25% - 9px);
|
||||
@@ -825,22 +807,21 @@ export default {
|
||||
.subtitle {
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
|
||||
.topbar-right .btn-hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
|
||||
.more-btn {
|
||||
display: flex !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* 响应式:窄屏时全屏显示 */
|
||||
@media screen and (max-width: 959px) {
|
||||
.page-layout {
|
||||
flex-direction: column !important;
|
||||
}
|
||||
|
||||
|
||||
.main-content {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user