feat(看板管理): 添加无缝滚动组件并优化看板样式
添加vue-seamless-scroll组件实现表格无缝滚动,优化看板布局和样式适配1920*1080分辨率 移除自定义滚动逻辑,增加时间范围选择功能,调整表格结构为固定表头+滚动内容
This commit is contained in:
parent
e01dd950b8
commit
7a3429dbe0
@ -1,6 +1,7 @@
|
||||
import Vue from 'vue'
|
||||
import VueCompositionAPI from '@vue/composition-api'
|
||||
import Cookies from 'js-cookie'
|
||||
import vueSeamlessScroll from 'vue-seamless-scroll'
|
||||
|
||||
import Element from 'element-ui'
|
||||
import 'normalize.css/normalize.css' // a modern alternative to CSS resets
|
||||
@ -126,6 +127,7 @@ Vue.component('v-charts', ECharts)
|
||||
Vue.use(VueCompositionAPI)
|
||||
Vue.use(permission)
|
||||
Vue.use(plugins)
|
||||
Vue.use(vueSeamlessScroll)
|
||||
Vue.use(Element, {
|
||||
size: Cookies.get('size') || 'small', // set element-ui default size
|
||||
})
|
||||
|
||||
@ -36,17 +36,42 @@
|
||||
<div class="flip-card-value scrap">{{ statistics.totalScrap }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 时间范围切换 -->
|
||||
<div class="time-range-switch">
|
||||
<div class="switch-label">数据范围:</div>
|
||||
<div class="switch-options">
|
||||
<div
|
||||
class="switch-option"
|
||||
:class="{ active: timeRange === 'daily' }"
|
||||
@click="setTimeRange('daily')"
|
||||
>
|
||||
每日
|
||||
</div>
|
||||
<div
|
||||
class="switch-option"
|
||||
:class="{ active: timeRange === 'weekly' }"
|
||||
@click="setTimeRange('weekly')"
|
||||
>
|
||||
每周
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 第二行:表格和图表区域 -->
|
||||
<div class="second-row">
|
||||
<!-- 左侧:质量统计表格 -->
|
||||
<div class="table-section">
|
||||
<div
|
||||
class="table-section"
|
||||
style="flex: 3"
|
||||
>
|
||||
<div class="section-title">质量统计列表</div>
|
||||
<div class="custom-table-wrapper">
|
||||
<div class="custom-table-wrapper" ref="tableWrapper">
|
||||
<!-- 固定表头 -->
|
||||
<table
|
||||
class="custom-table"
|
||||
class="custom-table custom-table-header"
|
||||
v-if="!loading"
|
||||
>
|
||||
<thead>
|
||||
@ -66,28 +91,39 @@
|
||||
<th width="108">结束时间</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody ref="tableBody">
|
||||
<tr
|
||||
v-for="(row, index) in qualityStatisticsTable"
|
||||
:key="index"
|
||||
class="table-row"
|
||||
>
|
||||
<td>{{ row.workorderId || "-" }}</td>
|
||||
<td>{{ row.finishedPartNumber || "-" }}</td>
|
||||
<td>{{ row.color || "-" }}</td>
|
||||
<td class="ellipsis">{{ row.productDescription || "-" }}</td>
|
||||
<td>{{ row.team || "-" }}</td>
|
||||
<td>{{ row.requireNumber || "-" }}</td>
|
||||
<td>{{ row.qualifiedNumber || "-" }}</td>
|
||||
<td>{{ offsetRate(row.qualifiedRate) }}%</td>
|
||||
<td>{{ row.paoguangTotal || "-" }}</td>
|
||||
<td>{{ row.damoTotal || "-" }}</td>
|
||||
<td>{{ row.baofeiTotal || "-" }}</td>
|
||||
<td>{{ row.startTime || "-" }}</td>
|
||||
<td>{{ row.endTime || "-" }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- 可滚动的表格内容 -->
|
||||
<div class="table-content-wrapper">
|
||||
<vue-seamless-scroll
|
||||
:data="qualityStatisticsTable"
|
||||
:class-option="scrollOptions"
|
||||
class="table-scroll"
|
||||
>
|
||||
<table class="custom-table custom-table-body">
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="(row, index) in qualityStatisticsTable"
|
||||
:key="index"
|
||||
class="table-row"
|
||||
>
|
||||
<td width="140">{{ row.workorderId || "-" }}</td>
|
||||
<td width="120">{{ row.finishedPartNumber || "-" }}</td>
|
||||
<td width="100">{{ row.color || "-" }}</td>
|
||||
<td width="150" class="ellipsis">{{ row.productDescription || "-" }}</td>
|
||||
<td width="80">{{ row.team || "-" }}</td>
|
||||
<td width="120">{{ row.requireNumber || "-" }}</td>
|
||||
<td width="80">{{ row.qualifiedNumber || "-" }}</td>
|
||||
<td width="80">{{ offsetRate(row.qualifiedRate) }}%</td>
|
||||
<td width="80">{{ row.paoguangTotal || "-" }}</td>
|
||||
<td width="80">{{ row.damoTotal || "-" }}</td>
|
||||
<td width="80">{{ row.baofeiTotal || "-" }}</td>
|
||||
<td width="108">{{ row.startTime || "-" }}</td>
|
||||
<td width="108">{{ row.endTime || "-" }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</vue-seamless-scroll>
|
||||
</div>
|
||||
<div
|
||||
class="loading-overlay"
|
||||
v-if="loading"
|
||||
@ -102,7 +138,7 @@
|
||||
<div class="charts-section">
|
||||
<!-- 上半部分:合格率分布饼图 -->
|
||||
<div class="chart-container">
|
||||
<div class="section-title">合格率分布</div>
|
||||
<div class="section-title">合格率分布(≥86%/<86%)</div>
|
||||
<div
|
||||
id="qualifiedRatePieChart"
|
||||
style="width: 100%; height: 200px"
|
||||
@ -126,11 +162,14 @@
|
||||
import { getQualityStatisticsCarouselBoardData } from "@/api/kanbanManagement/carouselBoard.js";
|
||||
import * as echarts from "echarts";
|
||||
import kbHeader from "./kbHeader.vue";
|
||||
import dayjs from "dayjs";
|
||||
import vueSeamlessScroll from "vue-seamless-scroll";
|
||||
|
||||
export default {
|
||||
name: "QualityStatisticsCard",
|
||||
components: {
|
||||
kbHeader,
|
||||
vueSeamlessScroll,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@ -138,8 +177,8 @@ export default {
|
||||
qualityStatisticsTable: [],
|
||||
tableHeight: "500px",
|
||||
search: {
|
||||
starttime: null,
|
||||
endtime: null,
|
||||
startTime: null,
|
||||
endTime: null,
|
||||
team: null,
|
||||
workorderid: null,
|
||||
partnumber: null,
|
||||
@ -157,31 +196,21 @@ export default {
|
||||
totalGrind: 0,
|
||||
totalScrap: 0,
|
||||
},
|
||||
// 时间范围选择
|
||||
timeRange: "daily", // 'daily'或'weekly'
|
||||
allDataList: [],
|
||||
// 图表实例
|
||||
qualifiedRatePieChart: null,
|
||||
top5Chart: null,
|
||||
tableScrollTimer: null,
|
||||
refreshTimer: null,
|
||||
scrollCheckTimer: null,
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
console.log("质量组件mounted生命周期调用");
|
||||
this.init();
|
||||
this.getQualityData();
|
||||
// 监听窗口大小变化
|
||||
window.addEventListener("resize", this.handleResize);
|
||||
|
||||
// 添加滚动检查定时器
|
||||
this.scrollCheckTimer = setInterval(() => {
|
||||
console.log("质量组件定时检查滚动");
|
||||
this.$nextTick(() => {
|
||||
this.startTableAutoScroll();
|
||||
});
|
||||
}, 10000);
|
||||
|
||||
// 设置5分钟定时刷新
|
||||
this.refreshTimer = setInterval(() => {
|
||||
this.getQualityData();
|
||||
@ -189,15 +218,12 @@ export default {
|
||||
},
|
||||
|
||||
updated() {
|
||||
// 数据更新后初始化图表和启动表格滚动
|
||||
// 数据更新后初始化图表
|
||||
this.$nextTick(() => {
|
||||
console.log("质量组件updated生命周期调用");
|
||||
if (this.qualityStatisticsTable.length > 0) {
|
||||
this.initQualifiedRatePieChart();
|
||||
this.initTop5Chart();
|
||||
}
|
||||
// 无论数据是否为空,都尝试启动滚动,确保表格有内容时能正常滚动
|
||||
this.startTableAutoScroll();
|
||||
});
|
||||
},
|
||||
|
||||
@ -212,29 +238,37 @@ export default {
|
||||
this.top5Chart.dispose();
|
||||
this.top5Chart = null;
|
||||
}
|
||||
// 清理表格滚动定时器(setInterval)
|
||||
if (this.tableScrollTimer) {
|
||||
clearInterval(this.tableScrollTimer);
|
||||
this.tableScrollTimer = null;
|
||||
}
|
||||
// 清理滚动检查定时器
|
||||
if (this.scrollCheckTimer) {
|
||||
clearInterval(this.scrollCheckTimer);
|
||||
this.scrollCheckTimer = null;
|
||||
}
|
||||
// 清理数据刷新定时器
|
||||
if (this.refreshTimer) {
|
||||
clearInterval(this.refreshTimer);
|
||||
this.refreshTimer = null;
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
// vue-seamless-scroll配置
|
||||
scrollOptions() {
|
||||
return {
|
||||
step: 0.5, // 滚动速度,值越大速度越快
|
||||
limitMoveNum: 1, // 开始无缝滚动的数据量,滚动的数据列表要大于等于这个值
|
||||
hoverStop: false, // 是否开启鼠标悬停停止
|
||||
direction: 1, // 0 向下 1 向上 2 向左 3 向右
|
||||
openWatch: true, // 开启数据监听,数据变化时自动滚动
|
||||
singleHeight: 0, // 单步运动停止的高度(默认值0是无缝不停止的滚动),只有direction为0或1时生效
|
||||
singleWidth: 0, // 单步运动停止的宽度(默认值0是无缝不停止的滚动),只有direction为2或3时生效
|
||||
waitTime: 1000, // 单步运动停止的时间(默认值1000ms)
|
||||
switchOffset: 0, // 左右切换的偏移量
|
||||
autoPlay: true, // 是否自动播放
|
||||
switchSingleStep: 150, // 切换步数
|
||||
switchDelay: 3000, // 切换延迟
|
||||
ease: "easeInOutQuad", // 缓动函数
|
||||
easing: "linear", // 动画过渡效果
|
||||
};
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
// 设置默认时间范围为今天
|
||||
const today = new Date();
|
||||
this.search.starttime = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 0, 0, 0);
|
||||
this.search.endtime = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 23, 59, 59);
|
||||
// 根据默认时间范围设置时间
|
||||
this.setTimeRange("daily");
|
||||
},
|
||||
|
||||
// 合格率去除小数
|
||||
@ -245,12 +279,38 @@ export default {
|
||||
return parseInt(num);
|
||||
},
|
||||
|
||||
// 设置时间范围
|
||||
setTimeRange(type) {
|
||||
this.timeRange = type;
|
||||
const today = dayjs();
|
||||
|
||||
if (type === "daily") {
|
||||
// 今天
|
||||
this.search.startTime = today.startOf("day").toDate();
|
||||
this.search.endTime = today.endOf("day").toDate();
|
||||
} else if (type === "weekly") {
|
||||
// 本周(周一到周日)
|
||||
this.search.startTime = today.startOf("week").toDate();
|
||||
this.search.endTime = today.endOf("week").toDate();
|
||||
}
|
||||
|
||||
// 重新查询数据
|
||||
this.getQualityData();
|
||||
},
|
||||
|
||||
// 格式化日期为字符串(传给后台)
|
||||
formatDateForBackend(date) {
|
||||
return dayjs(date).format("YYYY-MM-DD HH:mm:ss");
|
||||
},
|
||||
|
||||
// 获取质量数据
|
||||
getQualityData() {
|
||||
this.loading = true;
|
||||
// 设置默认查询参数
|
||||
// 设置默认查询参数,将日期转换为字符串格式
|
||||
let query = {
|
||||
...this.search,
|
||||
startTime: this.formatDateForBackend(this.search.startTime),
|
||||
endTime: this.formatDateForBackend(this.search.endTime),
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
};
|
||||
@ -260,13 +320,12 @@ export default {
|
||||
if (res.code == 200) {
|
||||
this.qualityStatisticsTable = res.data || [];
|
||||
this.allDataList = res.data || [];
|
||||
console.log("质量数据加载完成,共", this.qualityStatisticsTable.length, "条记录");
|
||||
|
||||
// 计算统计数据
|
||||
this.calculateStatistics();
|
||||
|
||||
// 数据加载完成后立即启动滚动
|
||||
this.$nextTick(() => {
|
||||
console.log("质量数据加载后启动滚动");
|
||||
this.startTableAutoScroll();
|
||||
});
|
||||
}
|
||||
@ -349,9 +408,9 @@ export default {
|
||||
// 过滤有合格数据的记录
|
||||
const validData = this.allDataList.filter((item) => item.qualifiedRate !== null && item.qualifiedRate !== "" && !isNaN(item.qualifiedRate));
|
||||
|
||||
// 统计合格率80%以上和80%以下的数量
|
||||
const above80 = validData.filter((item) => parseFloat(item.qualifiedRate) >= 80).length;
|
||||
const below80 = validData.filter((item) => parseFloat(item.qualifiedRate) < 80).length;
|
||||
// 统计合格率86%以上和86%以下的数量
|
||||
const above86 = validData.filter((item) => parseFloat(item.qualifiedRate) >= 86).length;
|
||||
const below86 = validData.filter((item) => parseFloat(item.qualifiedRate) < 86).length;
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
@ -361,7 +420,7 @@ export default {
|
||||
legend: {
|
||||
orient: "horizontal",
|
||||
bottom: 10,
|
||||
data: ["合格率≥80%", "合格率<80%"],
|
||||
data: ["合格率≥86%", "合格率<86%"],
|
||||
textStyle: {
|
||||
color: "#00ffff",
|
||||
fontSize: 12,
|
||||
@ -394,8 +453,8 @@ export default {
|
||||
},
|
||||
},
|
||||
data: [
|
||||
{ value: above80, name: "合格率≥80%", itemStyle: { color: "#67c23a" } },
|
||||
{ value: below80, name: "合格率<80%", itemStyle: { color: "#f56c6c" } },
|
||||
{ value: above86, name: "合格率≥86%", itemStyle: { color: "#67c23a" } },
|
||||
{ value: below86, name: "合格率<86%", itemStyle: { color: "#f56c6c" } },
|
||||
].filter((item) => item.value > 0), // 过滤掉数量为0的类别
|
||||
},
|
||||
],
|
||||
@ -507,77 +566,8 @@ export default {
|
||||
|
||||
this.top5Chart.setOption(option);
|
||||
},
|
||||
|
||||
// 表格自动滚动功能 - 修复滚动逻辑
|
||||
startTableAutoScroll() {
|
||||
// 先清理之前的定时器,防止累积
|
||||
if (this.tableScrollTimer) {
|
||||
clearInterval(this.tableScrollTimer);
|
||||
this.tableScrollTimer = null;
|
||||
}
|
||||
|
||||
// 确保表格元素已渲染
|
||||
this.$nextTick(() => {
|
||||
// 直接获取DOM元素,避免选择器问题
|
||||
const tableSection = this.$el.querySelector(".table-section");
|
||||
const wrapper = tableSection ? tableSection.querySelector(".custom-table-wrapper") : null;
|
||||
if (!wrapper) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 重要:确保容器内容高度大于容器高度才需要滚动
|
||||
const scrollableHeight = wrapper.scrollHeight - wrapper.clientHeight;
|
||||
if (scrollableHeight <= 0) {
|
||||
return; // 内容不足,不需要滚动
|
||||
}
|
||||
|
||||
// 优化滚动容器样式设置
|
||||
wrapper.style.overflow = "auto";
|
||||
wrapper.style.overflowX = "auto"; // 改为auto,允许水平滚动以防止内容被截断
|
||||
wrapper.style.scrollBehavior = "smooth";
|
||||
|
||||
// 重置滚动位置
|
||||
wrapper.scrollTop = 0;
|
||||
|
||||
// 滚动速度
|
||||
const scrollSpeed = 2;
|
||||
|
||||
// 使用setInterval实现滚动
|
||||
this.tableScrollTimer = setInterval(() => {
|
||||
try {
|
||||
if (wrapper && wrapper.isConnected) {
|
||||
// 检查元素是否仍在DOM中
|
||||
// 检查是否滚动到底部
|
||||
if (wrapper.scrollTop + 10 >= wrapper.scrollHeight - 600) {
|
||||
// 重置到顶部
|
||||
wrapper.scrollTop = 0;
|
||||
} else {
|
||||
// 持续滚动
|
||||
wrapper.scrollTop += scrollSpeed;
|
||||
}
|
||||
} else {
|
||||
// 元素已不在DOM中,清理定时器
|
||||
clearInterval(this.tableScrollTimer);
|
||||
this.tableScrollTimer = null;
|
||||
}
|
||||
} catch (error) {
|
||||
// 捕获可能的错误,避免卡顿
|
||||
clearInterval(this.tableScrollTimer);
|
||||
this.tableScrollTimer = null;
|
||||
}
|
||||
}, 50);
|
||||
});
|
||||
},
|
||||
|
||||
// 处理窗口大小变化
|
||||
handleResize() {
|
||||
// 获取窗口高度
|
||||
const windowHeight = window.innerHeight;
|
||||
// 计算可用高度,减去标题高度和padding等
|
||||
const availableHeight = windowHeight - 60;
|
||||
// 计算表格高度,确保表格固定高度且不会超出屏幕
|
||||
this.tableHeight = Math.min(Math.max(availableHeight - 140, 260), 600) + "px";
|
||||
|
||||
// 调整图表大小
|
||||
if (this.qualifiedRatePieChart) {
|
||||
this.qualifiedRatePieChart.resize();
|
||||
@ -592,40 +582,51 @@ export default {
|
||||
|
||||
<style scoped>
|
||||
.quality-statistics-card {
|
||||
padding: 10px;
|
||||
padding: 15px;
|
||||
background: transparent;
|
||||
width: 100%;
|
||||
height: 96vh; /* 确保至少填满整个视口高度 */
|
||||
height: 100vh;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
gap: 20px;
|
||||
overflow-x: auto; /* 允许容器水平滚动,确保内容不被截断 */
|
||||
}
|
||||
|
||||
/* 第一行布局 */
|
||||
/* 时间范围切换样式 */
|
||||
.time-range-switch {
|
||||
padding: 8px 0;
|
||||
text-align: center;
|
||||
background: #f5f7fa;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* 第一行布局 - 1920*1080优化 */
|
||||
.first-row {
|
||||
width: 100%;
|
||||
flex-shrink: 0;
|
||||
height: 120px !important;
|
||||
height: 200px !important;
|
||||
margin-bottom: 10px; /* 减小底部间距 */
|
||||
}
|
||||
|
||||
/* 第二行布局 */
|
||||
/* 第二行布局 - 1920*1080优化 */
|
||||
.second-row {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
gap: 20px;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.table-section {
|
||||
flex: 3; /* 保持与原grid-template-columns: 3fr 1fr类似的比例 */
|
||||
flex: 2; /* 调整为2:1的比例 */
|
||||
min-width: 0; /* 允许flex项收缩 */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.charts-section {
|
||||
flex: 1; /* 保持与原grid-template-columns: 3fr 1fr类似的比例 */
|
||||
flex: 1; /* 调整为2:1的比例 */
|
||||
min-width: 0; /* 允许flex项收缩 */
|
||||
}
|
||||
|
||||
@ -635,6 +636,56 @@ export default {
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(0, 255, 255, 0.3);
|
||||
padding: 15px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 时间范围切换样式 */
|
||||
.time-range-switch {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 20px;
|
||||
padding: 10px;
|
||||
background: rgba(0, 60, 120, 0.5);
|
||||
border-radius: 6px;
|
||||
border: 1px solid rgba(0, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.switch-label {
|
||||
color: #00ffff;
|
||||
font-size: 14px;
|
||||
margin-right: 15px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.switch-options {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.switch-option {
|
||||
padding: 6px 20px;
|
||||
background: rgba(0, 40, 80, 0.8);
|
||||
border: 1px solid rgba(0, 255, 255, 0.3);
|
||||
border-radius: 4px;
|
||||
color: #ffffff;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.switch-option:hover {
|
||||
background: rgba(0, 255, 255, 0.2);
|
||||
border-color: #00ffff;
|
||||
color: #00ffff;
|
||||
}
|
||||
|
||||
.switch-option.active {
|
||||
background: rgba(0, 255, 255, 0.3);
|
||||
border-color: #00ffff;
|
||||
color: #00ffff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.flip-card-container {
|
||||
@ -719,29 +770,24 @@ export default {
|
||||
|
||||
/* 自定义表格样式 */
|
||||
.custom-table-wrapper {
|
||||
/* 确保表格固定高度,不依赖flex:1,避免内容超出屏幕 */
|
||||
overflow-y: auto;
|
||||
overflow-x: auto; /* 允许水平滚动以适应小屏幕 */
|
||||
border-radius: 6px;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
background: rgba(0, 60, 120, 0.8);
|
||||
/* 确保高度已通过JS设置,这里不再设置 */
|
||||
min-width: min-content; /* 确保内容不会被压缩得太小 */
|
||||
border-radius: 6px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.custom-table {
|
||||
height: 260px;
|
||||
/* 固定表头样式 */
|
||||
.custom-table-header {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
color: #ffffff;
|
||||
background: rgba(0, 60, 120, 1);
|
||||
}
|
||||
|
||||
.custom-table thead {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.custom-table th {
|
||||
.custom-table-header th {
|
||||
background: rgba(0, 60, 120, 1);
|
||||
color: #00ffff;
|
||||
font-weight: bold;
|
||||
@ -749,13 +795,43 @@ export default {
|
||||
padding: 12px 5px;
|
||||
border-bottom: 1px solid rgba(0, 255, 255, 0.3);
|
||||
font-size: 13px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.custom-table td {
|
||||
/* 内容区域样式 */
|
||||
.table-content-wrapper {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
max-height: calc(100% - 46px); /* 减去表头高度 */
|
||||
}
|
||||
|
||||
/* vue-seamless-scroll样式 */
|
||||
.table-scroll {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 隐藏滚动条 */
|
||||
.table-scroll::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.table-scroll {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.custom-table-body {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.custom-table-body td {
|
||||
text-align: center;
|
||||
padding: 10px 5px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
font-size: 13px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.table-row:hover td {
|
||||
@ -820,6 +896,46 @@ export default {
|
||||
}
|
||||
|
||||
/* 响应式调整 */
|
||||
/* 1920*1080分辨率专属样式 */
|
||||
@media (min-width: 1920px) and (min-height: 1080px) {
|
||||
.quality-statistics-card {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.flip-card-label {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.flip-card-value {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.custom-table th,
|
||||
.custom-table td {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.flip-card {
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 大屏响应式调整 */
|
||||
@media (min-width: 1600px) and (max-width: 1919px) {
|
||||
.first-row {
|
||||
height: 200px !important;
|
||||
}
|
||||
|
||||
.flip-card-value {
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 中小屏响应式调整 */
|
||||
@media (max-width: 1400px) {
|
||||
.flip-card {
|
||||
min-width: calc(100% / 5 - 15px * 4 / 5); /* 调整为5列布局 */
|
||||
|
||||
@ -43,8 +43,9 @@
|
||||
<div class="table-section">
|
||||
<div class="section-title">工单列表</div>
|
||||
<div class="custom-table-wrapper">
|
||||
<!-- 固定表头 -->
|
||||
<table
|
||||
class="custom-table"
|
||||
class="custom-table custom-table-header"
|
||||
v-if="!loading"
|
||||
>
|
||||
<thead>
|
||||
@ -61,32 +62,43 @@
|
||||
<th width="100">状态</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody ref="tableBody">
|
||||
<tr
|
||||
v-for="(row, index) in workorderOnlineTable"
|
||||
:key="index"
|
||||
class="table-row"
|
||||
>
|
||||
<td>{{ index + 1 }}</td>
|
||||
<td>{{ row.clientWorkorder || "-" }}</td>
|
||||
<td>{{ row.blankNumber || "-" }}</td>
|
||||
<td>{{ row.finishedPartNumber || "-" }}</td>
|
||||
<td class="ellipsis">{{ row.productDescription || "-" }}</td>
|
||||
<td>{{ row.colour || "-" }}</td>
|
||||
<td>{{ row.specifications || "-" }}</td>
|
||||
<td>{{ row.vehicleNumber || "-" }}</td>
|
||||
<td>{{ row.previousNumber || "-" }}</td>
|
||||
<td>
|
||||
<span
|
||||
class="status-tag"
|
||||
:class="getStatusClass(row.status)"
|
||||
>
|
||||
{{ getStatusText(row.status) }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- 可滚动的表格内容 -->
|
||||
<div class="table-content-wrapper">
|
||||
<vue-seamless-scroll
|
||||
:data="workorderOnlineTable"
|
||||
:class-option="scrollOptions"
|
||||
class="table-scroll"
|
||||
>
|
||||
<table class="custom-table custom-table-body">
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="(row, index) in workorderOnlineTable"
|
||||
:key="index"
|
||||
class="table-row"
|
||||
>
|
||||
<td width="60">{{ index + 1 }}</td>
|
||||
<td width="130">{{ row.clientWorkorder || "-" }}</td>
|
||||
<td width="120">{{ row.blankNumber || "-" }}</td>
|
||||
<td width="150">{{ row.finishedPartNumber || "-" }}</td>
|
||||
<td class="ellipsis">{{ row.productDescription || "-" }}</td>
|
||||
<td width="100">{{ row.colour || "-" }}</td>
|
||||
<td width="100">{{ row.specifications || "-" }}</td>
|
||||
<td width="80">{{ row.vehicleNumber || "-" }}</td>
|
||||
<td width="80">{{ row.previousNumber || "-" }}</td>
|
||||
<td width="100">
|
||||
<span
|
||||
class="status-tag"
|
||||
:class="getStatusClass(row.status)"
|
||||
>
|
||||
{{ getStatusText(row.status) }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</vue-seamless-scroll>
|
||||
</div>
|
||||
<div
|
||||
class="loading-overlay"
|
||||
v-if="loading"
|
||||
@ -113,11 +125,14 @@
|
||||
import { getWorkOrderCarouselBoardData } from "@/api/kanbanManagement/carouselBoard.js";
|
||||
import * as echarts from "echarts";
|
||||
import kbHeader from "./kbHeader.vue";
|
||||
import dayjs from "dayjs";
|
||||
import vueSeamlessScroll from "vue-seamless-scroll";
|
||||
|
||||
export default {
|
||||
name: "WorkorderOnlineCard",
|
||||
components: {
|
||||
kbHeader,
|
||||
vueSeamlessScroll,
|
||||
},
|
||||
|
||||
data() {
|
||||
@ -126,9 +141,14 @@ export default {
|
||||
workorderOnlineTable: [],
|
||||
workorderChart: null,
|
||||
productionChart: null,
|
||||
tableScrollTimer: null,
|
||||
refreshTimer: null,
|
||||
scrollCheckTimer: null,
|
||||
// 搜索条件
|
||||
search: {
|
||||
factoryCode: "",
|
||||
lineCode: "",
|
||||
startTime: "",
|
||||
endTime: "",
|
||||
},
|
||||
// 今日统计数据
|
||||
todayStatistics: {
|
||||
totalPlan: 0, // 今日总计划数
|
||||
@ -139,24 +159,9 @@ export default {
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
console.log("工单组件mounted生命周期调用");
|
||||
this.init();
|
||||
this.getWorkorderData();
|
||||
window.addEventListener("resize", this.handleResize);
|
||||
// 组件挂载后延迟启动滚动,确保数据已加载
|
||||
setTimeout(() => {
|
||||
this.$nextTick(() => {
|
||||
console.log("延迟启动滚动");
|
||||
this.startTableAutoScroll();
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
// 添加一个定时器,每10秒重新启动一次滚动,确保滚动持续工作
|
||||
this.scrollCheckTimer = setInterval(() => {
|
||||
console.log("工单组件定时检查滚动");
|
||||
this.$nextTick(() => {
|
||||
this.startTableAutoScroll();
|
||||
});
|
||||
}, 5000); // 缩短为5秒检查一次
|
||||
|
||||
// 设置5分钟定时刷新
|
||||
this.refreshTimer = setInterval(() => {
|
||||
@ -165,15 +170,12 @@ export default {
|
||||
},
|
||||
|
||||
updated() {
|
||||
// 数据更新后初始化图表和启动表格滚动
|
||||
// 数据更新后初始化图表
|
||||
this.$nextTick(() => {
|
||||
console.log("工单组件updated生命周期调用");
|
||||
if (this.workorderOnlineTable.length > 0) {
|
||||
this.initWorkorderChart();
|
||||
this.initProductionTypeChart();
|
||||
}
|
||||
// 无论数据是否为空,都尝试启动滚动,确保表格有内容时能正常滚动
|
||||
this.startTableAutoScroll();
|
||||
});
|
||||
},
|
||||
beforeDestroy() {
|
||||
@ -187,27 +189,61 @@ export default {
|
||||
this.productionChart.dispose();
|
||||
this.productionChart = null;
|
||||
}
|
||||
// 清理表格滚动定时器(setInterval)
|
||||
if (this.tableScrollTimer) {
|
||||
clearInterval(this.tableScrollTimer);
|
||||
this.tableScrollTimer = null;
|
||||
}
|
||||
// 清理滚动检查定时器
|
||||
if (this.scrollCheckTimer) {
|
||||
clearInterval(this.scrollCheckTimer);
|
||||
this.scrollCheckTimer = null;
|
||||
}
|
||||
// 清理数据刷新定时器
|
||||
if (this.refreshTimer) {
|
||||
clearInterval(this.refreshTimer);
|
||||
this.refreshTimer = null;
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// vue-seamless-scroll配置
|
||||
scrollOptions() {
|
||||
return {
|
||||
step: 0.5, // 滚动速度,值越大速度越快
|
||||
limitMoveNum: 1, // 开始无缝滚动的数据量,滚动的数据列表要大于等于这个值
|
||||
hoverStop: false, // 是否开启鼠标悬停停止
|
||||
direction: 1, // 0 向下 1 向上 2 向左 3 向右
|
||||
openWatch: true, // 开启数据监听,数据变化时自动滚动
|
||||
singleHeight: 0, // 单步运动停止的高度(默认值0是无缝不停止的滚动),只有direction为0或1时生效
|
||||
singleWidth: 0, // 单步运动停止的宽度(默认值0是无缝不停止的滚动),只有direction为2或3时生效
|
||||
waitTime: 1000, // 单步运动停止的时间(默认值1000ms)
|
||||
switchOffset: 0, // 左右切换的偏移量
|
||||
autoPlay: true, // 是否自动播放
|
||||
switchSingleStep: 150, // 切换步数
|
||||
switchDelay: 3000, // 切换延迟
|
||||
ease: "easeInOutQuad", // 缓动函数
|
||||
easing: "linear", // 动画过渡效果
|
||||
};
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
// 设置默认时间范围:今天和昨天
|
||||
this.setDefaultTimeRange();
|
||||
},
|
||||
|
||||
// 设置默认时间范围(今天和昨天)
|
||||
setDefaultTimeRange() {
|
||||
const today = dayjs();
|
||||
const yesterday = today.subtract(1, "day");
|
||||
|
||||
this.search.startTime = yesterday.startOf("day").toDate();
|
||||
this.search.endTime = today.endOf("day").toDate();
|
||||
},
|
||||
|
||||
// 格式化日期为字符串(传给后台)
|
||||
formatDateForBackend(date) {
|
||||
return dayjs(date).format("YYYY-MM-DD HH:mm:ss");
|
||||
},
|
||||
|
||||
// 获取工单数据
|
||||
getWorkorderData() {
|
||||
this.loading = true;
|
||||
// 设置默认查询参数
|
||||
// 设置默认查询参数,将日期转换为字符串格式
|
||||
let query = {
|
||||
...this.search,
|
||||
startTime: this.formatDateForBackend(this.search.startTime),
|
||||
endTime: this.formatDateForBackend(this.search.endTime),
|
||||
pageNum: 1,
|
||||
pageSize: 20,
|
||||
};
|
||||
@ -216,16 +252,11 @@ export default {
|
||||
.then((res) => {
|
||||
if (res.code == 200) {
|
||||
this.workorderOnlineTable = res.data || [];
|
||||
console.log("工单数据加载完成,共", this.workorderOnlineTable.length, "条记录");
|
||||
|
||||
// 计算今日统计数据
|
||||
this.calculateTodayStatistics();
|
||||
|
||||
// 数据加载完成后立即启动滚动
|
||||
this.$nextTick(() => {
|
||||
console.log("数据加载后启动滚动");
|
||||
this.startTableAutoScroll();
|
||||
});
|
||||
// 数据加载完成,vue-seamless-scroll会自动处理滚动
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
@ -448,82 +479,7 @@ export default {
|
||||
this.productionChart.setOption(option);
|
||||
},
|
||||
|
||||
// 表格自动滚动功能 - 修复滚动逻辑
|
||||
startTableAutoScroll() {
|
||||
// 先清理之前的定时器,防止累积
|
||||
if (this.tableScrollTimer) {
|
||||
clearInterval(this.tableScrollTimer);
|
||||
this.tableScrollTimer = null;
|
||||
}
|
||||
|
||||
// 确保表格元素已渲染
|
||||
this.$nextTick(() => {
|
||||
// 直接获取DOM元素,避免选择器问题
|
||||
const tableSection = this.$el.querySelector(".table-section");
|
||||
const wrapper = tableSection ? tableSection.querySelector(".custom-table-wrapper") : null;
|
||||
|
||||
if (!wrapper) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 重要:确保容器内容高度大于容器高度才需要滚动
|
||||
const scrollableHeight = wrapper.scrollHeight - wrapper.clientHeight;
|
||||
if (scrollableHeight <= 0) {
|
||||
return; // 内容不足,不需要滚动
|
||||
}
|
||||
|
||||
// 优化滚动容器样式设置
|
||||
wrapper.style.overflow = "auto";
|
||||
wrapper.style.overflowX = "auto"; // 改为auto,允许水平滚动以防止内容被截断
|
||||
wrapper.style.scrollBehavior = "smooth";
|
||||
|
||||
// 重置滚动位置
|
||||
wrapper.scrollTop = 0;
|
||||
|
||||
// 滚动速度
|
||||
const scrollSpeed = 2;
|
||||
|
||||
// 使用setInterval实现滚动
|
||||
this.tableScrollTimer = setInterval(() => {
|
||||
try {
|
||||
if (wrapper && wrapper.isConnected) {
|
||||
// 检查元素是否仍在DOM中
|
||||
// 检查是否滚动到底部
|
||||
console.log(wrapper.scrollTop, wrapper.scrollHeight, wrapper.clientHeight);
|
||||
if (wrapper.scrollTop + 10 >= wrapper.scrollHeight - 600) {
|
||||
// 重置到顶部
|
||||
wrapper.scrollTop = 0;
|
||||
} else {
|
||||
// 持续滚动
|
||||
wrapper.scrollTop += scrollSpeed;
|
||||
}
|
||||
} else {
|
||||
// 元素已不在DOM中,清理定时器
|
||||
clearInterval(this.tableScrollTimer);
|
||||
this.tableScrollTimer = null;
|
||||
}
|
||||
} catch (error) {
|
||||
// 捕获可能的错误,避免卡顿
|
||||
clearInterval(this.tableScrollTimer);
|
||||
this.tableScrollTimer = null;
|
||||
}
|
||||
}, 50);
|
||||
});
|
||||
},
|
||||
|
||||
handleResize() {
|
||||
// 获取窗口高度
|
||||
const windowHeight = window.innerHeight;
|
||||
// 计算可用高度,减去标题高度和padding等
|
||||
const availableHeight = windowHeight - 60;
|
||||
// 调整第一行高度
|
||||
const firstRow = document.querySelector(".first-row");
|
||||
if (firstRow) {
|
||||
firstRow.style.height = "250px";
|
||||
}
|
||||
// 计算表格高度,确保表格固定高度且不会超出屏幕
|
||||
this.tableHeight = Math.min(Math.max(availableHeight - 200, 260), 600) + "px";
|
||||
|
||||
// 调整图表大小
|
||||
if (this.workorderChart) {
|
||||
this.workorderChart.resize();
|
||||
@ -538,24 +494,25 @@ export default {
|
||||
|
||||
<style scoped>
|
||||
.workorder-online-card {
|
||||
padding: 10px;
|
||||
padding: 15px;
|
||||
background: transparent;
|
||||
width: 100%;
|
||||
height: 96vh; /* 确保至少填满整个视口高度 */
|
||||
height: 100vh;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
overflow-x: auto; /* 允许容器水平滚动,确保内容不被截断 */
|
||||
gap: 20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* kbHeader组件已包含标题样式 */
|
||||
|
||||
/* 第一行布局 */
|
||||
/* 第一行布局 - 1920*1080优化 */
|
||||
.first-row {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
height: 200px;
|
||||
gap: 20px;
|
||||
height: 250px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.chart-section {
|
||||
@ -568,17 +525,20 @@ export default {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* 第二行布局 */
|
||||
/* 第二行布局 - 1920*1080优化 */
|
||||
.second-row {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
gap: 20px;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.table-section {
|
||||
flex: 2;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.bar-chart-section {
|
||||
@ -662,29 +622,24 @@ export default {
|
||||
|
||||
/* 自定义表格样式 */
|
||||
.custom-table-wrapper {
|
||||
/* 确保表格固定高度,不依赖flex:1,避免内容超出屏幕 */
|
||||
overflow-y: auto;
|
||||
overflow-x: auto; /* 允许水平滚动以适应小屏幕 */
|
||||
border-radius: 6px;
|
||||
background: rgba(0, 60, 120, 0.8);
|
||||
/* 确保高度已通过JS设置,这里不再设置 */
|
||||
min-width: min-content; /* 确保内容不会被压缩得太小 */
|
||||
min-width: min-content;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.custom-table {
|
||||
height: 260px;
|
||||
/* 固定表头样式 */
|
||||
.custom-table-header {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
color: #ffffff;
|
||||
background: rgba(0, 60, 120, 1);
|
||||
}
|
||||
|
||||
.custom-table thead {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.custom-table th {
|
||||
.custom-table-header th {
|
||||
background: rgba(0, 60, 120, 1);
|
||||
color: #00ffff;
|
||||
font-weight: bold;
|
||||
@ -692,6 +647,43 @@ export default {
|
||||
padding: 12px 5px;
|
||||
border-bottom: 1px solid rgba(0, 255, 255, 0.3);
|
||||
font-size: 13px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* 内容区域样式 */
|
||||
.table-content-wrapper {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
max-height: calc(100% - 44px); /* 减去表头高度 */
|
||||
}
|
||||
|
||||
/* vue-seamless-scroll样式 */
|
||||
.table-scroll {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 隐藏滚动条 */
|
||||
.table-scroll::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.table-scroll {
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
|
||||
.custom-table-body {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.custom-table-body td {
|
||||
text-align: center;
|
||||
padding: 10px 5px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
font-size: 13px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.custom-table td {
|
||||
@ -790,6 +782,42 @@ export default {
|
||||
}
|
||||
|
||||
/* 响应式调整 */
|
||||
/* 1920*1080分辨率专属样式 */
|
||||
@media (min-width: 1920px) and (min-height: 1080px) {
|
||||
.workorder-online-card {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.flip-card-label {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.flip-card-value {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.custom-table th,
|
||||
.custom-table td {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 大屏响应式调整 */
|
||||
@media (min-width: 1600px) and (max-width: 1919px) {
|
||||
.first-row {
|
||||
height: 220px;
|
||||
}
|
||||
|
||||
.flip-card-value {
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 中小屏响应式保持不变 */
|
||||
@media (max-width: 1400px) {
|
||||
.first-row {
|
||||
flex-direction: row;
|
||||
|
||||
@ -61,8 +61,8 @@ export default {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
background: linear-gradient(135deg, #001529 0%, #002140 100%);
|
||||
overflow: auto;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dashboard-border {
|
||||
@ -75,11 +75,15 @@ export default {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 优化轮播项样式 */
|
||||
/* 优化轮播项样式 - 1920*1080适配 */
|
||||
.el-carousel__item {
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
min-height: 100vh;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* 确保指示器清晰可见 */
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user