feat: 添加echarts依赖并实现折线图示例

refactor: 优化产线追溯页面样式和交互体验

fix: 修复画面超出屏幕问题和尺寸适配逻辑

docs: 添加相关功能实现计划和文档
This commit is contained in:
赵正易 2026-01-22 10:32:56 +08:00
parent 6edb64fbdb
commit 3780340ab7
10 changed files with 768 additions and 402 deletions

View File

@ -0,0 +1,54 @@
# 更好的尺寸变化适配方案
## 1. 问题分析
当前项目使用ResizeObserver监听窗口尺寸变化通过transform: scale实现大屏适配但存在以下问题
- ResizeObserver循环错误尤其是在对话框打开/关闭时
- 可能导致文字模糊和交互元素精准度问题
- 代码逻辑耦合在App.vue中不便于维护
## 2. 优化方案
### 2.1 方案选择
保留transform: scale的核心逻辑但优化实现方式解决ResizeObserver循环问题同时保持原有设计不变。
### 2.2 具体实现
1. **将尺寸适配逻辑封装成独立组件**
- 创建`src/components/ScreenAdapter.vue`组件
- 集中管理尺寸适配逻辑,便于维护和复用
2. **替换ResizeObserver为window.resize事件**
- 使用window.resize事件监听窗口变化
- 避免ResizeObserver导致的循环问题
3. **增强防抖和性能优化**
- 添加防抖处理,减少频繁触发
- 使用requestAnimationFrame确保DOM更新在浏览器重绘前完成
- 添加严格的尺寸变化检查
4. **优化缩放逻辑**
- 确保缩放操作不会触发新的尺寸变化
- 只有当实际需要时才更新DOM样式
5. **添加错误处理和边界情况处理**
- 处理极端尺寸情况
- 添加错误捕获机制
## 3. 实现步骤
1. 创建`ScreenAdapter.vue`组件
2. 实现优化后的尺寸适配逻辑
3. 在App.vue中使用新组件替换原有逻辑
4. 测试不同场景下的适配效果
5. 验证ResizeObserver循环错误是否解决
## 4. 预期效果
- 解决ResizeObserver循环错误
- 保持原有设计和布局不变
- 提高适配性能和稳定性
- 代码结构更清晰,便于维护
- 支持各种尺寸的屏幕适配
## 5. 技术要点
- Vue 3 Composition API
- 防抖函数优化
- requestAnimationFrame应用
- 组件化设计
- 性能优化

View File

@ -0,0 +1,50 @@
# 修复画面超出屏幕问题
## 1. 问题分析
- 画面超出屏幕的主要原因是当前的缩放逻辑存在问题
- App.vue中设置了固定的body和#app尺寸1920x1080
- ScreenAdapter组件的缩放逻辑可能计算错误
- 可能存在缩放比例方向错误的问题
## 2. 修复方案
### 2.1 方案选择
- 调整缩放逻辑,使用等比例缩放并限制最大缩放比例
- 确保内容始终在屏幕范围内
- 保持原有设计的视觉效果
### 2.2 具体实现
1. **修改ScreenAdapter组件**
- 更改缩放比例计算逻辑,使用等比例缩放
- 添加屏幕边界限制,确保内容不超出屏幕
- 优化缩放比例方向
2. **调整App.vue样式**
- 移除或调整固定的body和#app尺寸
- 确保根元素能自适应屏幕
3. **优化缩放策略**
- 使用最小缩放比例,确保内容完全显示
- 调整transform-origin确保内容居中显示
- 处理不同屏幕比例的适配
## 3. 实现步骤
1. 修改ScreenAdapter组件的缩放计算逻辑
2. 调整缩放比例,使用等比例缩放
3. 添加屏幕边界限制
4. 调整App.vue的样式设置
5. 测试不同屏幕尺寸下的显示效果
6. 验证画面是否不再超出屏幕
## 4. 预期效果
- 画面不再超出屏幕
- 保持原有设计的视觉效果
- 内容居中显示
- 适配不同尺寸的屏幕
- 保持良好的用户体验
## 5. 技术要点
- 等比例缩放计算
- 屏幕边界限制
- transform-origin调整
- 响应式设计
- 不同屏幕比例适配

View File

@ -0,0 +1,45 @@
# 恢复最初的尺寸适配模式
## 1. 问题分析
当前使用ScreenAdapter组件的样式出现问题需要恢复到最初的ResizeObserver实现模式。
## 2. 恢复方案
### 2.1 主要修改点
1. **移除ScreenAdapter组件**从App.vue中移除ScreenAdapter组件的引用和使用
2. **恢复ResizeObserver实现**恢复到最初的ResizeObserver监听窗口尺寸变化的实现
3. **恢复原始样式**将App.vue的样式恢复到最初的固定尺寸设置
4. **移除ScreenAdapter组件文件**删除不再使用的ScreenAdapter.vue文件
### 2.2 具体实现
1. **修改App.vue**
- 移除ScreenAdapter组件的引入和使用
- 恢复最初的ResizeObserver实现
- 恢复body和#app的固定尺寸设置
2. **删除ScreenAdapter.vue**
- 删除src/components/ScreenAdapter.vue文件
3. **恢复ResizeObserver逻辑**
- 使用最初的ResizeObserver实现但保留之前的优化如setTimeout打破循环
- 确保ResizeObserver不会导致循环错误
## 3. 实现步骤
1. 修改App.vue移除ScreenAdapter组件
2. 恢复ResizeObserver实现
3. 恢复原始样式设置
4. 删除ScreenAdapter.vue文件
5. 重启开发服务器测试
6. 验证样式是否恢复正常
## 4. 预期效果
- 样式恢复到最初的正常状态
- 保留之前对ResizeObserver循环错误的修复
- 画面不再超出屏幕
- 保持良好的用户体验
## 5. 技术要点
- Vue 3 Composition API
- ResizeObserver实现
- 样式恢复
- 组件移除
- 开发服务器测试

View File

@ -0,0 +1,55 @@
# 产线追溯页面优化方案
## 1. 问题分析
- **背景颜色**:当前页面使用深色背景(#2c3e50),需要改为淡色背景
- **文字样式**:文字颜色需要调整以适应淡色背景,确保可读性
- **详情按钮延迟**:点击详情按钮时存在延迟,需要优化异步操作
## 2. 优化方案
### 2.1 淡色背景实现
- 将主容器和相关组件的背景色改为淡色系(如#f5f7fa
- 保持标题和关键元素的视觉突出
- 调整边框和阴影,增强层次感
### 2.2 文字样式调整
- 更改文字颜色为深色,确保在淡色背景上的可读性
- 调整字体大小和行高,优化阅读体验
- 突出关键信息,如追溯码、状态等
### 2.3 详情按钮延迟优化
- 立即显示加载状态,提升用户感知
- 优化异步数据请求,减少等待时间
- 调整数据获取逻辑,优先显示关键信息
## 3. 实现步骤
1. **修改背景颜色**
- 更改.trace-container的background-color
- 调整.trace-search-form和.trace-results的背景色
- 修改对话框和其他组件的背景色
2. **调整文字样式**
- 修改标题和内容文字颜色
- 调整表单标签和输入框文字颜色
- 优化表格文字样式
3. **优化详情按钮**
- 在handleViewDetail函数中立即显示对话框
- 添加加载状态指示器
- 优化数据请求逻辑,并行获取数据
4. **测试和调整**
- 测试页面在淡色背景下的显示效果
- 验证文字可读性
- 检查详情按钮的响应速度
## 4. 预期效果
- 页面背景改为淡色系,视觉体验更舒适
- 文字清晰可读,关键信息突出
- 详情按钮点击后立即响应,显示加载状态
- 整体风格统一符合现代UI设计
## 5. 技术要点
- CSS变量或统一的颜色管理
- 异步操作优化
- 响应式设计原则
- 良好的用户体验设计

View File

@ -0,0 +1,21 @@
# 添加echarts依赖计划
## 1. 安装echarts依赖
- 使用yarn命令安装echarts`yarn add echarts`
- 这将在package.json中添加echarts依赖并安装到node_modules目录
## 2. 验证安装
- 检查package.json文件确认echarts已被添加到dependencies中
- 验证node_modules目录中是否存在echarts文件夹
## 3. 提供使用示例
- 创建一个简单的echarts组件示例展示如何在Vue 3项目中使用echarts
- 示例包括基本的图表初始化、数据配置和响应式更新
## 4. 测试运行
- 启动开发服务器,确保添加依赖后项目能正常构建和运行
## 5. 相关说明
- echarts完全兼容Vue 3
- 支持按需引入,可根据项目需要只引入必要的图表模块
- 提供了丰富的图表类型和配置选项,适用于各种数据可视化场景

View File

@ -15,6 +15,7 @@
"@types/lodash": "^4.14.149",
"axios": "^1.7.2",
"core-js": "^3.8.3",
"echarts": "^6.0.0",
"element-plus": "^2.7.7",
"mqtt": "^5.8.1",
"vue": "^3.2.13",

View File

@ -1,132 +1,133 @@
<template>
<router-view />
<router-view />
</template>
<script setup>
/// ===================== ============================
import { getCurrentInstance, onMounted, onUnmounted, ref, watch } from 'vue'
import { getCurrentInstance, onMounted, onUnmounted, ref } from 'vue'
const { proxy } = getCurrentInstance()
const screenWidth = ref(null)
const screenHeight = ref(null)
const resizeObserver = new ResizeObserver((entries) => {
if (entries.length > 0) {
const { width, height } = entries[0].contentRect
//
screenWidth.value = width
screenHeight.value = height
if (entries.length > 0) {
const { width, height } = entries[0].contentRect
//
screenWidth.value = width
screenHeight.value = height
// debounce
updateStyleDebounced()
}
// 使setTimeoutResizeObserver
setTimeout(() => {
updateStyleDebounced()
}, 0)
}
})
// debounce
const resizeTimeout = ref(null)
function debounce(func, wait) {
return function () {
clearTimeout(resizeTimeout.value)
resizeTimeout.value = setTimeout(func, wait)
}
return function () {
clearTimeout(resizeTimeout.value)
resizeTimeout.value = setTimeout(func, wait)
}
}
// transform
function updateStyle() {
const docWidth = document.documentElement.clientWidth
const docHeight = document.documentElement.clientHeight
const designWidth = 1920 // 稿
const designHeight = 1080 // 稿
const widthRatio = docWidth / designWidth
const heightRatio = docHeight / designHeight
document.body.style.transform = `scale(${widthRatio},${heightRatio})`
document.body.style.willChange = 'transform'
document.body.style.transformBox = 'fill-box'
document.body.style.transformOrigin = 'left top'
document.body.style.overflow = 'hidden'
//
setTimeout(function () {
const lateWidth = document.documentElement.clientWidth
const lateHeight = document.documentElement.clientHeight
if (lateWidth !== docWidth) {
const lateWidthRatio = lateWidth / designWidth
const lateHeightRatio = lateHeight / designHeight
document.body.style.transform = `scale(${lateWidthRatio},${lateHeightRatio})`
}
}, 10)
const docWidth = document.documentElement.clientWidth
const docHeight = document.documentElement.clientHeight
const designWidth = 1920 // 稿
const designHeight = 1080 // 稿
const widthRatio = docWidth / designWidth
const heightRatio = docHeight / designHeight
document.body.style.transform = `scale(${widthRatio},${heightRatio})`
document.body.style.willChange = 'transform'
document.body.style.transformBox = 'fill-box'
document.body.style.transformOrigin = 'left top'
document.body.style.overflow = 'hidden'
//
setTimeout(function () {
const lateWidth = document.documentElement.clientWidth
const lateHeight = document.documentElement.clientHeight
if (lateWidth !== docWidth) {
const lateWidthRatio = lateWidth / designWidth
const lateHeightRatio = lateHeight / designHeight
document.body.style.transform = `scale(${lateWidthRatio},${lateHeightRatio})`
}
}, 10)
}
const updateStyleDebounced = debounce(updateStyle, 150)
onMounted(() => {
proxy.$nextTick(() => {
//
resizeObserver.observe(document.documentElement)
proxy.$nextTick(() => {
//
resizeObserver.observe(document.documentElement)
//
screenWidth.value = document.documentElement.clientWidth
screenHeight.value = document.documentElement.clientHeight
})
//
screenWidth.value = document.documentElement.clientWidth
screenHeight.value = document.documentElement.clientHeight
})
// Signalr
SignalrStart()
})
onUnmounted(() => {
//
resizeObserver.disconnect()
// debouncetimeout
clearTimeout(resizeTimeout.value)
// resizeObserver.unobserve(document.documentElement);
//
resizeObserver.disconnect()
// debouncetimeout
clearTimeout(resizeTimeout.value)
})
/// ================================================================
/// ======================== Signalr ==============================
function SignalrStart() {
proxy.signalr.start().then(async (res) => {
// const params = { code: 200, message: '' }
// await proxy.signalr.SR.invoke('demo',params )
console.log('SignalrStart:', res)
})
proxy.signalr.start().then(async (res) => {
// const params = { code: 200, message: '' }
// await proxy.signalr.SR.invoke('demo',params )
console.log('SignalrStart:', res)
})
}
onMounted(() => {
SignalrStart()
})
/// =========================================================
</script>
<style lang="scss">
body {
margin: 0;
padding: 0;
width: 1920px;
height: 1080px;
margin: 0;
padding: 0;
width: 1920px;
height: 1080px;
}
#app {
width: 1920px;
height: 1080px;
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
background-color: #2c3e50;
// color:#fff;
width: 1920px;
height: 1080px;
font-family: Avenir, Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
background-color: #2c3e50;
// color:#fff;
}
.el-overlay,
.el-overlay-dialog {
width: 1920px;
height: 1080px;
width: 1920px;
height: 1080px;
}
nav {
padding: 30px;
padding: 30px;
a {
font-weight: bold;
color: #2c3e50;
a {
font-weight: bold;
color: #2c3e50;
&.router-link-exact-active {
color: #42b983;
}
}
&.router-link-exact-active {
color: #42b983;
}
}
}
.border {
border: 1px solid #000000;
border: 1px solid #000000;
}
.dialogBox {
transform: scale(1.5, 1.5);
margin: 0 auto;
transform-box: fill-box;
transform-origin: 0 0; /* 或者你想要的任何位置 */
transform: scale(1.5, 1.5);
margin: 0 auto;
transform-box: fill-box;
transform-origin: 0 0; /* 或者你想要的任何位置 */
}
</style>

View File

@ -0,0 +1,92 @@
<template>
<div class="echarts-container">
<h3>echarts示例 - 折线图</h3>
<div ref="chartRef" class="chart"></div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted, watch } from 'vue'
import * as echarts from 'echarts'
const chartRef = ref<HTMLElement | null>(null)
let chartInstance: echarts.ECharts | null = null
//
const chartData = ref({
xAxis: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
series: [
{
name: '销量',
data: [120, 200, 150, 80, 70, 110, 130],
type: 'line'
}
]
})
//
function initChart() {
if (chartRef.value) {
chartInstance = echarts.init(chartRef.value)
updateChart()
}
}
//
function updateChart() {
if (chartInstance) {
const option = {
title: {
text: '周销量统计'
},
tooltip: {
trigger: 'axis'
},
xAxis: {
type: 'category',
data: chartData.value.xAxis
},
yAxis: {
type: 'value'
},
series: chartData.value.series
}
chartInstance.setOption(option)
}
}
//
function handleResize() {
chartInstance?.resize()
}
onMounted(() => {
initChart()
window.addEventListener('resize', handleResize)
})
onUnmounted(() => {
window.removeEventListener('resize', handleResize)
chartInstance?.dispose()
})
//
watch(chartData, () => {
updateChart()
}, { deep: true })
</script>
<style scoped>
.echarts-container {
width: 100%;
height: 100%;
padding: 20px;
box-sizing: border-box;
}
.chart {
width: 100%;
height: 400px;
margin-top: 20px;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -3199,6 +3199,14 @@ easy-stack@1.0.1:
resolved "https://registry.npmmirror.com/easy-stack/-/easy-stack-1.0.1.tgz"
integrity sha512-wK2sCs4feiiJeFXn3zvY0p41mdU5VUgbgs1rNsc/y5ngFUijdWd+iIN8eoyuZHKB8xN6BL4PdWmzqFmxNg6V2w==
echarts@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/echarts/-/echarts-6.0.0.tgz#2935aa7751c282d1abbbf7d719d397199a15b9e7"
integrity sha512-Tte/grDQRiETQP4xz3iZWSvoHrkCQtwqd6hs+mifXcjrCuo2iKWbajFObuLJVBlDIJlOzgQPd1hsaKt/3+OMkQ==
dependencies:
tslib "2.3.0"
zrender "6.0.0"
ee-first@1.1.1:
version "1.1.1"
resolved "https://registry.npmmirror.com/ee-first/-/ee-first-1.1.1.tgz"
@ -6444,6 +6452,11 @@ ts-loader@^9.2.5:
semver "^7.3.4"
source-map "^0.7.4"
tslib@2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.0.tgz#803b8cdab3e12ba581a4ca41c8839bbb0dacb09e"
integrity sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==
tslib@^1.8.1:
version "1.14.1"
resolved "https://registry.npmmirror.com/tslib/-/tslib-1.14.1.tgz"
@ -6995,3 +7008,10 @@ yorkie@^2.0.0:
is-ci "^1.0.10"
normalize-path "^1.0.0"
strip-indent "^2.0.0"
zrender@6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/zrender/-/zrender-6.0.0.tgz#947077bc69cdea744134984927f132f3727f8079"
integrity sha512-41dFXEEXuJpNecuUQq6JlbybmnHaqqpGlbH1yxnA5V9MMP4SbohSVZsJIwz+zdjQXSSlR1Vc34EgH1zxyTDvhg==
dependencies:
tslib "2.3.0"