| | |
| | | <div class="bi-topbar"> |
| | | <img class="bi-topbar-title-bg" |
| | | src="@/assets/BI/biaoti.png" |
| | | alt="销售看板统计" /> |
| | | alt="销售统计看板" /> |
| | | <div class="bi-topbar-content"> |
| | | <div class="bi-topbar-left"> |
| | | <button class="fullscreen-btn" |
| | |
| | | <span>26℃</span> |
| | | <span class="bi-topbar-sep">湿度:1</span> --> |
| | | </div> |
| | | <div class="bi-topbar-title">销售看板统计</div> |
| | | <div class="bi-topbar-title">销售统计看板</div> |
| | | <div class="bi-topbar-meta"> |
| | | <span class="bi-topbar-time">{{ currentTime }}</span> |
| | | <span class="bi-topbar-sep">|</span> |
| | |
| | | <!-- 左上:销量趋势 --> |
| | | <div class="bi-panel bi-panel-top-left"> |
| | | <PanelHeader :isFullscreen="true" |
| | | title="销售分析-砌块" /> |
| | | title="销量分析趋势图" /> |
| | | <div class="panel-tabs"> |
| | | <span class="tab-item active">年</span> |
| | | <span class="tab-item">月</span> |
| | | <span class="tab-item" |
| | | :class="{ active: chartTimeDimension === '年' }" |
| | | @click="handleChartTimeDimensionChange('年')">年</span> |
| | | <span class="tab-item" |
| | | :class="{ active: chartTimeDimension === '月' }" |
| | | @click="handleChartTimeDimensionChange('月')">月</span> |
| | | </div> |
| | | <div class="panel-tabs2"> |
| | | <span class="tab-item" |
| | | :class="{ active: chartProductType === '砌块' }" |
| | | @click="handleChartProductTypeChange('砌块')">砌块</span> |
| | | <span class="tab-item" |
| | | :class="{ active: chartProductType === '板材' }" |
| | | @click="handleChartProductTypeChange('板材')">板材</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 class="bi-panel bi-panel-top-right"> |
| | | <PanelHeader :isFullscreen="true" |
| | | title="销售分析-板材" /> |
| | | title="销售金额分析" /> |
| | | <div class="panel-tabs"> |
| | | <span class="tab-item active">年</span> |
| | | <span class="tab-item">月</span> |
| | | <span class="tab-item" |
| | | :class="{ active: chartTimeDimension2 === '年' }" |
| | | @click="handleChartTimeDimensionChange2('年')">年</span> |
| | | <span class="tab-item" |
| | | :class="{ active: chartTimeDimension2 === '月' }" |
| | | @click="handleChartTimeDimensionChange2('月')">月</span> |
| | | </div> |
| | | <div class="panel-tabs2"> |
| | | <span class="tab-item" |
| | | :class="{ active: chartProductType2 === '砌块' }" |
| | | @click="handleChartProductTypeChange2('砌块')">砌块</span> |
| | | <span class="tab-item" |
| | | :class="{ active: chartProductType2 === '板材' }" |
| | | @click="handleChartProductTypeChange2('板材')">板材</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> |
| | | <span>单位:元</span> |
| | | </div> |
| | | <div ref="salesAmountChart" |
| | | class="echart-fill"></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 class="center-ring-box"> |
| | | <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 class="center-metric-label">总销售金额</div> |
| | | <div class="center-metric-value">{{ totalSalesAmount.toFixed(0) }}</div> |
| | | <div class="center-metric-unit">万元</div> |
| | | </div> |
| | | <div class="center-metric m2"> |
| | | <div class="center-metric-label">成交总订单</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 class="center-metric-label">累计客户</div> |
| | | <div class="center-metric-value">{{ centerNewCustomerCount }}</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 class="center-metric-label">销售方数</div> |
| | | <div class="center-metric-value">{{ totalSalesAreaCount.toFixed(0) }}</div> |
| | | <div class="center-metric-unit">方</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <!-- 左下:产品类型销量 --> |
| | | <div class="bi-panel bi-panel-bottom-left"> |
| | | <PanelHeader :isFullscreen="true" |
| | | title="客户销量排名分析-砌块" /> |
| | | title="销量数据统计" /> |
| | | <div class="panel-tabs"> |
| | | <span class="tab-item active">年</span> |
| | | <span class="tab-item">月</span> |
| | | <span class="tab-item" |
| | | :class="{ active: tableTimeDimension === '年' }" |
| | | @click="handleTableTimeDimensionChange('年')">年</span> |
| | | <span class="tab-item" |
| | | :class="{ active: tableTimeDimension === '月' }" |
| | | @click="handleTableTimeDimensionChange('月')">月</span> |
| | | </div> |
| | | <div class="panel-tabs2"> |
| | | <span class="tab-item" |
| | | :class="{ active: tableProductType === '砌块' }" |
| | | @click="handleTableProductTypeChange('砌块')">砌块</span> |
| | | <span class="tab-item" |
| | | :class="{ active: tableProductType === '板材' }" |
| | | @click="handleTableProductTypeChange('板材')">板材</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> |
| | | <span v-for="area in tableSalesAreas" |
| | | :key="area" |
| | | class="cf-tab" |
| | | :class="{ active: tableSelectedArea === area }" |
| | | @click="handleTableAreaChange(area)">{{ area }}</span> |
| | | </div> |
| | | <div ref="productTypeChart" |
| | | class="echart-fill"></div> |
| | | <div class="scroll-table-container"> |
| | | <table class="scroll-table"> |
| | | <thead> |
| | | <tr> |
| | | <th>序号</th> |
| | | <th>产品类型</th> |
| | | <th>年月</th> |
| | | <th>销售区</th> |
| | | <th>销量(m³)</th> |
| | | </tr> |
| | | </thead> |
| | | <div class="scroll-table-content"> |
| | | <tbody ref="blockTableBody"> |
| | | <tr :class="(index + 1) % 2 === 0 ? 'evenTableTr' : 'oddTableTr'" |
| | | v-for="(item, index) in filteredTableSalesData" |
| | | :key="item.period + item.area + index"> |
| | | <td>{{ index + 1 }}</td> |
| | | <td>{{ item.productType }}</td> |
| | | <td>{{ item.period }}</td> |
| | | <td>{{ item.area }}</td> |
| | | <td>{{ item.sales }}</td> |
| | | </tr> |
| | | </tbody> |
| | | </div> |
| | | </table> |
| | | </div> |
| | | <div class="panel-summary-row"> |
| | | <div class="summary-label">合计</div> |
| | | <div class="summary-value">{{ filteredTableSalesTotal }} m³</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <!-- 中下:新增客户分析(分产品类型趋势) --> |
| | |
| | | <PanelHeader :isFullscreen="true" |
| | | title="新增客户趋势分析" /> |
| | | <div class="panel-tabs"> |
| | | <span class="tab-item active">年</span> |
| | | <span class="tab-item">月</span> |
| | | <span class="tab-item" |
| | | :class="{ active: customerTimeDimension === '年' }" |
| | | @click="handleCustomerTimeDimensionChange('年')">年</span> |
| | | <span class="tab-item" |
| | | :class="{ active: customerTimeDimension === '月' }" |
| | | @click="handleCustomerTimeDimensionChange('月')">月</span> |
| | | </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> |
| | | <span>单位:家</span> |
| | | </div> |
| | | <div ref="productTypeTrendChart" |
| | | class="echart-fill"></div> |
| | |
| | | <!-- 右下:销售区域销量 --> |
| | | <div class="bi-panel bi-panel-bottom-right"> |
| | | <PanelHeader :isFullscreen="true" |
| | | title="客户销量排名分析-板材" /> |
| | | title="销售额数据统计" /> |
| | | <div class="panel-tabs"> |
| | | <span class="tab-item active">年</span> |
| | | <span class="tab-item">月</span> |
| | | <span class="tab-item" |
| | | :class="{ active: tableTimeDimension2 === '年' }" |
| | | @click="handleTableTimeDimensionChange2('年')">年</span> |
| | | <span class="tab-item" |
| | | :class="{ active: tableTimeDimension2 === '月' }" |
| | | @click="handleTableTimeDimensionChange2('月')">月</span> |
| | | </div> |
| | | <div class="panel-tabs2"> |
| | | <span class="tab-item" |
| | | :class="{ active: tableProductType2 === '砌块' }" |
| | | @click="handleTableProductTypeChange2('砌块')">砌块</span> |
| | | <span class="tab-item" |
| | | :class="{ active: tableProductType2 === '板材' }" |
| | | @click="handleTableProductTypeChange2('板材')">板材</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> |
| | | <span v-for="area in tableSalesAreas" |
| | | :key="area" |
| | | class="cf-tab" |
| | | :class="{ active: tableSelectedArea === area }" |
| | | @click="handleTableAreaChange2(area)">{{ area }}</span> |
| | | </div> |
| | | <div ref="salesAreaChart" |
| | | class="echart-fill"></div> |
| | | <div class="scroll-table-container"> |
| | | <table class="scroll-table"> |
| | | <thead> |
| | | <tr> |
| | | <th>序号</th> |
| | | <th>产品类型</th> |
| | | <th>年月</th> |
| | | <th>销售区</th> |
| | | <th>销售额(元)</th> |
| | | </tr> |
| | | </thead> |
| | | <div class="scroll-table-content"> |
| | | <tbody ref="boardTableBody"> |
| | | <tr :class="(index + 1) % 2 === 0 ? 'evenTableTr' : 'oddTableTr'" |
| | | v-for="(item, index) in filteredAmountSalesData" |
| | | :key="item.period + item.area + index"> |
| | | <td>{{ index + 1 }}</td> |
| | | <td>{{ item.productType }}</td> |
| | | <td>{{ item.period }}</td> |
| | | <td>{{ item.area }}</td> |
| | | <td>{{ item.sales }}</td> |
| | | </tr> |
| | | </tbody> |
| | | </div> |
| | | </table> |
| | | </div> |
| | | <div class="panel-summary-row"> |
| | | <div class="summary-label">合计</div> |
| | | <div class="summary-value2">{{ filteredAmountSalesTotal }} 元</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | import * as echarts from "echarts"; |
| | | import dayjs from "dayjs"; |
| | | import PanelHeader from "@/views/reportAnalysis/PSIDataAnalysis/components/PanelHeader.vue"; |
| | | import { |
| | | getDashboardStatistics, |
| | | getCustomerTrends, |
| | | getSalesAnalysisTrend, |
| | | getSalesAmountAnalysis, |
| | | } from "@/api/reportAnalysis/salesStatistics"; |
| | | |
| | | const router = useRouter(); |
| | | const screenRoot = ref(null); |
| | |
| | | const productTypeChart = ref(null); |
| | | const salesAreaChart = ref(null); |
| | | const productTypeTrendChart = ref(null); |
| | | const blockTableBody = ref(null); |
| | | const boardTableBody = ref(null); |
| | | |
| | | // 选择器数据 |
| | | // 销量分析趋势图 |
| | | const chartTimeDimension = ref("年"); |
| | | const chartTimeDimension2 = ref("年"); |
| | | |
| | | const chartSelectedArea = ref("全部"); |
| | | const chartProductType = ref("砌块"); |
| | | const chartProductType2 = ref("砌块"); |
| | | |
| | | // 销量数据统计 |
| | | const tableTimeDimension = ref("年"); |
| | | const tableSelectedArea = ref("全部"); |
| | | const tableSelectedArea2 = ref("全部"); |
| | | |
| | | const tableProductType = ref("砌块"); |
| | | const boardTimeDimension = ref("年"); |
| | | const boardSelectedArea = ref("全部"); |
| | | const boardProductType = ref("板材"); |
| | | const customerTimeDimension = ref("年"); |
| | | |
| | | const salesAreas = [ |
| | | "全部", |
| | | "A销售区", |
| | | "B销售区", |
| | | "C销售区", |
| | | "D销售区", |
| | | "E销售区", |
| | | ]; |
| | | |
| | | // 表格数据 |
| | | const blockSalesData = ref([ |
| | | { period: "2024-01", area: "***销售区", sales: 1250, sort: 1 }, |
| | | { period: "2024-02", area: "xxx销售区", sales: 1180, sort: 2 }, |
| | | { period: "2024-03", area: "xxx销售区", sales: 1050, sort: 3 }, |
| | | { period: "2024-04", area: "xxx销售区", sales: 980, sort: 4 }, |
| | | { period: "2024-05", area: "xxx销售区", sales: 920, sort: 5 }, |
| | | { period: "2024-06", area: "***销售区", sales: 880, sort: 6 }, |
| | | { period: "2024-07", area: "xxx销售区", sales: 850, sort: 7 }, |
| | | { period: "2024-08", area: "***销售区", sales: 820, sort: 8 }, |
| | | { period: "2024-09", area: "xxx销售区", sales: 790, sort: 9 }, |
| | | { period: "2024-10", area: "***销售区", sales: 750, sort: 10 }, |
| | | ]); |
| | | |
| | | const boardSalesData = ref([ |
| | | { period: "2024-01", area: "***销售区", sales: 980, sort: 1 }, |
| | | { period: "2024-02", area: "xxx销售区", sales: 920, sort: 2 }, |
| | | { period: "2024-03", area: "***销售区", sales: 880, sort: 3 }, |
| | | { period: "2024-04", area: "xxx销售区", sales: 850, sort: 4 }, |
| | | { period: "2024-05", area: "xxx销售区", sales: 820, sort: 5 }, |
| | | { period: "2024-06", area: "***销售区", sales: 790, sort: 6 }, |
| | | { period: "2024-07", area: "xxx销售区", sales: 750, sort: 7 }, |
| | | { period: "2024-08", area: "xxx销售区", sales: 720, sort: 8 }, |
| | | { period: "2024-09", area: "***销售区", sales: 690, sort: 9 }, |
| | | { period: "2024-10", area: "xxx销售区", sales: 650, sort: 10 }, |
| | | ]); |
| | | const cumulativeSalesVolumeChart = ref(null); |
| | | const cumulativeSalesAmountChart = ref(null); |
| | | |
| | |
| | | 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 totalSalesAmount = ref(1299); |
| | | const centerNewCustomerCount = ref(112); |
| | | const completedOrders = ref(1829); |
| | | const totalSalesAreaCount = ref(12); |
| | | |
| | | const newCustomerCount = computed(() => { |
| | | return filteredData.value.reduce((sum, item) => sum + item.newCustomers, 0); |
| | |
| | | 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 customerTrendsData = ref([]); |
| | | // 销量分析趋势数据 |
| | | const salesAnalysisTrendData = ref([]); |
| | | // 销量数据统计表格数据 |
| | | const tableSalesData = ref([]); |
| | | // 销量数据统计表格总计 |
| | | const tableSalesTotal = ref(0); |
| | | // 动态销售区域列表 |
| | | const tableSalesAreas = ref([]); |
| | | |
| | | // 变化率计算(模拟) |
| | | const salesVolumeChange = ref("+5.2"); |
| | | const salesAmountChange = ref("+7.8"); |
| | | const customerCountChange = ref("+3.5"); |
| | | const totalCustomerChange = ref("+2.1"); |
| | | |
| | | // 获取中心看板数据 |
| | | const fetchDashboardData = async () => { |
| | | try { |
| | | const response = await getDashboardStatistics(); |
| | | if (response && response.data) { |
| | | totalSalesAmount.value = response.data.price || 0; |
| | | completedOrders.value = response.data.delivery || 0; |
| | | centerNewCustomerCount.value = response.data.customer || 0; |
| | | totalSalesAreaCount.value = response.data.volume || 0; |
| | | } |
| | | } catch (error) { |
| | | console.error("获取中心看板数据失败:", error); |
| | | } |
| | | }; |
| | | |
| | | // 获取客户趋势数据 |
| | | const fetchCustomerTrendsData = async () => { |
| | | try { |
| | | const response = await getCustomerTrends({ |
| | | days: customerTimeDimension.value, |
| | | }); |
| | | if (response && response.data) { |
| | | // API返回的数据结构如下: |
| | | // { |
| | | // "dates": ["2026-01-01", "2025-01-01", ...], |
| | | // "customerTrends": [{"ALLIN": 4, "银川": 3, ...}, ...] |
| | | // } |
| | | customerTrendsData.value = response.data; |
| | | updateCharts(); |
| | | } |
| | | } catch (error) { |
| | | console.error("获取客户趋势数据失败:", error); |
| | | } |
| | | }; |
| | | |
| | | // 获取销量分析趋势数据 |
| | | const fetchSalesAnalysisTrendData = async () => { |
| | | try { |
| | | const response = await getSalesAnalysisTrend({ |
| | | type: chartProductType.value, // 砌块或板材 |
| | | days: chartTimeDimension.value, // 年或月 |
| | | }); |
| | | if (response && response.data) { |
| | | // API返回的数据结构如下: |
| | | // { |
| | | // "dates": ["2026-01-01", "2025-01-01", ...], |
| | | // "customerTrends": [{"内蒙古": 470, "银川": 3600, ...}, ...] |
| | | // } |
| | | salesAnalysisTrendData.value = response.data; |
| | | updateCharts(); |
| | | } |
| | | } catch (error) { |
| | | console.error("获取销量分析趋势数据失败:", error); |
| | | } |
| | | }; |
| | | |
| | | // 获取销量数据统计表格数据 |
| | | const fetchTableSalesData = async () => { |
| | | try { |
| | | const response = await getSalesAnalysisTrend({ |
| | | type: tableProductType.value, // 砌块或板材 |
| | | days: tableTimeDimension.value, // 年或月 |
| | | }); |
| | | if (response && response.data) { |
| | | // API返回的数据结构如下: |
| | | // { |
| | | // "dates": ["2026-01-01", "2025-01-01", ...], |
| | | // "customerTrends": [{"内蒙古": 470, "银川": 3600, ...}, ...] |
| | | // } |
| | | updateTableSalesData(response.data); |
| | | } |
| | | } catch (error) { |
| | | console.error("获取销量数据统计表格数据失败:", error); |
| | | } |
| | | }; |
| | | |
| | | // 更新销量数据统计表格 |
| | | const updateTableSalesData = data => { |
| | | if (!data || !data.dates || !data.customerTrends) { |
| | | return; |
| | | } |
| | | console.log(data, "datas"); |
| | | const dates = data.dates; |
| | | const customerTrends = data.customerTrends; |
| | | const tableData = []; |
| | | let total = 0; |
| | | const areaSet = new Set(); |
| | | |
| | | dates.forEach((date, index) => { |
| | | const trend = customerTrends[index]; |
| | | if (trend) { |
| | | // 提取所有销售区域 |
| | | Object.keys(trend).forEach(area => { |
| | | if (area !== "全部") { |
| | | areaSet.add(area); |
| | | const sales = trend[area] || 0; |
| | | tableData.push({ |
| | | period: date, |
| | | area: area, |
| | | productType: tableProductType.value, |
| | | sales: sales, |
| | | sort: tableData.length + 1, |
| | | }); |
| | | total += sales; |
| | | } |
| | | }); |
| | | } |
| | | }); |
| | | |
| | | // 更新销售区域列表,添加"全部"选项 |
| | | tableSalesAreas.value = ["全部", ...Array.from(areaSet)]; |
| | | // 确保 tableSelectedArea 在销售区域列表中 |
| | | if ( |
| | | tableSalesAreas.value.length > 0 && |
| | | !tableSalesAreas.value.includes(tableSelectedArea.value) |
| | | ) { |
| | | tableSelectedArea.value = "全部"; |
| | | } |
| | | |
| | | console.log(tableData); |
| | | tableSalesData.value = tableData; |
| | | tableSalesTotal.value = total; |
| | | }; |
| | | |
| | | // 处理图表时间维度变化 |
| | | const handleChartTimeDimensionChange = dimension => { |
| | | chartTimeDimension.value = dimension; |
| | | fetchSalesAnalysisTrendData(); |
| | | }; |
| | | |
| | | // 处理图表产品类型变化 |
| | | const handleChartProductTypeChange = type => { |
| | | chartProductType.value = type; |
| | | fetchSalesAnalysisTrendData(); |
| | | }; |
| | | |
| | | // 处理表格时间维度变化 |
| | | const handleTableTimeDimensionChange = dimension => { |
| | | tableTimeDimension.value = dimension; |
| | | fetchTableSalesData(); |
| | | // 重新启动滚动,根据时间维度决定是否滚动 |
| | | startBlockTableScroll(); |
| | | }; |
| | | |
| | | // 处理表格产品类型变化 |
| | | const handleTableProductTypeChange = type => { |
| | | tableProductType.value = type; |
| | | fetchTableSalesData(); |
| | | }; |
| | | |
| | | // 处理表格销售区域变化 |
| | | const handleTableAreaChange = area => { |
| | | tableSelectedArea.value = area; |
| | | }; |
| | | |
| | | // 表格数据 |
| | | const tableData = computed(() => { |
| | |
| | | }); |
| | | }); |
| | | |
| | | // 筛选后的表格数据 |
| | | const filteredTableSalesData = computed(() => { |
| | | if (tableSelectedArea.value === "全部") { |
| | | // 按年月分组汇总数据 |
| | | const groupedData = {}; |
| | | tableSalesData.value.forEach(item => { |
| | | const key = item.period; |
| | | if (!groupedData[key]) { |
| | | groupedData[key] = { |
| | | period: item.period, |
| | | area: "全部", |
| | | productType: item.productType, |
| | | sales: 0, |
| | | sort: 0, |
| | | }; |
| | | } |
| | | groupedData[key].sales += item.sales; |
| | | }); |
| | | // 转换为数组并按年月排序 |
| | | return Object.values(groupedData).sort((a, b) => { |
| | | return new Date(b.period) - new Date(a.period); |
| | | }); |
| | | } else { |
| | | return tableSalesData.value.filter( |
| | | item => item.area === tableSelectedArea.value |
| | | ); |
| | | } |
| | | }); |
| | | |
| | | // 筛选后的表格数据总计 |
| | | const filteredTableSalesTotal = computed(() => { |
| | | return filteredTableSalesData.value.reduce( |
| | | (total, item) => total + item.sales, |
| | | 0 |
| | | ); |
| | | }); |
| | | |
| | | // 销量趋势图表配置 |
| | | const salesVolumeChartOption = computed(() => { |
| | | const periods = ["6/9", "6/10", "6/11", "6/12", "6/12", "6/13"]; |
| | | const values = [132, 168, 168, 198, 168, 198]; |
| | | const colors = [ |
| | | "#00A4ED", |
| | | "#34D8F7", |
| | | "#4A8BFF", |
| | | "#8A6BFF", |
| | | "#C8C447", |
| | | "#FF6B6B", |
| | | "#FF9500", |
| | | "#4CD964", |
| | | "#5AC8FA", |
| | | ]; |
| | | const periodType = blockTimeDimension.value; |
| | | |
| | | // 生成时间段 |
| | | let periods = []; |
| | | let salesAreas = []; |
| | | let series = []; |
| | | |
| | | if ( |
| | | salesAnalysisTrendData.value && |
| | | salesAnalysisTrendData.value.dates && |
| | | salesAnalysisTrendData.value.customerTrends |
| | | ) { |
| | | // 使用API返回的日期 |
| | | periods = salesAnalysisTrendData.value.dates; |
| | | // 提取销售区域 |
| | | const customerTrends = salesAnalysisTrendData.value.customerTrends; |
| | | if (customerTrends.length > 0) { |
| | | // 提取销售区域并确保"全部"在第一个位置 |
| | | const allAreas = Object.keys(customerTrends[0]); |
| | | salesAreas = allAreas.sort((a, b) => { |
| | | if (a === "全部") return -1; |
| | | if (b === "全部") return 1; |
| | | return 0; |
| | | }); |
| | | // 为每个销售区生成数据 |
| | | series = salesAreas.map((area, index) => { |
| | | const data = customerTrends.map(trend => trend[area] || 0); |
| | | return { |
| | | name: area, |
| | | data: data, |
| | | type: "line", |
| | | smooth: false, |
| | | // symbolSize: getResponsiveValue(8), |
| | | lineStyle: { |
| | | width: getResponsiveValue(1), |
| | | color: colors[index % colors.length], |
| | | }, |
| | | itemStyle: { color: colors[index % colors.length] }, |
| | | areaStyle: { |
| | | opacity: 0.4, |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { offset: 0, color: colors[index % colors.length] + "80" }, |
| | | { offset: 1, color: colors[index % colors.length] + "00" }, |
| | | ]), |
| | | }, |
| | | }; |
| | | }); |
| | | } |
| | | } else { |
| | | // 模拟数据 |
| | | salesAreas = [ |
| | | "全部", |
| | | "A销售区", |
| | | "B销售区", |
| | | "C销售区", |
| | | "D销售区", |
| | | "E销售区", |
| | | ]; |
| | | const year = 2024; |
| | | if (periodType === "year") { |
| | | // 年度数据:12个月 |
| | | for (let month = 1; month <= 12; month++) { |
| | | periods.push(`${year}-${month.toString().padStart(2, "0")}`); |
| | | } |
| | | } else { |
| | | // 月度数据:30天 |
| | | const month = 1; |
| | | for (let day = 1; day <= 30; day++) { |
| | | periods.push( |
| | | `${year}-${month.toString().padStart(2, "0")}-${day |
| | | .toString() |
| | | .padStart(2, "0")}` |
| | | ); |
| | | } |
| | | } |
| | | // 为每个销售区生成数据 |
| | | series = salesAreas.map((area, index) => { |
| | | const data = periods.map(() => { |
| | | return periodType === "year" |
| | | ? Math.floor(Math.random() * 500) + 800 |
| | | : Math.floor(Math.random() * 50) + 20; |
| | | }); |
| | | |
| | | return { |
| | | name: area, |
| | | data: data, |
| | | type: "line", |
| | | smooth: false, |
| | | // symbolSize: getResponsiveValue(8), |
| | | lineStyle: { width: getResponsiveValue(1), color: colors[index] }, |
| | | itemStyle: { color: colors[index] }, |
| | | areaStyle: { |
| | | opacity: 0.4, |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { offset: 0, color: colors[index] + "80" }, |
| | | { offset: 1, color: colors[index] + "00" }, |
| | | ]), |
| | | }, |
| | | }; |
| | | }); |
| | | } |
| | | |
| | | return { |
| | | backgroundColor: "transparent", |
| | |
| | | borderColor: "rgba(64,158,255,0.25)", |
| | | borderWidth: getResponsiveValue(1), |
| | | textStyle: { color: "#B8C8E0", fontSize: getResponsiveValue(11) }, |
| | | formatter: "{b}: {c} 立方米", |
| | | formatter: function (params) { |
| | | let result = params[0].name + "<br/>"; |
| | | params.forEach(param => { |
| | | result += `${param.marker}${param.seriesName}: ${param.value} 立方米<br/>`; |
| | | }); |
| | | return result; |
| | | }, |
| | | }, |
| | | legend: { |
| | | data: salesAreas, |
| | | top: "10%", |
| | | right: "1%", |
| | | textStyle: { |
| | | color: "#B8C8E0", |
| | | fontSize: getResponsiveValue(10), |
| | | }, |
| | | itemWidth: getResponsiveValue(10), |
| | | itemHeight: getResponsiveValue(10), |
| | | }, |
| | | grid: { |
| | | left: "10%", |
| | | right: "4%", |
| | | bottom: "16%", |
| | | left: "1%", |
| | | right: "1%", |
| | | bottom: "1%", |
| | | top: "28%", |
| | | containLabel: true, |
| | | }, |
| | |
| | | }, |
| | | splitLine: { lineStyle: { color: "rgba(184,200,224,0.12)" } }, |
| | | }, |
| | | series: [ |
| | | { |
| | | data: values, |
| | | type: "line", |
| | | smooth: true, |
| | | symbolSize: getResponsiveValue(8), |
| | | lineStyle: { width: getResponsiveValue(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)" }, |
| | | ]), |
| | | }, |
| | | }, |
| | | ], |
| | | series: series, |
| | | }; |
| | | }); |
| | | |
| | | // 销售金额趋势图表配置 |
| | | const salesAmountChartOption = computed(() => { |
| | | const periods = ["6/9", "6/10", "6/11", "6/12", "6/12", "6/13"]; |
| | | const values = [132, 168, 168, 198, 168, 198]; |
| | | const { dates = [], customerTrends = [] } = salesAmountChartData.value; |
| | | |
| | | // 提取所有销售区域 |
| | | const areaSet = new Set(); |
| | | customerTrends.forEach(item => { |
| | | Object.keys(item).forEach(key => areaSet.add(key)); |
| | | }); |
| | | // 确保"全部"在第一个位置 |
| | | const salesAreas = Array.from(areaSet).sort((a, b) => { |
| | | if (a === "全部") return -1; |
| | | if (b === "全部") return 1; |
| | | return 0; |
| | | }); |
| | | |
| | | const colors = [ |
| | | "#00A4ED", |
| | | "#34D8F7", |
| | | "#4A8BFF", |
| | | "#8A6BFF", |
| | | "#C8C447", |
| | | "#FF6B6B", |
| | | ]; |
| | | |
| | | // 为每个销售区生成数据 |
| | | const series = salesAreas.map((area, index) => { |
| | | const data = customerTrends.map(item => item[area] || 0); |
| | | |
| | | return { |
| | | name: area, |
| | | data: data, |
| | | type: "bar", |
| | | smooth: true, |
| | | lineStyle: { |
| | | width: getResponsiveValue(3), |
| | | color: colors[index % colors.length], |
| | | }, |
| | | itemStyle: { color: colors[index % colors.length] }, |
| | | areaStyle: { |
| | | opacity: 0.2, |
| | | color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ |
| | | { offset: 0, color: colors[index % colors.length] + "80" }, |
| | | { offset: 1, color: colors[index % colors.length] + "00" }, |
| | | ]), |
| | | }, |
| | | }; |
| | | }); |
| | | |
| | | return { |
| | | backgroundColor: "transparent", |
| | |
| | | borderColor: "rgba(64,158,255,0.25)", |
| | | borderWidth: getResponsiveValue(1), |
| | | textStyle: { color: "#B8C8E0", fontSize: getResponsiveValue(11) }, |
| | | formatter: "{b}: {c} 万元", |
| | | formatter: function (params) { |
| | | let result = params[0]?.name + "<br/>" || ""; |
| | | params.forEach(param => { |
| | | result += `${param.marker}${param.seriesName}: ${param.value} 元<br/>`; |
| | | }); |
| | | return result; |
| | | }, |
| | | }, |
| | | legend: { |
| | | data: salesAreas, |
| | | top: "10%", |
| | | right: "1%", |
| | | textStyle: { |
| | | color: "#B8C8E0", |
| | | fontSize: getResponsiveValue(10), |
| | | }, |
| | | itemWidth: getResponsiveValue(10), |
| | | itemHeight: getResponsiveValue(10), |
| | | }, |
| | | grid: { |
| | | left: "10%", |
| | | right: "4%", |
| | | bottom: "16%", |
| | | left: "1%", |
| | | right: "1%", |
| | | bottom: "1%", |
| | | top: "28%", |
| | | containLabel: true, |
| | | }, |
| | | xAxis: { |
| | | type: "category", |
| | | data: periods, |
| | | data: dates, |
| | | axisLine: { lineStyle: { color: "rgba(184,200,224,0.25)" } }, |
| | | axisTick: { show: false }, |
| | | axisLabel: { |
| | |
| | | fontSize: getResponsiveValue(11), |
| | | margin: getResponsiveValue(10), |
| | | }, |
| | | splitLine: { show: false }, |
| | | }, |
| | | yAxis: { |
| | | type: "value", |
| | |
| | | }, |
| | | splitLine: { lineStyle: { color: "rgba(184,200,224,0.12)" } }, |
| | | }, |
| | | series: [ |
| | | { |
| | | data: values, |
| | | type: "line", |
| | | smooth: true, |
| | | symbolSize: getResponsiveValue(8), |
| | | itemStyle: { |
| | | color: "#00A4ED", |
| | | }, |
| | | lineStyle: { width: getResponsiveValue(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)" }, |
| | | ]), |
| | | }, |
| | | }, |
| | | ], |
| | | series: series, |
| | | }; |
| | | }); |
| | | |
| | |
| | | }; |
| | | }); |
| | | |
| | | // 新增客户趋势图表配置(按产品类型多折线) |
| | | // 新增客户趋势图表配置(按销售区和年月维度) |
| | | 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 colors = [ |
| | | "#00A4ED", |
| | | "#34D8F7", |
| | | "#4A8BFF", |
| | | "#8A6BFF", |
| | | "#C8C447", |
| | | "#FF6B6B", |
| | | "#FF8B6B", |
| | | "#FFCB6B", |
| | | "#8BC34A", |
| | | "#4CAF50", |
| | | ]; |
| | | 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 periodType = customerTimeDimension.value; |
| | | |
| | | const series = typeOrder.map(t => ({ |
| | | name: t, |
| | | type: "line", |
| | | smooth: true, |
| | | symbolSize: getResponsiveValue(7), |
| | | showSymbol: true, |
| | | data: map[t] || [], |
| | | lineStyle: { width: getResponsiveValue(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)" }, |
| | | ]), |
| | | }, |
| | | })); |
| | | // 生成时间段 |
| | | let periods = []; |
| | | let salesAreas = []; |
| | | let series = []; |
| | | |
| | | if ( |
| | | customerTrendsData.value && |
| | | customerTrendsData.value.dates && |
| | | customerTrendsData.value.customerTrends |
| | | ) { |
| | | // 使用API返回的数据 |
| | | periods = customerTrendsData.value.dates; |
| | | |
| | | // 提取所有销售区域 |
| | | const areaSet = new Set(); |
| | | customerTrendsData.value.customerTrends.forEach(item => { |
| | | Object.keys(item).forEach(key => { |
| | | areaSet.add(key); |
| | | }); |
| | | }); |
| | | // 确保"全部"在第一个位置 |
| | | salesAreas = Array.from(areaSet).sort((a, b) => { |
| | | if (a === "全部") return -1; |
| | | if (b === "全部") return 1; |
| | | return 0; |
| | | }); |
| | | |
| | | // 为每个销售区域生成数据 |
| | | series = salesAreas.map((area, index) => { |
| | | const data = customerTrendsData.value.customerTrends.map((item, i) => { |
| | | return item[area] || 0; |
| | | }); |
| | | |
| | | return { |
| | | name: area, |
| | | data: data, |
| | | type: "line", |
| | | smooth: false, |
| | | lineStyle: { |
| | | width: getResponsiveValue(1), |
| | | color: colors[index % colors.length], |
| | | }, |
| | | itemStyle: { color: colors[index % colors.length] }, |
| | | }; |
| | | }); |
| | | } else { |
| | | // 模拟数据 |
| | | const year = 2024; |
| | | if (periodType === "year") { |
| | | // 年度数据:12个月 |
| | | for (let month = 1; month <= 12; month++) { |
| | | periods.push(`${year}-${month.toString().padStart(2, "0")}`); |
| | | } |
| | | } else { |
| | | // 月度数据:30天 |
| | | const month = 1; |
| | | for (let day = 1; day <= 30; day++) { |
| | | periods.push( |
| | | `${year}-${month.toString().padStart(2, "0")}-${day |
| | | .toString() |
| | | .padStart(2, "0")}` |
| | | ); |
| | | } |
| | | } |
| | | |
| | | salesAreas = []; |
| | | series = salesAreas.map((area, index) => { |
| | | const data = periods.map(() => { |
| | | return periodType === "year" |
| | | ? Math.floor(Math.random() * 10) + 2 |
| | | : Math.floor(Math.random() * 3) + 1; |
| | | }); |
| | | |
| | | return { |
| | | name: area, |
| | | data: data, |
| | | type: "line", |
| | | smooth: false, |
| | | lineStyle: { width: getResponsiveValue(1), color: colors[index] }, |
| | | itemStyle: { color: colors[index] }, |
| | | }; |
| | | }); |
| | | } |
| | | |
| | | return { |
| | | backgroundColor: "transparent", |
| | | |
| | | legend: { |
| | | top: getResponsiveValue(10), |
| | | left: "center", |
| | | textStyle: { |
| | | color: "#B8C8E0", |
| | | fontSize: getResponsiveValue(11), |
| | | padding: [0, 0, 0, getResponsiveValue(2)], |
| | | }, |
| | | itemWidth: getResponsiveValue(12), |
| | | itemHeight: getResponsiveValue(10), |
| | | itemGap: getResponsiveValue(18), |
| | | }, |
| | | tooltip: { |
| | | trigger: "axis", |
| | | backgroundColor: "rgba(0,0,0,0.55)", |
| | | borderColor: "rgba(64,158,255,0.25)", |
| | | borderWidth: getResponsiveValue(1), |
| | | textStyle: { color: "#B8C8E0", fontSize: getResponsiveValue(11) }, |
| | | formatter: function (params) { |
| | | let result = params[0].name + "<br/>"; |
| | | params.forEach(param => { |
| | | result += `${param.marker}${param.seriesName}: ${param.value} 家<br/>`; |
| | | }); |
| | | return result; |
| | | }, |
| | | }, |
| | | legend: { |
| | | data: salesAreas, |
| | | top: "10%", |
| | | right: "1%", |
| | | textStyle: { |
| | | color: "#B8C8E0", |
| | | fontSize: getResponsiveValue(10), |
| | | }, |
| | | itemWidth: getResponsiveValue(10), |
| | | itemHeight: getResponsiveValue(10), |
| | | }, |
| | | grid: { |
| | | left: "10%", |
| | | right: "6%", |
| | | bottom: "14%", |
| | | top: "26%", |
| | | left: "1%", |
| | | right: "1%", |
| | | bottom: "1%", |
| | | top: "28%", |
| | | containLabel: true, |
| | | }, |
| | | xAxis: { |
| | |
| | | fontSize: getResponsiveValue(11), |
| | | margin: getResponsiveValue(10), |
| | | }, |
| | | splitLine: { show: false }, |
| | | }, |
| | | yAxis: { |
| | | type: "value", |
| | |
| | | }, |
| | | splitLine: { lineStyle: { color: "rgba(184,200,224,0.12)" } }, |
| | | }, |
| | | series, |
| | | series: series, |
| | | }; |
| | | }); |
| | | |
| | | // 累计销量趋势图表配置 |
| | | 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} 立方米", |
| | | textStyle: { fontSize: getResponsiveValue(11) }, |
| | | }, |
| | | xAxis: { |
| | | type: "category", |
| | | data: periods, |
| | | axisLabel: { fontSize: getResponsiveValue(11) }, |
| | | }, |
| | | yAxis: { |
| | | type: "value", |
| | | name: "累计销量(立方米)", |
| | | axisLabel: { fontSize: getResponsiveValue(11) }, |
| | | }, |
| | | series: [ |
| | | { |
| | | data: values, |
| | | type: "line", |
| | | smooth: true, |
| | | areaStyle: { |
| | | opacity: 0.3, |
| | | }, |
| | | itemStyle: { |
| | | color: "#E6A23C", |
| | | }, |
| | | lineStyle: { |
| | | width: getResponsiveValue(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} 万元", |
| | | textStyle: { fontSize: getResponsiveValue(11) }, |
| | | }, |
| | | xAxis: { |
| | | type: "category", |
| | | data: periods, |
| | | axisLabel: { fontSize: getResponsiveValue(11) }, |
| | | }, |
| | | yAxis: { |
| | | type: "value", |
| | | name: "累计销售金额(万元)", |
| | | axisLabel: { fontSize: getResponsiveValue(11) }, |
| | | }, |
| | | series: [ |
| | | { |
| | | data: values, |
| | | type: "bar", |
| | | itemStyle: { |
| | | color: "#F56C6C", |
| | | }, |
| | | }, |
| | | ], |
| | | }; |
| | | }); |
| | | |
| | | // 方法 |
| | | const goBack = () => { |
| | | router.back(); |
| | | }; |
| | | |
| | | const handleDateChange = () => { |
| | | // 处理日期变化 |
| | | updateCharts(); |
| | | }; |
| | | |
| | | const handleFilterChange = () => { |
| | | // 处理筛选条件变化 |
| | | updateCharts(); |
| | | }; |
| | | const baseWidth = ref(1650); |
| | | // 计算响应式值 |
| | | const getResponsiveValue = baseValue => { |
| | |
| | | } |
| | | }; |
| | | |
| | | // 表格动画控制 |
| | | let blockScrollTimer = null; |
| | | let boardScrollTimer = null; |
| | | let blockCurrentIndex = 0; |
| | | let boardCurrentIndex = 0; |
| | | const startBlockTableScroll = () => { |
| | | if (blockScrollTimer) { |
| | | clearInterval(blockScrollTimer); |
| | | } |
| | | |
| | | // 只有当时间维度不是"年"时才启动滚动 |
| | | if (tableTimeDimension.value === "年") { |
| | | return; |
| | | } |
| | | |
| | | const scrollTable = () => { |
| | | if (!blockTableBody.value) return; |
| | | const rows = blockTableBody.value.querySelectorAll("tr"); |
| | | if (rows.length === 0) return; |
| | | |
| | | const rowHeight = rows[0].offsetHeight; |
| | | |
| | | blockTableBody.value.style.transition = "transform 0.5s ease-in-out"; |
| | | blockTableBody.value.style.transform = `translateY(-${rowHeight}px)`; |
| | | |
| | | setTimeout(() => { |
| | | blockTableBody.value.style.transition = "none"; |
| | | blockTableBody.value.style.transform = "translateY(0)"; |
| | | |
| | | // 直接操作DOM,将第一行移到最后 |
| | | const firstRow = rows[0]; |
| | | blockTableBody.value.removeChild(firstRow); |
| | | blockTableBody.value.appendChild(firstRow); |
| | | }, 500); |
| | | }; |
| | | |
| | | blockScrollTimer = setInterval(scrollTable, 2000); |
| | | }; |
| | | |
| | | const startBoardTableScroll = () => { |
| | | if (boardScrollTimer) { |
| | | clearInterval(boardScrollTimer); |
| | | } |
| | | |
| | | const scrollTable = () => { |
| | | if (!boardTableBody.value || boardSalesData.value.length === 0) return; |
| | | |
| | | const rows = boardTableBody.value.querySelectorAll("tr"); |
| | | if (rows.length === 0) return; |
| | | |
| | | const rowHeight = rows[0].offsetHeight; |
| | | |
| | | boardTableBody.value.style.transition = "transform 0.5s ease-in-out"; |
| | | boardTableBody.value.style.transform = `translateY(-${rowHeight}px)`; |
| | | |
| | | setTimeout(() => { |
| | | boardTableBody.value.style.transition = "none"; |
| | | boardTableBody.value.style.transform = "translateY(0)"; |
| | | |
| | | const firstItem = boardSalesData.value[0]; |
| | | boardSalesData.value.shift(); |
| | | boardSalesData.value.push(firstItem); |
| | | }, 500); |
| | | }; |
| | | |
| | | boardScrollTimer = setInterval(scrollTable, 2000); |
| | | }; |
| | | |
| | | const stopTableScroll = () => { |
| | | if (blockScrollTimer) { |
| | | clearInterval(blockScrollTimer); |
| | | blockScrollTimer = null; |
| | | } |
| | | if (boardScrollTimer) { |
| | | clearInterval(boardScrollTimer); |
| | | boardScrollTimer = null; |
| | | } |
| | | if (amountScrollTimer.value) { |
| | | clearInterval(amountScrollTimer.value); |
| | | amountScrollTimer.value = null; |
| | | } |
| | | }; |
| | | |
| | | // 处理时间维度选择 |
| | | const handleBlockTimeDimensionChange = dimension => { |
| | | blockTimeDimension.value = dimension; |
| | | generateBlockSalesData(); |
| | | }; |
| | | |
| | | const handleBoardTimeDimensionChange = dimension => { |
| | | boardTimeDimension.value = dimension; |
| | | generateBoardSalesData(); |
| | | }; |
| | | const blockProductType = ref("砌块"); |
| | | // 处理产品类型选择 |
| | | const handleBlockProductTypeChange = type => { |
| | | blockProductType.value = type; |
| | | generateBlockSalesData(); |
| | | }; |
| | | |
| | | const handleBoardProductTypeChange = type => { |
| | | boardProductType.value = type; |
| | | generateBoardSalesData(); |
| | | }; |
| | | const blockSelectedArea = ref("全部"); |
| | | // 处理销售区选择 |
| | | const handleBlockAreaChange = area => { |
| | | blockSelectedArea.value = area; |
| | | generateBlockSalesData(); |
| | | }; |
| | | |
| | | const handleBoardAreaChange = area => { |
| | | boardSelectedArea.value = area; |
| | | generateBoardSalesData(); |
| | | }; |
| | | |
| | | // 处理新增客户趋势时间维度切换 |
| | | const handleCustomerTimeDimensionChange = dimension => { |
| | | customerTimeDimension.value = dimension; |
| | | fetchCustomerTrendsData(); |
| | | }; |
| | | const blockTimeDimension = ref("年"); |
| | | |
| | | // 生成砌块销售数据 |
| | | const generateBlockSalesData = () => { |
| | | const data = []; |
| | | const year = 2024; |
| | | |
| | | if (blockTimeDimension.value === "year") { |
| | | // 年度数据:12个月 |
| | | for (let month = 1; month <= 12; month++) { |
| | | const period = `${year}-${month.toString().padStart(2, "0")}`; |
| | | data.push({ |
| | | period: period, |
| | | area: blockSelectedArea.value, |
| | | productType: blockProductType.value, |
| | | sales: Math.floor(Math.random() * 500) + 800, |
| | | sort: month, |
| | | }); |
| | | } |
| | | } else { |
| | | // 月度数据:30天 |
| | | const month = 1; |
| | | for (let day = 1; day <= 30; day++) { |
| | | const period = `${year}-${month.toString().padStart(2, "0")}-${day |
| | | .toString() |
| | | .padStart(2, "0")}`; |
| | | data.push({ |
| | | period: period, |
| | | area: blockSelectedArea.value, |
| | | productType: blockProductType.value, |
| | | sales: Math.floor(Math.random() * 50) + 20, |
| | | sort: day, |
| | | }); |
| | | } |
| | | } |
| | | |
| | | blockSalesData.value = data; |
| | | // 更新图表 |
| | | updateCharts(); |
| | | }; |
| | | |
| | | // 生成板材销售数据 |
| | | const generateBoardSalesData = () => { |
| | | const data = []; |
| | | const year = 2024; |
| | | |
| | | if (boardTimeDimension.value === "year") { |
| | | // 年度数据:12个月 |
| | | for (let month = 1; month <= 12; month++) { |
| | | const period = `${year}-${month.toString().padStart(2, "0")}`; |
| | | data.push({ |
| | | period: period, |
| | | area: boardSelectedArea.value, |
| | | productType: boardProductType.value, |
| | | sales: Math.floor(Math.random() * 400) + 600, |
| | | sort: month, |
| | | }); |
| | | } |
| | | } else { |
| | | // 月度数据:30天 |
| | | const month = 1; |
| | | for (let day = 1; day <= 30; day++) { |
| | | const period = `${year}-${month.toString().padStart(2, "0")}-${day |
| | | .toString() |
| | | .padStart(2, "0")}`; |
| | | data.push({ |
| | | period: period, |
| | | area: boardSelectedArea.value, |
| | | productType: boardProductType.value, |
| | | sales: Math.floor(Math.random() * 40) + 15, |
| | | sort: day, |
| | | }); |
| | | } |
| | | } |
| | | |
| | | boardSalesData.value = data; |
| | | // 更新图表 |
| | | updateCharts(); |
| | | }; |
| | | |
| | | // 监听窗口大小变化 |
| | | const handleResize = () => { |
| | | console.log("resize"); |
| | |
| | | }; |
| | | |
| | | // 生命周期 |
| | | onMounted(() => { |
| | | onMounted(async () => { |
| | | // 启动顶部栏时间刷新 |
| | | if (!timeTicker) { |
| | | timeTicker = setInterval(() => { |
| | |
| | | endDate.format("YYYY-MM-DD"), |
| | | ]; |
| | | |
| | | // 生成初始数据 |
| | | generateBlockSalesData(); |
| | | generateBoardSalesData(); |
| | | |
| | | // 获取数据 |
| | | await fetchDashboardData(); |
| | | await fetchCustomerTrendsData(); |
| | | await fetchSalesAnalysisTrendData(); |
| | | await fetchTableSalesData(); |
| | | await fetchSalesAmountChartData(); |
| | | await fetchSalesAmountTableData(); |
| | | |
| | | // 等待DOM更新后初始化图表 |
| | | nextTick(() => { |
| | | initCharts(); |
| | | // 启动表格滚动动画 |
| | | startBlockTableScroll(); |
| | | startAmountTableScroll(); |
| | | }); |
| | | |
| | | // 添加窗口大小变化监听 |
| | | window.addEventListener("resize", handleResize); |
| | | document.addEventListener("fullscreenchange", handleFullscreenChange); |
| | | }); |
| | | |
| | | // 监听图表时间维度和产品类型变化 |
| | | watch([chartTimeDimension, chartProductType], async () => { |
| | | await fetchSalesAnalysisTrendData(); |
| | | }); |
| | | |
| | | // 监听表格时间维度和产品类型变化 |
| | | watch([tableTimeDimension, tableProductType], async () => { |
| | | await fetchTableSalesData(); |
| | | }); |
| | | |
| | | // 销售金额分析图表数据(右上) |
| | | const salesAmountChartData = ref({ |
| | | dates: [], |
| | | customerTrends: [], |
| | | }); |
| | | |
| | | // 销售额数据统计表格数据(右下) |
| | | const salesAmountTableData = ref({ |
| | | dates: [], |
| | | customerTrends: [], |
| | | }); |
| | | |
| | | // 销售额数据统计表格筛选状态(右下) |
| | | const tableTimeDimension2 = ref("年"); |
| | | const tableProductType2 = ref("砌块"); |
| | | |
| | | // 销售额数据统计表格数据 |
| | | const amountSalesData = ref([]); |
| | | const amountScrollTimer = ref(null); |
| | | |
| | | // 获取销售金额分析图表数据(右上) |
| | | const fetchSalesAmountChartData = async () => { |
| | | try { |
| | | const response = await getSalesAmountAnalysis({ |
| | | type: chartProductType2.value, |
| | | days: chartTimeDimension2.value, |
| | | }); |
| | | if (response?.data) { |
| | | salesAmountChartData.value = response.data; |
| | | updateCharts(); |
| | | } |
| | | } catch (error) { |
| | | console.error("获取销售金额分析图表数据失败:", error); |
| | | // 使用模拟数据 |
| | | salesAmountChartData.value = { |
| | | dates: [ |
| | | "2026-01-01", |
| | | "2025-01-01", |
| | | "2024-01-01", |
| | | "2023-01-01", |
| | | "2022-01-01", |
| | | ], |
| | | customerTrends: [ |
| | | { 内蒙古: 100, 银川: 200, 自提: 300, 其他: 150, 全部: 750 }, |
| | | { 内蒙古: 80, 银川: 180, 自提: 280, 其他: 130, 全部: 670 }, |
| | | { 内蒙古: 90, 银川: 190, 自提: 290, 其他: 140, 全部: 710 }, |
| | | { 内蒙古: 70, 银川: 170, 自提: 270, 其他: 120, 全部: 630 }, |
| | | { 内蒙古: 110, 银川: 210, 自提: 310, 其他: 160, 全部: 790 }, |
| | | ], |
| | | }; |
| | | } |
| | | }; |
| | | |
| | | // 获取销售额数据统计表格数据(右下) |
| | | const fetchSalesAmountTableData = async () => { |
| | | try { |
| | | const response = await getSalesAmountAnalysis({ |
| | | type: tableProductType2.value, |
| | | days: tableTimeDimension2.value, |
| | | }); |
| | | if (response?.data) { |
| | | salesAmountTableData.value = response.data; |
| | | updateAmountSalesData(); |
| | | } |
| | | } catch (error) { |
| | | console.error("获取销售额数据统计表格数据失败:", error); |
| | | // 使用模拟数据 |
| | | salesAmountTableData.value = { |
| | | dates: [ |
| | | "2026-01-01", |
| | | "2025-01-01", |
| | | "2024-01-01", |
| | | "2023-01-01", |
| | | "2022-01-01", |
| | | ], |
| | | customerTrends: [ |
| | | { 内蒙古: 100, 银川: 200, 自提: 300, 其他: 150, 全部: 750 }, |
| | | { 内蒙古: 80, 银川: 180, 自提: 280, 其他: 130, 全部: 670 }, |
| | | { 内蒙古: 90, 银川: 190, 自提: 290, 其他: 140, 全部: 710 }, |
| | | { 内蒙古: 70, 银川: 170, 自提: 270, 其他: 120, 全部: 630 }, |
| | | { 内蒙古: 110, 银川: 210, 自提: 310, 其他: 160, 全部: 790 }, |
| | | ], |
| | | }; |
| | | updateAmountSalesData(); |
| | | } |
| | | }; |
| | | |
| | | // 更新销售金额分析表格数据 |
| | | const updateAmountSalesData = () => { |
| | | const data = []; |
| | | const { dates, customerTrends } = salesAmountTableData.value; |
| | | |
| | | // 提取所有销售区域 |
| | | const areaSet = new Set(); |
| | | customerTrends.forEach(item => { |
| | | Object.keys(item).forEach(key => areaSet.add(key)); |
| | | }); |
| | | |
| | | // 更新销售区域列表,确保"全部"在第一位 |
| | | tableSalesAreas.value = [ |
| | | "全部", |
| | | ...Array.from(areaSet).filter(area => area !== "全部"), |
| | | ]; |
| | | |
| | | // 确保选中的区域在列表中 |
| | | if (!tableSalesAreas.value.includes(tableSelectedArea.value)) { |
| | | tableSelectedArea.value = "全部"; |
| | | } |
| | | |
| | | // 生成表格数据 |
| | | dates.forEach((date, index) => { |
| | | const trends = customerTrends[index] || {}; |
| | | Object.keys(trends).forEach(area => { |
| | | data.push({ |
| | | period: date, |
| | | area: area, |
| | | productType: tableProductType2.value, |
| | | sales: trends[area], |
| | | sort: data.length + 1, |
| | | }); |
| | | }); |
| | | }); |
| | | |
| | | amountSalesData.value = data; |
| | | }; |
| | | |
| | | // 筛选后的销售金额分析表格数据 |
| | | const filteredAmountSalesData = computed(() => { |
| | | if (tableSelectedArea.value === "全部") { |
| | | // 按年月分组汇总数据 |
| | | const groupedData = {}; |
| | | amountSalesData.value.forEach(item => { |
| | | const key = item.period; |
| | | if (!groupedData[key]) { |
| | | groupedData[key] = { |
| | | period: item.period, |
| | | area: "全部", |
| | | productType: tableProductType2.value, |
| | | sales: 0, |
| | | }; |
| | | } |
| | | groupedData[key].sales += item.sales; |
| | | }); |
| | | // 转换为数组并按年月排序 |
| | | return Object.values(groupedData).sort((a, b) => { |
| | | return new Date(b.period) - new Date(a.period); |
| | | }); |
| | | } else { |
| | | return amountSalesData.value.filter( |
| | | item => item.area === tableSelectedArea.value |
| | | ); |
| | | } |
| | | }); |
| | | |
| | | // 销售金额分析表格总计 |
| | | const filteredAmountSalesTotal = computed(() => { |
| | | return filteredAmountSalesData.value.reduce( |
| | | (total, item) => total + item.sales, |
| | | 0 |
| | | ); |
| | | }); |
| | | |
| | | // 处理销售金额分析图表时间维度变化(右上) |
| | | const handleChartTimeDimensionChange2 = dimension => { |
| | | chartTimeDimension2.value = dimension; |
| | | fetchSalesAmountChartData(); |
| | | }; |
| | | |
| | | // 处理销售金额分析图表产品类型变化(右上) |
| | | const handleChartProductTypeChange2 = type => { |
| | | chartProductType2.value = type; |
| | | fetchSalesAmountChartData(); |
| | | }; |
| | | |
| | | // 处理销售额数据统计表格时间维度变化(右下) |
| | | const handleTableTimeDimensionChange2 = dimension => { |
| | | tableTimeDimension2.value = dimension; |
| | | fetchSalesAmountTableData(); |
| | | // 重新启动滚动,根据时间维度决定是否滚动 |
| | | startAmountTableScroll(); |
| | | }; |
| | | |
| | | // 处理销售额数据统计表格产品类型变化(右下) |
| | | const handleTableProductTypeChange2 = type => { |
| | | tableProductType2.value = type; |
| | | fetchSalesAmountTableData(); |
| | | }; |
| | | |
| | | // 处理销售额数据统计表格销售区变化(右下) |
| | | const handleTableAreaChange2 = area => { |
| | | tableSelectedArea.value = area; |
| | | }; |
| | | |
| | | // 启动销售金额分析表格滚动 |
| | | const startAmountTableScroll = () => { |
| | | if (amountScrollTimer.value) { |
| | | clearInterval(amountScrollTimer.value); |
| | | } |
| | | // 只有当时间维度不是"年"时才启动滚动 |
| | | if (tableTimeDimension2.value === "年") { |
| | | return; |
| | | } |
| | | |
| | | const scrollTable = () => { |
| | | if (!boardTableBody.value) return; |
| | | const rows = boardTableBody.value.querySelectorAll("tr"); |
| | | if (rows.length === 0) return; |
| | | |
| | | const rowHeight = rows[0].offsetHeight; |
| | | |
| | | boardTableBody.value.style.transition = "transform 0.5s ease-in-out"; |
| | | boardTableBody.value.style.transform = `translateY(-${rowHeight}px)`; |
| | | |
| | | setTimeout(() => { |
| | | boardTableBody.value.style.transition = "none"; |
| | | boardTableBody.value.style.transform = "translateY(0)"; |
| | | |
| | | // 直接操作DOM,将第一行移到最后 |
| | | const firstRow = rows[0]; |
| | | boardTableBody.value.removeChild(firstRow); |
| | | boardTableBody.value.appendChild(firstRow); |
| | | }, 500); |
| | | }; |
| | | |
| | | amountScrollTimer.value = setInterval(scrollTable, 2000); |
| | | }; |
| | | |
| | | // 监听销售金额分析图表时间维度和产品类型变化(右上) |
| | | watch([chartTimeDimension2, chartProductType2], async () => { |
| | | // await fetchSalesAmountChartData(); |
| | | }); |
| | | |
| | | // 监听销售额数据统计表格时间维度和产品类型变化(右下) |
| | | watch([tableTimeDimension2, tableProductType2], async () => { |
| | | // await fetchSalesAmountTableData(); |
| | | }); |
| | | |
| | | // 获取产品类型标签类型 |
| | |
| | | border-bottom: 0.1vh solid rgba(64, 158, 255, 0.25); |
| | | } |
| | | |
| | | .panel-tabs { |
| | | .panel-tabs, |
| | | .panel-tabs2 { |
| | | position: absolute; |
| | | top: 0.8vh; |
| | | right: 1.2vh; |
| | | display: flex; |
| | | gap: 0.6vh; |
| | | z-index: 4; |
| | | } |
| | | |
| | | .panel-tabs { |
| | | right: 1.2vh; |
| | | } |
| | | |
| | | .panel-tabs2 { |
| | | right: 8vh; |
| | | } |
| | | .tab-item { |
| | | font-size: 1.2vh; |
| | | color: rgba(184, 200, 224, 0.75); |
| | |
| | | border: 0.1vh solid rgba(64, 158, 255, 0.25); |
| | | border-radius: 0.3vh; |
| | | line-height: 1.4; |
| | | cursor: pointer; |
| | | } |
| | | |
| | | .tab-item.active { |
| | |
| | | .bi-panel-body { |
| | | flex: 1; |
| | | padding: 0.8vh 1vh; |
| | | position: relative; |
| | | } |
| | | |
| | | .scroll-table-container { |
| | | height: 33vh; |
| | | overflow: hidden; |
| | | position: relative; |
| | | } |
| | | |
| | | .scroll-table { |
| | | width: 100%; |
| | | border-collapse: collapse; |
| | | color: #b8c8e0; |
| | | } |
| | | |
| | | .scroll-table th { |
| | | /* background-color: #0e2a54; */ |
| | | padding: 1.2vh; |
| | | text-align: left; |
| | | font-size: 1.2vh; |
| | | font-weight: bold; |
| | | border: 1px solid rgba(184, 200, 224, 0.2); |
| | | } |
| | | |
| | | .scroll-table td { |
| | | padding: 1vh; |
| | | font-size: 1.1vh; |
| | | border-bottom: 1px solid rgba(184, 200, 224, 0.1); |
| | | } |
| | | |
| | | .scroll-table tbody { |
| | | display: block; |
| | | height: 35vh; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .scroll-table thead, |
| | | .scroll-table tbody tr { |
| | | display: table; |
| | | width: 100%; |
| | | table-layout: fixed; |
| | | } |
| | | |
| | | .scroll-table th, |
| | | .scroll-table td { |
| | | width: 25%; |
| | | box-sizing: border-box; |
| | | font-size: 1.4vh; |
| | | } |
| | | |
| | | .scroll-table tbody tr { |
| | | transition: all 0.5s ease-in-out; |
| | | } |
| | | |
| | | /* .scroll-table tbody tr:nth-child(odd) { |
| | | background-color: rgba(64, 158, 255, 0.05); |
| | | } |
| | | |
| | | .scroll-table tbody tr:nth-child(even) { |
| | | background-color: rgba(64, 158, 255, 0.1); |
| | | } */ |
| | | .oddTableTr { |
| | | background-color: rgba(64, 158, 255, 0.05); |
| | | } |
| | | .evenTableTr { |
| | | background-color: rgba(64, 158, 255, 0.1); |
| | | } |
| | | |
| | | .scroll-table-container:hover tbody { |
| | | overflow: hidden; |
| | | } |
| | | |
| | | .echart-fill { |
| | |
| | | display: flex; |
| | | gap: 0.6vh; |
| | | margin: 0 0 0.5vh 0; |
| | | justify-self: end; |
| | | } |
| | | |
| | | .cf-tab { |
| | |
| | | border: 0.1vh solid rgba(64, 158, 255, 0.25); |
| | | padding: 0.3vh 0.9vh; |
| | | line-height: 1; |
| | | cursor: pointer; |
| | | } |
| | | |
| | | .cf-tab.active { |
| | |
| | | gap: 0.8vh; |
| | | font-size: 1.8vh; |
| | | color: #d9ecff; |
| | | font-weight: 700; |
| | | margin: 0 0 0.8vh 0; |
| | | line-height: 1; |
| | | } |
| | | |
| | | /* 面板底部合计行 */ |
| | | .panel-summary-row { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | padding: 0.8vh 1.2vh; |
| | | /* margin-top: 0.8vh; */ |
| | | background: #041e3c; |
| | | border-top: 0.1vh solid rgba(64, 158, 255, 0.25); |
| | | border-radius: 0 0 0.4vh 0.4vh; |
| | | width: 100%; |
| | | position: absolute; |
| | | bottom: 0; |
| | | } |
| | | |
| | | .summary-label { |
| | | font-size: 1.3vh; |
| | | font-weight: 700; |
| | | color: #b8c8e0; |
| | | } |
| | | |
| | | .summary-value { |
| | | font-size: 1.4vh; |
| | | font-weight: 800; |
| | | color: #00a4ed; |
| | | margin-right: 3.8vh; |
| | | text-shadow: 0 0 1vh rgba(0, 164, 237, 0.5); |
| | | } |
| | | |
| | | .summary-value2 { |
| | | font-size: 1.4vh; |
| | | font-weight: 800; |
| | | color: #00a4ed; |
| | | margin-right: 1.8vh; |
| | | text-shadow: 0 0 1vh rgba(0, 164, 237, 0.5); |
| | | } |
| | | .diamond { |
| | | width: 1vh; |
| | | height: 1vh; |
| | |
| | | .bi-panel-bottom-left { |
| | | grid-column: 1; |
| | | grid-row: 2; |
| | | overflow-y: auto; |
| | | } |
| | | .bi-panel-bottom-left::-webkit-scrollbar { |
| | | width: 0vh; |
| | | height: 0vh; |
| | | } |
| | | |
| | | .bi-panel-bottom-center { |
| | |
| | | .bi-panel-bottom-right { |
| | | grid-column: 3; |
| | | grid-row: 2; |
| | | overflow-y: auto; |
| | | } |
| | | .bi-panel-bottom-right::-webkit-scrollbar { |
| | | width: 0vh; |
| | | height: 0vh; |
| | | } |
| | | |
| | | /* 中心环浮层(绝对定位在网格上方) */ |
| | |
| | | width: 100%; |
| | | height: 100%; |
| | | object-fit: contain; |
| | | filter: drop-shadow(0 0 20px rgba(0, 164, 237, 0.35)); |
| | | filter: drop-shadow(0 0 2vh rgba(0, 164, 237, 0.35)); |
| | | } |
| | | |
| | | .center-ring-content { |
| | |
| | | |
| | | .m1 { |
| | | top: 2.5vh; |
| | | left: 2.3vw; |
| | | left: 4.8vh; |
| | | text-align: left; |
| | | } |
| | | |
| | | .m2 { |
| | | top: 4.1vh; |
| | | right: 4.3vw; |
| | | right: 8.6vh; |
| | | text-align: right; |
| | | } |
| | | |
| | |
| | | right: 4.4vh; |
| | | } |
| | | } |
| | | </style> |
| | | .scroll-table-content { |
| | | height: 39vh; |
| | | overflow: auto; |
| | | } |
| | | .scroll-table-content::-webkit-scrollbar { |
| | | width: 0; |
| | | height: 0; |
| | | background-color: transparent; |
| | | } |
| | | .total-row { |
| | | position: absolute; |
| | | bottom: 0vh; |
| | | left: 0; |
| | | width: 100%; |
| | | display: flex; |
| | | height: 3.5vh; |
| | | justify-content: space-around; |
| | | align-items: center; |
| | | background-color: #081843; |
| | | } |
| | | |
| | | .total-cell { |
| | | width: 20%; |
| | | font-size: 1.4vh; |
| | | margin-bottom: 0.5vh; |
| | | line-height: 3.5vh; |
| | | padding-left: 0.8vh; |
| | | color: #eaf6ff; |
| | | text-shadow: 0 0 0.8vh rgba(0, 229, 255, 0.22); |
| | | text-align: left; |
| | | color: #c3c3c3; |
| | | } |
| | | .total-cell2 { |
| | | width: 20%; |
| | | font-size: 1.4vh; |
| | | margin-bottom: 0.5vh; |
| | | line-height: 3.5vh; |
| | | color: #eaf6ff; |
| | | text-shadow: 0 0 0.8vh rgba(0, 229, 255, 0.22); |
| | | text-align: left; |
| | | color: #c3c3c3; |
| | | } |
| | | </style> |