| | |
| | | <template> |
| | | <div class="data-dashboard"> |
| | | <!-- 全屏按钮 - 移动到左上角 --> |
| | | <button class="fullscreen-btn" @click="toggleFullscreen" :title="isFullscreen ? '退出全屏' : '全屏显示'"> |
| | | <svg v-if="!isFullscreen" width="20" height="20" 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> |
| | | </button> |
| | | |
| | | <!-- 顶部标题栏 --> |
| | | <div class="dashboard-header"> |
| | | <div class="factory-name">{{ userStore.currentFactoryName }}</div> |
| | | </div> |
| | | |
| | | <!-- 主要内容区域 --> |
| | | <div class="dashboard-content"> |
| | | <!-- 左侧区域 --> |
| | | <div class="content-main"> |
| | | <div class="top-panels"> |
| | | <div class="left-panel"> |
| | | <!-- 客户信息统计分析 --> |
| | | <div class="panel-header"> |
| | | <span class="panel-title">客户信息统计分析</span> |
| | | </div> |
| | |
| | | <div class="panel-title-second"> |
| | | <div class="panel-title-icon"></div> |
| | | <div class="total-customers"> |
| | | <span class="label">总合同金额(元)</span> |
| | | <span class="label">总合同金额(元)</span> |
| | | <span class="value">{{sum}}</span> |
| | | </div> |
| | | <!-- <div class="jiantou"></div>--> |
| | | </div> |
| | | <!-- 饼图区域 --> |
| | | <div style="display: flex;align-items: center;gap: 20px;justify-content: space-evenly;height: 82%;margin-top: 20px"> |
| | | <div style="width: 240px; height: 240px; background-image: url('/src/assets/BI/zonghetongbingtubiankuang@2x.png'); background-size: contain; background-position: center; background-repeat: no-repeat; display: flex; align-items: center; justify-content: center;"> |
| | | <Echarts ref="chart" :legend="pieLegend" :chartStyle="chartStylePie" |
| | | <Echarts |
| | | ref="chart" |
| | | :legend="pieLegend" |
| | | :chartStyle="chartStylePie" |
| | | :series="materialPieSeries" |
| | | :tooltip="pieTooltip" |
| | | :options="{backgroundColor: 'transparent'}" |
| | | style="margin-left: 5px;"></Echarts> |
| | | style="margin-left: 5px;" |
| | | /> |
| | | </div> |
| | | <ul class="contract-list" style="margin: 0; padding: 0; display: flex; flex-direction: column;justify-content: space-around; height: 100%; overflow-y: auto; scroll-behavior: smooth;" ref="refContractList"> |
| | | <ul |
| | | ref="refContractList" |
| | | class="contract-list" |
| | | style="margin: 0; padding: 0; display: flex; flex-direction: column; justify-content: space-around; height: 100%; overflow-y: auto; scroll-behavior: smooth;" |
| | | > |
| | | <li v-for="item in materialPieSeries[0].data" :key="item.name" style="list-style: none; margin-bottom: 12px;"> |
| | | <div style="display: flex;align-items: center;justify-content: space-between;width: 100%"> |
| | | <div class="line" :style="{color: item.itemStyle.color}">■ {{item.name}}</div> |
| | | <div class="line" :style="{ color: item.itemStyle.color }">●{{ item.name }}</div> |
| | | <div style="font-weight: 700;font-size: 16px;color: #85B1E4;">¥{{item.value}}</div> |
| | | </div> |
| | | </li> |
| | | </ul> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 质量统计 --> |
| | | <div class="panel-header"> |
| | | <span class="panel-title">质量统计</span> |
| | | </div> |
| | | <div class="main-panel"> |
| | | <div class="panel-item-customers"> |
| | | <div class="quality-cards"> |
| | | <div class="quality-cardSec"> |
| | | <div class="quality-card one"></div> |
| | | <div class="quality-cardTitle"> |
| | | <div>原材料已检测数</div> |
| | | <div>{{qualityStatisticsObject.supplierNum}}件</div> |
| | | </div> |
| | | </div> |
| | | <div class="quality-cardSec"> |
| | | <div class="quality-card two"></div> |
| | | <div class="quality-cardTitle"> |
| | | <div>过程检验数量</div> |
| | | <div>{{qualityStatisticsObject.processNum}}件</div> |
| | | </div> |
| | | </div> |
| | | <div class="quality-cardSec"> |
| | | <div class="quality-card three"></div> |
| | | <div class="quality-cardTitle"> |
| | | <div>出厂已检数量</div> |
| | | <div>{{qualityStatisticsObject.factoryNum}}件</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <Echarts ref="chart" |
| | | :chartStyle="chartStyle" |
| | | :grid="grid" |
| | | :legend="barLegend" |
| | | :series="barSeries1" |
| | | :tooltip="tooltip" |
| | | :xAxis="xAxis1" |
| | | :yAxis="yAxis1" |
| | | :options="{backgroundColor: 'transparent', textStyle: {color: '#B8C8E0'}}" |
| | | style="height: 260px"></Echarts> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 中间区域 --> |
| | | <div class="center-panel"> |
| | | <!-- 顶部统计卡片 --> |
| | | <div class="stats-cards"> |
| | | <div class="stat-card"> |
| | | <img src="@/assets/BI/icon@2x.png" alt="图标" class="card-icon" /> |
| | |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 设备统计 --> |
| | | <div class="equipment-stats"> |
| | | <div class="equipment-header"> |
| | | <img src="@/assets/BI/shujutongjiicon@2x.png" alt="图标" class="equipment-icon" /> |
| | | <span class="equipment-title">设备统计</span> |
| | | </div> |
| | | <div class="equipment-items"> |
| | | <div class="equipment-item"> |
| | | <span class="equipment-value">{{equipmentNum}}</span> |
| | | <span class="equipment-label">设备总数</span> |
| | | </div> |
| | | <div class="equipment-item"> |
| | | <span class="equipment-value">{{equipmentRepair}}</span> |
| | | <span class="equipment-label">待维修设备</span> |
| | | </div> |
| | | <div class="equipment-item"> |
| | | <span class="equipment-value">{{equipmentMaintain}}</span> |
| | | <span class="equipment-label">待保养设备</span> |
| | | </div> |
| | | <div class="equipment-item"> |
| | | <span class="equipment-value">{{totalMeasuring}}</span> |
| | | <span class="equipment-label">计量器具总数</span> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 事件名称 --> |
| | | <div class="event-info"> |
| | | <div class="event-header"> |
| | | <img src="@/assets/BI/shijianmingxiicon@2x.png" alt="图标" class="event-icon" /> |
| | | <span class="event-title">事件名称</span> |
| | | </div> |
| | | <div class="event-content"> |
| | | <ul class="todo-list" v-if="todoList.length > 0" ref="refTodoList"> |
| | | <ul v-if="todoList.length > 0" ref="refTodoList" class="todo-list"> |
| | | <li v-for="item in todoList" :key="item.id"> |
| | | <div style="display: flex;flex-direction: column;justify-content: space-between;width: 100%;gap: 20px"> |
| | | <div style="display: flex;justify-content: space-between;align-items: center;"> |
| | |
| | | </div> |
| | | </li> |
| | | </ul> |
| | | <div v-else style="text-align: center"> |
| | | 暂无数据 |
| | | <div v-else style="text-align: center">暂无数据</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | <span class="financial-title">财务分析</span> |
| | | </div> |
| | | <div class="main-panel"> |
| | | <div class="panel-item-customers"> |
| | | <div class="panel-item-customers1 financial-panel1"> |
| | | <div class="event-header"> |
| | | <img src="@/assets/BI/shijianmingxiicon@2x.png" alt="图标" class="event-icon" /> |
| | | <span class="event-title">经营成果分析</span> |
| | | </div> |
| | | <Echarts ref="chart" |
| | | :chartStyle="chartStyle" |
| | | <Echarts |
| | | ref="chart" |
| | | :chartStyle="chartStyle2" |
| | | :grid="grid" |
| | | :legend="barLegend1" |
| | | :series="barSeries11" |
| | |
| | | :xAxis="xAxis3" |
| | | :yAxis="yAxis3" |
| | | :options="{backgroundColor: 'transparent', textStyle: {color: '#B8C8E0'}}" |
| | | style="height: 300px"></Echarts> |
| | | style="height: 300px" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 右侧区域 --> |
| | | <div class="right-panel"> |
| | | <!-- 应收应付统计 --> |
| | | <div class="panel-header"> |
| | | <span class="panel-title">应收应付统计</span> |
| | | </div> |
| | |
| | | <el-radio-button label="按季度" :value="3" /> |
| | | </el-radio-group> |
| | | </div> |
| | | <Echarts ref="chart" |
| | | <Echarts |
| | | ref="chart" |
| | | :color="barColors2" |
| | | :chartStyle="chartStyle" |
| | | :grid="grid" |
| | |
| | | :xAxis="xAxis" |
| | | :yAxis="yAxis" |
| | | :options="{backgroundColor: 'transparent', textStyle: {color: '#B8C8E0'}}" |
| | | style="height: 260px"></Echarts> |
| | | style="height: 260px" |
| | | /> |
| | | </div> |
| | | |
| | | <!-- 回款与开票分析 --> |
| | | <div class="panel-header"> |
| | | <span class="panel-title">回款与开票分析</span> |
| | | </div> |
| | | <div class="panel-item-customers" style="padding-top: 60px;"> |
| | | <Echarts ref="chart" :chartStyle="chartStyle" :grid="grid" :legend="lineLegend" :series="lineSeries" |
| | | :tooltip="tooltipLine" :xAxis="xAxis2" :yAxis="yAxis2" :options="{backgroundColor: 'transparent', textStyle: {color: '#FFFFFF'}}" style="height: 270px;"></Echarts> |
| | | <Echarts |
| | | ref="chart" |
| | | :chartStyle="chartStyle" |
| | | :grid="grid" |
| | | :legend="lineLegend" |
| | | :series="lineSeries" |
| | | :tooltip="tooltipLine" |
| | | :xAxis="xAxis2" |
| | | :yAxis="yAxis2" |
| | | :options="{ backgroundColor: 'transparent', textStyle: { color: '#FFFFFF' } }" |
| | | style="height: 270px;" |
| | | /> |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | |
| | | <script setup> |
| | | import * as echarts from 'echarts' |
| | | import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue' |
| | | import { reactive, ref, onMounted, onBeforeUnmount, nextTick } from 'vue' |
| | | import autofit from 'autofit.js' |
| | | import Echarts from "@/components/Echarts/echarts.vue"; |
| | | import useUserStore from '@/store/modules/user' |
| | | import { |
| | | analysisCustomerContractAmounts, getAmountHalfYear, |
| | | homeTodos, |
| | | qualityStatistics, |
| | | statisticsReceivablePayable |
| | | } from "@/api/viewIndex.js"; |
| | | import {staffOnJobListPage} from "@/api/personnelManagement/employeeRecord.js"; |
| | | import {listCustomer} from "@/api/basicData/customerFile.js"; |
| | | import {listSupplier} from "@/api/basicData/supplierManageFile.js"; |
| | | import {getLedgerPage} from "@/api/equipmentManagement/ledger.js"; |
| | | import {getRepairPage} from "@/api/equipmentManagement/repair.js"; |
| | | import {getUpkeepPage} from "@/api/equipmentManagement/upkeep.js"; |
| | | import {measuringInstrumentListPage} from "@/api/equipmentManagement/measurementEquipment.js"; |
| | | import {listPageAnalysis} from "@/api/financialManagement/expenseManagement.js"; |
| | | |
| | | // 全屏相关状态 |
| | | const isFullscreen = ref(false); |
| | | |
| | | // 用户store |
| | | const userStore = useUserStore() |
| | | |
| | | // 响应式数据 |
| | | const currentTime = ref('') |
| | | const currentDate = ref('') |
| | | const timer = ref(null) |
| | | const charts = ref([]) |
| | | |
| | | // 图表引用 |
| | | const customerPieChartRef = ref(null) |
| | | const salesBarChartRef = ref(null) |
| | | const dataBarChartRef = ref(null) |
| | |
| | | |
| | | const chartStylePie = { |
| | | width: '140%', |
| | | height: '140%' // 设置图表容器的高度 |
| | | height: '140%' |
| | | } |
| | | const materialPieSeries = ref([ |
| | | { |
| | |
| | | const totalSuppliers = ref(0) |
| | | const yny = ref(0) |
| | | const chain = ref(0) |
| | | const equipmentNum = ref(0) |
| | | const equipmentRepair = ref(0) |
| | | const equipmentMaintain = ref(0) |
| | | const totalMeasuring = ref(0) |
| | | const pieTooltip = reactive({ |
| | | trigger: 'item', |
| | | formatter: function (params) { |
| | | // 动态生成提示信息,基于数据项的 name 属性 |
| | | const description = params.name === '本月回款金额' ? '本月回款金额' : '应收款金额'; |
| | | return `<div style="color: #B8C8E0">${description} ${params.value}元 ${params.percent}%</div>`; |
| | | const description = params.name === '鏈湀鍥炴閲戦' ? '鏈湀鍥炴閲戦' : '搴旀敹娆鹃噾棰?'; |
| | | return `<div style="color: #B8C8E0">${description} ${params.value}鍏?${params.percent}%</div>`; |
| | | }, |
| | | position: 'right' |
| | | }) |
| | | |
| | | const qualityStatisticsObject = ref({ |
| | | supplierNum: 0, |
| | | processNum: 0, |
| | | factoryNum: 0, |
| | | }) |
| | | const chartStyle = { |
| | | width: '100%', |
| | | height: '150%' // 设置图表容器的高度 |
| | | height: '120%' |
| | | } |
| | | const chartStyle2 = { |
| | | width: '100%', |
| | | height: '100%' |
| | | } |
| | | const barSeries = ref([ |
| | | { |
| | |
| | | label: { |
| | | show: true |
| | | }, |
| | | showSymbol: true, // 显示圆点 |
| | | showSymbol: true, |
| | | }, |
| | | ]) |
| | | const tooltipLine = { |
| | |
| | | type: 'category', |
| | | data: ['应收应付统计'] |
| | | }] |
| | | const xAxis1 = ref([{ |
| | | type: 'category', |
| | | axisTick: { show: false }, |
| | | axisLabel: { color: '#B8C8E0' }, |
| | | data: [] |
| | | }]) |
| | | const yAxis1 = [{ |
| | | type: 'value', |
| | | axisLabel: { color: '#B8C8E0' } |
| | | }] |
| | | const xAxis3 = ref([{ |
| | | type: 'category', |
| | | axisTick: { show: false }, |
| | |
| | | axisLabel: { color: '#B8C8E0' } |
| | | }] |
| | | |
| | | // 待办事项 |
| | | const todoList = ref([]) |
| | | |
| | | // 窗口大小变化处理 |
| | | const handleResize = () => { |
| | | charts.value.forEach(chart => { |
| | | if (chart && chart.resize) { |
| | |
| | | }) |
| | | } |
| | | |
| | | // 销毁图表实例 |
| | | const disposeCharts = () => { |
| | | charts.value.forEach(chart => { |
| | | if (chart && chart.dispose) { |
| | |
| | | }) |
| | | charts.value = [] |
| | | } |
| | | // 合同金额 |
| | | const analysisCustomer = () => { |
| | | analysisCustomerContractAmounts().then((res) => { |
| | | sum.value = res.data.sum |
| | | yny.value = res.data.yny |
| | | chain.value = res.data.chain |
| | | // 为每个数据项分配随机颜色 |
| | | materialPieSeries.value[0].data = res.data.item.map(item => ({ |
| | | ...item, |
| | | itemStyle: { color: getRandomColor() } |
| | | })) |
| | | }) |
| | | } |
| | | // 质检统计 |
| | | const qualityStatisticsInfo = () => { |
| | | qualityStatistics().then((res) => { |
| | | res.data.item.forEach(item => { |
| | | xAxis1.value[0].data.push(item.date) |
| | | barSeries1.value[0].data.push(item.supplierNum) |
| | | barSeries1.value[1].data.push(item.processNum) |
| | | barSeries1.value[2].data.push(item.factoryNum) |
| | | }) |
| | | qualityStatisticsObject.value.supplierNum = res.data.supplierNum |
| | | qualityStatisticsObject.value.processNum = res.data.processNum |
| | | qualityStatisticsObject.value.factoryNum = res.data.factoryNum |
| | | }) |
| | | } |
| | | // 财务统计 |
| | | const accountStatisticsInfo = () => { |
| | | listPageAnalysis().then((res) => { |
| | | xAxis3.value[0].data = res.data.days |
| | |
| | | totalSuppliers.value = res.data.total |
| | | }); |
| | | } |
| | | const getLedgerNum = () => { |
| | | const params = { |
| | | pageNum: -1, |
| | | pageSize: -1, |
| | | } |
| | | getLedgerPage(params).then((res) => { |
| | | equipmentNum.value = res.data.total |
| | | }); |
| | | getRepairPage(params).then((res) => { |
| | | equipmentRepair.value = res.data.total |
| | | }); |
| | | getUpkeepPage(params).then((res) => { |
| | | equipmentMaintain.value = res.data.total |
| | | }); |
| | | measuringInstrumentListPage(params).then((res) => { |
| | | totalMeasuring.value = res.data.total |
| | | }); |
| | | } |
| | | // 待办事项 |
| | | const todoInfoS = () => { |
| | | homeTodos().then((res) => { |
| | | todoList.value = res.data |
| | | // 在获取到待办事项数据后,初始化滚动功能 |
| | | nextTick(() => { |
| | | initTodoListScroll() |
| | | }) |
| | | }) |
| | | } |
| | | // 应付应收统计 |
| | | const statisticsReceivable = (type) => { |
| | | const statisticsReceivable = () => { |
| | | statisticsReceivablePayable({type: radio1.value}).then((res) => { |
| | | // 设置应付金额数据 |
| | | barSeries.value[0].data = [ |
| | | { value: res.data.payableMoney } |
| | | ] |
| | | // 设置应收金额数据 |
| | | barSeries.value[1].data = [ |
| | | { value: res.data.receivableMoney } |
| | | ] |
| | |
| | | } |
| | | const getAmountHalfYearNum = async () => { |
| | | const res = await getAmountHalfYear() |
| | | console.log(res) |
| | | const monthName = [] |
| | | const receiptAmount = [] |
| | | const invoiceAmount = [] |
| | |
| | | receiptAmount.push(item.receiptAmount) |
| | | invoiceAmount.push(item.invoiceAmount) |
| | | }) |
| | | // 正确响应式赋值:创建新的 xAxis 和 series 对象 |
| | | xAxis2.value[0].data = monthName |
| | | xAxis2.value[0].data = monthName.map(item => item.replace(/~/g, '\n~')); |
| | | lineSeries.value = [ |
| | | { |
| | | name: '开票', |
| | | name: '寮€绁?', |
| | | type: 'line', |
| | | data: receiptAmount, |
| | | stack: 'Total', |
| | |
| | | showSymbol: true, |
| | | }, |
| | | { |
| | | name: '回款', |
| | | name: '鍥炴', |
| | | type: 'line', |
| | | data: invoiceAmount, |
| | | stack: 'Total', |
| | |
| | | ] |
| | | } |
| | | |
| | | // 自动轮换周、月、季度的定时器 |
| | | const autoSwitchTimer = ref(null) |
| | | // 初始化待办事项列表滚动功能 |
| | | const initTodoListScroll = () => { |
| | | const todoList = refTodoList.value |
| | | // 强制启用滚动,不检查任何条件 |
| | | if (todoList) { |
| | | // 创建一个克隆项,用于实现无缝滚动 |
| | | const scrollItems = Array.from(todoList.querySelectorAll('li')) |
| | | if (scrollItems.length > 0) { |
| | | // 确保有足够的项目用于滚动 |
| | | // 如果项目太少,多复制几次以确保滚动效果 |
| | | if (scrollItems.length < 4) { |
| | | const originalItems = [...scrollItems] |
| | | for (let i = 0; i < 4; i++) { |
| | |
| | | todoList.appendChild(clone) |
| | | }) |
| | | } |
| | | // 重新获取所有项目 |
| | | scrollItems.push(...Array.from(todoList.querySelectorAll('li')).slice(scrollItems.length)); |
| | | } |
| | | const itemHeight = scrollItems[0]?.offsetHeight || 0 |
| | | const containerHeight = todoList.clientHeight |
| | | const cloneCount = Math.ceil(containerHeight / itemHeight) + 2 |
| | | |
| | | // 克隆前几个项目并添加到列表末尾,实现无缝滚动 |
| | | for (let i = 0; i < cloneCount; i++) { |
| | | const clone = scrollItems[i % scrollItems.length].cloneNode(true) |
| | | todoList.appendChild(clone) |
| | | } |
| | | |
| | | let scrollPosition = 0 |
| | | const scrollSpeed = 1.5 // 增加滚动速度,使滚动更加明显 |
| | | const pauseTime = 3000 // 滚动暂停时间 |
| | | const scrollSpeed = 1.5 |
| | | const pauseTime = 3000 |
| | | let isPaused = false |
| | | let lastTimestamp = 0 |
| | | |
| | | // 连续滚动动画函数 |
| | | function scrollAnimation(timestamp) { |
| | | if (!lastTimestamp) lastTimestamp = timestamp |
| | | const deltaTime = timestamp - lastTimestamp |
| | | lastTimestamp = timestamp |
| | | |
| | | if (!isPaused) { |
| | | scrollPosition += scrollSpeed * (deltaTime / 16) // 标准化为60fps的速度 |
| | | |
| | | // 当滚动超过原始内容长度时,重置位置实现无缝滚动 |
| | | scrollPosition += scrollSpeed * (deltaTime / 16) |
| | | const maxScroll = Math.max(todoList.scrollHeight - containerHeight - cloneCount * itemHeight, itemHeight * scrollItems.length) |
| | | if (scrollPosition >= maxScroll) { |
| | | scrollPosition = 0 |
| | |
| | | todoList.scrollTop = scrollPosition |
| | | } |
| | | } |
| | | |
| | | todoList._animationFrame = requestAnimationFrame(scrollAnimation) |
| | | } |
| | | |
| | | // 启动滚动动画 |
| | | todoList._animationFrame = requestAnimationFrame(scrollAnimation) |
| | | |
| | | // 设置滚动-暂停-滚动的循环效果 |
| | | const pauseTimer = setInterval(() => { |
| | | isPaused = !isPaused |
| | | }, pauseTime) |
| | | |
| | | // 清理定时器 |
| | | todoList._pauseTimer = pauseTimer |
| | | } |
| | | } |
| | | } |
| | | const getRandomColor = () => { |
| | | // 生成浅色:R、G、B 分量都在 150-255 之间 |
| | | const r = Math.floor(Math.random() * 106) + 150; // 150-255 |
| | | const g = Math.floor(Math.random() * 106) + 150; // 150-255 |
| | | const b = Math.floor(Math.random() * 106) + 150; // 150-255 |
| | | // 将 RGB 转换为十六进制颜色 |
| | | const r = Math.floor(Math.random() * 106) + 150; |
| | | const g = Math.floor(Math.random() * 106) + 150; |
| | | const b = Math.floor(Math.random() * 106) + 150; |
| | | return '#' + r.toString(16).padStart(2, '0') + g.toString(16).padStart(2, '0') + b.toString(16).padStart(2, '0'); |
| | | } |
| | | |
| | | // 更新时间 |
| | | const updateTime = () => { |
| | | const now = new Date() |
| | | currentTime.value = now.toLocaleTimeString('zh-CN', { hour12: false }) |
| | |
| | | }) |
| | | } |
| | | |
| | | // 初始化时间 |
| | | const initTime = () => { |
| | | updateTime() |
| | | timer.value = setInterval(updateTime, 1000) |
| | | } |
| | | // 全屏功能实现 - 针对data-dashboard元素 |
| | | const toggleFullscreen = () => { |
| | | const element = document.querySelector('.data-dashboard') |
| | | |
| | | if (!element) return |
| | | |
| | | if (!isFullscreen.value) { |
| | | if (element.requestFullscreen) { |
| | | element.requestFullscreen() |
| | |
| | | } |
| | | } |
| | | |
| | | // 监听全屏变化事件 |
| | | const handleFullscreenChange = () => { |
| | | const fullscreenElement = document.fullscreenElement || |
| | | document.webkitFullscreenElement || |
| | |
| | | isFullscreen.value = fullscreenElement && fullscreenElement.classList.contains('data-dashboard') |
| | | } |
| | | |
| | | // 生命周期钩子 |
| | | onMounted(() => { |
| | | initTime() |
| | | // 使用nextTick确保DOM完全渲染后再初始化图表 |
| | | nextTick(() => { |
| | | // 初始化autofit自适应 |
| | | autofit.init({ dh: 1080, dw: 1920, el: '.data-dashboard', resize: true }, false) |
| | | |
| | | // 添加自动滚动动画效果 - 客户信息列表 |
| | | const contractList = refContractList.value |
| | | if (contractList && contractList.scrollHeight > contractList.clientHeight) { |
| | | // 创建一个克隆项,用于实现无缝滚动 |
| | | const scrollItems = Array.from(contractList.querySelectorAll('li')) |
| | | const itemHeight = scrollItems[0]?.offsetHeight || 0 |
| | | const containerHeight = contractList.clientHeight |
| | | const cloneCount = Math.ceil(containerHeight / itemHeight) + 2 |
| | | |
| | | // 克隆前几个项目并添加到列表末尾,实现无缝滚动 |
| | | for (let i = 0; i < cloneCount; i++) { |
| | | const clone = scrollItems[i % scrollItems.length].cloneNode(true) |
| | | contractList.appendChild(clone) |
| | | } |
| | | |
| | | let scrollPosition = 0 |
| | | const scrollSpeed = 1.5 // 增加滚动速度,使滚动更加明显 |
| | | const pauseTime = 3000 // 滚动暂停时间 |
| | | const scrollSpeed = 1.5 |
| | | const pauseTime = 3000 |
| | | let isPaused = false |
| | | let lastTimestamp = 0 |
| | | |
| | | // 连续滚动动画函数 |
| | | function scrollAnimation(timestamp) { |
| | | if (!lastTimestamp) lastTimestamp = timestamp |
| | | const deltaTime = timestamp - lastTimestamp |
| | | lastTimestamp = timestamp |
| | | |
| | | if (!isPaused) { |
| | | scrollPosition += scrollSpeed * (deltaTime / 16) // 标准化为60fps的速度 |
| | | |
| | | // 当滚动超过原始内容长度时,重置位置实现无缝滚动 |
| | | scrollPosition += scrollSpeed * (deltaTime / 16) |
| | | if (scrollPosition >= contractList.scrollHeight - containerHeight - cloneCount * itemHeight) { |
| | | scrollPosition = 0 |
| | | contractList.scrollTop = 0 |
| | |
| | | contractList.scrollTop = scrollPosition |
| | | } |
| | | } |
| | | |
| | | timerScroll.value = requestAnimationFrame(scrollAnimation) |
| | | } |
| | | |
| | | // 启动滚动动画 |
| | | timerScroll.value = requestAnimationFrame(scrollAnimation) |
| | | |
| | | // 设置滚动-暂停-滚动的循环效果 |
| | | const pauseTimer = setInterval(() => { |
| | | isPaused = !isPaused |
| | | }, pauseTime) |
| | | |
| | | // 清理定时器 |
| | | contractList._pauseTimer = pauseTimer |
| | | } |
| | | |
| | | // 待办事项列表滚动功能已移至todoInfoS函数中,在获取数据后初始化 |
| | | }) |
| | | |
| | | window.addEventListener('resize', handleResize) |
| | | analysisCustomer() |
| | | qualityStatisticsInfo() |
| | | accountStatisticsInfo() |
| | | getNum() |
| | | getLedgerNum() |
| | | todoInfoS() |
| | | statisticsReceivable() |
| | | getAmountHalfYearNum() |
| | | |
| | | // 设置自动轮换周、月、季度的定时器,每10秒切换一次 |
| | | autoSwitchTimer.value = setInterval(() => { |
| | | // 循环切换:1(周) -> 2(月) -> 3(季度) -> 1(周) |
| | | radio1.value = radio1.value === 3 ? 1 : radio1.value + 1 |
| | | statisticsReceivable() |
| | | }, 10000) // 10秒切换一次 |
| | | }, 10000) |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | |
| | | if (timerScroll.value) { |
| | | cancelAnimationFrame(timerScroll.value) |
| | | } |
| | | // 清理滚动列表的暂停定时器 |
| | | const contractList = refContractList.value |
| | | if (contractList && contractList._pauseTimer) { |
| | | clearInterval(contractList._pauseTimer) |
| | | } |
| | | |
| | | // 清理待办事项列表的动画和定时器 |
| | | const todoList = refTodoList.value |
| | | if (todoList) { |
| | | if (todoList._animationFrame) { |
| | |
| | | todoList._pauseTimer = null |
| | | } |
| | | } |
| | | |
| | | // 清理自动轮换周、月、季度的定时器 |
| | | if (autoSwitchTimer.value) { |
| | | clearInterval(autoSwitchTimer.value) |
| | | autoSwitchTimer.value = null |
| | | } |
| | | |
| | | window.removeEventListener('resize', handleResize) |
| | | window.removeEventListener('fullscreenchange', handleFullscreenChange) |
| | | window.removeEventListener('webkitfullscreenchange', handleFullscreenChange) |
| | | window.removeEventListener('MSFullscreenChange', handleFullscreenChange) |
| | | // 移除我们添加的autofit动态调整监听器 |
| | | if (window._autofitUpdateHandler) { |
| | | window.removeEventListener('resize', window._autofitUpdateHandler) |
| | | delete window._autofitUpdateHandler |
| | | } |
| | | disposeCharts() |
| | | // 关闭autofit |
| | | autofit.off() |
| | | }) |
| | | </script> |
| | |
| | | background-repeat: no-repeat; |
| | | } |
| | | |
| | | /* 全屏状态的样式 */ |
| | | .data-dashboard:fullscreen { |
| | | width: 100%; |
| | | height: 100%; |
| | | margin: 0; |
| | | padding: 0; |
| | | background-color: inherit; |
| | | z-index: 9999; |
| | | } |
| | | |
| | | /* Webkit浏览器前缀 */ |
| | | .data-dashboard:-webkit-full-screen { |
| | | width: 100%; |
| | | height: 100%; |
| | | margin: 0; |
| | | padding: 0; |
| | | background-color: inherit; |
| | | z-index: 9999; |
| | | } |
| | | |
| | | /* MS浏览器前缀 */ |
| | | .data-dashboard:fullscreen, |
| | | .data-dashboard:-webkit-full-screen, |
| | | .data-dashboard:-ms-fullscreen { |
| | | width: 100%; |
| | | height: 100%; |
| | |
| | | z-index: 9999; |
| | | } |
| | | |
| | | |
| | | .dashboard-header { |
| | | position: relative; |
| | | z-index: 1; |
| | | height: 170px; |
| | | height: 60px; |
| | | background-image: url("@/assets/BI/biaoti.png"); |
| | | background-size: cover; |
| | | background-position: center; |
| | |
| | | |
| | | .factory-name { |
| | | font-weight: 600; |
| | | font-size: 52px; |
| | | font-size: 30px; |
| | | color: #FFFFFF; |
| | | top: 32px; |
| | | top: 6px; |
| | | position: absolute; |
| | | } |
| | | |
| | |
| | | display: flex; |
| | | gap: 30px; |
| | | padding: 0 30px; |
| | | height: calc(100% - 120px); |
| | | height: calc(100% - 80px); |
| | | overflow: hidden; |
| | | } |
| | | |
| | | /* 确保各面板能够正确显示 */ |
| | | .left-panel, .center-panel, .right-panel { |
| | | .content-main { |
| | | flex: 2.5; |
| | | min-width: 0; |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 24px; |
| | | } |
| | | |
| | | .top-panels { |
| | | display: flex; |
| | | gap: 30px; |
| | | } |
| | | |
| | | .left-panel, |
| | | .center-panel, |
| | | .right-panel { |
| | | overflow: hidden; |
| | | } |
| | | |
| | |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 20px; |
| | | min-width: 0; |
| | | } |
| | | |
| | | .panel-item-customers { |
| | | border: 1px solid #1A58B0; |
| | | padding: 18px; |
| | | width: 100%; |
| | | height: 540px; |
| | | height: 500px; |
| | | } |
| | | .panel-item-customers1 { |
| | | border: 1px solid #1A58B0; |
| | | padding: 18px; |
| | | width: 100%; |
| | | height: 690px; |
| | | } |
| | | |
| | | .panel-title-second { |
| | | height: 60px; |
| | | display: flex; |
| | |
| | | margin-bottom: 20px; |
| | | align-items: center; |
| | | } |
| | | .quality-cards { |
| | | display: flex; |
| | | gap: 12px; |
| | | width: 100%; |
| | | height: 94px; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | .quality-cardSec { |
| | | display: flex; |
| | | } |
| | | .quality-cardTitle { |
| | | font-weight: 400; |
| | | font-size: 14px; |
| | | color: #FFFFFF; |
| | | display: flex; |
| | | align-items: flex-start; |
| | | flex-direction: column; |
| | | } |
| | | .quality-card { |
| | | width: 80px; |
| | | height: 60px; |
| | | background-size: cover; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | } |
| | | .quality-card.one { |
| | | background-image: url("@/assets/BI/yuancailiaoyijianicon@2x.png"); |
| | | } |
| | | .quality-card.two { |
| | | background-image: url("@/assets/BI/guochengyijianicon@2x.png"); |
| | | } |
| | | .quality-card.three { |
| | | background-image: url("@/assets/BI/chuchangyijianicon@2x.png"); |
| | | |
| | | } |
| | | .panel-title-icon { |
| | | width: 60px; |
| | | height: 60px; |
| | |
| | | padding-left: 46px; |
| | | line-height: 36px; |
| | | } |
| | | |
| | | .total-customers { |
| | | background-image: url("@/assets/BI/hetongjineback@2x.png"); |
| | | background-size: cover; |
| | |
| | | height: 82%; |
| | | overflow-y: auto; |
| | | width: 460px; |
| | | /* 隐藏滚动条但保留滚动功能 */ |
| | | scrollbar-width: none; /* Firefox */ |
| | | -ms-overflow-style: none; /* IE和Edge */ |
| | | scrollbar-width: none; |
| | | -ms-overflow-style: none; |
| | | } |
| | | |
| | | /* Chrome、Safari和Opera */ |
| | | .contract-list::-webkit-scrollbar { |
| | | display: none; |
| | | } |
| | | |
| | | .line { |
| | | position: relative; |
| | | width: 230px; |
| | | } |
| | | |
| | | .line::after { |
| | | content: ''; |
| | | position: absolute; |
| | |
| | | background-color: #C9C5C5; |
| | | border-radius: 2px; |
| | | } |
| | | |
| | | .contract-list li { |
| | | margin-top: 10px; |
| | | } |
| | | |
| | | .stats-cards { |
| | | display: flex; |
| | | gap: 30px; |
| | |
| | | background-size: 100% 100%; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | height: 142px; |
| | | height: 128px; |
| | | } |
| | | |
| | | .card-icon { |
| | | width: 100px; |
| | | height: 100px; |
| | | width: 80px; |
| | | height: 80px; |
| | | margin: 20px 20px 0 10px; |
| | | } |
| | | |
| | |
| | | |
| | | .card-label { |
| | | font-weight: 400; |
| | | font-size: 19px; |
| | | font-size: 16px; |
| | | color: rgba(208,231,255,0.7); |
| | | } |
| | | |
| | | .equipment-stats { |
| | | border: 1px solid #1A58B0; |
| | | padding: 18px; |
| | | height: 240px; |
| | | } |
| | | .equipment-header { |
| | | font-weight: 500; |
| | | font-size: 21px; |
| | | display: flex; |
| | | border-bottom: 1px solid; |
| | | border-image: linear-gradient( 270deg, rgba(0,126,255,0) 0%, rgba(0,126,255,0.4549) 35%, #007EFF 78%, #007EFF 100%) 1; |
| | | padding-bottom: 2px; |
| | | } |
| | | .equipment-title { |
| | | font-weight: 500; |
| | | font-size: 21px; |
| | | background: linear-gradient(360deg, #056DFF 0%, #43E8FC 100%); |
| | | -webkit-background-clip: text; |
| | | -webkit-text-fill-color: transparent; |
| | | background-clip: text; |
| | | line-height: 50px; |
| | | } |
| | | .equipment-icon { |
| | | width: 50px; |
| | | height: 50px; |
| | | } |
| | | .equipment-items { |
| | | display: flex; |
| | | justify-content: space-around; |
| | | gap: 30px; |
| | | } |
| | | |
| | | .equipment-item { |
| | | text-align: center; |
| | | } |
| | | |
| | | .equipment-value { |
| | | display: block; |
| | | font-weight: 500; |
| | | font-size: 40px; |
| | | color: #FFFFFF; |
| | | width: 120px; |
| | | height: 110px; |
| | | line-height: 110px; |
| | | background-image: url("@/assets/BI/shujutongji@2x.png"); |
| | | background-size: 100% 100%; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .equipment-label { |
| | | font-weight: 500; |
| | | font-size: 21px; |
| | | color: #FFFFFE; |
| | | } |
| | | |
| | | .event-info { |
| | |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | padding: 20px; |
| | | height: 186px; |
| | | height: 400px; |
| | | display: flex; |
| | | flex-direction: column; |
| | | } |
| | | |
| | | .event-header { |
| | | display: flex; |
| | | align-items: center; |
| | | } |
| | | |
| | | .event-content { |
| | | flex: 1; |
| | | min-height: 0; |
| | | margin-top: 12px; |
| | | } |
| | | |
| | | .event-icon { |
| | | width: 40px; |
| | | height: 40px; |
| | | } |
| | | |
| | | .event-title { |
| | | font-weight: 500; |
| | | font-size: 24px; |
| | | color: #FFFFFE; |
| | | line-height: 30px; |
| | | } |
| | | |
| | | .todo-list { |
| | | list-style: none; |
| | | padding: 0; |
| | | margin: 0; |
| | | height: 120px; /* 按用户要求调整高度 */ |
| | | height: 100%; |
| | | overflow: hidden; |
| | | font-size: 15px; |
| | | } |
| | | |
| | | .todo-list li { |
| | | border-radius: 8px; |
| | | margin-bottom: 12px; |
| | |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .todo-title { |
| | | font-weight: 400; |
| | | font-size: 20px; |
| | | color: #FFFFFE; |
| | | position: relative; |
| | | } |
| | | |
| | | .todo-title::before { |
| | | content: ''; /* 必需,表示这里有一个内容 */ |
| | | content: ''; |
| | | position: absolute; |
| | | left: -10px; /* 定位到左侧 */ |
| | | top: 50%; /* 垂直居中 */ |
| | | transform: translateY(-50%); /* 微调垂直居中 */ |
| | | width: 6px; /* 圆的直径 */ |
| | | height: 6px; /* 圆的直径 */ |
| | | left: -10px; |
| | | top: 50%; |
| | | transform: translateY(-50%); |
| | | width: 6px; |
| | | height: 6px; |
| | | background: #498CEB; |
| | | border-radius: 50%; /* 让其变成圆形 */ |
| | | border-radius: 50%; |
| | | } |
| | | .todo-division { |
| | | font-weight: 400; |
| | | font-size: 20px; |
| | | color: #FFFFFE; |
| | | } |
| | | |
| | | .todo-division, |
| | | .todo-time { |
| | | font-weight: 400; |
| | | font-size: 20px; |
| | | color: #FFFFFE; |
| | | } |
| | | |
| | | .financial-header { |
| | | background-image: url("@/assets/BI/caiwufenxiback@2x.png"); |
| | | background-size: 100% 100%; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | } |
| | | |
| | | .financial-title { |
| | | width: 100%; |
| | | font-weight: 500; |
| | |
| | | line-height: 36px; |
| | | } |
| | | |
| | | /* 自定义单选按钮组样式 */ |
| | | .financial-panel { |
| | | height: 360px; |
| | | } |
| | | .financial-panel1 { |
| | | height: 420px; |
| | | } |
| | | |
| | | .main-panel .panel-item-customers { |
| | | height: 360px; |
| | | } |
| | | |
| | | .custom-radio-group :deep(.el-radio-button__inner) { |
| | | background-color: transparent; |
| | | color: white; |