折线图看着是 ECharts 里最基础的一类图表但把它放进 Vue 项目之后翻车的方式会突然变得五花八门静态 HTML 里跑得好好的配置搬进单文件组件就变成一片空白路由切回来图表宽度只剩 100px接口数据明明更新了曲线纹丝不动。这些问题八成不是 ECharts 的锅也不是 Vue 的锅而是两者的生命周期没有对齐——ECharts 需要一个有真实宽高的 DOM 才能初始化而 Vue 的模板渲染、响应式更新、组件销毁都有自己的节奏节奏没踩上图就出不来。这篇内容围绕 Vue 与 ECharts 折线图展开讲清楚三件事一个能稳定运行的折线图组件应该怎么搭折线图的配置项里哪些参数真正决定观感以及我在真实项目里踩过的几个坑完整的排查链路是什么样。适合已经会写 Vue 组件、但一碰到图表就反复试错的开发者也适合刚接手可视化大屏、需要快速把折线图做稳的人。下面的代码以 Vue 3 组合式 API 为主涉及 Vue 2 的差异我会单独标出来。1. 折线图在 Vue 里画不出来问题通常出在哪三个环节1.1 三种引入方式决定了你的构建体积和调试手感在 Vue 项目里用 ECharts落地方式基本就三种选错一种会让后面的调试成本翻倍。第一种是通过script标签引入全量包把echarts挂到全局。这种方式在早期 jQuery 时代的页面里很常见进到 Vue 单文件组件里就变成组件内部去读window.echarts。它的问题不在于写法难看而在于类型提示全丢、打包工具无法分析依赖、版本升级时没有任何编译期保护。除非你在做一个嵌入到别人页面的小挂件否则不建议。第二种是 npm 全量安装后import * as echarts from echarts。这是绝大多数教程的写法好处是所有图表、所有组件都在想换柱状图、饼图、地图都不用改引入。代价是打包体积——全量包未压缩接近 1MBgzip 之后也在 300KB 上下对于只画一条折线图的页面来说这里面的地图、关系图、桑基图全是白付的流量。第三种是按需引入只注册折线图真正用到的那几个模块引入方式大致体积gzip 量级适合场景主要代价CDN 全局变量不占业务包体积嵌入型小挂件无类型、无法 tree-shakingnpm 全量引入300KB 左右图表类型多、迭代快首屏体积偏大npm 按需引入100KB 以内仅折线相关图表类型固定的业务页新增图表类型要改注册代码按需引入的写法在 ECharts 5 里已经比较统一从echarts/core拿use和init图表类型从echarts/charts拿坐标轴、提示框这些从echarts/components拿渲染器从echarts/renderers拿。折线图最少需要LineChart、GridComponent、TooltipComponent、LegendComponent和CanvasRenderer。这里有个细节GridComponent不是网格它承载的是直角坐标系的布局折线图没注册它坐标轴就不会出现很多人第一次按需引入时图能画出来但一根轴线都没有就是漏了它。1.2 init 必须放在挂载之后created 里拿不到容器ECharts 的init需要一个真实的 DOM 节点这个节点必须已经在文档里、并且能读到一个非零的宽高。Vue 2 里对应mountedVue 3 组合式 API 里对应onMounted。放在created里this.$refs.chart是undefined因为模板还没渲染成真实节点放在setup顶层直接执行ref 也还是空的。这里要区分模板里用v-if还是v-show。v-if在条件为假时节点根本不存在你在onMounted里拿到的ref.value就是null直接init(null)会报错。如果容器的显示与否由数据控制优先用v-show让 DOM 一直在只是display: none。但display: none又带来第二个问题此时offsetWidth是 0初始化出来的画布尺寸会走 ECharts 的兜底逻辑。所以正确的顺序是容器可见之后再 init而不是挂载了就 init。nextTick在这里的角色是等一次 DOM 更新完成。当你在数据变化后需要重新测量容器尺寸或者重新设置 option 时await nextTick()之后再操作是稳妥的做法。它不解决容器本身没宽高的问题只解决DOM 还没同步完的问题这两件事不要混为一谈。1.3 容器没有显式高度是白屏的第一嫌疑ECharts 的容器拿不到高度时控制台会打出一句很明显的警告Cant get dom width or height。它的兜底行为是给一个默认尺寸所以你看到的不是报错崩溃而是一张尺寸诡异、内容可能完全看不见的图。这类问题最常见的三个来源容器只写了width: 100%没写高度块级元素高度由内容决定而内容是一张 canvas形成循环依赖最终塌成 0。容器高度写成height: 100%但它的父元素没有确定高度百分比高度会退化成auto。卡片用了flex布局子项没设min-height或者被flex-shrink压扁。排查方法很直接在init之前把offsetWidth和offsetHeight打出来。console.log(chartRef.value.offsetWidth, chartRef.value.offsetHeight)两个都是 0就别急着调 option先把 CSS 的尺寸链路捋直。常用的稳妥写法是给容器一个确定的高度值或者给一条明确的链路外层卡片定高内部容器height: 100%同时给容器加min-height兜底。2. 搭一个能长期维护的折线图组件2.1 依赖安装与按需注册的完整代码先把依赖装上ECharts 本身没有额外的 peer 依赖直接装即可。npm install echarts如果你用的是 Vue 3 Vite装完直接在组件里按需引入就行。为了避免每个组件都写一遍use(...)建议单独开一个文件做统一注册项目里所有图表组件都从这里引入init// src/utils/echarts.js import * as echarts from echarts/core import { LineChart } from echarts/charts import { GridComponent, TooltipComponent, LegendComponent, DataZoomComponent } from echarts/components import { CanvasRenderer } from echarts/renderers echarts.use([ LineChart, GridComponent, TooltipComponent, LegendComponent, DataZoomComponent, CanvasRenderer ]) export default echarts这样做的好处是注册逻辑只有一份后面要加柱状图或者数据缩放只改这一个文件。DataZoomComponent属于折线图场景里很容易被低估的一个组件当横轴是时间、数据点多于三五十个的时候一个可拖拽的缩放条能救回很多可读性而且它的代码量几乎为零。2.2 实例该放在哪shallowRef 还是 markRaw这一点是 Vue 3 用户最容易忽略的。ECharts 的实例是一个结构非常复杂的对象内部有大量循环引用。如果把它放进ref()或者reactive()Vue 会给它套一层 Proxy 做深度响应式代理结果是性能下降严重时会在setOption或resize的过程中出现难以定位的异常。正确做法是两种情况二选一用shallowRef存实例它只代理第一层不会递归进去或者用markRaw把实例标记为永不代理。我个人的习惯是shallowRef因为语义更清楚——我确实需要一个响应式的引用变量只是不需要它内部响应。Vue 2 里对应的问题是不要把实例挂在data()里返回因为 Vue 2 会递归Object.defineProperty整个对象。放在this.$options上或者干脆用组件实例的一个非响应式属性比如在created里this.chart null但不在data中声明都可以。template div refchartRef classline-chart/div /template script setup import { ref, shallowRef, onMounted, onBeforeUnmount, watch, nextTick } from vue import echarts from /utils/echarts const props defineProps({ categories: { type: Array, default: () [] }, seriesData: { type: Array, default: () [] } }) const chartRef ref(null) const chart shallowRef(null) /script style scoped .line-chart { width: 100%; height: 360px; min-height: 240px; } /style样式里那个min-height不是可有可无的装饰。在响应式布局里窄屏或者侧边栏展开的瞬间容器宽度可能被压到极小的值ECharts 在初始化时读到的宽度会直接影响初始画布尺寸min-height至少保证了高度这一维不会塌陷。2.3 初始化、数据下发、销毁的完整时序一个稳定的折线图组件生命周期可以归纳为三段挂载时初始化、数据变化时更新、卸载时销毁。这三段各写错一处就会出现第一次进来正常第二次进来图叠在一起这种典型症状。onMounted(() { if (!chartRef.value) return chart.value echarts.init(chartRef.value) chart.value.setOption(buildOption(props.categories, props.seriesData)) window.addEventListener(resize, handleResize) }) function handleResize() { chart.value?.resize() } watch( () [props.categories, props.seriesData], async () { await nextTick() chart.value?.setOption(buildOption(props.categories, props.seriesData)) }, { deep: true } ) onBeforeUnmount(() { window.removeEventListener(resize, handleResize) chart.value?.dispose() chart.value null })这段代码里有三个刻意的选择。init之后立刻setOption而不是等 watch 触发是为了让空数据状态也有坐标系骨架用户不会看到一段时间的绝对空白。watch里先await nextTick()是因为数据变化后如果同时伴随 DOM 结构变化比如图例文案影响了布局需要等 DOM 稳定后再重绘。dispose之前先移除 resize 监听是因为监听回调里访问了chart.value如果先 dispose 再移除或者忘了移除组件卸载后回调仍然在跑会拿着一堆已销毁的实例做无用功。2.4 把 option 拆出去别让它长在组件里折线图的 option 一旦涉及双 Y 轴、数据缩放、格式化函数二三十行是常态再叠上复杂度更高的场景几十行也正常。把它直接写在setOption的括号里组件的可读性会迅速崩坏而且每次改动都要在模板和配置之间来回跳。我现在的习惯是单独一个buildOption函数甚至单独一个options/line.js文件函数只接收纯数据返回纯配置export function buildOption(categories, seriesData) { return { grid: { left: 8, right: 16, top: 40, bottom: 8, containLabel: true }, tooltip: { trigger: axis, axisPointer: { type: line } }, legend: { top: 8, icon: roundRect, itemWidth: 12, itemHeight: 6 }, xAxis: { type: category, boundaryGap: false, data: categories, axisTick: { show: false }, axisLine: { lineStyle: { color: #dcdfe6 } } }, yAxis: { type: value, scale: true, splitLine: { lineStyle: { color: #f0f2f5 } } }, series: seriesData.map((item) ({ name: item.name, type: line, smooth: true, showSymbol: false, lineStyle: { width: 2 }, data: item.data })) } }把配置抽成纯函数还有个隐性收益它天然可测试。你不必启动整个 Vue 组件只要给函数喂两组数据断言返回对象的series.length和xAxis.data长度一致就能覆盖掉一大类低级错误。图表组件的单元测试一向难写但这个边界切出来之后配置逻辑是完全可以脱离浏览器环境验证的。3. 折线图配置项里真正影响观感的几个参数3.1 xAxis 的 boundaryGap 与刻度密度boundaryGap这个参数在折线图里几乎必调。类目轴的默认值是true意思是数据点两侧留出半个类目宽度的空白画出来的折线两端会各空一截看起来像是没画满。折线图通常表示连续趋势所以惯例是设成false让首尾数据点贴着轴线两端。刻度密度是第二个高频问题。横轴有三十个日期时默认的interval: auto会尽量都显示结果就是标签挤成一团、互相重叠。ECharts 5 提供了hideOverlap: true让重叠的标签自动隐藏这是成本最低的解法。如果横轴是较长的文本比如2024年第三季度那就要改成倾斜xAxis: { type: category, boundaryGap: false, axisLabel: { hideOverlap: true, rotate: 30, margin: 12 } }旋转角度不建议超过 45 度超过之后阅读成本急剧上升读者的头要歪着看。真到了那个程度说明横轴标签太长或者数据点太密正确的做法不是继续转而是缩短标签formatter里做截断或者上dataZoom让用户自己控制显示区间。还有一个容易混淆的点xAxis.type用category还是time。如果横轴是等间隔的日期每天一个点、每月一个点category更省事标签内容自己拼。如果数据点之间的时间间隔不均匀比如有的订单间隔三小时、有的间隔五天那必须用time类型否则折线会把不均匀的时间拉成均匀的视觉距离趋势是失真的。这个失真在业务图表里是很危险的看的人会得出错误结论。3.2 多折线与双 Y 轴的对应关系多条折线有两种组织方式。如果各条线的量纲一致比如都是访问量直接往series数组里塞多个对象配好namelegend会自动按name生成。如果量纲完全不同比如一条是订单量几百的量级一条是转化率零点几的量级画在同一个 Y 轴上转化率那条线会被压成贴着零轴的一条直线完全看不出波动。这时候需要双 Y 轴。核心是两件事yAxis变成数组每个series用yAxisIndex指明绑哪一根。yAxis: [ { type: value, name: 订单量, scale: true, splitLine: { lineStyle: { color: #f0f2f5 } } }, { type: value, name: 转化率, scale: true, axisLabel: { formatter: {value}% }, splitLine: { show: false } } ], series: [ { name: 订单量, type: line, yAxisIndex: 0, data: orderData }, { name: 转化率, type: line, yAxisIndex: 1, data: rateData } ]两个细节值得注意。右侧 Y 轴要关掉splitLine否则两组网格线交错背景会变得很花反而干扰读数。scale: true让 Y 轴不从 0 开始而是根据数据范围自动缩放这在数据波动范围远离零点时非常有用比如数值在 980 到 1020 之间浮动从 0 开始会让整条线看起来是平的scale: true能把这段波动放大出来。但要小心scale: true会改变视觉上的陡峭程度如果图表要用于对比不同时间段的涨幅最好固定min和max避免两个页面因为自动缩放产生误导。3.3 tooltip 的内容组织与自动换行折线图的tooltip几乎都用trigger: axis鼠标在横轴某个位置悬停时一次性展示该位置上所有系列的值。这是折线图相比柱状图的体验优势因为折线的数据点是稀疏的用户很难精准命中某一个点。默认的提示内容是系列名: 数值多系列时会自动列表展示。如果每个系列的数值需要带单位、需要做千分位、或者需要在一行里塞下很长的业务文案就要自己写formatter。ECharts 5 还提供了valueFormatter只处理数值部分不用接管整个模板比较适合只要加个单位这种轻度定制tooltip: { trigger: axis, valueFormatter: (value) ${value} 单, axisPointer: { type: line, lineStyle: { type: dashed } } }关于 tooltip 自动换行这是个被问得很多的点。默认情况下tooltip的容器会用white-space: nowrap保证内容不折行所以长文本会直接撑宽整个提示框宽到超出屏幕边缘。想要换行有两条路一条是在formatter返回值里主动插入br/自己控制折行位置另一条是通过extraCssText放开换行限制tooltip: { trigger: axis, extraCssText: max-width: 320px; white-space: normal; word-break: break-all; }我一般两者结合用extraCssText设最大宽度兜底用formatter控制关键字段的换行位置保证名称和数值不会被拆到两行的两头。3.4 smooth、areaStyle、symbol 该不该开symbol控制数据点的显示。数据点少于二十个时建议开启让用户能看清每个具体的采样位置数据点几十上百时一定要showSymbol: false否则一排圆点连成一条粗线趋势反而看不清。这个参数是性能与可读性的双重考虑不是审美问题。smooth: true是平滑曲线。它让折线变成贝塞尔曲线视觉上柔和很多但要注意它会改变数值的呈现——曲线在两点之间可能凸出到实际数据范围之外出现过冲。如果图表用于精确读数比如财务报表、监控指标建议保持smooth: false用直角折线忠实反映数据。我做业务报表时的默认值是false只有在大屏展示、趋势示意类的场景才开平滑。areaStyle是面积填充通常配合单个系列使用能强调总量的感觉。多系列同时开面积填充时后画的会盖住先画的必须设置opacity比如areaStyle: { opacity: 0.15 }否则下面那层就完全看不见了。另外面积填充配合渐变色比纯色观感更好但要克制一屏里超过两条带面积的折线视觉噪音就开始明显了。4. Vue 生命周期与 ECharts 实例的三次对账4.1 数据更新后为什么曲线不动接口返回了新数据图还是老的这个问题的排查要分层看。第一层数据本身有没有变成响应式的。如果你是在onMounted里直接发请求、拿到结果赋给一个普通变量不是ref/reactivewatch永远不会触发。这个错误在组合式 API 里很常见因为let data []和const data ref([])写起来只差几个字符但行为完全不同。第二层watch有没有监听对对象。监听props.seriesData这种数组或对象如果不加deep: true只有当引用被整体替换时才会触发如果父组件是往同一个数组里push引用没变watch不响应。我一般直接监听() [props.categories, props.seriesData]这种返回数组的 getter配合deep: true覆盖得更全面。第三层setOption的合并行为。ECharts 默认是合并模式新传入的 option 会和旧的做融合。这在大多数时候是好事比如只更新series.data时xAxis的配置不用重复传。但它也有陷阱如果前后两次数据的系列数量不同原来三条线新数据只有两条合并模式下第三条线的配置还在可能会残留。切换数据维度的场景建议显式用完全替换chart.value?.setOption(buildOption(categories, series), { notMerge: true })notMerge: true会丢掉所有旧配置代价是每次都要传完整 option好处是状态绝对干净。我的经验是同一套图表结构下更新数据用默认合并切换图表形态或系列结构时用notMerge: true。4.2 dispose 漏了会怎样组件卸载时不调disposeECharts 实例不会被回收它内部注册的事件监听、定时器、以及那张 canvas 都还挂在内存里。单次影响不明显但在单页应用里反复进出同一个页面实例会越积越多表现为页面越用越卡最后浏览器标签页内存飙升。更隐蔽的一种症状是重复 init。如果同一个 DOM 节点被init了两次ECharts 会提示There is a chart instance already initialized on the dom而且第二个实例会覆盖第一个第一个就永远泄漏了。这种问题常出现在 keep-alive 缓存加onActivated里重复初始化的写法上。所以销毁这一步必须写全移除所有手动绑定的事件监听、dispose实例、把持有实例的变量置空。变量置空这一步不是多此一举它切断了闭包对外部实例的引用让垃圾回收能真正生效。4.3 resize 的三种触发源图表的宽高变化通常来自三个地方浏览器窗口尺寸变化、页面布局变化侧边栏折叠、面板拖拽、以及容器自身的尺寸变化比如被放进一个可调整的弹窗。只监听window.resize只能覆盖第一种。侧边栏折叠不会改变窗口尺寸但容器宽度变了图表会被拉伸变形——具体表现是 canvas 还是原来的宽度但被 CSS 拉伸了看起来文字模糊、比例失调。ECharts 5 支持直接传入ResizeObserver能覆盖后两种let observer null onMounted(() { chart.value echarts.init(chartRef.value) observer new ResizeObserver(() { chart.value?.resize() }) observer.observe(chartRef.value) }) onBeforeUnmount(() { observer?.disconnect() observer null chart.value?.dispose() })这里必须注意一点ResizeObserver的回调会在容器尺寸变化时频繁触发resize()本身有一定的计算开销。如果布局变化的动画时间较长比如侧边栏 300ms 的过渡回调会被调用几十次。稳妥的做法是加一个节流或者用requestAnimationFrame把同帧内的多次调用合并成一次。4.4 keep-alive 与标签页切换下的图表被keep-alive缓存的组件切走时走deactivated切回来走activatedonMounted只执行一次。这意味着两件事切回来时不会重新初始化如果图表在切走期间因为布局变化尺寸变了你还得在activated里调一次resize()如果图表在切走时是display: none状态初始化的尺寸可能是错的。我处理这类场景的固定套路是onMounted里负责 initonActivated里只做两件事——nextTick之后resize()以及必要时重新setOption一次。绝对不要在onActivated里重新init那会命中上面说的重复初始化问题。还有一种更隐蔽的情况图表的容器在一个默认隐藏的弹窗或折叠面板里。隐藏状态下offsetWidth为 0初始化出来的图尺寸是错的弹窗打开后再resize()大多数情况下能修正但如果初始化时走的是 ECharts 的 100×100 兜底逻辑resize()也不一定能把比例恢复正确。稳妥的做法是等容器真正可见之后再初始化用v-if控制图表组件的挂载或者监听弹窗的打开事件延迟初始化。5. 几个真实踩过的坑与完整排查链路5.1 图表宽度只剩 100px这是我遇到次数最多的一个问题值得把排查链路完整写一遍。现象页面首次进入时图表正常从其他路由切回来之后折线图被压缩成很窄的一条高度也不对。第一步确认是不是 100×100 兜底。打开控制台量一下 canvas 的实际尺寸如果是 100×100 或者接近这个值基本可以确定初始化时容器尺寸是 0走的是 ECharts 的默认值。这一步很关键它把样式问题和初始化时机问题区分开了。第二步确认容器在初始化那一刻的尺寸。在init之前打印offsetWidth/offsetHeight同时在浏览器开发者工具里检查容器的盒模型。如果打印出来是 0说明时机不对如果打印出来是正常的数值但 canvas 还是 100那问题在别处。第三步找时机问题的来源。我的案例里罪魁祸首是路由切换动画。新页面在过渡动画期间被设置了一个缩放或透明度变化的类名某些情况下容器处于不可见状态而 ECharts 的init恰好在这个时间窗口里执行了。解决办法是把初始化时机往后挪在过渡结束之后或者干脆在onMounted里加一次requestAnimationFrame。第四步加兜底。不管排查结果是什么我都会在组件里保留一个可见性修正逻辑如果init时读到的尺寸为 0就先不初始化等下一次resize事件或者ResizeObserver反馈容器有尺寸了再初始化。这段兜底代码救过我很多次尤其是在别人维护的布局组件里你无法保证容器的可见时机。提示判断容器是否可用的标准不是节点存在而是offsetWidth 0 offsetHeight 0。这两个条件同时满足ECharts 才可能画对。5.2 数据更新了但曲线不动按三层排查这个问题的排查顺序我固定为响应式 → watch → setOption。先看数据源是不是响应式的在watch回调里打一行日志看回调有没有触发。没触发就是响应式的问题检查是不是用了普通变量或者是不是在修改一个没有声明为响应式的对象属性。触发了但图没变往第二层看setOption有没有被调用调用时传的 option 里series的data是不是新数组。如果data指向的还是旧数组的引用ECharts 会认为没有变化。这种问题常出现在拿到新数据后往旧数组里赋值的写法上。第三层就是合并策略。同一套系列结构下默认合并通常没问题但如果你之前的setOption里传过series的某个属性比如lineStyle新数据没传旧值会保留。想让状态完全确定就上notMerge: true。5.3 数据点上千时的卡顿处理折线图的性能瓶颈基本都在数据点数量上。两三百个点以内Canvas 渲染毫无压力上到几千个点尤其是多条线叠加就会出现明显的渲染卡顿和交互延迟。我在一个监控场景里处理过两万点的折线图最终的方案是三层叠减。第一层是开sampling: lttb这是 ECharts 内置的降采样算法会在保留趋势特征的前提下大幅减少参与绘制的点数视觉上几乎看不出差异。第二层是关掉动画animation: false大数据量下动画的开销远超收益而且动画过程中用户想交互也点不准。第三层是加dataZoom限制默认展示的区间让用户按需放大而不是一次性把所有点都画在屏幕上。series: [{ type: line, smooth: false, showSymbol: false, sampling: lttb, animation: false, data: bigData }]还有一点值得提醒数据量大时不要开smooth平滑曲线的计算量比直角折线大而且降采样之后的点再平滑视觉收益很小。showSymbol: false在大数据量下也是必须的几万个圆点的绘制开销相当可观。5.4 暗色模式切换与 pxtorem 对 canvas 无效主题切换的需求在管理后台里很常见。ECharts 的配置项是 JS 对象CSS 变量对它无效所以你不能靠切换一个 CSS class 来让图表变暗。我的做法是把颜色抽成一套常量切换主题时重新调用buildOption并setOption同时把坐标轴、分割线、文字颜色一起换掉。注意setOption合并模式下颜色会被覆盖所以这里用notMerge: true更干净。ECharts 本身也支持注册主题用echarts.registerTheme(dark, themeObj)然后init时传入主题名但这种方式在运行时切换需要先dispose再重新init成本更高。运行时的灵活切换我还是更倾向走配置重设这条路。另一个高频疑问是项目里用了postcss-pxtorem为什么 ECharts 里的字号没跟着变原因很直接——pxtorem处理的是 CSS 文件里的px单位而 ECharts 的fontSize是写在 JS 对象里的数字根本不经过 PostCSS。所以你在 option 里写fontSize: 12它永远是 12 个 CSS 像素不会随根字号缩放。解决办法是自己在 JS 里做换算。读取根元素字号按设计稿基准比如 16px算出缩放比例把这个比例乘到需要缩放的数值上function px(size) { const rootFontSize parseFloat( getComputedStyle(document.documentElement).fontSize ) return size * (rootFontSize / 16) } // 使用 axisLabel: { fontSize: px(12) }这套换算要把坐标系里的fontSize、itemWidth、lineStyle.width、symbolSize这些都过一遍工作量不小但比把所有字号写死要靠谱得多。如果项目对移动端适配要求不高另一种更省事的做法是干脆不改 ECharts 的字号让它固定像素值只保证容器尺寸是弹性的多数后台图表这样处理就够了。我个人在这个问题上的体会是ECharts 的适配几乎没有银弹pxtorem这类自动方案到 canvas 边界就失效了必须手工接管。与其后期到处救火不如在项目一开始就把px()这个换算函数放在工具目录里约定所有图表配置里的尺寸数值都走它。这样即使后面设计稿基准变了改一处就能全局生效。
阅读完成 · 觉得有帮助?