| | |
| | | <template> |
| | | <div class="data-dashboard"> |
| | | <div class="scale-container"> |
| | | <div class="data-dashboard" :style="{ transform: `scale(${scaleRatio})` }"> |
| | | <!-- 全屏按钮 - 移动到左上角 --> |
| | | <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"> |
| | |
| | | |
| | | <!-- 顶部标题栏 --> |
| | | <div class="dashboard-header"> |
| | | <div class="factory-name">{{ userStore.currentFactoryName }}</div> |
| | | </div> |
| | | |
| | | <!-- 主要内容区域 --> |
| | |
| | | |
| | | <!-- 质量统计 --> |
| | | <div class="panel-header"> |
| | | <span class="panel-title">质量统计</span> |
| | | <span class="panel-title">近4月质量统计</span> |
| | | </div> |
| | | <div class="main-panel"> |
| | | <div class="panel-item-customers"> |
| | |
| | | </div> |
| | | |
| | | <div class="financial-header"> |
| | | <span class="financial-title">财务分析</span> |
| | | <span class="financial-title">各生产订单的完成进度统计</span> |
| | | </div> |
| | | <div class="main-panel"> |
| | | <div class="panel-item-customers"> |
| | | <div class="event-header"> |
| | | <img src="@/assets/BI/shijianmingxiicon@2x.png" alt="图标" class="event-icon" /> |
| | | <span class="event-title">经营成果分析</span> |
| | | <div class="order-statistics-cards"> |
| | | <div class="quality-cardSec"> |
| | | <div class="quality-card four"></div> |
| | | <div class="quality-cardTitle"> |
| | | <div>总订单数</div> |
| | | <div>{{orderStatisticsObject.totalOrderCount}}件</div> |
| | | </div> |
| | | </div> |
| | | <div class="quality-cardSec"> |
| | | <div class="quality-card five"></div> |
| | | <div class="quality-cardTitle"> |
| | | <div>未完成订单数</div> |
| | | <div>{{orderStatisticsObject.uncompletedOrderCount}}件</div> |
| | | </div> |
| | | </div> |
| | | <div class="quality-cardSec"> |
| | | <div class="quality-card six"></div> |
| | | <div class="quality-cardTitle"> |
| | | <div>部分完成订单数</div> |
| | | <div>{{orderStatisticsObject.partialCompletedOrderCount}}件</div> |
| | | </div> |
| | | </div> |
| | | <div class="quality-cardSec"> |
| | | <div class="quality-card seven"></div> |
| | | <div class="quality-cardTitle"> |
| | | <div>已完成订单数</div> |
| | | <div>{{orderStatisticsObject.completedOrderCount}}件</div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | <Echarts ref="chart" |
| | | :chartStyle="chartStyle" |
| | | :grid="grid" |
| | | :legend="barLegend1" |
| | | :series="barSeries11" |
| | | :tooltip="tooltip" |
| | | :xAxis="xAxis3" |
| | | :yAxis="yAxis3" |
| | | :options="{backgroundColor: 'transparent', textStyle: {color: '#B8C8E0'}}" |
| | | style="height: 300px"></Echarts> |
| | | <div class="progress-table-container" ref="progressTableRef" @scroll="handleTableScroll"> |
| | | <table class="progress-table"> |
| | | <thead> |
| | | <tr> |
| | | <th>生产订单号</th> |
| | | <th>产品名称</th> |
| | | <th>规格</th> |
| | | <th>需求数量</th> |
| | | <th>完成数量</th> |
| | | <th>完成进度</th> |
| | | </tr> |
| | | </thead> |
| | | <tbody> |
| | | <tr |
| | | v-for="(item, index) in progressTableData" |
| | | :key="index" |
| | | :ref="el => setRowRef(el, index)" |
| | | :class="{ 'row-under-header': isRowUnderHeader(index) }" |
| | | > |
| | | <td>{{ item.npsNo || '-' }}</td> |
| | | <td>{{ item.productCategory || '-' }}</td> |
| | | <td>{{ item.specificationModel || '-' }}</td> |
| | | <td>{{ item.quantity || 0 }}</td> |
| | | <td>{{ item.completeQuantity || 0 }}</td> |
| | | <td> |
| | | <el-progress |
| | | :percentage="calculateProgress(item)" |
| | | :color="progressColor(calculateProgress(item))" |
| | | :status="calculateProgress(item) >= 100 ? 'success' : ''" |
| | | :stroke-width="8" |
| | | /> |
| | | </td> |
| | | </tr> |
| | | </tbody> |
| | | </table> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | <div class="panel-item-customers"> |
| | | <div style="display: flex;justify-content: space-between;margin-bottom: 20px;"> |
| | | <div class="section-title">应收应付统计</div> |
| | | <el-radio-group v-model="radio1" size="large" @change="statisticsReceivable" class="custom-radio-group"> |
| | | <el-radio-button label="按周" :value="1" /> |
| | | <el-radio-button label="按月" :value="2" /> |
| | | <el-radio-button label="按季度" :value="3" /> |
| | | </el-radio-group> |
| | | <!-- <el-radio-group v-model="radio1" size="large" @change="statisticsReceivable" class="custom-radio-group">--> |
| | | <!-- <el-radio-button label="按周" :value="1" />--> |
| | | <!-- <el-radio-button label="按月" :value="2" />--> |
| | | <!-- <el-radio-button label="按季度" :value="3" />--> |
| | | <!-- </el-radio-group>--> |
| | | </div> |
| | | <Echarts ref="chart" |
| | | :color="barColors2" |
| | |
| | | |
| | | <!-- 回款与开票分析 --> |
| | | <div class="panel-header"> |
| | | <span class="panel-title">回款与开票分析</span> |
| | | <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" |
| | |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup> |
| | | import * as echarts from 'echarts' |
| | | import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue' |
| | | import { ref, reactive, 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 |
| | | statisticsReceivablePayable, |
| | | getProgressStatistics, |
| | | getWorkInProcessTurnover |
| | | } from "@/api/viewIndex.js"; |
| | | import {staffOnJobListPage} from "@/api/personnelManagement/employeeRecord.js"; |
| | | import {listCustomer} from "@/api/basicData/customerFile.js"; |
| | |
| | | import {getUpkeepPage} from "@/api/equipmentManagement/upkeep.js"; |
| | | import {measuringInstrumentListPage} from "@/api/equipmentManagement/measurementEquipment.js"; |
| | | import {listPageAnalysis} from "@/api/financialManagement/expenseManagement.js"; |
| | | import {productOrderListPage} from "@/api/productionManagement/productionOrder.js"; |
| | | |
| | | // 全屏相关状态 |
| | | const isFullscreen = ref(false); |
| | | |
| | | // 缩放比例 |
| | | const scaleRatio = ref(1) |
| | | // 设计尺寸(基准尺寸)- 根据实际设计稿调整 |
| | | const designWidth = 1920 |
| | | const designHeight = 1080 |
| | | |
| | | // 用户store |
| | | const userStore = useUserStore() |
| | | |
| | | // 响应式数据 |
| | | const currentTime = ref('') |
| | |
| | | const realtimeLineChartRef = ref(null) |
| | | const refContractList = ref(null) |
| | | const refTodoList = ref(null) |
| | | const progressTableRef = ref(null) |
| | | const timerScroll = ref(null) |
| | | const progressTableScrollTimer = ref(null) |
| | | const isTableScrolling = ref(false) |
| | | const tableScrollTimeout = ref(null) |
| | | const tableRowRefs = ref([]) |
| | | const rowsUnderHeader = ref(new Set()) |
| | | |
| | | const chartStylePie = { |
| | | width: '140%', |
| | |
| | | supplierNum: 0, |
| | | processNum: 0, |
| | | factoryNum: 0, |
| | | }) |
| | | |
| | | // 订单统计对象 |
| | | const orderStatisticsObject = ref({ |
| | | totalOrderCount: 0, |
| | | uncompletedOrderCount: 0, |
| | | partialCompletedOrderCount: 0, |
| | | completedOrderCount: 0, |
| | | }) |
| | | const chartStyle = { |
| | | width: '100%', |
| | |
| | | const barLegend = { |
| | | show: true, |
| | | textStyle: { color: '#B8C8E0' }, |
| | | data: ['原材料不合格数', '过程不合格数', '出厂不合格数'] |
| | | data: ['原材料合格数', '过程合格数', '出不合格数'] |
| | | } |
| | | const barLegend1 = { |
| | | show: true, |
| | | show: false, |
| | | textStyle: { color: '#B8C8E0' }, |
| | | data: ['总收入', '总支出', '净收入'] |
| | | data: [] |
| | | } |
| | | const barSeries11 = ref([ |
| | | { |
| | | name: '总收入', |
| | | name: '生产订单统计', |
| | | type: 'bar', |
| | | barGap: 0, |
| | | emphasis: { |
| | | focus: 'series' |
| | | }, |
| | | itemStyle: { |
| | | color: { |
| | | type: 'linear', |
| | | x: 0, |
| | | y: 0, |
| | | x2: 0, |
| | | y2: 1, |
| | | colorStops: [ |
| | | { offset: 1, color: '#00A4ED' }, |
| | | { offset: 0, color: '#4EE4FF' } |
| | | // 使用函数根据数据索引返回不同颜色 |
| | | color: function(params) { |
| | | const colorStops = [ |
| | | [ |
| | | { offset: 1, color: '#00A4ED' }, |
| | | { offset: 0, color: '#4EE4FF' } |
| | | ], |
| | | [ |
| | | { offset: 1, color: '#3378FF' }, |
| | | { offset: 0, color: '#4E8AFF' } |
| | | ], |
| | | [ |
| | | { offset: 1, color: '#FF6B6B' }, |
| | | { offset: 0, color: '#FF8E8E' } |
| | | ], |
| | | [ |
| | | { offset: 1, color: '#537EF5' }, |
| | | { offset: 0, color: '#9061F8' } |
| | | ] |
| | | ] |
| | | const stops = colorStops[params.dataIndex] || colorStops[0] |
| | | return { |
| | | type: 'linear', |
| | | x: 0, |
| | | y: 0, |
| | | x2: 0, |
| | | y2: 1, |
| | | colorStops: stops |
| | | } |
| | | } |
| | | }, |
| | | data: [] |
| | | }, |
| | | { |
| | | name: '总支出', |
| | | type: 'bar', |
| | | emphasis: { |
| | | focus: 'series' |
| | | }, |
| | | itemStyle: { |
| | | color: { |
| | | type: 'linear', |
| | | x: 0, |
| | | y: 0, |
| | | x2: 0, |
| | | y2: 1, |
| | | colorStops: [ |
| | | { offset: 1, color: '#3378FF' }, |
| | | { offset: 0, color: '#4E8AFF' } |
| | | ] |
| | | } |
| | | }, |
| | | data: [] |
| | | }, |
| | | { |
| | | name: '净收入', |
| | | type: 'bar', |
| | | emphasis: { |
| | | focus: 'series' |
| | | }, |
| | | itemStyle: { |
| | | color: { |
| | | type: 'linear', |
| | | x: 0, |
| | | y: 0, |
| | | x2: 0, |
| | | y2: 1, |
| | | colorStops: [ |
| | | { offset: 1, color: '#537EF5' }, |
| | | { offset: 0, color: '#9061F8' } |
| | | ] |
| | | } |
| | | }, |
| | | data: [] |
| | | }, |
| | | } |
| | | ]) |
| | | const barSeries1 = ref([ |
| | | { |
| | | name: '原材料不合格数', |
| | | name: '原材料合格数', |
| | | type: 'bar', |
| | | barGap: 0, |
| | | emphasis: { |
| | |
| | | data: [] |
| | | }, |
| | | { |
| | | name: '过程不合格数', |
| | | name: '过程合格数', |
| | | type: 'bar', |
| | | emphasis: { |
| | | focus: 'series' |
| | |
| | | data: [] |
| | | }, |
| | | { |
| | | name: '出厂不合格数', |
| | | name: '出厂合格数', |
| | | type: 'bar', |
| | | emphasis: { |
| | | focus: 'series' |
| | |
| | | // 待办事项 |
| | | const todoList = ref([]) |
| | | |
| | | // 窗口大小变化处理 |
| | | const handleResize = () => { |
| | | // 生产订单完成进度表格数据 |
| | | const progressTableData = ref([]) |
| | | |
| | | // 计算完成进度百分比 |
| | | const calculateProgress = (item) => { |
| | | if (!item) return 0 |
| | | // 优先使用completionStatus字段 |
| | | if (item.completionStatus !== undefined && item.completionStatus !== null) { |
| | | const percentage = Number(item.completionStatus) |
| | | if (isNaN(percentage)) return 0 |
| | | return Math.min(Math.max(Math.round(percentage), 0), 100) |
| | | } |
| | | // 如果没有completionStatus,则根据完成数量和需求数量计算 |
| | | if (!item.quantity || item.quantity === 0) return 0 |
| | | const percentage = (item.completeQuantity || 0) / item.quantity * 100 |
| | | return Math.min(Math.max(Math.round(percentage), 0), 100) |
| | | } |
| | | |
| | | // 根据进度百分比返回颜色 |
| | | const progressColor = (percentage) => { |
| | | const p = percentage || 0 |
| | | if (p < 30) return "#f56c6c" |
| | | if (p < 50) return "#e6a23c" |
| | | if (p < 80) return "#409eff" |
| | | return "#67c23a" |
| | | } |
| | | |
| | | // 计算缩放比例 |
| | | const calculateScale = () => { |
| | | const container = document.querySelector('.scale-container') |
| | | if (!container) return |
| | | |
| | | // 获取容器的实际尺寸 |
| | | const rect = container.getBoundingClientRect?.() |
| | | const containerWidth = container.clientWidth || rect?.width || window.innerWidth |
| | | const containerHeight = container.clientHeight || rect?.height || window.innerHeight |
| | | |
| | | // 计算宽高缩放比例,取较小值以保证内容完整显示(等比缩放) |
| | | const scaleX = containerWidth / designWidth |
| | | const scaleY = containerHeight / designHeight |
| | | scaleRatio.value = Math.min(scaleX, scaleY) |
| | | |
| | | // 触发图表resize |
| | | charts.value.forEach(chart => { |
| | | if (chart && chart.resize) { |
| | | chart.resize() |
| | | } |
| | | }) |
| | | } |
| | | |
| | | // 窗口大小变化处理 |
| | | const handleResize = () => { |
| | | // 延迟执行,确保DOM更新完成 |
| | | setTimeout(() => { |
| | | calculateScale() |
| | | }, 100) |
| | | } |
| | | |
| | | // 销毁图表实例 |
| | |
| | | qualityStatisticsObject.value.factoryNum = res.data.factoryNum |
| | | }) |
| | | } |
| | | // 财务统计 |
| | | const accountStatisticsInfo = () => { |
| | | listPageAnalysis().then((res) => { |
| | | xAxis3.value[0].data = res.data.days |
| | | barSeries11.value[0].data = res.data.totalIncome |
| | | barSeries11.value[1].data = res.data.totalExpense |
| | | barSeries11.value[2].data = res.data.netIncome |
| | | // 各生产订单的完成进度统计 |
| | | const progressStatisticsInfo = () => { |
| | | // 从统计接口获取统计数据 |
| | | getProgressStatistics().then((res) => { |
| | | console.log("生产订单完成进度统计数据:", res) |
| | | |
| | | if (!res || !res.data) { |
| | | console.warn('生产订单完成进度统计数据为空') |
| | | return |
| | | } |
| | | |
| | | // 从接口获取统计数据 |
| | | orderStatisticsObject.value = { |
| | | totalOrderCount: res.data.totalOrderCount || 0, |
| | | uncompletedOrderCount: res.data.uncompletedOrderCount || 0, |
| | | partialCompletedOrderCount: res.data.partialCompletedOrderCount || 0, |
| | | completedOrderCount: res.data.completedOrderCount || 0 |
| | | } |
| | | progressTableData.value = res.data.completedOrderDetails || [] |
| | | // 重置行引用 |
| | | tableRowRefs.value = [] |
| | | rowsUnderHeader.value.clear() |
| | | |
| | | // 在获取到数据后,初始化滚动功能 |
| | | nextTick(() => { |
| | | initProgressTableScroll() |
| | | }) |
| | | }).catch((error) => { |
| | | console.error('获取生产订单完成进度统计失败:', error) |
| | | }) |
| | | } |
| | | // 财务统计 |
| | | // const accountStatisticsInfo = () => { |
| | | // listPageAnalysis().then((res) => { |
| | | // xAxis3.value[0].data = res.data.days |
| | | // barSeries11.value[0].data = res.data.totalIncome |
| | | // }) |
| | | // } |
| | | const getNum = () => { |
| | | const params = { |
| | | pageNum: -1, |
| | |
| | | getLedgerPage(params).then((res) => { |
| | | equipmentNum.value = res.data.total |
| | | }); |
| | | getRepairPage(params).then((res) => { |
| | | getRepairPage({...params, status:0}).then((res) => { |
| | | equipmentRepair.value = res.data.total |
| | | }); |
| | | getUpkeepPage(params).then((res) => { |
| | | getUpkeepPage({...params, status:0}).then((res) => { |
| | | equipmentMaintain.value = res.data.total |
| | | }); |
| | | measuringInstrumentListPage(params).then((res) => { |
| | |
| | | |
| | | // 自动轮换周、月、季度的定时器 |
| | | const autoSwitchTimer = ref(null) |
| | | |
| | | // 设置行引用 |
| | | const setRowRef = (el, index) => { |
| | | if (el) { |
| | | tableRowRefs.value[index] = el |
| | | } |
| | | } |
| | | |
| | | // 判断行是否在表头下方 |
| | | const isRowUnderHeader = (index) => { |
| | | return rowsUnderHeader.value.has(index) |
| | | } |
| | | |
| | | // 处理表格滚动事件 |
| | | const handleTableScroll = () => { |
| | | const tableContainer = progressTableRef.value |
| | | if (!tableContainer) return |
| | | |
| | | const thead = tableContainer.querySelector('thead') |
| | | if (!thead) return |
| | | |
| | | const theadHeight = thead.offsetHeight |
| | | const containerRect = tableContainer.getBoundingClientRect() |
| | | const containerTop = containerRect.top |
| | | const theadBottom = containerTop + theadHeight |
| | | |
| | | // 清空之前的记录 |
| | | rowsUnderHeader.value.clear() |
| | | |
| | | // 检查每一行是否在表头下方(被表头遮挡) |
| | | tableRowRefs.value.forEach((row, index) => { |
| | | if (row) { |
| | | const rowRect = row.getBoundingClientRect() |
| | | const rowTop = rowRect.top |
| | | const rowBottom = rowRect.bottom |
| | | |
| | | // 如果行与表头有重叠(行在表头下方被遮挡) |
| | | // 行的顶部在表头底部下方,但行的底部在表头底部上方,说明被遮挡 |
| | | if (rowTop < theadBottom && rowBottom > containerTop) { |
| | | rowsUnderHeader.value.add(index) |
| | | } |
| | | } |
| | | }) |
| | | |
| | | // 清除之前的定时器 |
| | | if (tableScrollTimeout.value) { |
| | | clearTimeout(tableScrollTimeout.value) |
| | | } |
| | | |
| | | // 滚动停止后清空淡化标记 |
| | | tableScrollTimeout.value = setTimeout(() => { |
| | | rowsUnderHeader.value.clear() |
| | | }, 150) |
| | | } |
| | | |
| | | // 初始化生产订单进度表格滚动功能 |
| | | const initProgressTableScroll = () => { |
| | | const tableContainer = progressTableRef.value |
| | | if (!tableContainer) return |
| | | |
| | | // 清理之前的滚动动画和定时器 |
| | | if (progressTableScrollTimer.value) { |
| | | cancelAnimationFrame(progressTableScrollTimer.value) |
| | | progressTableScrollTimer.value = null |
| | | } |
| | | if (tableContainer._pauseTimer) { |
| | | clearInterval(tableContainer._pauseTimer) |
| | | tableContainer._pauseTimer = null |
| | | } |
| | | |
| | | const tbody = tableContainer.querySelector('tbody') |
| | | if (!tbody) return |
| | | |
| | | // 清理之前可能存在的克隆行(保留原始数据行) |
| | | // 原始数据行的数量应该等于 progressTableData.value.length |
| | | const originalCount = progressTableData.value.length |
| | | const allRows = Array.from(tbody.querySelectorAll('tr')) |
| | | if (allRows.length > originalCount) { |
| | | // 移除所有超过原始数量的行(这些是克隆的行) |
| | | for (let i = originalCount; i < allRows.length; i++) { |
| | | allRows[i].remove() |
| | | } |
| | | } |
| | | |
| | | const scrollItems = Array.from(tbody.querySelectorAll('tr')) |
| | | if (scrollItems.length === 0) return |
| | | |
| | | // 获取原始数据项数量 |
| | | const originalItemCount = scrollItems.length |
| | | |
| | | // 计算容器高度和表头高度 |
| | | const thead = tableContainer.querySelector('thead') |
| | | const theadHeight = thead ? thead.offsetHeight : 40 |
| | | const containerHeight = tableContainer.clientHeight |
| | | const visibleHeight = containerHeight - theadHeight |
| | | |
| | | // 计算原始数据的总高度 |
| | | const itemHeight = scrollItems[0]?.offsetHeight || 40 |
| | | const totalContentHeight = itemHeight * originalItemCount |
| | | |
| | | // 如果数据量不够,容器可以完全显示所有数据,就不需要滚动和克隆 |
| | | if (totalContentHeight <= visibleHeight) { |
| | | // 数据量少,不需要滚动,直接返回 |
| | | return |
| | | } |
| | | |
| | | // 数据量足够,需要滚动,进行克隆以实现无缝滚动 |
| | | const cloneCount = Math.ceil(visibleHeight / itemHeight) + 2 |
| | | |
| | | // 克隆前几个项目并添加到列表末尾,实现无缝滚动 |
| | | for (let i = 0; i < cloneCount; i++) { |
| | | const clone = scrollItems[i % originalItemCount].cloneNode(true) |
| | | tbody.appendChild(clone) |
| | | } |
| | | |
| | | let scrollPosition = 0 |
| | | 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) |
| | | |
| | | // 计算最大滚动位置(原始内容的高度) |
| | | const maxScroll = itemHeight * originalItemCount |
| | | |
| | | // 当滚动超过原始内容长度时,重置位置实现无缝滚动 |
| | | if (scrollPosition >= maxScroll) { |
| | | scrollPosition = 0 |
| | | tableContainer.scrollTop = 0 |
| | | } else { |
| | | tableContainer.scrollTop = scrollPosition |
| | | } |
| | | } |
| | | |
| | | progressTableScrollTimer.value = requestAnimationFrame(scrollAnimation) |
| | | } |
| | | |
| | | // 启动滚动动画 |
| | | progressTableScrollTimer.value = requestAnimationFrame(scrollAnimation) |
| | | |
| | | // 设置滚动-暂停-滚动的循环效果 |
| | | const pauseTimer = setInterval(() => { |
| | | isPaused = !isPaused |
| | | }, pauseTime) |
| | | |
| | | // 清理定时器 |
| | | tableContainer._pauseTimer = pauseTimer |
| | | } |
| | | |
| | | // 初始化待办事项列表滚动功能 |
| | | const initTodoListScroll = () => { |
| | | const todoList = refTodoList.value |
| | |
| | | updateTime() |
| | | timer.value = setInterval(updateTime, 1000) |
| | | } |
| | | // 全屏功能实现 - 针对data-dashboard元素 |
| | | // 全屏功能实现 - 针对scale-container元素 |
| | | const toggleFullscreen = () => { |
| | | const element = document.querySelector('.data-dashboard') |
| | | const element = document.querySelector('.scale-container') |
| | | |
| | | if (!element) return |
| | | |
| | |
| | | const fullscreenElement = document.fullscreenElement || |
| | | document.webkitFullscreenElement || |
| | | document.msFullscreenElement |
| | | isFullscreen.value = fullscreenElement && fullscreenElement.classList.contains('data-dashboard') |
| | | isFullscreen.value = fullscreenElement && fullscreenElement.classList.contains('scale-container') |
| | | |
| | | // 全屏状态变化时,延迟重新计算缩放比例(确保DOM更新完成) |
| | | setTimeout(() => { |
| | | calculateScale() |
| | | }, 200) |
| | | } |
| | | |
| | | // 生命周期钩子 |
| | |
| | | initTime() |
| | | // 使用nextTick确保DOM完全渲染后再初始化图表 |
| | | nextTick(() => { |
| | | // 初始化autofit自适应 |
| | | autofit.init({ dh: 1440, dw: 2560, el: '.data-dashboard', resize: true }, false) |
| | | // 计算初始缩放比例 |
| | | calculateScale() |
| | | |
| | | // 初始化autofit自适应(如果需要保留autofit,可以保留,但主要缩放由scale-container控制) |
| | | // autofit.init({ dh: 800, dw: 1280, el: '.data-dashboard', resize: true }, false) |
| | | |
| | | // 添加自动滚动动画效果 - 客户信息列表 |
| | | const contractList = refContractList.value |
| | |
| | | }) |
| | | |
| | | window.addEventListener('resize', handleResize) |
| | | window.addEventListener('fullscreenchange', handleFullscreenChange) |
| | | window.addEventListener('webkitfullscreenchange', handleFullscreenChange) |
| | | window.addEventListener('MSFullscreenChange', handleFullscreenChange) |
| | | analysisCustomer() |
| | | qualityStatisticsInfo() |
| | | accountStatisticsInfo() |
| | | // accountStatisticsInfo() |
| | | progressStatisticsInfo() |
| | | getNum() |
| | | getLedgerNum() |
| | | todoInfoS() |
| | |
| | | } |
| | | } |
| | | |
| | | // 清理生产订单进度表格的动画和定时器 |
| | | const progressTable = progressTableRef.value |
| | | if (progressTable) { |
| | | if (progressTableScrollTimer.value) { |
| | | cancelAnimationFrame(progressTableScrollTimer.value) |
| | | progressTableScrollTimer.value = null |
| | | } |
| | | if (progressTable._pauseTimer) { |
| | | clearInterval(progressTable._pauseTimer) |
| | | progressTable._pauseTimer = null |
| | | } |
| | | } |
| | | |
| | | // 清理表格滚动定时器 |
| | | if (tableScrollTimeout.value) { |
| | | clearTimeout(tableScrollTimeout.value) |
| | | tableScrollTimeout.value = null |
| | | } |
| | | |
| | | // 清理自动轮换周、月、季度的定时器 |
| | | if (autoSwitchTimer.value) { |
| | | clearInterval(autoSwitchTimer.value) |
| | |
| | | </script> |
| | | |
| | | <style scoped> |
| | | /* 外部缩放容器 - 占据整个视口 */ |
| | | .scale-container { |
| | | position: relative; |
| | | width: 100%; |
| | | /* 页面在常规布局下(有顶栏)默认减去 84px,避免内容被裁切 */ |
| | | height: calc(100vh - 84px); |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | background-color: #000; |
| | | overflow: hidden; |
| | | } |
| | | |
| | | /* 内部内容区域 - 固定设计尺寸 */ |
| | | .data-dashboard { |
| | | position: relative; |
| | | width: 100%; |
| | | height: 100%; |
| | | overflow: hidden; |
| | | width: 1920px; |
| | | height: 1080px; |
| | | background-image: url("@/assets/BI/backImage@2x.png"); |
| | | background-size: cover; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | transform-origin: center center; |
| | | } |
| | | |
| | | /* 全屏状态的样式 */ |
| | | .data-dashboard:fullscreen { |
| | | width: 100%; |
| | | height: 100%; |
| | | /* 全屏状态的样式 - 作用于scale-container */ |
| | | .scale-container:fullscreen { |
| | | width: 100vw; |
| | | height: 100vh; |
| | | margin: 0; |
| | | padding: 0; |
| | | background-color: inherit; |
| | | background-color: #000; |
| | | z-index: 9999; |
| | | } |
| | | |
| | | /* Webkit浏览器前缀 */ |
| | | .data-dashboard:-webkit-full-screen { |
| | | width: 100%; |
| | | height: 100%; |
| | | .scale-container:-webkit-full-screen { |
| | | width: 100vw; |
| | | height: 100vh; |
| | | margin: 0; |
| | | padding: 0; |
| | | background-color: inherit; |
| | | background-color: #000; |
| | | z-index: 9999; |
| | | } |
| | | |
| | | /* MS浏览器前缀 */ |
| | | .data-dashboard:-ms-fullscreen { |
| | | width: 100%; |
| | | height: 100%; |
| | | .scale-container:-ms-fullscreen { |
| | | width: 100vw; |
| | | height: 100vh; |
| | | margin: 0; |
| | | padding: 0; |
| | | background-color: inherit; |
| | | background-color: #000; |
| | | z-index: 9999; |
| | | } |
| | | |
| | |
| | | .dashboard-header { |
| | | position: relative; |
| | | z-index: 1; |
| | | height: 170px; |
| | | height: 86px; |
| | | background-image: url("@/assets/BI/biaoti.png"); |
| | | background-size: cover; |
| | | background-position: center; |
| | | background-repeat: no-repeat; |
| | | display: flex; |
| | | align-items: center; |
| | | justify-content: center; |
| | | } |
| | | |
| | | .factory-name { |
| | | font-weight: 600; |
| | | font-size: 52px; |
| | | color: #FFFFFF; |
| | | top: 16px; |
| | | position: absolute; |
| | | } |
| | | |
| | | .fullscreen-btn { |
| | |
| | | display: flex; |
| | | gap: 30px; |
| | | padding: 0 30px; |
| | | height: calc(100% - 120px); |
| | | height: calc(100% - 86px); |
| | | overflow: hidden; |
| | | } |
| | | |
| | |
| | | } |
| | | .quality-card.three { |
| | | background-image: url("@/assets/BI/chuchangyijianicon@2x.png"); |
| | | |
| | | } |
| | | |
| | | /* 订单统计卡片样式 */ |
| | | .order-statistics-cards { |
| | | display: flex; |
| | | gap: 12px; |
| | | width: 100%; |
| | | height: 94px; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | margin-bottom: 20px; |
| | | } |
| | | |
| | | .quality-card.four { |
| | | background-image: url("@/assets/BI/yuancailiaoyijianicon@2x.png"); |
| | | } |
| | | |
| | | .quality-card.five { |
| | | background-image: url("@/assets/BI/guochengyijianicon@2x.png"); |
| | | } |
| | | |
| | | .quality-card.six { |
| | | background-image: url("@/assets/BI/chuchangyijianicon@2x.png"); |
| | | } |
| | | |
| | | .quality-card.seven { |
| | | background-image: url("@/assets/BI/yuancailiaoyijianicon@2x.png"); |
| | | } |
| | | .panel-title-icon { |
| | | width: 60px; |
| | |
| | | border-color: rgba(255, 255, 255, 0.5); |
| | | box-shadow: -1px 0 0 0 rgba(255, 255, 255, 0.5); |
| | | } |
| | | |
| | | /* 生产订单进度表格样式 */ |
| | | .progress-table-container { |
| | | height: 250px; |
| | | overflow-y: auto; |
| | | overflow-x: hidden; |
| | | margin-top: 10px; |
| | | scrollbar-width: none; /* Firefox */ |
| | | -ms-overflow-style: none; /* IE和Edge */ |
| | | } |
| | | |
| | | .progress-table-container::-webkit-scrollbar { |
| | | display: none; /* Chrome、Safari和Opera */ |
| | | } |
| | | |
| | | .progress-table { |
| | | width: 100%; |
| | | border-collapse: collapse; |
| | | color: #B8C8E0; |
| | | font-size: 12px; |
| | | table-layout: fixed; |
| | | } |
| | | |
| | | .progress-table thead { |
| | | position: sticky; |
| | | top: 0; |
| | | background-color: rgba(26, 88, 176, 0.9); |
| | | z-index: 10; |
| | | } |
| | | |
| | | .progress-table th { |
| | | padding: 8px 6px; |
| | | text-align: left; |
| | | font-weight: 500; |
| | | border-bottom: 1px solid rgba(184, 200, 224, 0.3); |
| | | color: #B8C8E0; |
| | | font-size: 12px; |
| | | white-space: nowrap; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | } |
| | | |
| | | .progress-table th:nth-child(1) { width: 15%; } /* 生产订单号 */ |
| | | .progress-table th:nth-child(2) { width: 15%; } /* 产品名称 */ |
| | | .progress-table th:nth-child(3) { width: 15%; } /* 规格 */ |
| | | .progress-table th:nth-child(4) { width: 12%; } /* 需求数量 */ |
| | | .progress-table th:nth-child(5) { width: 12%; } /* 完成数量 */ |
| | | .progress-table th:nth-child(6) { width: 31%; } /* 完成进度 */ |
| | | |
| | | .progress-table td { |
| | | padding: 8px 6px; |
| | | border-bottom: 1px solid rgba(184, 200, 224, 0.1); |
| | | white-space: nowrap; |
| | | overflow: hidden; |
| | | text-overflow: ellipsis; |
| | | font-size: 12px; |
| | | transition: opacity 0.3s ease; |
| | | } |
| | | |
| | | .progress-table tbody tr:hover { |
| | | background-color: rgba(184, 200, 224, 0.1); |
| | | } |
| | | |
| | | .progress-table tbody tr.row-under-header { |
| | | opacity: 0.5; |
| | | } |
| | | |
| | | /* el-progress 组件样式调整 */ |
| | | .progress-table :deep(.el-progress) { |
| | | width: 100%; |
| | | } |
| | | |
| | | .progress-table :deep(.el-progress-bar__outer) { |
| | | background-color: rgba(184, 200, 224, 0.2); |
| | | } |
| | | |
| | | .progress-table :deep(.el-progress__text) { |
| | | color: #B8C8E0; |
| | | font-size: 11px; |
| | | } |
| | | </style> |