<template>
|
<div class="data-board">
|
<div class="header">
|
<h1 class="title">经营数据看板</h1>
|
<div class="filter">
|
<el-button-group>
|
<el-button :type="currentDateType === 0 ? 'primary' : 'default'" @click="onDateTypeChange(0)">今日</el-button>
|
<el-button :type="currentDateType === 1 ? 'primary' : 'default'" @click="onDateTypeChange(1)">近7日</el-button>
|
<el-button :type="currentDateType === 2 ? 'primary' : 'default'" @click="onDateTypeChange(2)">近30日</el-button>
|
<el-button :type="currentDateType === 3 ? 'primary' : 'default'" @click="onDateTypeChange(3)">半年</el-button>
|
<el-button :type="currentDateType === 4 ? 'primary' : 'default'" @click="onDateTypeChange(4)">一年</el-button>
|
</el-button-group>
|
<el-select v-model="currentShopId" filterable placeholder="请选择寄存点" style="width: 200px; margin: 0 10px;">
|
<el-option
|
v-for="shop in shopList"
|
:key="shop.id"
|
:label="shop.name"
|
:value="shop.id"
|
></el-option>
|
</el-select>
|
<el-button type="primary" @click="onSearch">查询</el-button>
|
<el-button @click="onReset">重置</el-button>
|
</div>
|
</div>
|
|
<div class="stats-row">
|
<div class="stat-card">
|
<div class="stat-label">入驻门店总数</div>
|
<div class="stat-value" v-if="overviewData">{{ overviewData.shopCount || 0 }}</div>
|
<div class="stat-value" v-else>-</div>
|
</div>
|
<div class="stat-card">
|
<div class="stat-label">门店入驻率</div>
|
<div class="stat-value" v-if="overviewData">{{ overviewData.shopSettlementRate || 0 }}%</div>
|
<div class="stat-value" v-else>-</div>
|
</div>
|
<div class="stat-card">
|
<div class="stat-label">认证司机总数</div>
|
<div class="stat-value" v-if="overviewData">{{ overviewData.driverCount || 0 }}</div>
|
<div class="stat-value" v-else>-</div>
|
</div>
|
<div class="stat-card">
|
<div class="stat-label">司机通过率</div>
|
<div class="stat-value" v-if="overviewData">{{ overviewData.driverPassRate || 0 }}%</div>
|
<div class="stat-value" v-else>-</div>
|
</div>
|
<div class="stat-card">
|
<div class="stat-label">累计会员总数</div>
|
<div class="stat-value" v-if="overviewData">{{ overviewData.memberCount || 0 }}</div>
|
<div class="stat-value" v-else>-</div>
|
</div>
|
<div class="stat-card">
|
<div class="stat-label">周期总订单数</div>
|
<div class="stat-value" v-if="overviewData">{{ overviewData.orderCount || 0 }}</div>
|
<div class="stat-value" v-else>-</div>
|
</div>
|
<div class="stat-card">
|
<div class="stat-label">周期营收总金额</div>
|
<div class="stat-value" v-if="overviewData">¥{{ (overviewData.totalRevenue || 0).toFixed(2) }}</div>
|
<div class="stat-value" v-else>-</div>
|
</div>
|
<div class="stat-card">
|
<div class="stat-label">周期退款单数</div>
|
<div class="stat-value" v-if="overviewData">{{ overviewData.refundOrderCount || 0 }}</div>
|
<div class="stat-value" v-else>-</div>
|
</div>
|
</div>
|
|
<div class="charts-row">
|
<div class="chart-card">
|
<div class="chart-header">
|
<span class="chart-title">订单行李类型占比</span>
|
<el-button type="text" size="mini" @click="exportPieChart">导出</el-button>
|
</div>
|
<div class="chart-content">
|
<div ref="pieChart" class="pie-chart"></div>
|
</div>
|
</div>
|
|
<div class="chart-card">
|
<div class="chart-header">
|
<span class="chart-title">新增会员走势</span>
|
<div class="date-selectors">
|
<el-select v-model="memberYear" placeholder="选择年份" @change="onMemberYearChange">
|
<el-option
|
v-for="year in memberYearOptions"
|
:key="year"
|
:label="year + '年'"
|
:value="year"
|
></el-option>
|
</el-select>
|
<el-select v-model="memberMonth" placeholder="选择月份" :disabled="!memberYear" @change="getMemberTrendData" clearable>
|
<el-option
|
v-for="month in memberMonthOptions"
|
:key="month"
|
:label="month + '月'"
|
:value="month"
|
></el-option>
|
</el-select>
|
</div>
|
</div>
|
<div class="chart-content">
|
<div ref="memberChart" class="member-chart"></div>
|
</div>
|
</div>
|
</div>
|
|
<div class="charts-row">
|
<div class="chart-card">
|
<div class="chart-header">
|
<span class="chart-title">订单量趋势</span>
|
<div class="date-selectors">
|
<el-select v-model="orderYear" placeholder="选择年份" @change="onOrderYearChange">
|
<el-option
|
v-for="year in orderYearOptions"
|
:key="year"
|
:label="year + '年'"
|
:value="year"
|
></el-option>
|
</el-select>
|
<el-select v-model="orderMonth" placeholder="选择月份" :disabled="!orderYear" @change="getOrderTrendData" clearable>
|
<el-option
|
v-for="month in orderMonthOptions"
|
:key="month"
|
:label="month + '月'"
|
:value="month"
|
></el-option>
|
</el-select>
|
</div>
|
</div>
|
<div class="chart-content">
|
<div ref="orderChart" class="order-chart"></div>
|
</div>
|
</div>
|
|
<div class="chart-card">
|
<div class="chart-header">
|
<span class="chart-title">营收增长曲线</span>
|
<div class="date-selectors">
|
<el-select v-model="revenueYear" placeholder="选择年份" @change="onRevenueYearChange">
|
<el-option
|
v-for="year in revenueYearOptions"
|
:key="year"
|
:label="year + '年'"
|
:value="year"
|
></el-option>
|
</el-select>
|
<el-select v-model="revenueMonth" placeholder="选择月份" :disabled="!revenueYear" @change="getRevenueTrendData" clearable>
|
<el-option
|
v-for="month in revenueMonthOptions"
|
:key="month"
|
:label="month + '月'"
|
:value="month"
|
></el-option>
|
</el-select>
|
</div>
|
</div>
|
<div class="chart-content">
|
<div ref="revenueChart" class="revenue-chart"></div>
|
</div>
|
</div>
|
</div>
|
|
<!-- 平台财务总览 -->
|
<div class="chart-card">
|
<div class="chart-header">
|
<span class="chart-title">平台财务总览</span>
|
<div class="filter-group">
|
<el-date-picker type="daterange" range-separator="至" start-placeholder="开始月份" end-placeholder="结束月份" v-model="financeDateRange" value-format="yyyy-MM" style="margin-right: 10px" :picker-options="financePickerOptions" clearable></el-date-picker>
|
<el-button type="primary" size="mini" @click="getFinanceOverviewData">查询</el-button>
|
<el-button size="mini" @click="resetFinanceDate">重置</el-button>
|
<el-button type="success" size="mini" @click="exportFinanceData">导出</el-button>
|
</div>
|
</div>
|
<div class="chart-content">
|
<el-table :data="financeList" stripe>
|
<el-table-column prop="date" label="年月" min-width="100px"></el-table-column>
|
<el-table-column prop="storageRevenue" label="寄存订单营收" min-width="120px"></el-table-column>
|
<el-table-column prop="deliveryRevenue" label="寄送订单营收" min-width="120px"></el-table-column>
|
<el-table-column prop="totalRevenue" label="平台总营收" min-width="120px"></el-table-column>
|
<el-table-column prop="shopFeeTotal" label="门店分成总额" min-width="120px"></el-table-column>
|
<el-table-column prop="driverFeeTotal" label="司机分成总额" min-width="120px"></el-table-column>
|
<el-table-column prop="refundAmount" label="退款总金额" min-width="120px"></el-table-column>
|
<el-table-column prop="netRevenue" label="平台净营收" min-width="120px"></el-table-column>
|
</el-table>
|
</div>
|
</div>
|
|
<div class="header" style="margin-top: 20px;">
|
<h1 class="title">业绩排名分析</h1>
|
<div class="filter">
|
<el-select v-model="shopTopYear" placeholder="选择年份" style="width: 120px; margin-right: 10px;" @change="onShopTopYearChange">
|
<el-option
|
v-for="year in shopTopYearOptions"
|
:key="year"
|
:label="year + '年'"
|
:value="year"
|
></el-option>
|
</el-select>
|
<el-select v-model="shopTopMonth" placeholder="选择月份" style="width: 120px;" :disabled="!shopTopYear" @change="onShopTopMonthChange" clearable>
|
<el-option
|
v-for="month in shopTopMonthOptions"
|
:key="month"
|
:label="month + '月'"
|
:value="month"
|
></el-option>
|
</el-select>
|
</div>
|
</div>
|
|
<!-- 业绩排名分析 -->
|
<div class="charts-row" style="margin-top: 20px; margin-bottom: 0;">
|
<div class="chart-card" style="width: calc(50% - 20px);">
|
<div class="chart-header">
|
<span class="chart-title">门店业绩TOP10</span>
|
</div>
|
<div class="chart-content">
|
<el-table :data="storeTopList" stripe>
|
<el-table-column type="index" label="排名" width="60px"></el-table-column>
|
<el-table-column prop="shopName" label="门店名称" min-width="120px"></el-table-column>
|
<el-table-column prop="completedCount" label="总完成订单量" min-width="120px"></el-table-column>
|
<el-table-column prop="totalRevenue" label="总营收金额" min-width="120px"></el-table-column>
|
<el-table-column prop="shopFeeTotal" label="门店分成总额" min-width="120px"></el-table-column>
|
<el-table-column prop="refundCount" label="退款单数" min-width="120px"></el-table-column>
|
<el-table-column prop="penaltyAmount" label="责任扣款总额" min-width="120px"></el-table-column>
|
</el-table>
|
</div>
|
</div>
|
|
<div class="chart-card" style="width: calc(50% - 20px);">
|
<div class="chart-header">
|
<span class="chart-title">司机业绩TOP10</span>
|
</div>
|
<div class="chart-content">
|
<el-table :data="driverTopList" stripe>
|
<el-table-column type="index" label="排名" width="60px"></el-table-column>
|
<el-table-column prop="driverName" label="司机姓名" min-width="120px"></el-table-column>
|
<el-table-column prop="completedCount" label="总完成订单量" min-width="120px"></el-table-column>
|
<el-table-column prop="totalRevenue" label="总营收金额" min-width="120px"></el-table-column>
|
<el-table-column prop="driverFeeTotal" label="司机分成总额" min-width="120px"></el-table-column>
|
<el-table-column prop="refundCount" label="退款单数" min-width="120px"></el-table-column>
|
<el-table-column prop="penaltyAmount" label="责任扣款总额" min-width="120px"></el-table-column>
|
</el-table>
|
</div>
|
</div>
|
</div>
|
</div>
|
</template>
|
|
<script>
|
import { overview, memberTrend, orderTrend, revenueTrend, financeOverview, exportExcel, luggageTypeExport, shopTop, driverTop } from '@/api/business/dataBoard'
|
import { fetchList } from '@/api/business/shopInfo'
|
import * as echarts from 'echarts'
|
|
export default {
|
name: 'DataBoard',
|
data () {
|
const currentYear = new Date().getFullYear()
|
const currentMonth = new Date().getMonth() + 1
|
const startOfYear = `${currentYear}-01`
|
const endOfYear = `${currentYear}-${String(currentMonth).padStart(2, '0')}`
|
return {
|
overviewData: {},
|
currentDateType: 0,
|
currentShopId: null,
|
shopList: [],
|
pieChartInstance: null,
|
currentYear,
|
currentMonth,
|
memberYear: currentYear,
|
memberMonth: null,
|
memberYearOptions: [],
|
memberMonthOptions: [],
|
memberTrendData: [],
|
memberChartInstance: null,
|
orderYear: currentYear,
|
orderMonth: null,
|
orderYearOptions: [],
|
orderMonthOptions: [],
|
orderTrendData: [],
|
orderChartInstance: null,
|
revenueYear: currentYear,
|
revenueMonth: null,
|
revenueYearOptions: [],
|
revenueMonthOptions: [],
|
revenueTrendData: [],
|
revenueChartInstance: null,
|
shopTopYear: currentYear,
|
shopTopMonth: null,
|
shopTopYearOptions: [],
|
shopTopMonthOptions: [],
|
driverTopList: [],
|
financeDateRange: [startOfYear, endOfYear],
|
financePickerOptions: {
|
disabledDate: (time) => {
|
// 禁用未来日期
|
if (time.getTime() > Date.now()) {
|
return true
|
}
|
// 限制日期范围不超过2年
|
if (this.financeMinDate) {
|
const twoYears = 2 * 365 * 24 * 60 * 60 * 1000
|
const maxTime = this.financeMinDate.getTime() + twoYears
|
if (time.getTime() > maxTime) {
|
return true
|
}
|
}
|
return false
|
},
|
onPick: ({ maxDate, minDate }) => {
|
this.financeMinDate = minDate
|
if (maxDate) {
|
this.financeMinDate = null
|
}
|
}
|
},
|
financeMinDate: null,
|
financeList: [],
|
storeTopList: [],
|
driverTopList: [
|
{ driverName: '张伟大', phone: '18356981111', totalOrder: '100', totalIncome: '90720.00', driverShare: '90720.00', refundCount: '100', deductionTotal: '90720.00' },
|
{ driverName: '李明', phone: '18356982222', totalOrder: '80', totalIncome: '32432.00', driverShare: '32432.00', refundCount: '80', deductionTotal: '32432.00' },
|
{ driverName: '张杰', phone: '18356981111', totalOrder: '70', totalIncome: '90720.00', driverShare: '90720.00', refundCount: '70', deductionTotal: '90720.00' },
|
{ driverName: '孙浩', phone: '18356982222', totalOrder: '70', totalIncome: '90720.00', driverShare: '90720.00', refundCount: '70', deductionTotal: '90720.00' },
|
{ driverName: '李梦', phone: '18356982222', totalOrder: '70', totalIncome: '90720.00', driverShare: '90720.00', refundCount: '70', deductionTotal: '90720.00' },
|
{ driverName: '刘梓贤', phone: '18356982222', totalOrder: '40', totalIncome: '90720.00', driverShare: '90720.00', refundCount: '40', deductionTotal: '90720.00' },
|
{ driverName: '王浩然', phone: '18356981111', totalOrder: '40', totalIncome: '90720.00', driverShare: '90720.00', refundCount: '40', deductionTotal: '90720.00' },
|
{ driverName: '张伟大', phone: '18356982222', totalOrder: '40', totalIncome: '90720.00', driverShare: '90720.00', refundCount: '40', deductionTotal: '90720.00' },
|
{ driverName: '李明', phone: '18356981111', totalOrder: '40', totalIncome: '90720.00', driverShare: '90720.00', refundCount: '40', deductionTotal: '90720.00' },
|
{ driverName: '张杰', phone: '18356982222', totalOrder: '40', totalIncome: '90720.00', driverShare: '90720.00', refundCount: '40', deductionTotal: '90720.00' }
|
]
|
}
|
},
|
created () {
|
this.getOverviewData()
|
this.getShopList()
|
this.initMemberDateOptions()
|
this.getMemberTrendData()
|
this.initOrderDateOptions()
|
this.getOrderTrendData()
|
this.initRevenueDateOptions()
|
this.getRevenueTrendData()
|
this.initShopTopDateOptions()
|
this.getShopTopData()
|
this.getDriverTopData()
|
this.getFinanceOverviewData()
|
},
|
mounted() {
|
this.initPieChart()
|
this.renderPieChart()
|
this.initMemberChart()
|
this.renderMemberChart()
|
this.initOrderChart()
|
this.renderOrderChart()
|
this.initRevenueChart()
|
this.renderRevenueChart()
|
window.addEventListener('resize', this.handleResize)
|
},
|
beforeDestroy() {
|
window.removeEventListener('resize', this.handleResize)
|
if (this.pieChartInstance) {
|
this.pieChartInstance.dispose()
|
}
|
if (this.memberChartInstance) {
|
this.memberChartInstance.dispose()
|
}
|
if (this.orderChartInstance) {
|
this.orderChartInstance.dispose()
|
}
|
if (this.revenueChartInstance) {
|
this.revenueChartInstance.dispose()
|
}
|
},
|
methods: {
|
async getOverviewData(dateType = 0, shopId = null) {
|
const response = await overview({ dateType, shopId })
|
this.overviewData = response || null
|
this.$nextTick(() => {
|
this.renderPieChart()
|
})
|
},
|
onDateTypeChange(dateType) {
|
this.currentDateType = dateType
|
this.getOverviewData(dateType, this.currentShopId)
|
},
|
onSearch() {
|
this.getOverviewData(this.currentDateType, this.currentShopId)
|
},
|
onReset() {
|
this.currentDateType = 0
|
this.currentShopId = null
|
this.getOverviewData(0, null)
|
},
|
async getShopList() {
|
const response = await fetchList({
|
capacity: 99999,
|
page: 1,
|
model: {
|
versionType: 0,
|
auditStatus: 3
|
}
|
})
|
this.shopList = response.records || []
|
},
|
initPieChart() {
|
this.pieChartInstance = echarts.init(this.$refs.pieChart)
|
},
|
renderPieChart() {
|
if (!this.pieChartInstance) {
|
this.initPieChart()
|
}
|
|
const data = this.overviewData.luggageTypeList || []
|
const chartData = data.map(item => ({
|
value: item.orderCount,
|
name: item.luggageName,
|
luggageCount: item.luggageCount
|
}))
|
|
const option = {
|
tooltip: {
|
trigger: 'item',
|
formatter: (params) => {
|
return `${params.name}<br/>行李数: ${params.data.luggageCount}`
|
}
|
},
|
series: [
|
{
|
name: '行李类型',
|
type: 'pie',
|
radius: ['40%', '70%'],
|
avoidLabelOverlap: true,
|
itemStyle: {
|
borderRadius: 10,
|
borderColor: '#fff',
|
borderWidth: 2
|
},
|
label: {
|
show: true,
|
position: 'outside',
|
formatter: '{b}'
|
},
|
emphasis: {
|
label: {
|
show: true,
|
fontSize: 16,
|
fontWeight: 'bold'
|
}
|
},
|
labelLine: {
|
show: true,
|
length: 30,
|
length2: 20,
|
smooth: true
|
},
|
data: chartData
|
}
|
]
|
}
|
|
this.pieChartInstance.setOption(option)
|
},
|
handleResize() {
|
if (this.pieChartInstance) {
|
this.pieChartInstance.resize()
|
}
|
if (this.memberChartInstance) {
|
this.memberChartInstance.resize()
|
}
|
if (this.orderChartInstance) {
|
this.orderChartInstance.resize()
|
}
|
if (this.revenueChartInstance) {
|
this.revenueChartInstance.resize()
|
}
|
},
|
async exportPieChart() {
|
try {
|
const dateType = this.currentDateType
|
const shopId = this.currentShopId
|
const params = { dateType, shopId }
|
|
const response = await luggageTypeExport(params)
|
|
// 创建下载链接
|
const blob = new Blob([response.data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
|
const url = window.URL.createObjectURL(blob)
|
const link = document.createElement('a')
|
link.href = url
|
|
// 生成文件名
|
const dateTypeNames = ['今日', '近七天', '近30天', '近半年', '近一年']
|
const shopName = this.shopList.find(shop => shop.id === shopId)?.name || '全部寄存点'
|
const fileName = `订单行李类型占比_${dateTypeNames[dateType]}_${shopName}.xlsx`
|
|
link.setAttribute('download', fileName)
|
document.body.appendChild(link)
|
link.click()
|
document.body.removeChild(link)
|
window.URL.revokeObjectURL(url)
|
|
this.$message.success('导出成功')
|
} catch (error) {
|
console.error('导出行李类型数据失败:', error)
|
this.$message.error('导出失败,请稍后重试')
|
}
|
},
|
initMemberDateOptions() {
|
// 生成年份选项,最多到当前年
|
const startYear = this.currentYear - 5
|
this.memberYearOptions = []
|
for (let year = startYear; year <= this.currentYear; year++) {
|
this.memberYearOptions.push(year)
|
}
|
// 初始化月份选项
|
this.updateMemberMonthOptions()
|
},
|
updateMemberMonthOptions() {
|
this.memberMonthOptions = []
|
const maxMonth = this.memberYear === this.currentYear ? this.currentMonth : 12
|
for (let month = 1; month <= maxMonth; month++) {
|
this.memberMonthOptions.push(month)
|
}
|
// 如果当前选择的月份超过最大允许值,自动调整
|
if (this.memberMonth > maxMonth) {
|
this.memberMonth = maxMonth
|
}
|
},
|
onMemberYearChange() {
|
this.updateMemberMonthOptions()
|
this.getMemberTrendData()
|
},
|
initOrderDateOptions() {
|
// 生成年份选项,最多到当前年
|
const startYear = this.currentYear - 5
|
this.orderYearOptions = []
|
for (let year = startYear; year <= this.currentYear; year++) {
|
this.orderYearOptions.push(year)
|
}
|
// 初始化月份选项
|
this.updateOrderMonthOptions()
|
},
|
updateOrderMonthOptions() {
|
this.orderMonthOptions = []
|
const maxMonth = this.orderYear === this.currentYear ? this.currentMonth : 12
|
for (let month = 1; month <= maxMonth; month++) {
|
this.orderMonthOptions.push(month)
|
}
|
// 如果当前选择的月份超过最大允许值,自动调整
|
if (this.orderMonth && this.orderMonth > maxMonth) {
|
this.orderMonth = maxMonth
|
}
|
},
|
onOrderYearChange() {
|
this.updateOrderMonthOptions()
|
this.getOrderTrendData()
|
},
|
async getOrderTrendData() {
|
const params = {}
|
if (this.orderMonth) {
|
params.month = `${this.orderYear}-${String(this.orderMonth).padStart(2, '0')}`
|
} else {
|
params.year = this.orderYear
|
}
|
const response = await orderTrend(params)
|
this.orderTrendData = response || []
|
this.$nextTick(() => {
|
this.renderOrderChart()
|
})
|
},
|
initRevenueDateOptions() {
|
// 生成年份选项,最多到当前年
|
const startYear = this.currentYear - 5
|
this.revenueYearOptions = []
|
for (let year = startYear; year <= this.currentYear; year++) {
|
this.revenueYearOptions.push(year)
|
}
|
// 初始化月份选项
|
this.updateRevenueMonthOptions()
|
},
|
updateRevenueMonthOptions() {
|
this.revenueMonthOptions = []
|
const maxMonth = this.revenueYear === this.currentYear ? this.currentMonth : 12
|
for (let month = 1; month <= maxMonth; month++) {
|
this.revenueMonthOptions.push(month)
|
}
|
// 如果当前选择的月份超过最大允许值,自动调整
|
if (this.revenueMonth && this.revenueMonth > maxMonth) {
|
this.revenueMonth = maxMonth
|
}
|
},
|
onRevenueYearChange() {
|
this.updateRevenueMonthOptions()
|
this.getRevenueTrendData()
|
},
|
async getRevenueTrendData() {
|
const params = {}
|
if (this.revenueMonth) {
|
params.month = `${this.revenueYear}-${String(this.revenueMonth).padStart(2, '0')}`
|
} else {
|
params.year = this.revenueYear
|
}
|
const response = await revenueTrend(params)
|
this.revenueTrendData = response || []
|
this.$nextTick(() => {
|
this.renderRevenueChart()
|
})
|
},
|
async getFinanceOverviewData() {
|
if (!this.financeDateRange || this.financeDateRange.length !== 2) {
|
// 如果日期范围为空,使用默认范围(今年1月到当前月)
|
const currentYear = new Date().getFullYear()
|
const currentMonth = new Date().getMonth() + 1
|
const startOfYear = `${currentYear}-01`
|
const endOfYear = `${currentYear}-${String(currentMonth).padStart(2, '0')}`
|
this.financeDateRange = [startOfYear, endOfYear]
|
}
|
const params = {
|
startDate: this.financeDateRange[0],
|
endDate: this.financeDateRange[1]
|
}
|
const response = await financeOverview(params)
|
this.financeList = response || []
|
},
|
resetFinanceDate() {
|
const currentYear = new Date().getFullYear()
|
const currentMonth = new Date().getMonth() + 1
|
const startOfYear = `${currentYear}-01`
|
const endOfYear = `${currentYear}-${String(currentMonth).padStart(2, '0')}`
|
this.financeDateRange = [startOfYear, endOfYear]
|
this.getFinanceOverviewData()
|
},
|
initShopTopDateOptions() {
|
// 生成年份选项,最多到当前年
|
const startYear = this.currentYear - 5
|
this.shopTopYearOptions = []
|
for (let year = startYear; year <= this.currentYear; year++) {
|
this.shopTopYearOptions.push(year)
|
}
|
// 初始化月份选项
|
this.updateShopTopMonthOptions()
|
},
|
updateShopTopMonthOptions() {
|
this.shopTopMonthOptions = []
|
const maxMonth = this.shopTopYear === this.currentYear ? this.currentMonth : 12
|
for (let month = 1; month <= maxMonth; month++) {
|
this.shopTopMonthOptions.push(month)
|
}
|
// 如果当前选择的月份超过最大允许值,自动调整
|
if (this.shopTopMonth && this.shopTopMonth > maxMonth) {
|
this.shopTopMonth = maxMonth
|
}
|
},
|
onShopTopYearChange() {
|
this.updateShopTopMonthOptions()
|
// 同时刷新门店和司机业绩数据
|
this.getShopTopData()
|
this.getDriverTopData()
|
},
|
onShopTopMonthChange() {
|
// 同时刷新门店和司机业绩数据
|
this.getShopTopData()
|
this.getDriverTopData()
|
},
|
async exportFinanceData() {
|
try {
|
// 确保有有效的日期范围
|
let startDate, endDate
|
if (!this.financeDateRange || this.financeDateRange.length !== 2) {
|
const currentYear = new Date().getFullYear()
|
const currentMonth = new Date().getMonth() + 1
|
startDate = `${currentYear}-01`
|
endDate = `${currentYear}-${String(currentMonth).padStart(2, '0')}`
|
} else {
|
startDate = this.financeDateRange[0]
|
endDate = this.financeDateRange[1]
|
}
|
|
const params = {
|
startDate,
|
endDate,
|
type: 'finance' // 导出类型标识
|
}
|
|
const response = await exportExcel(params)
|
|
// 创建下载链接
|
const blob = new Blob([response.data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' })
|
const url = window.URL.createObjectURL(blob)
|
const link = document.createElement('a')
|
link.href = url
|
link.setAttribute('download', `财务总览_${startDate}_至_${endDate}.xlsx`)
|
document.body.appendChild(link)
|
link.click()
|
document.body.removeChild(link)
|
window.URL.revokeObjectURL(url)
|
|
this.$message.success('导出成功')
|
} catch (error) {
|
console.error('导出财务数据失败:', error)
|
this.$message.error('导出失败,请稍后重试')
|
}
|
},
|
async getShopTopData() {
|
try {
|
const params = {}
|
if (this.shopTopMonth) {
|
params.month = `${this.shopTopYear}-${String(this.shopTopMonth).padStart(2, '0')}`
|
} else {
|
params.year = this.shopTopYear
|
}
|
const response = await shopTop(params)
|
this.storeTopList = response || []
|
} catch (error) {
|
console.error('获取门店业绩TOP10数据失败:', error)
|
}
|
},
|
async getDriverTopData() {
|
try {
|
const params = {}
|
if (this.shopTopMonth) {
|
params.month = `${this.shopTopYear}-${String(this.shopTopMonth).padStart(2, '0')}`
|
} else {
|
params.year = this.shopTopYear
|
}
|
const response = await driverTop(params)
|
this.driverTopList = response || []
|
} catch (error) {
|
console.error('获取司机业绩TOP10数据失败:', error)
|
}
|
},
|
async getMemberTrendData() {
|
const params = {}
|
if (this.memberMonth) {
|
params.month = `${this.memberYear}-${String(this.memberMonth).padStart(2, '0')}`
|
} else {
|
params.year = this.memberYear
|
}
|
const response = await memberTrend(params)
|
console.log('会员走势数据:', response)
|
this.memberTrendData = response || []
|
this.$nextTick(() => {
|
this.renderMemberChart()
|
})
|
},
|
initMemberChart() {
|
this.memberChartInstance = echarts.init(this.$refs.memberChart)
|
},
|
renderMemberChart() {
|
if (!this.memberChartInstance) {
|
this.initMemberChart()
|
}
|
|
const xAxisData = this.memberTrendData.map(item => item.date || item.name)
|
const seriesData = this.memberTrendData.map(item => item.count)
|
|
const option = {
|
tooltip: {
|
trigger: 'axis',
|
axisPointer: {
|
type: 'shadow'
|
}
|
},
|
grid: {
|
left: '3%',
|
right: '4%',
|
bottom: '3%',
|
containLabel: true
|
},
|
xAxis: [
|
{
|
type: 'category',
|
data: xAxisData,
|
axisTick: {
|
alignWithLabel: true
|
}
|
}
|
],
|
yAxis: [
|
{
|
type: 'value',
|
name: '新增会员数'
|
}
|
],
|
series: [
|
{
|
name: '新增会员',
|
type: 'bar',
|
barWidth: '60%',
|
data: seriesData
|
}
|
]
|
}
|
|
this.memberChartInstance.setOption(option)
|
},
|
initOrderChart() {
|
this.orderChartInstance = echarts.init(this.$refs.orderChart)
|
},
|
renderOrderChart() {
|
if (!this.orderChartInstance) {
|
this.initOrderChart()
|
}
|
|
const xAxisData = this.orderTrendData.map(item => item.date)
|
const localData = this.orderTrendData.map(item => item.localCount)
|
const remoteData = this.orderTrendData.map(item => item.remoteCount)
|
|
const option = {
|
tooltip: {
|
trigger: 'axis',
|
axisPointer: {
|
type: 'cross'
|
},
|
formatter: (params) => {
|
let result = params[0].name + '<br/>'
|
params.forEach(param => {
|
result += param.marker + param.seriesName + ': ' + Math.round(param.value) + '<br/>'
|
})
|
return result
|
}
|
},
|
legend: {
|
data: ['就地寄存', '同城寄送']
|
},
|
grid: {
|
left: '3%',
|
right: '4%',
|
bottom: '3%',
|
containLabel: true
|
},
|
xAxis: [
|
{
|
type: 'category',
|
boundaryGap: false,
|
data: xAxisData
|
}
|
],
|
yAxis: [
|
{
|
type: 'value',
|
name: '订单数量',
|
axisLabel: {
|
formatter: (value) => Math.round(value)
|
},
|
axisPointer: {
|
label: {
|
formatter: (params) => Math.round(params.value)
|
}
|
}
|
}
|
],
|
series: [
|
{
|
name: '就地寄存',
|
type: 'line',
|
smooth: true,
|
symbol: 'circle',
|
symbolSize: 6,
|
data: localData
|
},
|
{
|
name: '同城寄送',
|
type: 'line',
|
smooth: true,
|
symbol: 'circle',
|
symbolSize: 6,
|
data: remoteData
|
}
|
]
|
}
|
|
this.orderChartInstance.setOption(option)
|
},
|
initRevenueChart() {
|
this.revenueChartInstance = echarts.init(this.$refs.revenueChart)
|
},
|
renderRevenueChart() {
|
if (!this.revenueChartInstance) {
|
this.initRevenueChart()
|
}
|
|
const xAxisData = this.revenueTrendData.map(item => item.date)
|
const localData = this.revenueTrendData.map(item => item.localRevenue)
|
const remoteData = this.revenueTrendData.map(item => item.remoteRevenue)
|
|
const option = {
|
tooltip: {
|
trigger: 'axis',
|
axisPointer: {
|
type: 'cross'
|
},
|
formatter: (params) => {
|
let result = params[0].axisValue + '<br/>'
|
params.forEach(item => {
|
result += `${item.marker}${item.seriesName}: ¥${item.value.toFixed(2)}<br/>`
|
})
|
return result
|
}
|
},
|
legend: {
|
data: ['就地寄存', '同城寄送']
|
},
|
grid: {
|
left: '3%',
|
right: '4%',
|
bottom: '3%',
|
containLabel: true
|
},
|
xAxis: [
|
{
|
type: 'category',
|
boundaryGap: false,
|
data: xAxisData
|
}
|
],
|
yAxis: [
|
{
|
type: 'value',
|
name: '营收金额(元)',
|
axisLabel: {
|
formatter: '¥{value}'
|
}
|
}
|
],
|
series: [
|
{
|
name: '就地寄存',
|
type: 'line',
|
smooth: true,
|
symbol: 'circle',
|
symbolSize: 6,
|
data: localData
|
},
|
{
|
name: '同城寄送',
|
type: 'line',
|
smooth: true,
|
symbol: 'circle',
|
symbolSize: 6,
|
data: remoteData
|
}
|
]
|
}
|
|
this.revenueChartInstance.setOption(option)
|
}
|
}
|
}
|
</script>
|
|
<style scoped>
|
.data-board {
|
padding: 20px;
|
box-sizing: border-box;
|
background: #f5f7fa;
|
height: calc(100vh - 60px);
|
overflow-y: scroll;
|
}
|
.header {
|
display: flex;
|
/* justify-content: space-between; */
|
align-items: center;
|
}
|
.title {
|
font-size: 24px;
|
font-weight: bold;
|
color: #303133;
|
}
|
.filter {
|
display: flex;
|
align-items: center;
|
margin-left: 30px;
|
}
|
.stats-row {
|
display: flex;
|
align-items: center;
|
flex-wrap: wrap;
|
justify-content: space-between;
|
margin-bottom: 20px;
|
}
|
.stat-card {
|
width: calc(100% / 4 - 15px);
|
background: white;
|
padding: 20px;
|
box-sizing: border-box;
|
border-radius: 8px;
|
margin-top: 20px;
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
}
|
.stat-label {
|
font-size: 14px;
|
color: #909399;
|
margin-bottom: 10px;
|
}
|
.stat-value {
|
font-size: 32px;
|
font-weight: bold;
|
color: #303133;
|
}
|
.charts-row {
|
display: flex;
|
gap: 20px;
|
margin-bottom: 20px;
|
}
|
.chart-card {
|
flex: 1;
|
background: white;
|
padding: 20px;
|
box-sizing: border-box;
|
border-radius: 8px;
|
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
}
|
.chart-header {
|
display: flex;
|
justify-content: space-between;
|
align-items: center;
|
}
|
.chart-title {
|
font-size: 16px;
|
font-weight: bold;
|
color: #303133;
|
}
|
.chart-tags {
|
display: flex;
|
gap: 10px;
|
}
|
.chart-content {
|
/* height: 300px; */
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
}
|
.chart-placeholder {
|
width: 100%;
|
text-align: center;
|
}
|
|
.pie-chart {
|
width: 100%;
|
height: 300px;
|
}
|
|
.date-selectors {
|
display: flex;
|
gap: 10px;
|
}
|
|
.date-selectors .el-select {
|
width: 100px;
|
}
|
|
.member-chart {
|
width: 100%;
|
height: 300px;
|
}
|
|
.order-chart {
|
width: 100%;
|
height: 300px;
|
}
|
|
.revenue-chart {
|
width: 100%;
|
height: 300px;
|
}
|
|
.filter-group {
|
display: flex;
|
align-items: center;
|
gap: 10px;
|
}
|
|
.chart-content {
|
padding-top: 20px;
|
}
|
|
.el-table {
|
font-size: 14px;
|
}
|
|
.el-table th {
|
background-color: #f5f7fa;
|
font-weight: bold;
|
}
|
|
</style>
|