移动端适配规范(Vue 3 + Element Plus + ECharts)
两条核心原则
- 内容密度高:小屏尽量多展示。收窄外边距、图表满宽、表格列紧凑、摘要卡片多列。
- 绝不遮挡:ECharts 轴标签/图例不重叠不裁切;表格横滑不出现固定列覆盖;悬浮元素(tooltip/toolbox/dataZoom/visualMap)不压数据。
断点统一用 768px(手机)与 959/992px(平板过渡)。
1. 断点检测:统一用 useIsMobile
CSS @media 改不到的地方(ECharts option、el-dialog 宽度、el-table 列 :fixed/:min-width、el-descriptions 列数、图表高度)必须用 JS 响应式判断。不要每个组件各写一份 window.innerWidth + resize 监听。
// src/composables/useIsMobile.ts —— 已存在,直接复用
import { useIsMobile } from '@/composables/useIsMobile'
const { isMobile } = useIsMobile() // 基于 matchMedia('(max-width: 768px)') 的只读响应式 ref
组件里凡是 option 依赖 isMobile 的,用 computed 或在 watch(isMobile, ...) 里重算 + nextTick(() => chart?.resize())。
2. 全局 CSS(已在 src/assets/main.css 落地,新组件自动继承)
@media (max-width: 959px) / (max-width: 480px) 已统一处理,新页面通常无需重复写:
el-main.main-content去掉默认 20px 内边距;子元素内边距 8px(≤480 为 4px)——外框窄、内容贴边。el-card__header/__body内边距收窄(10px / ≤480 为 8px)。el-table字号 12px,单元格.cellpadding0 4px,行 padding6px 0;横滑-webkit-overflow-scrolling: touch。el-form--inline在移动端变单列栅格,el-select/el-input/...宽度 100%。el-dialog:not(.is-fullscreen)强制width:100%贴顶——超宽弹窗(如width="1400px")无需单独改,全局兜底为全屏。- 弹窗内
el-row > el-col自动堆叠为整宽。 el-pagination隐藏 sizes/jumper/total,仅留翻页。
页面根节点自定义 padding 会被全局 !important 覆盖,不用纠结各页 padding 值。
3. ECharts 图表:消除遮挡(最重要)
chart.resize() 只缩放画布,不会重算 option,所以旋转/间隔/图例问题必须在 option 层按 isMobile 重算。每个图表都要做:
const mobile = isMobile.value
option = {
tooltip: { confine: true, /* ... */ }, // 防 tooltip 溢出屏幕
legend: { type: 'scroll', top: mobile ? 26 : 40 }, // 多系列不换行压图
grid: mobile
? { left: 6, right: 10, top: 48, bottom: 56, containLabel: true } // 收紧边距
: { /* 桌面原值 */ },
xAxis: {
axisLabel: {
rotate: mobile ? 45 : 0,
hideOverlap: true, // 关键:自动隐藏重叠标签
interval: 'auto',
fontSize: mobile ? 10 : 12,
// 日期去年份省宽:'2026-07-11' -> '07-11'
formatter: mobile ? (v) => (v?.length >= 10 ? v.slice(5) : v) : undefined,
},
},
yAxis: { name: mobile ? '' : '原轴名', axisLabel: { fontSize: mobile ? 10 : 12 } }, // 移动端省略长轴名
toolbox: { show: !mobile }, // 移动端隐藏 toolbox,避免压 legend
dataZoom: mobile
? [{ type: 'inside' }, { type: 'slider', height: 14, bottom: 4 }] // 滑块独立留高,不压 x 轴标签
: [/* 桌面原值 */],
}
watch(isMobile, () => { if (chart) { /* 重建 option */ ; nextTick(() => chart.resize()) } })
图表高度:折线/柱状移动端降到 ~300px(isMobile ? '300px' : '420px'),单屏能同时看到图和下方表格。
热力图专项(最易遮挡):
- grid 左右边距在移动端大幅收紧(桌面
left:140之类会吃掉大半屏)。 visualMap移动端改orient:'horizontal'放底部{ left:'center', bottom:2, itemWidth:12, itemHeight:70 },为矩阵腾横向空间。- 隐藏格内数字
series.label.show = !mobile,数值改由 tooltip 展示。 - 长行业名
yAxis.axisLabel: { width: 52, overflow: 'truncate' }。 - 去掉桌面用的顶部/右侧镜像轴(移动端只保留主轴)。
通用图表容器(如 HeatmapChart.vue):用 ResizeObserver 观察容器而非只监听 window.resize,这样侧栏收起、Tab 切换、弹窗列堆叠导致的宽度变化也能重排。
易踩坑:不要把 window.addEventListener('resize', ...) 写在 loadData() 里(每次拉数据重复注册),放 onMounted。
4. 表格:横滑 + 紧凑,移动端不要固定列
教训:
fixed="left"在移动端横滑时会与滚动内容重叠。移动端一律取消固定列,改为纯横向滚动。
<!-- 固定列:桌面固定,移动端取消 -->
<el-table-column type="index" label="#" :width="isMobile ? 34 : 50" :fixed="isMobile ? false : 'left'" />
<el-table-column prop="name" label="名称" :min-width="isMobile ? 92 : 180" :fixed="isMobile ? false : 'left'" />
<!-- 右侧操作列同理 :fixed="isMobile ? false : 'right'" -->
列宽收窄(让一屏多显示几列,对齐 legulegu 观感):所有 min-width 按 isMobile 给更小值。经验值:序号 34、名称/代码类 ~92、纯数值 ~68-78、带进度条的 RPS 单元 ~118。
<el-table-column :min-width="isMobile ? 78 : 140" />
<el-table :height="isMobile ? undefined : 640" :max-height="isMobile ? 560 : undefined" />
- 表格高度:移动端
height设undefined(或用max-height),避免与页面双重滚动。 - 单元格若有
.rps-cell之类,不要设死min-width,让其随列收缩。 - 组件里的表格(非 view)同样适用;确认没有遗留的硬编码
fixed="left"。
5. 筛选器 / 头部 / 工具栏
el-form :inline→ 全局已处理为堆叠;写死style="width:200px"的 select 会被全局 100% 覆盖,但新代码尽量别写死宽度。- 标题 + 筛选器并排的
.header-controls(justify-content:space-between)必须加移动端堆叠:@media (max-width: 768px) { .header-controls { flex-direction: column; align-items: stretch; gap: 8px; } } - 复用组件里写死
min-width:220px的下拉(如IndustryFilter),移动端要min-width:0; width:100%。 - 快捷按钮组(时间范围等)移动端
flex-wrap或网格grid-template-columns: repeat(3,1fr),不要justify-content:flex-end右挤。 - 摘要卡片网格移动端用 2 列(
repeat(2,minmax(0,1fr)))提升密度,别用单列。 - 为单行布局调的负 margin(如
margin-bottom:-18px)在换行后会遮挡,移动端要归零。
6. K 线 / 趋势弹窗规范
点击名称弹出的趋势 K 线图,统一:
- 去外框:包裹图表的
el-card换成普通<div>(v-loading照常可用),去掉卡片边框/阴影/内边距,图表铺满。 - 默认最近 2 个月:快捷区间加「最近2月」并设为默认;区间计算按月而非按年:
type TrendShortcut = '2m' | '1y' | '3y' | '5y' const monthMap = { '2m': 2, '1y': 12, '3y': 36, '5y': 60 } start.setMonth(end.getMonth() - monthMap[range]) const trendShortcut = ref<TrendShortcut>('2m') // 默认值 + 打开弹窗时的 reset 都用 '2m' - 图表高度
:height="isMobile ? '300px' : '420px'"。 - 弹窗宽度不用手动改,全局已强制移动端全屏。
参考实现:StockSwingPracticeView.vue(内联弹窗)、components/MajorIndexTrendDialog.vue(独立弹窗组件)。
7. 改造流程(对现有页面做移动端优化时)
- 排查:文件多时用子 agent 并行审计,产出「问题 + 修复建议」清单,避免占用主上下文。重点找:图表 option 是否随
isMobile重算、fixed列、写死的 px 宽度、header-controls是否换行、弹窗宽度、内联表单。 - 分层改:基础设施(
useIsMobile/全局 CSS)→ 图表遮挡 → 视图布局。每层改完验证。 - 验证:每批改动后跑
无法真机截图,改完提示用户在 DevTools 375/390px + 真机确认:图表标签是否重叠、表格横滑无固定列覆盖、一屏列数、外边距、横竖屏切换重排。npx vue-tsc --build # 类型检查 npm run build # 生产构建(chunk-size 警告是既有的,非错误)
参考文件
- 断点:
src/composables/useIsMobile.ts - 全局样式:
src/assets/main.css - 折线/柱状图范例:
src/components/TrendChart.vue、IndexVolumeTrend.vue、AdlTrend.vue - 热力图范例:
src/components/IndustryBreadthAnalysis.vue、TurnoverHeatmap.vue、FundFlowHeatmap.vue、HeatmapChart.vue(ResizeObserver) - 表格/弹窗范例:
src/views/strategy/IndexRpsView.vue、src/views/markt_data/MajorIndexRpsView.vue、src/views/indival_stock_data/StockSwingPracticeView.vue