| ¶Ô±ÈÐÂÎļþ |
| | |
| | | <template> |
| | | <div class="sales-statistics-container"> |
| | | <div class="data-dashboard"> |
| | | <!-- 页颿 é¢ --> |
| | | <!-- <div class="dashboard-header"> |
| | | <div class="factory-name">éå®ç»è®¡çæ¿</div> |
| | | </div> --> |
| | | <!-- ç鿡件 --> |
| | | <div class="filter-area"> |
| | | <div class="filter-section"> |
| | | <span class="filter-label">æ¶é´èå´ï¼</span> |
| | | <el-date-picker v-model="dateRange" |
| | | type="daterange" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | @change="handleDateChange" |
| | | style="width: 240px;" /> |
| | | </div> |
| | | <div class="filter-section"> |
| | | <span class="filter-label">产åç±»åï¼</span> |
| | | <el-select v-model="productType" |
| | | placeholder="è¯·éæ©äº§åç±»å" |
| | | @change="handleFilterChange" |
| | | style="width: 160px;"> |
| | | <el-option label="å
¨é¨" |
| | | value="" /> |
| | | <el-option label="ç å" |
| | | value="block" /> |
| | | <el-option label="æ¿æ" |
| | | value="board" /> |
| | | <el-option label="åæ" |
| | | value="profile" /> |
| | | </el-select> |
| | | </div> |
| | | <div class="filter-section"> |
| | | <span class="filter-label">éå®åºåï¼</span> |
| | | <el-select v-model="salesArea" |
| | | placeholder="è¯·éæ©éå®åºå" |
| | | @change="handleFilterChange" |
| | | style="width: 160px;"> |
| | | <el-option label="å
¨é¨" |
| | | value="" /> |
| | | <el-option label="åä¸" |
| | | value="east" /> |
| | | <el-option label="åå" |
| | | value="north" /> |
| | | <el-option label="åå" |
| | | value="south" /> |
| | | <el-option label="西å" |
| | | value="southwest" /> |
| | | <el-option label="西å" |
| | | value="northwest" /> |
| | | </el-select> |
| | | </div> |
| | | <div class="filter-section"> |
| | | <span class="filter-label">ç»è®¡ç»´åº¦ï¼</span> |
| | | <el-select v-model="statDimension" |
| | | placeholder="è¯·éæ©ç»è®¡ç»´åº¦" |
| | | @change="handleFilterChange" |
| | | style="width: 160px;"> |
| | | <el-option label="æåº¦" |
| | | value="month" /> |
| | | <el-option label="年度" |
| | | value="year" /> |
| | | </el-select> |
| | | </div> |
| | | </div> |
| | | <div class="dashboard-content"> |
| | | <!-- æ ¸å¿ææ å¡ç --> |
| | | <div class="row row-1"> |
| | | <div class="panel-card card-1"> |
| | | <div class="panel-title">å计éé</div> |
| | | <div class="stats-grid"> |
| | | <div class="stat-item"> |
| | | <div class="stat-value sales-volume-color">{{ totalSalesVolume }}</div> |
| | | <div class="stat-unit">ç«æ¹ç±³</div> |
| | | <div class="stat-change">{{ salesVolumeChange }}%</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="panel-card card-2"> |
| | | <div class="panel-title">éå®éé¢</div> |
| | | <div class="stats-grid"> |
| | | <div class="stat-item"> |
| | | <div class="stat-value sales-amount-color">{{ totalSalesAmount }}</div> |
| | | <div class="stat-unit">ä¸å
</div> |
| | | <div class="stat-change">{{ salesAmountChange }}%</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="panel-card card-3"> |
| | | <div class="panel-title">æ°å¢å®¢æ·</div> |
| | | <div class="stats-grid"> |
| | | <div class="stat-item"> |
| | | <div class="stat-value new-customer-color">{{ newCustomerCount }}</div> |
| | | <div class="stat-unit">个</div> |
| | | <div class="stat-change">{{ customerCountChange }}%</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="panel-card card-4"> |
| | | <div class="panel-title">å计客æ·</div> |
| | | <div class="stats-grid"> |
| | | <div class="stat-item"> |
| | | <div class="stat-value total-customer-color">{{ totalCustomerCount }}</div> |
| | | <div class="stat-unit">个</div> |
| | | <div class="stat-change">{{ totalCustomerChange }}%</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <!-- ééåéå®éé¢è¶å¿ --> |
| | | <div class="row row-2"> |
| | | <div class="panel-card card-5"> |
| | | <div class="panel-title">ééè¶å¿</div> |
| | | <div class="chart-container"> |
| | | <div ref="salesVolumeChart" |
| | | style="width: 100%; height: 100%;"></div> |
| | | </div> |
| | | </div> |
| | | <div class="panel-card card-6"> |
| | | <div class="panel-title">éå®éé¢è¶å¿</div> |
| | | <div class="chart-container"> |
| | | <div ref="salesAmountChart" |
| | | style="width: 100%; height: 100%;"></div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <!-- ç´¯è®¡æ°æ®è¶å¿ --> |
| | | <!-- <div class="row row-3"> |
| | | <div class="panel-card card-10"> |
| | | <div class="panel-title">累计ééè¶å¿</div> |
| | | <div class="chart-container"> |
| | | <div ref="cumulativeSalesVolumeChart" |
| | | style="width: 100%; height: 100%;"></div> |
| | | </div> |
| | | </div> |
| | | <div class="panel-card card-11"> |
| | | <div class="panel-title">累计éå®éé¢è¶å¿</div> |
| | | <div class="chart-container"> |
| | | <div ref="cumulativeSalesAmountChart" |
| | | style="width: 100%; height: 100%;"></div> |
| | | </div> |
| | | </div> |
| | | </div> --> |
| | | <!-- å¾è¡¨åºååè¡¨æ ¼ --> |
| | | <div class="row row-4"> |
| | | <!-- 左边ï¼è¯¦ç»æ°æ®è¡¨æ ¼ --> |
| | | <div class="panel-card card-9" |
| | | style="flex: 2;"> |
| | | <div class="panel-title">éå®ç»è®¡è¯¦ç»æ°æ®</div> |
| | | <div class="table-container"> |
| | | <el-table :data="tableData" |
| | | style="width: 100%"> |
| | | <el-table-column prop="productType" |
| | | label="产åç±»å" |
| | | width="120" |
| | | align="center"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getProductTypeType(scope.row.productType)"> |
| | | {{ scope.row.productType }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="salesArea" |
| | | label="éå®åºå" |
| | | width="120" |
| | | align="center"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getSalesAreaType(scope.row.salesArea)"> |
| | | {{ scope.row.salesArea }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="period" |
| | | label="ç»è®¡å¨æ" |
| | | width="120" /> |
| | | <el-table-column prop="salesVolume" |
| | | label="éé(ç«æ¹ç±³)" |
| | | align="right"> |
| | | <template #default="scope"> |
| | | <span class="data-value">{{ scope.row.salesVolume }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="salesAmount" |
| | | label="éå®éé¢(ä¸å
)" |
| | | align="right"> |
| | | <template #default="scope"> |
| | | <span class="data-value">{{ scope.row.salesAmount }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="newCustomers" |
| | | label="æ°å¢å®¢æ·(个)" |
| | | width="150" |
| | | align="right"> |
| | | <template #default="scope"> |
| | | <span class="data-value">{{ scope.row.newCustomers }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="totalCustomers" |
| | | label="å计客æ·(个)" |
| | | width="150" |
| | | align="right"> |
| | | <template #default="scope"> |
| | | <span class="data-value">{{ scope.row.totalCustomers }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | </div> |
| | | <!-- å³è¾¹ï¼äº§åç±»ååå¸åéå®åºååå¸ --> |
| | | <div class="chart-column" |
| | | style="flex: 1; display: flex; flex-direction: column; gap: 20px;"> |
| | | <div class="panel-card card-7" |
| | | style="flex: 1;"> |
| | | <div class="panel-title">产åç±»ååå¸</div> |
| | | <div class="chart-container"> |
| | | <div ref="productTypeChart" |
| | | style="width: 100%; height: 100%;"></div> |
| | | </div> |
| | | </div> |
| | | <div class="panel-card card-8" |
| | | style="flex: 1;"> |
| | | <div class="panel-title">éå®åºååå¸</div> |
| | | <div class="chart-container"> |
| | | <div ref="salesAreaChart" |
| | | style="width: 100%; height: 100%;"></div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import { |
| | | ref, |
| | | computed, |
| | | onMounted, |
| | | onBeforeUnmount, |
| | | watch, |
| | | nextTick, |
| | | } from "vue"; |
| | | import { useRouter } from "vue-router"; |
| | | import * as echarts from "echarts"; |
| | | import dayjs from "dayjs"; |
| | | |
| | | const router = useRouter(); |
| | | |
| | | // ç鿡件 |
| | | const dateRange = ref([]); |
| | | const productType = ref(""); |
| | | const salesArea = ref(""); |
| | | const statDimension = ref("month"); |
| | | |
| | | // å¾è¡¨å¼ç¨ |
| | | const salesVolumeChart = ref(null); |
| | | const salesAmountChart = ref(null); |
| | | const productTypeChart = ref(null); |
| | | const salesAreaChart = ref(null); |
| | | const cumulativeSalesVolumeChart = ref(null); |
| | | const cumulativeSalesAmountChart = ref(null); |
| | | |
| | | // å¾è¡¨å®ä¾ |
| | | let salesVolumeChartInstance = null; |
| | | let salesAmountChartInstance = null; |
| | | let productTypeChartInstance = null; |
| | | let salesAreaChartInstance = null; |
| | | let cumulativeSalesVolumeChartInstance = null; |
| | | let cumulativeSalesAmountChartInstance = null; |
| | | |
| | | // æ¨¡ææ°æ® |
| | | const mockData = [ |
| | | // 2026å¹´1ææ°æ® |
| | | { |
| | | productType: "ç å", |
| | | salesArea: "åä¸", |
| | | period: "2026-01", |
| | | salesVolume: 1200, |
| | | salesAmount: 180, |
| | | newCustomers: 5, |
| | | totalCustomers: 120, |
| | | }, |
| | | { |
| | | productType: "ç å", |
| | | salesArea: "åå", |
| | | period: "2026-01", |
| | | salesVolume: 800, |
| | | salesAmount: 120, |
| | | newCustomers: 3, |
| | | totalCustomers: 80, |
| | | }, |
| | | { |
| | | productType: "ç å", |
| | | salesArea: "åå", |
| | | period: "2026-01", |
| | | salesVolume: 600, |
| | | salesAmount: 90, |
| | | newCustomers: 2, |
| | | totalCustomers: 60, |
| | | }, |
| | | { |
| | | productType: "æ¿æ", |
| | | salesArea: "åä¸", |
| | | period: "2026-01", |
| | | salesVolume: 900, |
| | | salesAmount: 270, |
| | | newCustomers: 4, |
| | | totalCustomers: 100, |
| | | }, |
| | | { |
| | | productType: "æ¿æ", |
| | | salesArea: "åå", |
| | | period: "2026-01", |
| | | salesVolume: 500, |
| | | salesAmount: 150, |
| | | newCustomers: 2, |
| | | totalCustomers: 70, |
| | | }, |
| | | { |
| | | productType: "åæ", |
| | | salesArea: "åä¸", |
| | | period: "2026-01", |
| | | salesVolume: 400, |
| | | salesAmount: 200, |
| | | newCustomers: 3, |
| | | totalCustomers: 50, |
| | | }, |
| | | // 2026å¹´2ææ°æ® |
| | | { |
| | | productType: "ç å", |
| | | salesArea: "åä¸", |
| | | period: "2026-02", |
| | | salesVolume: 1300, |
| | | salesAmount: 195, |
| | | newCustomers: 4, |
| | | totalCustomers: 124, |
| | | }, |
| | | { |
| | | productType: "ç å", |
| | | salesArea: "åå", |
| | | period: "2026-02", |
| | | salesVolume: 850, |
| | | salesAmount: 127.5, |
| | | newCustomers: 2, |
| | | totalCustomers: 82, |
| | | }, |
| | | { |
| | | productType: "ç å", |
| | | salesArea: "åå", |
| | | period: "2026-02", |
| | | salesVolume: 650, |
| | | salesAmount: 97.5, |
| | | newCustomers: 1, |
| | | totalCustomers: 61, |
| | | }, |
| | | { |
| | | productType: "æ¿æ", |
| | | salesArea: "åä¸", |
| | | period: "2026-02", |
| | | salesVolume: 950, |
| | | salesAmount: 285, |
| | | newCustomers: 3, |
| | | totalCustomers: 103, |
| | | }, |
| | | { |
| | | productType: "æ¿æ", |
| | | salesArea: "åå", |
| | | period: "2026-02", |
| | | salesVolume: 550, |
| | | salesAmount: 165, |
| | | newCustomers: 1, |
| | | totalCustomers: 71, |
| | | }, |
| | | { |
| | | productType: "åæ", |
| | | salesArea: "åä¸", |
| | | period: "2026-02", |
| | | salesVolume: 450, |
| | | salesAmount: 225, |
| | | newCustomers: 2, |
| | | totalCustomers: 52, |
| | | }, |
| | | // 2026å¹´3ææ°æ® |
| | | { |
| | | productType: "ç å", |
| | | salesArea: "åä¸", |
| | | period: "2026-03", |
| | | salesVolume: 1400, |
| | | salesAmount: 210, |
| | | newCustomers: 6, |
| | | totalCustomers: 130, |
| | | }, |
| | | { |
| | | productType: "ç å", |
| | | salesArea: "åå", |
| | | period: "2026-03", |
| | | salesVolume: 900, |
| | | salesAmount: 135, |
| | | newCustomers: 3, |
| | | totalCustomers: 85, |
| | | }, |
| | | { |
| | | productType: "ç å", |
| | | salesArea: "åå", |
| | | period: "2026-03", |
| | | salesVolume: 700, |
| | | salesAmount: 105, |
| | | newCustomers: 2, |
| | | totalCustomers: 63, |
| | | }, |
| | | { |
| | | productType: "æ¿æ", |
| | | salesArea: "åä¸", |
| | | period: "2026-03", |
| | | salesVolume: 1000, |
| | | salesAmount: 300, |
| | | newCustomers: 5, |
| | | totalCustomers: 108, |
| | | }, |
| | | { |
| | | productType: "æ¿æ", |
| | | salesArea: "åå", |
| | | period: "2026-03", |
| | | salesVolume: 600, |
| | | salesAmount: 180, |
| | | newCustomers: 2, |
| | | totalCustomers: 73, |
| | | }, |
| | | { |
| | | productType: "åæ", |
| | | salesArea: "åä¸", |
| | | period: "2026-03", |
| | | salesVolume: 500, |
| | | salesAmount: 250, |
| | | newCustomers: 3, |
| | | totalCustomers: 55, |
| | | }, |
| | | // 西åå西åå°åºæ°æ® |
| | | { |
| | | productType: "ç å", |
| | | salesArea: "西å", |
| | | period: "2026-03", |
| | | salesVolume: 500, |
| | | salesAmount: 75, |
| | | newCustomers: 2, |
| | | totalCustomers: 40, |
| | | }, |
| | | { |
| | | productType: "æ¿æ", |
| | | salesArea: "西å", |
| | | period: "2026-03", |
| | | salesVolume: 300, |
| | | salesAmount: 90, |
| | | newCustomers: 1, |
| | | totalCustomers: 30, |
| | | }, |
| | | { |
| | | productType: "ç å", |
| | | salesArea: "西å", |
| | | period: "2026-03", |
| | | salesVolume: 400, |
| | | salesAmount: 60, |
| | | newCustomers: 1, |
| | | totalCustomers: 35, |
| | | }, |
| | | { |
| | | productType: "æ¿æ", |
| | | salesArea: "西å", |
| | | period: "2026-03", |
| | | salesVolume: 200, |
| | | salesAmount: 60, |
| | | newCustomers: 1, |
| | | totalCustomers: 25, |
| | | }, |
| | | ]; |
| | | |
| | | // 计ç®å±æ§ |
| | | const filteredData = computed(() => { |
| | | let result = [...mockData]; |
| | | |
| | | // æäº§åç±»åçé |
| | | if (productType.value) { |
| | | result = result.filter(item => { |
| | | const typeMap = { block: "ç å", board: "æ¿æ", profile: "åæ" }; |
| | | return item.productType === typeMap[productType.value]; |
| | | }); |
| | | } |
| | | |
| | | // æéå®åºåçé |
| | | if (salesArea.value) { |
| | | result = result.filter(item => { |
| | | const areaMap = { |
| | | east: "åä¸", |
| | | north: "åå", |
| | | south: "åå", |
| | | southwest: "西å", |
| | | northwest: "西å", |
| | | }; |
| | | return item.salesArea === areaMap[salesArea.value]; |
| | | }); |
| | | } |
| | | |
| | | // ææ¶é´èå´çé |
| | | if (dateRange.value && dateRange.value.length === 2) { |
| | | const startDate = dayjs(dateRange.value[0]); |
| | | const endDate = dayjs(dateRange.value[1]); |
| | | |
| | | result = result.filter(item => { |
| | | const itemDate = dayjs(item.period); |
| | | return ( |
| | | itemDate.isAfter(startDate.subtract(1, "day")) && |
| | | itemDate.isBefore(endDate.add(1, "day")) |
| | | ); |
| | | }); |
| | | } |
| | | |
| | | return result; |
| | | }); |
| | | |
| | | // æ ¸å¿ææ è®¡ç® |
| | | const totalSalesVolume = computed(() => { |
| | | return filteredData.value.reduce((sum, item) => sum + item.salesVolume, 0); |
| | | }); |
| | | |
| | | const totalSalesAmount = computed(() => { |
| | | return filteredData.value |
| | | .reduce((sum, item) => sum + item.salesAmount, 0) |
| | | .toFixed(2); |
| | | }); |
| | | |
| | | const newCustomerCount = computed(() => { |
| | | return filteredData.value.reduce((sum, item) => sum + item.newCustomers, 0); |
| | | }); |
| | | |
| | | const totalCustomerCount = computed(() => { |
| | | // è®¡ç®æ¯ä¸ªåºåå产åç±»åçæå¤§å®¢æ·æ° |
| | | const customerMap = {}; |
| | | filteredData.value.forEach(item => { |
| | | const key = `${item.productType}-${item.salesArea}`; |
| | | if (!customerMap[key] || item.totalCustomers > customerMap[key]) { |
| | | customerMap[key] = item.totalCustomers; |
| | | } |
| | | }); |
| | | return Object.values(customerMap).reduce((sum, count) => sum + count, 0); |
| | | }); |
| | | |
| | | // ååç计ç®ï¼æ¨¡æï¼ |
| | | const salesVolumeChange = ref("+5.2"); |
| | | const salesAmountChange = ref("+7.8"); |
| | | const customerCountChange = ref("+3.5"); |
| | | const totalCustomerChange = ref("+2.1"); |
| | | |
| | | // è¡¨æ ¼æ°æ® |
| | | const tableData = computed(() => { |
| | | return filteredData.value.map(item => { |
| | | // 计ç®ç´¯è®¡å¼ï¼æ¨¡æï¼ |
| | | const cumulativeSalesVolume = item.salesVolume * 1.5; |
| | | const cumulativeSalesAmount = item.salesAmount * 1.5; |
| | | const cumulativeNewCustomers = item.newCustomers * 2; |
| | | |
| | | return { |
| | | ...item, |
| | | cumulativeSalesVolume, |
| | | cumulativeSalesAmount, |
| | | cumulativeNewCustomers, |
| | | }; |
| | | }); |
| | | }); |
| | | |
| | | // ééè¶å¿å¾è¡¨é
ç½® |
| | | const salesVolumeChartOption = computed(() => { |
| | | // æå¨æåç» |
| | | const periodMap = {}; |
| | | filteredData.value.forEach(item => { |
| | | if (!periodMap[item.period]) { |
| | | periodMap[item.period] = 0; |
| | | } |
| | | periodMap[item.period] += item.salesVolume; |
| | | }); |
| | | |
| | | const periods = Object.keys(periodMap).sort(); |
| | | const values = periods.map(period => periodMap[period]); |
| | | |
| | | return { |
| | | tooltip: { |
| | | trigger: "axis", |
| | | formatter: "{b}: {c} ç«æ¹ç±³", |
| | | }, |
| | | xAxis: { |
| | | type: "category", |
| | | data: periods, |
| | | }, |
| | | yAxis: { |
| | | type: "value", |
| | | name: "ééï¼ç«æ¹ç±³ï¼", |
| | | }, |
| | | series: [ |
| | | { |
| | | data: values, |
| | | type: "line", |
| | | smooth: true, |
| | | lineStyle: { |
| | | width: 3, |
| | | }, |
| | | itemStyle: { |
| | | color: "#409EFF", |
| | | }, |
| | | }, |
| | | ], |
| | | }; |
| | | }); |
| | | |
| | | // éå®éé¢è¶å¿å¾è¡¨é
ç½® |
| | | const salesAmountChartOption = computed(() => { |
| | | // æå¨æåç» |
| | | const periodMap = {}; |
| | | filteredData.value.forEach(item => { |
| | | if (!periodMap[item.period]) { |
| | | periodMap[item.period] = 0; |
| | | } |
| | | periodMap[item.period] += item.salesAmount; |
| | | }); |
| | | |
| | | const periods = Object.keys(periodMap).sort(); |
| | | const values = periods.map(period => periodMap[period]); |
| | | |
| | | return { |
| | | tooltip: { |
| | | trigger: "axis", |
| | | formatter: "{b}: {c} ä¸å
", |
| | | }, |
| | | xAxis: { |
| | | type: "category", |
| | | data: periods, |
| | | }, |
| | | yAxis: { |
| | | type: "value", |
| | | name: "éå®éé¢ï¼ä¸å
ï¼", |
| | | }, |
| | | series: [ |
| | | { |
| | | data: values, |
| | | type: "bar", |
| | | itemStyle: { |
| | | color: "#67C23A", |
| | | }, |
| | | }, |
| | | ], |
| | | }; |
| | | }); |
| | | |
| | | // 产åç±»ååå¸å¾è¡¨é
ç½® |
| | | const productTypeChartOption = computed(() => { |
| | | // æäº§åç±»ååç» |
| | | const typeMap = {}; |
| | | filteredData.value.forEach(item => { |
| | | if (!typeMap[item.productType]) { |
| | | typeMap[item.productType] = 0; |
| | | } |
| | | typeMap[item.productType] += item.salesVolume; |
| | | }); |
| | | |
| | | const types = Object.keys(typeMap); |
| | | const values = types.map(type => typeMap[type]); |
| | | |
| | | return { |
| | | tooltip: { |
| | | trigger: "item", |
| | | formatter: "{b}: {c} ç«æ¹ç±³ ({d}%)", |
| | | }, |
| | | series: [ |
| | | { |
| | | type: "pie", |
| | | radius: "60%", |
| | | data: types.map((type, index) => ({ |
| | | name: type, |
| | | value: values[index], |
| | | })), |
| | | emphasis: { |
| | | itemStyle: { |
| | | shadowBlur: 10, |
| | | shadowOffsetX: 0, |
| | | shadowColor: "rgba(0, 0, 0, 0.5)", |
| | | }, |
| | | }, |
| | | }, |
| | | ], |
| | | }; |
| | | }); |
| | | |
| | | // éå®åºååå¸å¾è¡¨é
ç½® |
| | | const salesAreaChartOption = computed(() => { |
| | | // æéå®åºååç» |
| | | const areaMap = {}; |
| | | filteredData.value.forEach(item => { |
| | | if (!areaMap[item.salesArea]) { |
| | | areaMap[item.salesArea] = 0; |
| | | } |
| | | areaMap[item.salesArea] += item.salesVolume; |
| | | }); |
| | | |
| | | const areas = Object.keys(areaMap); |
| | | const values = areas.map(area => areaMap[area]); |
| | | |
| | | return { |
| | | tooltip: { |
| | | trigger: "item", |
| | | formatter: "{b}: {c} ç«æ¹ç±³ ({d}%)", |
| | | }, |
| | | series: [ |
| | | { |
| | | type: "pie", |
| | | radius: "60%", |
| | | data: areas.map((area, index) => ({ |
| | | name: area, |
| | | value: values[index], |
| | | })), |
| | | emphasis: { |
| | | itemStyle: { |
| | | shadowBlur: 10, |
| | | shadowOffsetX: 0, |
| | | shadowColor: "rgba(0, 0, 0, 0.5)", |
| | | }, |
| | | }, |
| | | }, |
| | | ], |
| | | }; |
| | | }); |
| | | |
| | | // 累计ééè¶å¿å¾è¡¨é
ç½® |
| | | const cumulativeSalesVolumeChartOption = computed(() => { |
| | | // æå¨æåç» |
| | | const periodMap = {}; |
| | | let cumulativeValue = 0; |
| | | |
| | | // æå¨ææåº |
| | | const sortedData = [...filteredData.value].sort((a, b) => |
| | | a.period.localeCompare(b.period) |
| | | ); |
| | | |
| | | sortedData.forEach(item => { |
| | | cumulativeValue += item.salesVolume; |
| | | periodMap[item.period] = cumulativeValue; |
| | | }); |
| | | |
| | | const periods = Object.keys(periodMap).sort(); |
| | | const values = periods.map(period => periodMap[period]); |
| | | |
| | | return { |
| | | tooltip: { |
| | | trigger: "axis", |
| | | formatter: "{b}: {c} ç«æ¹ç±³", |
| | | }, |
| | | xAxis: { |
| | | type: "category", |
| | | data: periods, |
| | | }, |
| | | yAxis: { |
| | | type: "value", |
| | | name: "累计ééï¼ç«æ¹ç±³ï¼", |
| | | }, |
| | | series: [ |
| | | { |
| | | data: values, |
| | | type: "line", |
| | | smooth: true, |
| | | areaStyle: { |
| | | opacity: 0.3, |
| | | }, |
| | | itemStyle: { |
| | | color: "#E6A23C", |
| | | }, |
| | | lineStyle: { |
| | | width: 3, |
| | | }, |
| | | }, |
| | | ], |
| | | }; |
| | | }); |
| | | |
| | | // 累计éå®éé¢è¶å¿å¾è¡¨é
ç½® |
| | | const cumulativeSalesAmountChartOption = computed(() => { |
| | | // æå¨æåç» |
| | | const periodMap = {}; |
| | | let cumulativeValue = 0; |
| | | |
| | | // æå¨ææåº |
| | | const sortedData = [...filteredData.value].sort((a, b) => |
| | | a.period.localeCompare(b.period) |
| | | ); |
| | | |
| | | sortedData.forEach(item => { |
| | | cumulativeValue += item.salesAmount; |
| | | periodMap[item.period] = cumulativeValue; |
| | | }); |
| | | |
| | | const periods = Object.keys(periodMap).sort(); |
| | | const values = periods.map(period => periodMap[period]); |
| | | |
| | | return { |
| | | tooltip: { |
| | | trigger: "axis", |
| | | formatter: "{b}: {c} ä¸å
", |
| | | }, |
| | | xAxis: { |
| | | type: "category", |
| | | data: periods, |
| | | }, |
| | | yAxis: { |
| | | type: "value", |
| | | name: "累计éå®éé¢ï¼ä¸å
ï¼", |
| | | }, |
| | | series: [ |
| | | { |
| | | data: values, |
| | | type: "bar", |
| | | itemStyle: { |
| | | color: "#F56C6C", |
| | | }, |
| | | }, |
| | | ], |
| | | }; |
| | | }); |
| | | |
| | | // æ¹æ³ |
| | | const goBack = () => { |
| | | router.back(); |
| | | }; |
| | | |
| | | const handleDateChange = () => { |
| | | // å¤çæ¥æåå |
| | | updateCharts(); |
| | | }; |
| | | |
| | | const handleFilterChange = () => { |
| | | // å¤çç鿡件åå |
| | | updateCharts(); |
| | | }; |
| | | |
| | | // åå§åå¾è¡¨ |
| | | const initCharts = () => { |
| | | // åå§åééè¶å¿å¾è¡¨ |
| | | if (salesVolumeChart.value && !salesVolumeChartInstance) { |
| | | salesVolumeChartInstance = echarts.init(salesVolumeChart.value); |
| | | } |
| | | |
| | | // åå§åéå®éé¢è¶å¿å¾è¡¨ |
| | | if (salesAmountChart.value && !salesAmountChartInstance) { |
| | | salesAmountChartInstance = echarts.init(salesAmountChart.value); |
| | | } |
| | | |
| | | // åå§å产åç±»ååå¸å¾è¡¨ |
| | | if (productTypeChart.value && !productTypeChartInstance) { |
| | | productTypeChartInstance = echarts.init(productTypeChart.value); |
| | | } |
| | | |
| | | // åå§åéå®åºååå¸å¾è¡¨ |
| | | if (salesAreaChart.value && !salesAreaChartInstance) { |
| | | salesAreaChartInstance = echarts.init(salesAreaChart.value); |
| | | } |
| | | |
| | | // åå§å累计ééè¶å¿å¾è¡¨ |
| | | if (cumulativeSalesVolumeChart.value && !cumulativeSalesVolumeChartInstance) { |
| | | cumulativeSalesVolumeChartInstance = echarts.init( |
| | | cumulativeSalesVolumeChart.value |
| | | ); |
| | | } |
| | | |
| | | // åå§å累计éå®éé¢è¶å¿å¾è¡¨ |
| | | if (cumulativeSalesAmountChart.value && !cumulativeSalesAmountChartInstance) { |
| | | cumulativeSalesAmountChartInstance = echarts.init( |
| | | cumulativeSalesAmountChart.value |
| | | ); |
| | | } |
| | | |
| | | updateCharts(); |
| | | }; |
| | | |
| | | // æ´æ°å¾è¡¨ |
| | | const updateCharts = () => { |
| | | // æ´æ°ééè¶å¿å¾è¡¨ |
| | | if (salesVolumeChartInstance) { |
| | | salesVolumeChartInstance.setOption(salesVolumeChartOption.value); |
| | | } |
| | | |
| | | // æ´æ°éå®éé¢è¶å¿å¾è¡¨ |
| | | if (salesAmountChartInstance) { |
| | | salesAmountChartInstance.setOption(salesAmountChartOption.value); |
| | | } |
| | | |
| | | // æ´æ°äº§åç±»ååå¸å¾è¡¨ |
| | | if (productTypeChartInstance) { |
| | | productTypeChartInstance.setOption(productTypeChartOption.value); |
| | | } |
| | | |
| | | // æ´æ°éå®åºååå¸å¾è¡¨ |
| | | if (salesAreaChartInstance) { |
| | | salesAreaChartInstance.setOption(salesAreaChartOption.value); |
| | | } |
| | | |
| | | // æ´æ°ç´¯è®¡ééè¶å¿å¾è¡¨ |
| | | if (cumulativeSalesVolumeChartInstance) { |
| | | cumulativeSalesVolumeChartInstance.setOption( |
| | | cumulativeSalesVolumeChartOption.value |
| | | ); |
| | | } |
| | | |
| | | // æ´æ°ç´¯è®¡éå®éé¢è¶å¿å¾è¡¨ |
| | | if (cumulativeSalesAmountChartInstance) { |
| | | cumulativeSalesAmountChartInstance.setOption( |
| | | cumulativeSalesAmountChartOption.value |
| | | ); |
| | | } |
| | | }; |
| | | |
| | | // çå¬çªå£å¤§å°åå |
| | | const handleResize = () => { |
| | | if (salesVolumeChartInstance) { |
| | | salesVolumeChartInstance.resize(); |
| | | } |
| | | if (salesAmountChartInstance) { |
| | | salesAmountChartInstance.resize(); |
| | | } |
| | | if (productTypeChartInstance) { |
| | | productTypeChartInstance.resize(); |
| | | } |
| | | if (salesAreaChartInstance) { |
| | | salesAreaChartInstance.resize(); |
| | | } |
| | | if (cumulativeSalesVolumeChartInstance) { |
| | | cumulativeSalesVolumeChartInstance.resize(); |
| | | } |
| | | if (cumulativeSalesAmountChartInstance) { |
| | | cumulativeSalesAmountChartInstance.resize(); |
| | | } |
| | | }; |
| | | |
| | | // çå½å¨æ |
| | | onMounted(() => { |
| | | // 设置é»è®¤æ¥æèå´ä¸ºæè¿3个æ |
| | | const endDate = dayjs(); |
| | | const startDate = endDate.subtract(3, "month"); |
| | | dateRange.value = [ |
| | | startDate.format("YYYY-MM-DD"), |
| | | endDate.format("YYYY-MM-DD"), |
| | | ]; |
| | | |
| | | // çå¾
DOMæ´æ°ååå§åå¾è¡¨ |
| | | nextTick(() => { |
| | | initCharts(); |
| | | }); |
| | | |
| | | // æ·»å çªå£å¤§å°ååçå¬ |
| | | window.addEventListener("resize", handleResize); |
| | | }); |
| | | |
| | | // è·å产åç±»åæ ç¾ç±»å |
| | | const getProductTypeType = type => { |
| | | const typeMap = { |
| | | ç å: "primary", |
| | | æ¿æ: "success", |
| | | åæ: "warning", |
| | | }; |
| | | return typeMap[type] || "info"; |
| | | }; |
| | | |
| | | // è·åéå®åºåæ ç¾ç±»å |
| | | const getSalesAreaType = area => { |
| | | const typeMap = { |
| | | åä¸: "primary", |
| | | åå: "success", |
| | | åå: "warning", |
| | | 西å: "danger", |
| | | 西å: "info", |
| | | }; |
| | | return typeMap[area] || "info"; |
| | | }; |
| | | |
| | | // ç»ä»¶å¸è½½æ¶éæ¯å¾è¡¨å®ä¾ |
| | | onBeforeUnmount(() => { |
| | | if (salesVolumeChartInstance) { |
| | | salesVolumeChartInstance.dispose(); |
| | | } |
| | | if (salesAmountChartInstance) { |
| | | salesAmountChartInstance.dispose(); |
| | | } |
| | | if (productTypeChartInstance) { |
| | | productTypeChartInstance.dispose(); |
| | | } |
| | | if (salesAreaChartInstance) { |
| | | salesAreaChartInstance.dispose(); |
| | | } |
| | | if (cumulativeSalesVolumeChartInstance) { |
| | | cumulativeSalesVolumeChartInstance.dispose(); |
| | | } |
| | | if (cumulativeSalesAmountChartInstance) { |
| | | cumulativeSalesAmountChartInstance.dispose(); |
| | | } |
| | | |
| | | // ç§»é¤çªå£å¤§å°ååçå¬ |
| | | window.removeEventListener("resize", handleResize); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | /* å¤é¨å®¹å¨ - å æ®æ´ä¸ªè§å£ */ |
| | | .sales-statistics-container { |
| | | position: relative; |
| | | width: 100%; |
| | | /* 页é¢å¨å¸¸è§å¸å±ä¸ï¼æé¡¶æ ï¼é»è®¤åå» 84pxï¼é¿å
å
容被è£å */ |
| | | min-height: calc(100vh - 84px); |
| | | background-color: #f5f7fa; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | /* å
é¨å
容åºå - èªéåºå®½åº¦ */ |
| | | .data-dashboard { |
| | | position: relative; |
| | | width: 100%; |
| | | min-height: 100%; |
| | | background-color: #ffffff; |
| | | box-shadow: 0 0 20px rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | .filter-area { |
| | | padding: 20px; |
| | | background-color: #ffffff; |
| | | border-bottom: 1px solid #e4e7ed; |
| | | display: flex; |
| | | gap: 40px; |
| | | align-items: center; |
| | | flex-wrap: wrap; |
| | | } |
| | | |
| | | .filter-section { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .filter-label { |
| | | font-size: 14px; |
| | | font-weight: 500; |
| | | color: #303133; |
| | | white-space: nowrap; |
| | | } |
| | | |
| | | .dashboard-content { |
| | | position: relative; |
| | | z-index: 1; |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 20px; |
| | | padding: 20px; |
| | | min-height: 800px; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | /* è¡å¸å± */ |
| | | .row { |
| | | display: flex; |
| | | gap: 20px; |
| | | align-items: stretch; |
| | | } |
| | | |
| | | /* 第ä¸è¡ï¼4ä¸ªææ å¡ç */ |
| | | .row-1 { |
| | | height: 180px; |
| | | } |
| | | |
| | | /* 第äºè¡ï¼2个è¶å¿å¾è¡¨ */ |
| | | .row-2 { |
| | | height: 350px; |
| | | } |
| | | |
| | | /* 第ä¸è¡ï¼ç´¯è®¡æ°æ®è¶å¿ */ |
| | | .row-3 { |
| | | height: 350px; |
| | | } |
| | | |
| | | /* 第åè¡ï¼è¡¨æ ¼åå¾è¡¨ */ |
| | | .row-4 { |
| | | height: 600px; |
| | | } |
| | | |
| | | /* å¡çæ ·å¼ */ |
| | | .panel-card { |
| | | background-color: #ffffff; |
| | | border-radius: 8px; |
| | | border: 1px solid #e4e7ed; |
| | | overflow: hidden; |
| | | display: flex; |
| | | flex-direction: column; |
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); |
| | | transition: all 0.3s ease; |
| | | } |
| | | |
| | | .panel-card:hover { |
| | | box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12); |
| | | transform: translateY(-2px); |
| | | } |
| | | |
| | | /* å¡çå¸å± */ |
| | | .card-1 { |
| | | flex: 1; |
| | | } |
| | | |
| | | .card-2 { |
| | | flex: 1; |
| | | } |
| | | |
| | | .card-3 { |
| | | flex: 1; |
| | | } |
| | | |
| | | .card-4 { |
| | | flex: 1; |
| | | } |
| | | |
| | | .card-5 { |
| | | flex: 1; |
| | | } |
| | | |
| | | .card-6 { |
| | | flex: 1; |
| | | } |
| | | |
| | | .card-7 { |
| | | flex: 1; |
| | | } |
| | | |
| | | .card-8 { |
| | | flex: 1; |
| | | } |
| | | |
| | | .card-9 { |
| | | flex: 1; |
| | | } |
| | | |
| | | .card-10 { |
| | | flex: 1; |
| | | } |
| | | |
| | | .card-11 { |
| | | flex: 1; |
| | | } |
| | | |
| | | .panel-title { |
| | | padding: 15px 20px; |
| | | font-size: 16px; |
| | | font-weight: 500; |
| | | color: #303133; |
| | | border-bottom: 1px solid #e4e7ed; |
| | | background-color: #fafafa; |
| | | } |
| | | |
| | | .card-1 .panel-title { |
| | | border-left: 4px solid #409eff; |
| | | } |
| | | |
| | | .card-2 .panel-title { |
| | | border-left: 4px solid #67c23a; |
| | | } |
| | | |
| | | .card-3 .panel-title { |
| | | border-left: 4px solid #e6a23c; |
| | | } |
| | | |
| | | .card-4 .panel-title { |
| | | border-left: 4px solid #f56c6c; |
| | | } |
| | | |
| | | .card-5 .panel-title { |
| | | border-left: 4px solid #409eff; |
| | | } |
| | | |
| | | .card-6 .panel-title { |
| | | border-left: 4px solid #67c23a; |
| | | } |
| | | |
| | | .card-7 .panel-title { |
| | | border-left: 4px solid #e6a23c; |
| | | } |
| | | |
| | | .card-8 .panel-title { |
| | | border-left: 4px solid #f56c6c; |
| | | } |
| | | |
| | | .card-9 .panel-title { |
| | | border-left: 4px solid #409eff; |
| | | } |
| | | |
| | | .card-10 .panel-title { |
| | | border-left: 4px solid #67c23a; |
| | | } |
| | | |
| | | .card-11 .panel-title { |
| | | border-left: 4px solid #e6a23c; |
| | | } |
| | | |
| | | .chart-container { |
| | | flex: 1; |
| | | padding: 20px; |
| | | } |
| | | |
| | | .table-container { |
| | | flex: 1; |
| | | padding: 20px; |
| | | overflow: auto; |
| | | } |
| | | |
| | | .stats-grid { |
| | | flex: 1; |
| | | padding: 15px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .stat-item { |
| | | background-color: #fafafa; |
| | | border-radius: 8px; |
| | | padding: 15px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | border: 1px solid #e4e7ed; |
| | | min-height: 80px; |
| | | width: 100%; |
| | | } |
| | | |
| | | .stat-value { |
| | | font-size: 24px; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | margin-bottom: 5px; |
| | | } |
| | | |
| | | .sales-volume-color { |
| | | color: #409eff; |
| | | text-shadow: 0 2px 4px rgba(64, 158, 255, 0.3); |
| | | } |
| | | |
| | | .sales-amount-color { |
| | | color: #67c23a; |
| | | text-shadow: 0 2px 4px rgba(103, 194, 58, 0.3); |
| | | } |
| | | |
| | | .new-customer-color { |
| | | color: #e6a23c; |
| | | text-shadow: 0 2px 4px rgba(230, 162, 60, 0.3); |
| | | } |
| | | |
| | | .total-customer-color { |
| | | color: #f56c6c; |
| | | text-shadow: 0 2px 4px rgba(245, 108, 108, 0.3); |
| | | } |
| | | |
| | | .stat-unit { |
| | | font-size: 12px; |
| | | color: #909399; |
| | | margin-bottom: 3px; |
| | | } |
| | | |
| | | .stat-change { |
| | | font-size: 12px; |
| | | color: #67c23a; |
| | | } |
| | | |
| | | /* è¡¨æ ¼æ ·å¼ */ |
| | | :deep(.el-table) { |
| | | border-radius: 8px; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | :deep(.el-table th) { |
| | | background-color: #fafafa; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | :deep(.el-table tr:hover > td) { |
| | | background-color: #ecf5ff; |
| | | } |
| | | |
| | | .data-value { |
| | | font-weight: bold; |
| | | color: #409eff; |
| | | } |
| | | |
| | | /* ä¸æéæ©æ¡æ ·å¼ */ |
| | | :deep(.el-select) { |
| | | width: 100%; |
| | | } |
| | | |
| | | :deep(.el-date-picker) { |
| | | width: 100%; |
| | | } |
| | | </style> |
| | |
| | | <template> |
| | | <div class="sales-statistics-container"> |
| | | <div class="data-dashboard"> |
| | | <!-- 页颿 é¢ --> |
| | | <!-- <div class="dashboard-header"> |
| | | <div class="factory-name">éå®ç»è®¡çæ¿</div> |
| | | </div> --> |
| | | <!-- ç鿡件 --> |
| | | <div class="filter-area"> |
| | | <div class="filter-section"> |
| | | <span class="filter-label">æ¶é´èå´ï¼</span> |
| | | <el-date-picker v-model="dateRange" |
| | | type="daterange" |
| | | range-separator="è³" |
| | | start-placeholder="å¼å§æ¥æ" |
| | | end-placeholder="ç»ææ¥æ" |
| | | value-format="YYYY-MM-DD" |
| | | @change="handleDateChange" |
| | | style="width: 240px;" /> |
| | | <div ref="screenRoot" class="sales-statistics-container" :class="{ 'is-fullscreen': isFullscreen }"> |
| | | <div class="bi-bg"></div> |
| | | |
| | | <div class="bi-topbar"> |
| | | <img class="bi-topbar-title-bg" src="@/assets/BI/biaoti.png" alt="éå®çæ¿ç»è®¡" /> |
| | | <div class="bi-topbar-content"> |
| | | <div class="bi-topbar-left"> |
| | | <span class="status-sun">â</span> |
| | | <span>26â</span> |
| | | <span class="bi-topbar-sep">湿度ï¼1</span> |
| | | </div> |
| | | <div class="filter-section"> |
| | | <span class="filter-label">产åç±»åï¼</span> |
| | | <el-select v-model="productType" |
| | | placeholder="è¯·éæ©äº§åç±»å" |
| | | @change="handleFilterChange" |
| | | style="width: 160px;"> |
| | | <el-option label="å
¨é¨" |
| | | value="" /> |
| | | <el-option label="ç å" |
| | | value="block" /> |
| | | <el-option label="æ¿æ" |
| | | value="board" /> |
| | | <el-option label="åæ" |
| | | value="profile" /> |
| | | </el-select> |
| | | <div class="bi-topbar-title">éå®çæ¿ç»è®¡</div> |
| | | <div class="bi-topbar-meta"> |
| | | <span class="bi-topbar-time">{{ currentTime }}</span> |
| | | <span class="bi-topbar-sep">|</span> |
| | | <span class="bi-topbar-date">{{ currentDateText }}</span> |
| | | </div> |
| | | <div class="filter-section"> |
| | | <span class="filter-label">éå®åºåï¼</span> |
| | | <el-select v-model="salesArea" |
| | | placeholder="è¯·éæ©éå®åºå" |
| | | @change="handleFilterChange" |
| | | style="width: 160px;"> |
| | | <el-option label="å
¨é¨" |
| | | value="" /> |
| | | <el-option label="åä¸" |
| | | value="east" /> |
| | | <el-option label="åå" |
| | | value="north" /> |
| | | <el-option label="åå" |
| | | value="south" /> |
| | | <el-option label="西å" |
| | | value="southwest" /> |
| | | <el-option label="西å" |
| | | value="northwest" /> |
| | | </el-select> |
| | | </div> |
| | | <div class="filter-section"> |
| | | <span class="filter-label">ç»è®¡ç»´åº¦ï¼</span> |
| | | <el-select v-model="statDimension" |
| | | placeholder="è¯·éæ©ç»è®¡ç»´åº¦" |
| | | @change="handleFilterChange" |
| | | style="width: 160px;"> |
| | | <el-option label="æåº¦" |
| | | value="month" /> |
| | | <el-option label="年度" |
| | | value="year" /> |
| | | </el-select> |
| | | <button class="fullscreen-btn" @click="toggleFullscreen" :title="isFullscreen ? 'éåºå
¨å±' : 'å
¨å±æ¾ç¤º'"> |
| | | <svg v-if="!isFullscreen" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| | | <path d="M8 3v3a2 2 0 0 1-2 2H3m18 0h-3a2 2 0 0 1-2-2V3m0 18v-3a2 2 0 0 1 2-2h3M3 16h3a2 2 0 0 1 2 2v3"/> |
| | | </svg> |
| | | <svg v-else width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> |
| | | <path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"/> |
| | | </svg> |
| | | </button> |
| | | </div> |
| | | </div> |
| | | <div class="dashboard-content"> |
| | | <!-- æ ¸å¿ææ å¡ç --> |
| | | <div class="row row-1"> |
| | | <div class="panel-card card-1"> |
| | | <div class="panel-title">å计éé</div> |
| | | <div class="stats-grid"> |
| | | <div class="stat-item"> |
| | | <div class="stat-value sales-volume-color">{{ totalSalesVolume }}</div> |
| | | <div class="stat-unit">ç«æ¹ç±³</div> |
| | | <div class="stat-change">{{ salesVolumeChange }}%</div> |
| | | |
| | | <div class="bi-dashboard-grid"> |
| | | <!-- å·¦ä¸ï¼ééè¶å¿ --> |
| | | <div class="bi-panel bi-panel-top-left"> |
| | | <PanelHeader title="éå®åæ-ç å" /> |
| | | <div class="panel-tabs"> |
| | | <span class="tab-item active">å¹´</span> |
| | | <span class="tab-item">æ</span> |
| | | </div> |
| | | <div class="bi-panel-body"> |
| | | <div class="chart-filter-tabs"> |
| | | <span class="cf-tab active">***éå®åº</span> |
| | | <span class="cf-tab">xxxéå®åº</span> |
| | | <span class="cf-tab">xxxéå®åº</span> |
| | | <span class="cf-tab">xxxéå®åº</span> |
| | | <span class="cf-tab">xxxéå®åº</span> |
| | | </div> |
| | | <div class="chart-unit-row"> |
| | | <span>åä½ï¼ç«æ¹ç±³</span> |
| | | <span class="dot-legend">æ¿æ</span> |
| | | </div> |
| | | <div ref="salesVolumeChart" class="echart-fill"></div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- å³ä¸ï¼éå®éé¢ --> |
| | | <div class="bi-panel bi-panel-top-right"> |
| | | <PanelHeader title="éå®åæ-æ¿æ" /> |
| | | <div class="panel-tabs"> |
| | | <span class="tab-item active">å¹´</span> |
| | | <span class="tab-item">æ</span> |
| | | </div> |
| | | <div class="bi-panel-body"> |
| | | <div class="chart-filter-tabs"> |
| | | <span class="cf-tab active">***éå®åº</span> |
| | | <span class="cf-tab">xxxéå®åº</span> |
| | | <span class="cf-tab">xxxéå®åº</span> |
| | | <span class="cf-tab">xxxéå®åº</span> |
| | | <span class="cf-tab">xxxéå®åº</span> |
| | | </div> |
| | | <div class="chart-unit-row"> |
| | | <span>åä½ï¼ä»¶</span> |
| | | <span class="dot-legend">æ¿æ</span> |
| | | </div> |
| | | <div ref="salesAmountChart" class="echart-fill"></div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- ä¸é´ä¸å¿ç¯ --> |
| | | <div class="center-ring"> |
| | | <img |
| | | class="center-ring-bg" |
| | | src="@/assets/BI/zonghetongbingtubiankuang@2x.png" |
| | | alt="" |
| | | /> |
| | | <div class="center-ring-content"> |
| | | <div class="center-ring-title">éå®<br />ä¸å¿</div> |
| | | |
| | | <div class="center-metric m1"> |
| | | <div class="center-metric-label">æ°å¢å®¢æ·</div> |
| | | <div class="center-metric-value">{{ centerNewCustomerCount }}</div> |
| | | <div class="center-metric-unit">人</div> |
| | | </div> |
| | | |
| | | <div class="center-metric m2"> |
| | | <div class="center-metric-label">æäº¤æ»è®¢å</div> |
| | | <div class="center-metric-value">{{ completedOrders }}</div> |
| | | <div class="center-metric-unit">å</div> |
| | | </div> |
| | | |
| | | <div class="center-metric m3"> |
| | | <div class="center-metric-label">æ°å¢è®¢å</div> |
| | | <div class="center-metric-value">{{ salesOrderCount }}</div> |
| | | <div class="center-metric-unit">å</div> |
| | | </div> |
| | | |
| | | <div class="center-metric m4"> |
| | | <div class="center-metric-label">æ»éå®åº</div> |
| | | <div class="center-metric-value">{{ totalSalesAreaCount }}</div> |
| | | <div class="center-metric-unit">åº</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div class="panel-card card-2"> |
| | | <div class="panel-title">éå®éé¢</div> |
| | | <div class="stats-grid"> |
| | | <div class="stat-item"> |
| | | <div class="stat-value sales-amount-color">{{ totalSalesAmount }}</div> |
| | | <div class="stat-unit">ä¸å
</div> |
| | | <div class="stat-change">{{ salesAmountChange }}%</div> |
| | | |
| | | <!-- å·¦ä¸ï¼äº§åç±»åéé --> |
| | | <div class="bi-panel bi-panel-bottom-left"> |
| | | <PanelHeader title="客æ·ééæååæ-ç å" /> |
| | | <div class="panel-tabs"> |
| | | <span class="tab-item active">å¹´</span> |
| | | <span class="tab-item">æ</span> |
| | | </div> |
| | | <div class="bi-panel-body"> |
| | | <div class="chart-filter-tabs"> |
| | | <span class="cf-tab active">***éå®åº</span> |
| | | <span class="cf-tab">xxxéå®åº</span> |
| | | <span class="cf-tab">xxxéå®åº</span> |
| | | <span class="cf-tab">xxxéå®åº</span> |
| | | <span class="cf-tab">xxxéå®åº</span> |
| | | </div> |
| | | <div ref="productTypeChart" class="echart-fill"></div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- ä¸ä¸ï¼æ°å¢å®¢æ·åæï¼å产åç±»åè¶å¿ï¼ --> |
| | | <div class="bi-panel bi-panel-bottom-center"> |
| | | <PanelHeader title="æ°å¢å®¢æ·è¶å¿åæ" /> |
| | | <div class="panel-tabs"> |
| | | <span class="tab-item active">å¹´</span> |
| | | <span class="tab-item">æ</span> |
| | | </div> |
| | | <div class="panel-card card-3"> |
| | | <div class="panel-title">æ°å¢å®¢æ·</div> |
| | | <div class="stats-grid"> |
| | | <div class="stat-item"> |
| | | <div class="stat-value new-customer-color">{{ newCustomerCount }}</div> |
| | | <div class="stat-unit">个</div> |
| | | <div class="stat-change">{{ customerCountChange }}%</div> |
| | | <div class="bi-panel-body"> |
| | | <div class="chart-mini-title"> |
| | | <span class="diamond"></span> |
| | | <span>æ°å¢å®¢æ·æ°</span> |
| | | </div> |
| | | <div class="chart-unit-row chart-unit-single"> |
| | | <span>åä½ï¼äºº</span> |
| | | </div> |
| | | <div ref="productTypeTrendChart" class="echart-fill"></div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- å³ä¸ï¼éå®åºåéé --> |
| | | <div class="bi-panel bi-panel-bottom-right"> |
| | | <PanelHeader title="客æ·ééæååæ-æ¿æ" /> |
| | | <div class="panel-tabs"> |
| | | <span class="tab-item active">å¹´</span> |
| | | <span class="tab-item">æ</span> |
| | | </div> |
| | | <div class="panel-card card-4"> |
| | | <div class="panel-title">å计客æ·</div> |
| | | <div class="stats-grid"> |
| | | <div class="stat-item"> |
| | | <div class="stat-value total-customer-color">{{ totalCustomerCount }}</div> |
| | | <div class="stat-unit">个</div> |
| | | <div class="stat-change">{{ totalCustomerChange }}%</div> |
| | | <div class="bi-panel-body"> |
| | | <div class="chart-filter-tabs"> |
| | | <span class="cf-tab active">***éå®åº</span> |
| | | <span class="cf-tab">xxxéå®åº</span> |
| | | <span class="cf-tab">xxxéå®åº</span> |
| | | <span class="cf-tab">xxxéå®åº</span> |
| | | <span class="cf-tab">xxxéå®åº</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <!-- ééåéå®éé¢è¶å¿ --> |
| | | <div class="row row-2"> |
| | | <div class="panel-card card-5"> |
| | | <div class="panel-title">ééè¶å¿</div> |
| | | <div class="chart-container"> |
| | | <div ref="salesVolumeChart" |
| | | style="width: 100%; height: 100%;"></div> |
| | | </div> |
| | | </div> |
| | | <div class="panel-card card-6"> |
| | | <div class="panel-title">éå®éé¢è¶å¿</div> |
| | | <div class="chart-container"> |
| | | <div ref="salesAmountChart" |
| | | style="width: 100%; height: 100%;"></div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <!-- ç´¯è®¡æ°æ®è¶å¿ --> |
| | | <!-- <div class="row row-3"> |
| | | <div class="panel-card card-10"> |
| | | <div class="panel-title">累计ééè¶å¿</div> |
| | | <div class="chart-container"> |
| | | <div ref="cumulativeSalesVolumeChart" |
| | | style="width: 100%; height: 100%;"></div> |
| | | </div> |
| | | </div> |
| | | <div class="panel-card card-11"> |
| | | <div class="panel-title">累计éå®éé¢è¶å¿</div> |
| | | <div class="chart-container"> |
| | | <div ref="cumulativeSalesAmountChart" |
| | | style="width: 100%; height: 100%;"></div> |
| | | </div> |
| | | </div> |
| | | </div> --> |
| | | <!-- å¾è¡¨åºååè¡¨æ ¼ --> |
| | | <div class="row row-4"> |
| | | <!-- 左边ï¼è¯¦ç»æ°æ®è¡¨æ ¼ --> |
| | | <div class="panel-card card-9" |
| | | style="flex: 2;"> |
| | | <div class="panel-title">éå®ç»è®¡è¯¦ç»æ°æ®</div> |
| | | <div class="table-container"> |
| | | <el-table :data="tableData" |
| | | style="width: 100%"> |
| | | <el-table-column prop="productType" |
| | | label="产åç±»å" |
| | | width="120" |
| | | align="center"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getProductTypeType(scope.row.productType)"> |
| | | {{ scope.row.productType }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="salesArea" |
| | | label="éå®åºå" |
| | | width="120" |
| | | align="center"> |
| | | <template #default="scope"> |
| | | <el-tag :type="getSalesAreaType(scope.row.salesArea)"> |
| | | {{ scope.row.salesArea }} |
| | | </el-tag> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="period" |
| | | label="ç»è®¡å¨æ" |
| | | width="120" /> |
| | | <el-table-column prop="salesVolume" |
| | | label="éé(ç«æ¹ç±³)" |
| | | align="right"> |
| | | <template #default="scope"> |
| | | <span class="data-value">{{ scope.row.salesVolume }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="salesAmount" |
| | | label="éå®éé¢(ä¸å
)" |
| | | align="right"> |
| | | <template #default="scope"> |
| | | <span class="data-value">{{ scope.row.salesAmount }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="newCustomers" |
| | | label="æ°å¢å®¢æ·(个)" |
| | | width="150" |
| | | align="right"> |
| | | <template #default="scope"> |
| | | <span class="data-value">{{ scope.row.newCustomers }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | <el-table-column prop="totalCustomers" |
| | | label="å计客æ·(个)" |
| | | width="150" |
| | | align="right"> |
| | | <template #default="scope"> |
| | | <span class="data-value">{{ scope.row.totalCustomers }}</span> |
| | | </template> |
| | | </el-table-column> |
| | | </el-table> |
| | | </div> |
| | | </div> |
| | | <!-- å³è¾¹ï¼äº§åç±»ååå¸åéå®åºååå¸ --> |
| | | <div class="chart-column" |
| | | style="flex: 1; display: flex; flex-direction: column; gap: 20px;"> |
| | | <div class="panel-card card-7" |
| | | style="flex: 1;"> |
| | | <div class="panel-title">产åç±»ååå¸</div> |
| | | <div class="chart-container"> |
| | | <div ref="productTypeChart" |
| | | style="width: 100%; height: 100%;"></div> |
| | | </div> |
| | | </div> |
| | | <div class="panel-card card-8" |
| | | style="flex: 1;"> |
| | | <div class="panel-title">éå®åºååå¸</div> |
| | | <div class="chart-container"> |
| | | <div ref="salesAreaChart" |
| | | style="width: 100%; height: 100%;"></div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <div ref="salesAreaChart" class="echart-fill"></div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | import { useRouter } from "vue-router"; |
| | | import * as echarts from "echarts"; |
| | | import dayjs from "dayjs"; |
| | | import PanelHeader from "@/views/reportAnalysis/PSIDataAnalysis/components/PanelHeader.vue"; |
| | | |
| | | const router = useRouter(); |
| | | const screenRoot = ref(null); |
| | | const isFullscreen = ref(false); |
| | | |
| | | // 顶鍿 æ¶é´ï¼ç¨äºå¹é
BI大屿æå¾ï¼ |
| | | const now = ref(dayjs()) |
| | | const currentTime = computed(() => now.value.format("HH:mm:ss")) |
| | | const currentDateText = computed(() => { |
| | | const weekMap = { |
| | | 0: "æææ¥", |
| | | 1: "ææä¸", |
| | | 2: "ææäº", |
| | | 3: "ææä¸", |
| | | 4: "ææå", |
| | | 5: "ææäº", |
| | | 6: "ææå
", |
| | | } |
| | | return `${now.value.format("YYYY-MM-DD")} ${weekMap[now.value.day()] || ""}` |
| | | }) |
| | | let timeTicker = null |
| | | |
| | | const handleFullscreenChange = () => { |
| | | isFullscreen.value = !!document.fullscreenElement; |
| | | nextTick(() => { |
| | | handleResize(); |
| | | }); |
| | | }; |
| | | |
| | | const toggleFullscreen = async () => { |
| | | const rootEl = screenRoot.value; |
| | | if (!rootEl) return; |
| | | try { |
| | | if (!document.fullscreenElement) { |
| | | await rootEl.requestFullscreen(); |
| | | } else { |
| | | await document.exitFullscreen(); |
| | | } |
| | | } catch (error) { |
| | | console.error("å
¨å±åæ¢å¤±è´¥:", error); |
| | | } |
| | | }; |
| | | |
| | | // ç鿡件 |
| | | const dateRange = ref([]); |
| | |
| | | const salesAmountChart = ref(null); |
| | | const productTypeChart = ref(null); |
| | | const salesAreaChart = ref(null); |
| | | const productTypeTrendChart = ref(null); |
| | | const cumulativeSalesVolumeChart = ref(null); |
| | | const cumulativeSalesAmountChart = ref(null); |
| | | |
| | |
| | | let salesAmountChartInstance = null; |
| | | let productTypeChartInstance = null; |
| | | let salesAreaChartInstance = null; |
| | | let productTypeTrendChartInstance = null; |
| | | let cumulativeSalesVolumeChartInstance = null; |
| | | let cumulativeSalesAmountChartInstance = null; |
| | | |
| | |
| | | return Object.values(customerMap).reduce((sum, count) => sum + count, 0); |
| | | }); |
| | | |
| | | // ä¸é´ä¸å¿ç¯ææ ï¼ç¨äºå¤§å±å±ç¤ºï¼ä½¿ç¨ç°æç»è®¡æ°æ®åæ å°ï¼ |
| | | const centerNewCustomerCount = computed(() => 112); |
| | | const completedOrders = computed(() => 1829); |
| | | const salesOrderCount = computed(() => 34); |
| | | const totalSalesAreaCount = computed(() => 12); |
| | | |
| | | // ååç计ç®ï¼æ¨¡æï¼ |
| | | const salesVolumeChange = ref("+5.2"); |
| | | const salesAmountChange = ref("+7.8"); |
| | |
| | | |
| | | // ééè¶å¿å¾è¡¨é
ç½® |
| | | const salesVolumeChartOption = computed(() => { |
| | | // æå¨æåç» |
| | | const periodMap = {}; |
| | | filteredData.value.forEach(item => { |
| | | if (!periodMap[item.period]) { |
| | | periodMap[item.period] = 0; |
| | | } |
| | | periodMap[item.period] += item.salesVolume; |
| | | }); |
| | | |
| | | const periods = Object.keys(periodMap).sort(); |
| | | const values = periods.map(period => periodMap[period]); |
| | | const periods = ["6/9", "6/10", "6/11", "6/12", "6/12", "6/13"]; |
| | | const values = [132, 168, 168, 198, 168, 198]; |
| | | |
| | | return { |
| | | backgroundColor: "transparent", |
| | | tooltip: { |
| | | trigger: "axis", |
| | | backgroundColor: "rgba(0,0,0,0.55)", |
| | | borderColor: "rgba(64,158,255,0.25)", |
| | | borderWidth: 1, |
| | | textStyle: { color: "#B8C8E0" }, |
| | | formatter: "{b}: {c} ç«æ¹ç±³", |
| | | }, |
| | | grid: { |
| | | left: "10%", |
| | | right: "4%", |
| | | bottom: "16%", |
| | | top: "28%", |
| | | containLabel: true, |
| | | }, |
| | | xAxis: { |
| | | type: "category", |
| | | data: periods, |
| | | axisLine: { lineStyle: { color: "rgba(184,200,224,0.25)" } }, |
| | | axisTick: { show: false }, |
| | | axisLabel: { color: "#B8C8E0", fontSize: 11, margin: 10 }, |
| | | splitLine: { show: false }, |
| | | }, |
| | | yAxis: { |
| | | type: "value", |
| | | name: "ééï¼ç«æ¹ç±³ï¼", |
| | | name: "", |
| | | axisLine: { lineStyle: { color: "rgba(184,200,224,0.25)" } }, |
| | | axisLabel: { color: "#B8C8E0", fontSize: 11, margin: 8 }, |
| | | splitLine: { lineStyle: { color: "rgba(184,200,224,0.12)" } }, |
| | | }, |
| | | series: [ |
| | | { |
| | | data: values, |
| | | type: "line", |
| | | smooth: true, |
| | | lineStyle: { |
| | | width: 3, |
| | | }, |
| | | itemStyle: { |
| | | color: "#409EFF", |
| | | symbolSize: 8, |
| | | lineStyle: { width: 3, color: "#00A4ED" }, |
| | | itemStyle: { color: "#00A4ED" }, |
| | | areaStyle: { |
| | | opacity: 1, |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { offset: 0, color: "rgba(0,164,237,0.35)" }, |
| | | { offset: 1, color: "rgba(0,164,237,0)" }, |
| | | ]), |
| | | }, |
| | | }, |
| | | ], |
| | |
| | | |
| | | // éå®éé¢è¶å¿å¾è¡¨é
ç½® |
| | | const salesAmountChartOption = computed(() => { |
| | | // æå¨æåç» |
| | | const periodMap = {}; |
| | | filteredData.value.forEach(item => { |
| | | if (!periodMap[item.period]) { |
| | | periodMap[item.period] = 0; |
| | | } |
| | | periodMap[item.period] += item.salesAmount; |
| | | }); |
| | | |
| | | const periods = Object.keys(periodMap).sort(); |
| | | const values = periods.map(period => periodMap[period]); |
| | | const periods = ["6/9", "6/10", "6/11", "6/12", "6/12", "6/13"]; |
| | | const values = [132, 168, 168, 198, 168, 198]; |
| | | |
| | | return { |
| | | backgroundColor: "transparent", |
| | | tooltip: { |
| | | trigger: "axis", |
| | | backgroundColor: "rgba(0,0,0,0.55)", |
| | | borderColor: "rgba(64,158,255,0.25)", |
| | | borderWidth: 1, |
| | | textStyle: { color: "#B8C8E0" }, |
| | | formatter: "{b}: {c} ä¸å
", |
| | | }, |
| | | grid: { |
| | | left: "10%", |
| | | right: "4%", |
| | | bottom: "16%", |
| | | top: "28%", |
| | | containLabel: true, |
| | | }, |
| | | xAxis: { |
| | | type: "category", |
| | | data: periods, |
| | | axisLine: { lineStyle: { color: "rgba(184,200,224,0.25)" } }, |
| | | axisTick: { show: false }, |
| | | axisLabel: { color: "#B8C8E0", fontSize: 11, margin: 10 }, |
| | | }, |
| | | yAxis: { |
| | | type: "value", |
| | | name: "éå®éé¢ï¼ä¸å
ï¼", |
| | | name: "", |
| | | axisLine: { lineStyle: { color: "rgba(184,200,224,0.25)" } }, |
| | | axisLabel: { color: "#B8C8E0", fontSize: 11, margin: 8 }, |
| | | splitLine: { lineStyle: { color: "rgba(184,200,224,0.12)" } }, |
| | | }, |
| | | series: [ |
| | | { |
| | | data: values, |
| | | type: "bar", |
| | | type: "line", |
| | | smooth: true, |
| | | symbolSize: 8, |
| | | itemStyle: { |
| | | color: "#67C23A", |
| | | color: "#00A4ED", |
| | | }, |
| | | lineStyle: { width: 3, color: "#00A4ED" }, |
| | | areaStyle: { |
| | | opacity: 1, |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { offset: 0, color: "rgba(0,164,237,0.35)" }, |
| | | { offset: 1, color: "rgba(0,164,237,0)" }, |
| | | ]), |
| | | }, |
| | | }, |
| | | ], |
| | | }; |
| | | }); |
| | | |
| | | // 产åç±»ååå¸å¾è¡¨é
ç½® |
| | | // 产åç±»åééå¾è¡¨é
ç½®ï¼æ¨ªåæ±ç¶ï¼ |
| | | const productTypeChartOption = computed(() => { |
| | | // æäº§åç±»ååç» |
| | | const typeMap = {}; |
| | | filteredData.value.forEach(item => { |
| | | if (!typeMap[item.productType]) { |
| | | typeMap[item.productType] = 0; |
| | | } |
| | | typeMap[item.productType] += item.salesVolume; |
| | | }); |
| | | |
| | | const types = Object.keys(typeMap); |
| | | const values = types.map(type => typeMap[type]); |
| | | const types = ["客æ·BB", "客æ·AA", "客æ·CC", "客æ·DD", "客æ·DD", "客æ·DD"]; |
| | | const values = [130, 120, 102, 90, 90, 70]; |
| | | const barColors = ["#34D8F7", "#4A8BFF", "#8A6BFF", "#C8C447", "#C8C447", "#C8C447"]; |
| | | |
| | | return { |
| | | backgroundColor: "transparent", |
| | | tooltip: { |
| | | trigger: "item", |
| | | formatter: "{b}: {c} ç«æ¹ç±³ ({d}%)", |
| | | trigger: "axis", |
| | | axisPointer: { type: "shadow" }, |
| | | backgroundColor: "rgba(0,0,0,0.55)", |
| | | borderColor: "rgba(64,158,255,0.25)", |
| | | borderWidth: 1, |
| | | textStyle: { color: "#B8C8E0" }, |
| | | formatter: "{b}: {c} ç«æ¹ç±³", |
| | | }, |
| | | grid: { |
| | | left: "14%", |
| | | right: "6%", |
| | | top: "16%", |
| | | bottom: "8%", |
| | | containLabel: true, |
| | | }, |
| | | xAxis: { |
| | | type: "value", |
| | | axisLine: { show: false }, |
| | | axisLabel: { color: "#B8C8E0", fontSize: 11 }, |
| | | splitLine: { lineStyle: { color: "rgba(184,200,224,0.12)" } }, |
| | | }, |
| | | yAxis: { |
| | | type: "category", |
| | | data: types, |
| | | axisTick: { show: false }, |
| | | axisLine: { show: false }, |
| | | axisLabel: { color: "#B8C8E0", fontSize: 11, margin: 8 }, |
| | | }, |
| | | series: [ |
| | | { |
| | | type: "pie", |
| | | radius: "60%", |
| | | data: types.map((type, index) => ({ |
| | | name: type, |
| | | value: values[index], |
| | | })), |
| | | emphasis: { |
| | | name: "ééï¼ç«æ¹ç±³ï¼", |
| | | type: "bar", |
| | | barWidth: 14, |
| | | data: values, |
| | | itemStyle: { |
| | | shadowBlur: 10, |
| | | shadowOffsetX: 0, |
| | | shadowColor: "rgba(0, 0, 0, 0.5)", |
| | | color: params => barColors[params.dataIndex] || "#00A4ED", |
| | | borderRadius: [6, 6, 6, 6], |
| | | }, |
| | | label: { |
| | | show: false, |
| | | }, |
| | | }, |
| | | ], |
| | | }; |
| | | }); |
| | | |
| | | // éå®åºååå¸å¾è¡¨é
ç½® |
| | | // éå®åºåééå¾è¡¨é
ç½®ï¼æ¨ªåæ±ç¶ï¼ |
| | | const salesAreaChartOption = computed(() => { |
| | | // æéå®åºååç» |
| | | const areaMap = {}; |
| | | filteredData.value.forEach(item => { |
| | | if (!areaMap[item.salesArea]) { |
| | | areaMap[item.salesArea] = 0; |
| | | } |
| | | areaMap[item.salesArea] += item.salesVolume; |
| | | }); |
| | | |
| | | const areas = Object.keys(areaMap); |
| | | const values = areas.map(area => areaMap[area]); |
| | | const areas = ["客æ·BB", "客æ·AA", "客æ·CC", "客æ·DD", "客æ·DD", "客æ·DD"]; |
| | | const values = [130, 120, 102, 90, 90, 70]; |
| | | const barColors = ["#34D8F7", "#4A8BFF", "#8A6BFF", "#C8C447", "#C8C447", "#C8C447"]; |
| | | |
| | | return { |
| | | backgroundColor: "transparent", |
| | | tooltip: { |
| | | trigger: "item", |
| | | formatter: "{b}: {c} ç«æ¹ç±³ ({d}%)", |
| | | trigger: "axis", |
| | | axisPointer: { type: "shadow" }, |
| | | backgroundColor: "rgba(0,0,0,0.55)", |
| | | borderColor: "rgba(64,158,255,0.25)", |
| | | borderWidth: 1, |
| | | textStyle: { color: "#B8C8E0" }, |
| | | formatter: "{b}: {c} ç«æ¹ç±³", |
| | | }, |
| | | grid: { |
| | | left: "14%", |
| | | right: "6%", |
| | | top: "16%", |
| | | bottom: "8%", |
| | | containLabel: true, |
| | | }, |
| | | xAxis: { |
| | | type: "value", |
| | | axisLine: { show: false }, |
| | | axisLabel: { color: "#B8C8E0", fontSize: 11 }, |
| | | splitLine: { lineStyle: { color: "rgba(184,200,224,0.12)" } }, |
| | | }, |
| | | yAxis: { |
| | | type: "category", |
| | | data: areas, |
| | | axisTick: { show: false }, |
| | | axisLine: { show: false }, |
| | | axisLabel: { color: "#B8C8E0", fontSize: 11, margin: 8 }, |
| | | }, |
| | | series: [ |
| | | { |
| | | type: "pie", |
| | | radius: "60%", |
| | | data: areas.map((area, index) => ({ |
| | | name: area, |
| | | value: values[index], |
| | | })), |
| | | emphasis: { |
| | | name: "ééï¼ç«æ¹ç±³ï¼", |
| | | type: "bar", |
| | | barWidth: 14, |
| | | data: values, |
| | | itemStyle: { |
| | | shadowBlur: 10, |
| | | shadowOffsetX: 0, |
| | | shadowColor: "rgba(0, 0, 0, 0.5)", |
| | | }, |
| | | color: params => barColors[params.dataIndex] || "#00A4ED", |
| | | borderRadius: [6, 6, 6, 6], |
| | | }, |
| | | }, |
| | | ], |
| | | }; |
| | | }); |
| | | |
| | | // æ°å¢å®¢æ·è¶å¿å¾è¡¨é
ç½®ï¼æäº§åç±»åå¤æçº¿ï¼ |
| | | const productTypeTrendChartOption = computed(() => { |
| | | const typeOrder = ["AAAéå®åº", "BBBéå®åº", "CCCéå®åº", "DDDéå®åº"]; |
| | | const colorMap = { |
| | | "AAAéå®åº": "#65A0FF", |
| | | "BBBéå®åº": "#33F5FF", |
| | | "CCCéå®åº": "#FFD54A", |
| | | "DDDéå®åº": "#EE52FF", |
| | | }; |
| | | const areaColorMap = { |
| | | "AAAéå®åº": "rgba(101,160,255,0.28)", |
| | | "BBBéå®åº": "rgba(51,245,255,0.30)", |
| | | "CCCéå®åº": "rgba(255,213,74,0.25)", |
| | | "DDDéå®åº": "rgba(238,82,255,0.25)", |
| | | }; |
| | | const periods = ["6/9", "6/10", "6/11", "6/12", "6/12", "6/13", "6/14", "6/15"]; |
| | | const map = { |
| | | "AAAéå®åº": [85, 112, 112, 112, 140, 112, 112, 140], |
| | | "BBBéå®åº": [140, 180, 180, 180, 230, 180, 180, 230], |
| | | "CCCéå®åº": [112, 140, 140, 140, 180, 140, 140, 180], |
| | | "DDDéå®åº": [200, 165, 200, 200, 165, 165, 140, 140], |
| | | }; |
| | | |
| | | const series = typeOrder.map((t) => ({ |
| | | name: t, |
| | | type: "line", |
| | | smooth: true, |
| | | symbolSize: 7, |
| | | showSymbol: true, |
| | | data: map[t] || [], |
| | | lineStyle: { width: 3, color: colorMap[t] }, |
| | | itemStyle: { color: colorMap[t] }, |
| | | areaStyle: { |
| | | opacity: 0.25, |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { offset: 0, color: areaColorMap[t] }, |
| | | { offset: 1, color: "rgba(0,0,0,0)" }, |
| | | ]), |
| | | }, |
| | | })); |
| | | |
| | | return { |
| | | backgroundColor: "transparent", |
| | | legend: { |
| | | top: 10, |
| | | left: "center", |
| | | textStyle: { color: "#B8C8E0", fontSize: 11, padding: [0, 0, 0, 2] }, |
| | | itemWidth: 12, |
| | | itemHeight: 10, |
| | | itemGap: 18, |
| | | }, |
| | | tooltip: { |
| | | trigger: "axis", |
| | | backgroundColor: "rgba(0,0,0,0.55)", |
| | | borderColor: "rgba(64,158,255,0.25)", |
| | | borderWidth: 1, |
| | | textStyle: { color: "#B8C8E0" }, |
| | | }, |
| | | grid: { |
| | | left: "10%", |
| | | right: "6%", |
| | | bottom: "14%", |
| | | top: "26%", |
| | | containLabel: true, |
| | | }, |
| | | xAxis: { |
| | | type: "category", |
| | | data: periods, |
| | | axisLine: { lineStyle: { color: "rgba(184,200,224,0.25)" } }, |
| | | axisTick: { show: false }, |
| | | axisLabel: { color: "#B8C8E0", fontSize: 11, margin: 10 }, |
| | | }, |
| | | yAxis: { |
| | | type: "value", |
| | | name: "", |
| | | axisLine: { lineStyle: { color: "rgba(184,200,224,0.25)" } }, |
| | | axisLabel: { color: "#B8C8E0", fontSize: 11, margin: 8 }, |
| | | splitLine: { lineStyle: { color: "rgba(184,200,224,0.12)" } }, |
| | | }, |
| | | series, |
| | | }; |
| | | }); |
| | | |
| | |
| | | salesAreaChartInstance = echarts.init(salesAreaChart.value); |
| | | } |
| | | |
| | | // åå§åæ°å¢å®¢æ·è¶å¿å¾è¡¨ |
| | | if (productTypeTrendChart.value && !productTypeTrendChartInstance) { |
| | | productTypeTrendChartInstance = echarts.init(productTypeTrendChart.value); |
| | | } |
| | | |
| | | // åå§å累计ééè¶å¿å¾è¡¨ |
| | | if (cumulativeSalesVolumeChart.value && !cumulativeSalesVolumeChartInstance) { |
| | | cumulativeSalesVolumeChartInstance = echarts.init( |
| | |
| | | salesAreaChartInstance.setOption(salesAreaChartOption.value); |
| | | } |
| | | |
| | | // æ´æ°æ°å¢å®¢æ·è¶å¿å¾è¡¨ |
| | | if (productTypeTrendChartInstance) { |
| | | productTypeTrendChartInstance.setOption(productTypeTrendChartOption.value); |
| | | } |
| | | |
| | | // æ´æ°ç´¯è®¡ééè¶å¿å¾è¡¨ |
| | | if (cumulativeSalesVolumeChartInstance) { |
| | | cumulativeSalesVolumeChartInstance.setOption( |
| | |
| | | if (salesAreaChartInstance) { |
| | | salesAreaChartInstance.resize(); |
| | | } |
| | | if (productTypeTrendChartInstance) { |
| | | productTypeTrendChartInstance.resize(); |
| | | } |
| | | if (cumulativeSalesVolumeChartInstance) { |
| | | cumulativeSalesVolumeChartInstance.resize(); |
| | | } |
| | |
| | | |
| | | // çå½å¨æ |
| | | onMounted(() => { |
| | | // å¯å¨é¡¶é¨æ æ¶é´å·æ° |
| | | if (!timeTicker) { |
| | | timeTicker = setInterval(() => { |
| | | now.value = dayjs(); |
| | | }, 1000); |
| | | } |
| | | |
| | | // 设置é»è®¤æ¥æèå´ä¸ºæè¿3个æ |
| | | const endDate = dayjs(); |
| | | const startDate = endDate.subtract(3, "month"); |
| | |
| | | |
| | | // æ·»å çªå£å¤§å°ååçå¬ |
| | | window.addEventListener("resize", handleResize); |
| | | document.addEventListener("fullscreenchange", handleFullscreenChange); |
| | | }); |
| | | |
| | | // è·å产åç±»åæ ç¾ç±»å |
| | |
| | | |
| | | // ç»ä»¶å¸è½½æ¶éæ¯å¾è¡¨å®ä¾ |
| | | onBeforeUnmount(() => { |
| | | if (timeTicker) { |
| | | clearInterval(timeTicker); |
| | | timeTicker = null; |
| | | } |
| | | |
| | | if (salesVolumeChartInstance) { |
| | | salesVolumeChartInstance.dispose(); |
| | | } |
| | |
| | | if (salesAreaChartInstance) { |
| | | salesAreaChartInstance.dispose(); |
| | | } |
| | | |
| | | if (productTypeTrendChartInstance) { |
| | | productTypeTrendChartInstance.dispose(); |
| | | } |
| | | if (cumulativeSalesVolumeChartInstance) { |
| | | cumulativeSalesVolumeChartInstance.dispose(); |
| | | } |
| | |
| | | |
| | | // ç§»é¤çªå£å¤§å°ååçå¬ |
| | | window.removeEventListener("resize", handleResize); |
| | | document.removeEventListener("fullscreenchange", handleFullscreenChange); |
| | | }); |
| | | </script> |
| | | |
| | | <style scoped> |
| | | /* å¤é¨å®¹å¨ - å æ®æ´ä¸ªè§å£ */ |
| | | .sales-statistics-container { |
| | | position: relative; |
| | | width: 100%; |
| | | /* 页é¢å¨å¸¸è§å¸å±ä¸ï¼æé¡¶æ ï¼é»è®¤åå» 84pxï¼é¿å
å
容被è£å */ |
| | | min-height: calc(100vh - 84px); |
| | | background-color: #f5f7fa; |
| | | overflow: hidden; |
| | | color: #B8C8E0; |
| | | background: #041026; |
| | | } |
| | | |
| | | /* å
é¨å
容åºå - èªéåºå®½åº¦ */ |
| | | .data-dashboard { |
| | | .sales-statistics-container.is-fullscreen { |
| | | min-height: 100vh; |
| | | height: 100vh; |
| | | } |
| | | |
| | | /* æ·±è²èæ¯å¾ */ |
| | | .bi-bg { |
| | | position: absolute; |
| | | inset: 0; |
| | | background-image: url("@/assets/BI/backImage@2x.png"); |
| | | background-size: cover; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | z-index: 0; |
| | | } |
| | | |
| | | /* 顶鍿 颿 */ |
| | | .bi-topbar { |
| | | position: relative; |
| | | width: 100%; |
| | | min-height: 100%; |
| | | background-color: #ffffff; |
| | | box-shadow: 0 0 20px rgba(0, 0, 0, 0.1); |
| | | } |
| | | |
| | | .filter-area { |
| | | padding: 20px; |
| | | background-color: #ffffff; |
| | | border-bottom: 1px solid #e4e7ed; |
| | | z-index: 2; |
| | | height: 58px; |
| | | display: flex; |
| | | gap: 40px; |
| | | align-items: center; |
| | | flex-wrap: wrap; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .filter-section { |
| | | .bi-topbar-title-bg { |
| | | position: absolute; |
| | | top: 0; |
| | | left: 0; |
| | | height: 58px; |
| | | width: 100%; |
| | | object-fit: cover; |
| | | z-index: 0; |
| | | pointer-events: none; |
| | | } |
| | | |
| | | .bi-topbar-content { |
| | | position: relative; |
| | | z-index: 1; |
| | | width: 100%; |
| | | padding: 0 28px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .bi-topbar-title { |
| | | position: absolute; |
| | | left: 50%; |
| | | transform: translateX(-50%); |
| | | font-size: 26px; |
| | | font-weight: 800; |
| | | letter-spacing: 1px; |
| | | background: linear-gradient(180deg, #ffffff 0%, #B8DFFF 100%); |
| | | -webkit-background-clip: text; |
| | | background-clip: text; |
| | | -webkit-text-fill-color: transparent; |
| | | color: transparent; |
| | | text-shadow: 0 0 26px rgba(0, 164, 237, 0.55); |
| | | } |
| | | |
| | | .bi-topbar-left { |
| | | position: absolute; |
| | | left: 10px; |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | color: rgba(208, 231, 255, 0.85); |
| | | font-size: 13px; |
| | | } |
| | | |
| | | .status-sun { |
| | | color: #ffd85e; |
| | | text-shadow: 0 0 10px rgba(255, 216, 94, 0.8); |
| | | font-size: 13px; |
| | | line-height: 1; |
| | | } |
| | | |
| | | .bi-topbar-meta { |
| | | position: absolute; |
| | | right: 52px; |
| | | top: 16px; |
| | | font-size: 12px; |
| | | font-weight: 500; |
| | | letter-spacing: 0.5px; |
| | | color: rgba(208, 231, 255, 0.85); |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .filter-label { |
| | | font-size: 14px; |
| | | font-weight: 500; |
| | | color: #303133; |
| | | white-space: nowrap; |
| | | .fullscreen-btn { |
| | | position: absolute; |
| | | right: 10px; |
| | | top: 12px; |
| | | transform: none; |
| | | border: 1px solid rgba(64, 158, 255, 0.45); |
| | | background: rgba(0, 164, 237, 0.14); |
| | | color: #d0e7ff; |
| | | width: 34px; |
| | | height: 34px; |
| | | border-radius: 6px; |
| | | padding: 0; |
| | | cursor: pointer; |
| | | transition: all 0.2s ease; |
| | | z-index: 10; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .dashboard-content { |
| | | .fullscreen-btn:hover { |
| | | background: rgba(0, 164, 237, 0.24); |
| | | box-shadow: 0 0 12px rgba(0, 164, 237, 0.3); |
| | | } |
| | | |
| | | .bi-topbar-sep { |
| | | opacity: 0.7; |
| | | } |
| | | |
| | | /* 主ä½ç½æ ¼å¸å± */ |
| | | .bi-dashboard-grid { |
| | | position: relative; |
| | | z-index: 1; |
| | | z-index: 2; |
| | | height: calc(100vh - 84px - 58px); |
| | | min-height: 450px; |
| | | padding: 10px 18px 14px; |
| | | display: grid; |
| | | grid-template-columns: 1fr 1.05fr 1fr; |
| | | grid-template-rows: 1fr 1fr; |
| | | gap: 12px; |
| | | } |
| | | |
| | | .sales-statistics-container.is-fullscreen .bi-dashboard-grid { |
| | | height: calc(100vh - 58px); |
| | | } |
| | | |
| | | .bi-panel { |
| | | background: rgba(3, 18, 46, 0.62); |
| | | border: 1px solid rgba(64, 158, 255, 0.35); |
| | | border-radius: 4px; |
| | | overflow: hidden; |
| | | box-shadow: 0 0 22px rgba(0, 164, 237, 0.12); |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 20px; |
| | | padding: 20px; |
| | | min-height: 800px; |
| | | overflow: hidden; |
| | | position: relative; |
| | | } |
| | | |
| | | /* è¡å¸å± */ |
| | | .row { |
| | | .bi-panel-title { |
| | | height: 44px; |
| | | display: flex; |
| | | gap: 20px; |
| | | align-items: stretch; |
| | | align-items: center; |
| | | padding: 0 18px; |
| | | font-size: 15px; |
| | | font-weight: 700; |
| | | color: #B8C8E0; |
| | | background: linear-gradient( |
| | | 90deg, |
| | | rgba(0, 164, 237, 0.2), |
| | | rgba(0, 164, 237, 0.04) |
| | | ); |
| | | border-bottom: 1px solid rgba(64, 158, 255, 0.25); |
| | | } |
| | | |
| | | /* 第ä¸è¡ï¼4ä¸ªææ å¡ç */ |
| | | .row-1 { |
| | | height: 180px; |
| | | } |
| | | |
| | | /* 第äºè¡ï¼2个è¶å¿å¾è¡¨ */ |
| | | .row-2 { |
| | | height: 350px; |
| | | } |
| | | |
| | | /* 第ä¸è¡ï¼ç´¯è®¡æ°æ®è¶å¿ */ |
| | | .row-3 { |
| | | height: 350px; |
| | | } |
| | | |
| | | /* 第åè¡ï¼è¡¨æ ¼åå¾è¡¨ */ |
| | | .row-4 { |
| | | height: 600px; |
| | | } |
| | | |
| | | /* å¡çæ ·å¼ */ |
| | | .panel-card { |
| | | background-color: #ffffff; |
| | | border-radius: 8px; |
| | | border: 1px solid #e4e7ed; |
| | | overflow: hidden; |
| | | .panel-tabs { |
| | | position: absolute; |
| | | top: 8px; |
| | | right: 12px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); |
| | | transition: all 0.3s ease; |
| | | gap: 6px; |
| | | z-index: 4; |
| | | } |
| | | |
| | | .panel-card:hover { |
| | | box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12); |
| | | transform: translateY(-2px); |
| | | .tab-item { |
| | | font-size: 12px; |
| | | color: rgba(184, 200, 224, 0.75); |
| | | padding: 1px 5px; |
| | | border: 1px solid rgba(64, 158, 255, 0.25); |
| | | border-radius: 3px; |
| | | line-height: 1.4; |
| | | } |
| | | |
| | | /* å¡çå¸å± */ |
| | | .card-1 { |
| | | .tab-item.active { |
| | | color: #ffffff; |
| | | border-color: rgba(0, 164, 237, 0.65); |
| | | background: rgba(0, 164, 237, 0.22); |
| | | } |
| | | |
| | | .bi-panel-body { |
| | | flex: 1; |
| | | padding: 8px 10px; |
| | | } |
| | | |
| | | .card-2 { |
| | | flex: 1; |
| | | .echart-fill { |
| | | width: 100%; |
| | | height: 100%; |
| | | } |
| | | |
| | | .card-3 { |
| | | flex: 1; |
| | | .chart-filter-tabs { |
| | | display: flex; |
| | | gap: 6px; |
| | | margin: 0 0 5px 0; |
| | | } |
| | | |
| | | .card-4 { |
| | | flex: 1; |
| | | .cf-tab { |
| | | font-size: 11px; |
| | | color: rgba(184, 200, 224, 0.68); |
| | | background: rgba(18, 56, 106, 0.65); |
| | | border: 1px solid rgba(64, 158, 255, 0.25); |
| | | padding: 3px 9px; |
| | | line-height: 1; |
| | | } |
| | | |
| | | .card-5 { |
| | | flex: 1; |
| | | .cf-tab.active { |
| | | color: #D9ECFF; |
| | | background: rgba(0, 108, 208, 0.85); |
| | | border-color: rgba(64, 158, 255, 0.65); |
| | | } |
| | | |
| | | .card-6 { |
| | | flex: 1; |
| | | .chart-unit-row { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | font-size: 12px; |
| | | color: rgba(208, 231, 255, 0.88); |
| | | margin-bottom: 4px; |
| | | padding: 0 2px; |
| | | } |
| | | |
| | | .card-7 { |
| | | flex: 1; |
| | | .dot-legend::before { |
| | | content: ""; |
| | | display: inline-block; |
| | | width: 8px; |
| | | height: 8px; |
| | | background: #65A0FF; |
| | | margin-right: 6px; |
| | | } |
| | | |
| | | .card-8 { |
| | | flex: 1; |
| | | .chart-mini-title { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | font-size: 18px; |
| | | color: #D9ECFF; |
| | | font-weight: 700; |
| | | margin: 0 0 8px 0; |
| | | line-height: 1; |
| | | } |
| | | |
| | | .card-9 { |
| | | flex: 1; |
| | | .diamond { |
| | | width: 10px; |
| | | height: 10px; |
| | | background: #1E8BFF; |
| | | transform: rotate(45deg); |
| | | display: inline-block; |
| | | } |
| | | |
| | | .card-10 { |
| | | flex: 1; |
| | | .chart-unit-single { |
| | | justify-content: flex-start; |
| | | margin-bottom: 2px; |
| | | } |
| | | |
| | | .card-11 { |
| | | flex: 1; |
| | | .bi-panel-top-left .echart-fill, |
| | | .bi-panel-top-right .echart-fill { |
| | | height: calc(100% - 44px); |
| | | } |
| | | |
| | | .panel-title { |
| | | padding: 15px 20px; |
| | | .bi-panel-bottom-left .echart-fill, |
| | | .bi-panel-bottom-right .echart-fill { |
| | | height: calc(100% - 28px); |
| | | } |
| | | |
| | | .bi-panel-bottom-center .echart-fill { |
| | | height: calc(100% - 44px); |
| | | } |
| | | |
| | | .bi-panel-top-left { |
| | | grid-column: 1; |
| | | grid-row: 1; |
| | | position: relative; |
| | | } |
| | | |
| | | .bi-panel-top-right { |
| | | grid-column: 3; |
| | | grid-row: 1; |
| | | position: relative; |
| | | } |
| | | |
| | | .bi-panel-bottom-left { |
| | | grid-column: 1; |
| | | grid-row: 2; |
| | | } |
| | | |
| | | .bi-panel-bottom-center { |
| | | grid-column: 2; |
| | | grid-row: 2; |
| | | } |
| | | |
| | | .bi-panel-bottom-right { |
| | | grid-column: 3; |
| | | grid-row: 2; |
| | | } |
| | | |
| | | /* ä¸å¿ç¯æµ®å±ï¼ç»å¯¹å®ä½å¨ç½æ ¼ä¸æ¹ï¼ */ |
| | | .center-ring { |
| | | grid-column: 2; |
| | | grid-row: 1 / span 2; |
| | | position: absolute; |
| | | left:25%; |
| | | top: 25%; |
| | | transform: translate(-50%, -50%); |
| | | width: 400px; |
| | | height: 275px; |
| | | z-index: 3; |
| | | pointer-events: none; |
| | | } |
| | | |
| | | .center-ring-bg { |
| | | width: 100%; |
| | | height: 100%; |
| | | object-fit: contain; |
| | | filter: drop-shadow(0 0 20px rgba(0, 164, 237, 0.35)); |
| | | } |
| | | |
| | | .center-ring-content { |
| | | position: absolute; |
| | | inset: 0; |
| | | } |
| | | |
| | | .center-ring-content::before, |
| | | .center-ring-content::after { |
| | | content: ""; |
| | | position: absolute; |
| | | left: 50%; |
| | | top: 56%; |
| | | width: 370px; |
| | | height: 146px; |
| | | transform: translate(-50%, -50%) rotate(-18deg); |
| | | border: 2px solid rgba(40, 186, 255, 0.45); |
| | | border-radius: 50%; |
| | | filter: drop-shadow(0 0 8px rgba(0, 164, 237, 0.35)); |
| | | opacity: 0.7; |
| | | } |
| | | |
| | | .center-ring-content::after { |
| | | width: 360px; |
| | | height: 150px; |
| | | transform: translate(-50%, -50%) rotate(26deg); |
| | | border-color: rgba(80, 220, 255, 0.35); |
| | | opacity: 0.55; |
| | | } |
| | | |
| | | .center-ring-title { |
| | | position: absolute; |
| | | top: 116px; |
| | | left: 50%; |
| | | transform: translateX(-50%); |
| | | font-size: 50px; |
| | | line-height: 1.06; |
| | | text-align: center; |
| | | font-weight: 900; |
| | | color: #EAF6FF; |
| | | text-shadow: 0 0 34px rgba(0, 164, 237, 0.6); |
| | | z-index: 2; |
| | | } |
| | | |
| | | .center-ring-title::before { |
| | | content: ""; |
| | | position: absolute; |
| | | left: 50%; |
| | | top: 50%; |
| | | width: 155px; |
| | | height: 155px; |
| | | transform: translate(-50%, -50%); |
| | | background: radial-gradient(circle, rgba(43, 199, 255, 0.26) 0%, rgba(8, 28, 61, 0.86) 70%); |
| | | border: 2px solid rgba(39, 198, 255, 0.46); |
| | | border-radius: 50%; |
| | | box-shadow: 0 0 20px rgba(0, 164, 237, 0.45), inset 0 0 26px rgba(0, 164, 237, 0.2); |
| | | z-index: -1; |
| | | } |
| | | |
| | | .center-metric { |
| | | position: absolute; |
| | | width: 155px; |
| | | z-index: 3; |
| | | text-align: center; |
| | | } |
| | | |
| | | .center-metric::before { |
| | | content: ""; |
| | | position: absolute; |
| | | left: 50%; |
| | | top: 50%; |
| | | width: 104px; |
| | | height: 104px; |
| | | transform: translate(-50%, -50%); |
| | | border-radius: 50%; |
| | | border: 2px solid rgba(71, 223, 255, 0.4); |
| | | background: radial-gradient(circle, rgba(37, 177, 255, 0.25) 0%, rgba(8, 33, 69, 0.55) 70%); |
| | | box-shadow: 0 0 18px rgba(0, 164, 237, 0.35), inset 0 0 18px rgba(0, 164, 237, 0.2); |
| | | z-index: -1; |
| | | } |
| | | |
| | | .center-metric-label { |
| | | font-size: 16px; |
| | | font-weight: 500; |
| | | color: #303133; |
| | | border-bottom: 1px solid #e4e7ed; |
| | | background-color: #fafafa; |
| | | color: rgba(234, 246, 255, 0.9); |
| | | margin-top: 4px; |
| | | } |
| | | |
| | | .card-1 .panel-title { |
| | | border-left: 4px solid #409eff; |
| | | .center-metric-value { |
| | | font-size: 48px; |
| | | font-weight: 700; |
| | | color: #EAF6FF; |
| | | text-shadow: 0 0 8px rgba(0, 229, 255, 0.22); |
| | | line-height: 1.1; |
| | | } |
| | | |
| | | .card-2 .panel-title { |
| | | border-left: 4px solid #67c23a; |
| | | .center-metric-unit { |
| | | margin-top: 2px; |
| | | font-size: 14px; |
| | | color: rgba(208, 231, 255, 0.85); |
| | | } |
| | | |
| | | .card-3 .panel-title { |
| | | border-left: 4px solid #e6a23c; |
| | | .m1 { |
| | | top: -6px; |
| | | left: -26px; |
| | | text-align: left; |
| | | } |
| | | |
| | | .card-4 .panel-title { |
| | | border-left: 4px solid #f56c6c; |
| | | .m2 { |
| | | top: -6px; |
| | | right: -26px; |
| | | text-align: right; |
| | | } |
| | | |
| | | .card-5 .panel-title { |
| | | border-left: 4px solid #409eff; |
| | | .m3 { |
| | | bottom: 66px; |
| | | left: -30px; |
| | | text-align: left; |
| | | } |
| | | |
| | | .card-6 .panel-title { |
| | | border-left: 4px solid #67c23a; |
| | | .m4 { |
| | | bottom: 66px; |
| | | right: -30px; |
| | | text-align: right; |
| | | } |
| | | |
| | | .card-7 .panel-title { |
| | | border-left: 4px solid #e6a23c; |
| | | @media (max-width: 1100px) { |
| | | .bi-topbar-content { |
| | | padding: 0 14px; |
| | | } |
| | | |
| | | .card-8 .panel-title { |
| | | border-left: 4px solid #f56c6c; |
| | | .center-ring { |
| | | left: 45.2%; |
| | | width: 330px; |
| | | height: 245px; |
| | | top: 24px; |
| | | } |
| | | |
| | | .card-9 .panel-title { |
| | | border-left: 4px solid #409eff; |
| | | .center-ring-title { |
| | | top: 122px; |
| | | font-size: 26px; |
| | | } |
| | | |
| | | .card-10 .panel-title { |
| | | border-left: 4px solid #67c23a; |
| | | .m1 { |
| | | top: 52px; |
| | | left: 42px; |
| | | } |
| | | |
| | | .card-11 .panel-title { |
| | | border-left: 4px solid #e6a23c; |
| | | .m2 { |
| | | top: 54px; |
| | | right: 42px; |
| | | } |
| | | |
| | | .chart-container { |
| | | flex: 1; |
| | | padding: 20px; |
| | | .m3 { |
| | | bottom: 62px; |
| | | left: 48px; |
| | | } |
| | | |
| | | .table-container { |
| | | flex: 1; |
| | | padding: 20px; |
| | | overflow: auto; |
| | | .m4 { |
| | | bottom: 68px; |
| | | right: 44px; |
| | | } |
| | | |
| | | .stats-grid { |
| | | flex: 1; |
| | | padding: 15px; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .stat-item { |
| | | background-color: #fafafa; |
| | | border-radius: 8px; |
| | | padding: 15px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | align-items: center; |
| | | justify-content: center; |
| | | border: 1px solid #e4e7ed; |
| | | min-height: 80px; |
| | | width: 100%; |
| | | } |
| | | |
| | | .stat-value { |
| | | font-size: 24px; |
| | | font-weight: 600; |
| | | color: #303133; |
| | | margin-bottom: 5px; |
| | | } |
| | | |
| | | .sales-volume-color { |
| | | color: #409eff; |
| | | text-shadow: 0 2px 4px rgba(64, 158, 255, 0.3); |
| | | } |
| | | |
| | | .sales-amount-color { |
| | | color: #67c23a; |
| | | text-shadow: 0 2px 4px rgba(103, 194, 58, 0.3); |
| | | } |
| | | |
| | | .new-customer-color { |
| | | color: #e6a23c; |
| | | text-shadow: 0 2px 4px rgba(230, 162, 60, 0.3); |
| | | } |
| | | |
| | | .total-customer-color { |
| | | color: #f56c6c; |
| | | text-shadow: 0 2px 4px rgba(245, 108, 108, 0.3); |
| | | } |
| | | |
| | | .stat-unit { |
| | | font-size: 12px; |
| | | color: #909399; |
| | | margin-bottom: 3px; |
| | | } |
| | | |
| | | .stat-change { |
| | | font-size: 12px; |
| | | color: #67c23a; |
| | | } |
| | | |
| | | /* è¡¨æ ¼æ ·å¼ */ |
| | | :deep(.el-table) { |
| | | border-radius: 8px; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | :deep(.el-table th) { |
| | | background-color: #fafafa; |
| | | font-weight: 500; |
| | | } |
| | | |
| | | :deep(.el-table tr:hover > td) { |
| | | background-color: #ecf5ff; |
| | | } |
| | | |
| | | .data-value { |
| | | font-weight: bold; |
| | | color: #409eff; |
| | | } |
| | | |
| | | /* ä¸æéæ©æ¡æ ·å¼ */ |
| | | :deep(.el-select) { |
| | | width: 100%; |
| | | } |
| | | |
| | | :deep(.el-date-picker) { |
| | | width: 100%; |
| | | } |
| | | </style> |