From ea2fb93a0dfcde8f5b66825b20f9d9b835a28acc Mon Sep 17 00:00:00 2001
From: MrShi <1878285526@qq.com>
Date: 星期五, 22 五月 2026 10:54:09 +0800
Subject: [PATCH] 提交
---
admin/src/views/index.vue | 1309 ++++++++++++++++++++++++++++++++++++++++++++++++-----------
1 files changed, 1,059 insertions(+), 250 deletions(-)
diff --git a/admin/src/views/index.vue b/admin/src/views/index.vue
index 20cab96..f1c26ab 100644
--- a/admin/src/views/index.vue
+++ b/admin/src/views/index.vue
@@ -1,278 +1,1087 @@
<template>
- <div class="main_home">
- <div class="home_header">
- <div class="mb10 fs17">涓嬪崍濂斤紝{{ userInfo.realname }}</div>
- <div class="fs13">
- 浠婂ぉ鏄� {{ nowDate }} {{ nowWeek }}锛屾杩庤闂珮閾佺珯琛屾潕瀵勫瓨绠$悊绯荤粺
+ <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="璐d换鎵f鎬婚" 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="璐d换鎵f鎬婚" min-width="120px"></el-table-column>
+ </el-table>
+ </div>
</div>
</div>
</div>
</template>
<script>
-import dayjs from 'dayjs'
+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'
-import { weeks } from '@/utils/config'
-const colors = ['#52a4f7', '#7678f7', '#5fc6d5']
+
export default {
- components: {
- },
+ 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 {
- searchForm: {
- timeType: null,
- timeName: '鍏ㄩ儴'
+ 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
+ }
+ }
},
- colors,
- nowDate: '',
- nowWeek: '',
- headerData: {},
- headerData1: {},
- staticData0: {},
- staticData01: {},
- staticData1: {},
- staticData2: {},
- staticData3: {},
- staticData4: {},
- manningRatio: []
- }
- },
- computed: {
- userInfo () {
- return this.$store.state.userInfo
+ 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.updateDate()
- // this.initData()
+ 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: {
- updateDate () {
- this.nowDate = dayjs().format('YYYY骞碝鏈圖鏃�')
- this.nowWeek = weeks[new Date().getDay()]
- }
+ 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 lang="scss" scoped>
-
-::v-deep .el-input--small .el-input__inner {
- height: 30px !important; // 杩欓噷灏辨槸淇敼榛樿楂樺害
- width: 120px;
-}
-div {
+<style scoped>
+.data-board {
+ padding: 20px;
box-sizing: border-box;
+ background: #f5f7fa;
+ height: calc(100vh - 60px);
+ overflow-y: scroll;
}
-
-.home_title {
- font-weight: 600;
- font-size: 16px;
- color: #222222;
- line-height: 22px;
-}
-.mb50{
- margin-bottom: 50px;
-}
-.main {
+.header {
display: flex;
- position: relative;
- z-index: 99;
-
- .app_content {
- flex: 1;
-
- .static_card {
- height: 187px;
- color: #fff;
- display: flex;
- justify-content: space-between;
-
- .card {
- flex: 1;
- height: 187px;
- background: linear-gradient(270deg, #29aeff 0%, #207ff7 100%);
- box-shadow: 0px 2px 10px 0px rgba(32, 127, 247, 0.4);
- border-radius: 8px;
- margin-right: 14px;
-
- &:nth-of-type(2) {
- background: linear-gradient(270deg, #8383ff 0%, #6b6eff 100%);
- }
-
- &:nth-of-type(3) {
- background: linear-gradient(270deg, #42d49d 0%, #12bb8b 100%);
- }
-
- &:nth-of-type(4) {
- margin-right: 0;
- background: linear-gradient(270deg, #c430dee3 0%, #cd04b9cf 100%);
- //linear-gradient(270deg, #de3049b8 0%, #cd0421d4 100%);
- //background: linear-gradient(270deg, #30d3de 0%, #04b7cd 100%);
- }
-
- .header {
- height: 103px;
- display: flex;
- justify-content: space-between;
- align-items: center;
- padding: 20px 20px 12px;
- border-bottom: 1px solid rgba(255, 255, 255, 0.2);
-
- img {
- width: 40px;
- height: 40px;
- }
-
- .num {
- font-weight: 600;
- font-size: 30px;
- margin-top: 12px;
- }
- }
-
- .content {
- height: 82px;
- display: flex;
- flex-direction: column;
- justify-content: space-between;
- font-size: 13px;
- padding: 15px 20px 20px;
- }
- }
- }
-
- .funcs {
- height: 149px;
- padding: 20px;
- background: #fff;
- margin: 10px 0;
-
- .list {
- display: flex;
- padding-top: 20px;
-
- .item {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- margin-right: 24px;
- cursor: pointer;
-
- img {
- width: 44px;
- height: 44px;
- }
- }
- }
- }
- }
- .static_wrap {
- .wrap {
- background: #fff;
- padding: 20px 20px 10px;
- border-radius: 2px;
- border: 1px solid #eeeeee;
- height: 280px;
- flex: 1;
- margin-top: 10px;
- margin-bottom: 20px;
- &:nth-of-type(2n) {
- //margin-left: 10px;
- }
- .echart {
- width: 100%;
- height: 190px;
- position: relative;
- display: flex;
- div{
- flex: 1;
- height: 190px;
- }
- }
-
- .header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 30px;
-
- .more {
- font-size: 13px;
- color: #999999;
- }
- }
- }
-
- .static1 {
- .content {
- display: flex;
- justify-content: center;
- align-items: center;
- height: 100%;
-
- .echart_wrap {
- position: relative;
-
- .pie_text {
- position: absolute;
- left: 50%;
- top: 50%;
- transform: translate(-50%, -50%);
- z-index: 999;
- }
- }
-
- .echart {
- width: 150px;
- height: 150px;
- }
-
- .list {
- margin-left: 36px;
-
- .item {
- display: flex;
- align-items: center;
- margin: 8px 0;
-
- .icon {
- width: 16px;
- height: 16px;
- border-radius: 50%;
- margin-right: 6px;
- background: linear-gradient(270deg, #29aeff 0%, #207ff7 100%);
- }
-
- .text {
- margin-right: 6px;
- }
- }
- }
- }
- }
- }
+ /* justify-content: space-between; */
+ align-items: center;
}
-
-.main_home {
- background: #f4f7fc;
- position: relative;
+.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%;
- height: 100%;
- overflow: auto;
- padding: 92px 20px 20px;
- .home_header {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 200px;
- padding: 20px;
- color: #fff;
- background: linear-gradient(180deg, #076ae5 0%, rgba(32, 127, 247, 0) 100%);
- }
+ text-align: center;
}
-</style>
+
+.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>
\ No newline at end of file
--
Gitblit v1.9.3