Skip to content

mock首页的接口与搭建首页顶部的静态与组件的拆分(Day11 1/2) ​

本页对应 day11,共 5 段视频,约 2 小时 14 分。

学习目标 ​

  1. 能使用 mockjs 创建首页假接口,配置状态码与返回数据,并在入口文件引入使 mock 生效,通过实际请求验证接口可用。
  2. 能为 mock 接口创建独立请求实例与 API 函数,正确设置基础路径并调用获取首页数据。
  3. 能清理首页无用结构,将首页顶部按上、中、下拆分为子组件,并在父组件中完成引入与使用。
  4. 能封装可复用卡片组件,使用多个具名插槽暴露 header、number、chart、footer 区域,并在父组件中按需填充内容。
  5. 能在首页父组件挂载时请求并存储数据,通过 props 下发到顶部组件,再将今日销售额、日同比、月同比、昨日销售额等数据渲染到第一个卡片。
  6. 能在第一个卡片中引入阿里 SVG 图标、调整布局样式,并为数字区域添加 CSS animation 动画。

知识点 ​

先明确本节目标 ​

首页要做的是工作报表式的数据可视化:折线图、柱状图、水球图等,这些图形在工作中很常见。当前首页还放着之前演示用的组件,先把没用的结构删掉,留一个简单占位确认页面干净。真正的问题是:首页没有真实接口,数据从哪来?转写里的“work / walkS / 框着点GS”都应纠正为 mockjs,不是 mock.js。

  • 为什么需要:前端静态页面和组件先写完了,后端接口可能还没好;没有数据就无法继续联调渲染。
  • 怎么想:用 mockjs 造一个假接口,让首页仍然走“发请求 → 拿数据 → 渲染”的流程,而不是把 JSON 直接塞进组件。
  • 怎么做:准备 JSON 数据,安装 mockjs,定义 Mock.mock 接口,在入口文件引入一次,再复制一份 mockRequest 发请求。
  • 易错点:mockjs 和 mock.js 是两个东西;假接口也要注意基础路径,不能直接复用真实请求的 baseURL。

准备 JSON 数据 ​

转写里的“阶省 / ja森 / 接层”都是 JSON。已写好的项目里有一个 mock 文件夹,里面放着首页需要的 JSON 数据;把它复制到当前项目的 src/mock/data.json。这个 JSON 是一个整体对象,里面有今日销售额、今日订单量、今日交易用户数、累积用户数等首页数据。

  • 为什么需要:首页顶部、中间、底部的数据都来自同一份首页数据,先集中放 JSON 便于一次返回。
  • 怎么想:首页所有可视化数据放在一个 JSON 对象里,接口发一次请求即可,后续父组件拿到数据再分发给子组件。
  • 怎么做:在 src/mock 下放 data.json,文件名按项目实际命名。
  • 易错点:JSON 文件不能写 export,export 是 JS/TS 关键字;JSON 虽然不写导出,但可以被默认引入。

安装并引入 mockjs ​

安装 mockjs 后,在 src/mock/index.ts 中引入:

ts
// @ts-ignore
import Mock from 'mockjs'
import data from './data.json'

mockjs 是 JS 包,没有 TS 类型声明,直接引入会出现红色波浪线。转写里的“艾特TS1个轴”就是 // @ts-ignore,用来忽略 TS 类型校验。

  • 为什么需要:没有类型声明时 TS 会报红线,但不影响运行。
  • 怎么想:先保证功能跑通,再用注释忽略类型检查。
  • 怎么做:在 import Mock from 'mockjs' 上一行写 // @ts-ignore。
  • 易错点:不要因为红线就删掉引入;也不要写成 mock.js。

定义假接口 ​

在 src/mock/index.ts 中写:

ts
Mock.mock('/mock/home', {
  code: 200,
  data
})

第一个参数是假接口地址 /mock/home,第二个参数是接口返回对象。转写里说返回状态码可以是 200 或项目约定的成功码,返回数据就是刚才的 JSON。

  • 为什么需要:Mock.mock 把地址和返回数据绑定起来,后续 axios 才能请求到。
  • 怎么想:接口地址要和请求路径拼接一致;返回对象里放 code 和 data。
  • 怎么做:第一个参数写 /mock/home,第二个参数写 { code: 200, data }。
  • 易错点:路径不要多写或少写;data 的键值要和后续取值一致。

让 mock 文件执行一次 ​

Mock.mock 是一个函数,文件不执行就不会注册接口。因此在入口文件(如 main.ts)中引入:

ts
import './mock'
  • 为什么需要:只定义不执行,假接口不会生效。
  • 怎么想:这里不需要接收变量,只需要让 src/mock/index.ts 执行一次。
  • 怎么做:在入口文件写 import './mock',默认会找 index.ts。
  • 易错点:不要写 import mock from './mock' 期待它导出;该文件没有默认导出。

复制 request 做 mockRequest ​

真实请求的 baseURL 和 mock 请求不同,不能直接拿原来的 request 用。复制一份请求实例,把基础路径改成 /mock:

ts
import axios from 'axios'

const mockRequest = axios.create({
  baseURL: '/mock'
})

export default mockRequest
  • 为什么需要:原 request 的基础路径是真实接口路径,mock 接口以 /mock 开头。
  • 怎么想:复制一份,只改 baseURL,避免影响真实接口。
  • 怎么做:新建 mockRequest.ts,导出 mockRequest。
  • 易错点:不要直接改原 request 的 baseURL;/mock/home 由 baseURL: '/mock' 加 /home 拼成。

写 API 并在首页验证 ​

在 API 文件中写:

ts
import mockRequest from '@/api/mockRequest'

export const reqChart = () => mockRequest.get('/home')

然后在首页 onMounted 中测试:

ts
import { onMounted } from 'vue'
import { reqChart } from '@/api/home'

onMounted(async () => {
  const result = await reqChart()
  console.log(result)
})

刷新页面,如果控制台能打印出数据,说明 mock 接口已经通了;验证完把测试打印删掉。转写里还强调:首页数据请求发一次即可,因为顶部、中间、底部用的是同一份数据;后续由父组件取到数据再传给子组件。

  • 为什么需要:先验证接口通不通,再写渲染逻辑,避免把问题混在一起。
  • 怎么想:用 onMounted 在组件挂载后发一次请求,打印结果。
  • 怎么做:封装 reqChart,首页调用并 console.log(result)。
  • 易错点:如果打印不到,先查入口是否引入 ./mock、baseURL 是否为 /mock、请求路径是否为 /home、JSON 是否被正确默认引入。

先清理首页并准备 mock 假接口 ​

为什么需要:首页要做数据可视化,但后端接口还没有;如果等接口,静态结构和组件拆分就无法推进。怎么想:先用 mockjs 造一个和真实接口同形的假接口,让页面能发请求、拿数据、渲染。怎么做:安装 mockjs,在 src/mock/index.ts 中引入并拦截 /mock/home,返回 { code: 200, data: homeData }。注意 mockjs 是 JS 文件,TS 引入可能报红,可用 @ts-ignore 忽略类型校验;JSON 文件不能写 export,只能默认导入。易错点:不要把 mockjs 写成 mock.js;mock 文件必须在入口 main.ts 中引入一次才会执行。

ts
// src/mock/index.ts
// @ts-ignore
import Mock from 'mockjs'
import homeData from './home.json'

Mock.mock('/mock/home', 'get', {
  code: 200,
  data: homeData
})
ts
// src/main.ts
import './mock'

拆分首页顶部为上中下组件 ​

为什么需要:首页顶部结构多,全写在一个文件里难维护。怎么想:先按“上、中、下”一分为三,再在顶部内部拆卡片。怎么做:建 components/Home/Top、Middle、Bottom,首页引入三个子组件。易错点:不要一上来拆得过细;先保证父子结构和数据流清楚。

vue
<Top :data="homeData" />
<Middle />
<Bottom />

顶部卡片用插槽复用 ​

为什么需要:四个卡片外壳一致,只有标题、数字、图表、底部文字不同。怎么想:用一个 Card 组件复用四次,用具名插槽留坑。怎么做:Card 中留 header、number、chart、footer 四个具名插槽;父组件用 #header、#number 等填内容。易错点:Vue3 具名插槽写 #header,不要沿用 Vue2 的 v-slot:header 混写;默认插槽一般只有一个,多个坑要用具名插槽。

vue
<!-- Card/index.vue -->
<template>
  <div class="card">
    <div class="header"><slot name="header" /></div>
    <div class="number"><slot name="number" /></div>
    <div class="chart"><slot name="chart" /></div>
    <div class="footer"><slot name="footer" /></div>
  </div>
</template>

父组件请求一次并下发数据 ​

为什么需要:首页所有图表共用同一份首页数据,发多次请求浪费。怎么想:在首页父组件挂载时请求一次,存到 ref,再通过 props 传给顶部。怎么做:封装 getHomeData,onMounted 调用;接口返回后 homeData.value = result.data。易错点:接口字段如 date["-1"] 这种特殊 key 不能用点语法,要用中括号;首次渲染可能是空对象,子组件要判空。

ts
const homeData = ref({})
const getHomeData = async () => {
  const result = await reqHome()
  homeData.value = result.data
}
onMounted(() => getHomeData())

第二个卡片:折线图封装与调试 ​

为什么需要:折线图配置多,单独封装便于维护。怎么想:ECharts 三步:引包、准备有高度的容器、初始化实例并 setOption。怎么做:onMounted 初始化,xAxis/yAxis 的 show 设 false,grid 四边设 0 贴边,series.smooth 做圆滑曲线,areaStyle 填色,tooltip.confine 限制在容器内。易错点:容器没高度图表不显示;window.resize 只响应浏览器窗口,不响应容器变化;数据首次为空时用 v-if 等有数据再渲染。

ts
const option = {
  tooltip: { confine: true, axisPointer: { type: 'line' } },
  grid: { left: 0, right: 0, top: 0, bottom: 0 },
  xAxis: { type: 'category', show: false, boundaryGap: false },
  yAxis: { show: false },
  series: [{ type: 'line', smooth: true, symbol: 'none', areaStyle: {}, data: [] }]
}

第三个卡片:用 vue-echarts 简化图表 ​

为什么需要:原生 ECharts 要手动初始化、setOption、处理自适应。怎么想:vue-echarts 把 ECharts 封装成 Vue 组件,配置项照旧。怎么做:安装 echarts 和 vue-echarts,全局注册 VChart,模板中写 <VChart :option="getOption()" autoresize />。易错点:option 建议用函数返回对象,避免模板里堆大段配置;autoresize 能跟随容器变化;提示框十字指示器用 axisPointer.type: 'cross'。

ts
const getOption = () => ({
  tooltip: { axisPointer: { type: 'cross' } },
  xAxis: { type: 'category', show: false },
  yAxis: { show: false },
  series: [{ type: 'bar', data: [10, 20, 30, 40] }]
})

本节的关键不是“把图堆出来”,而是先 mock 数据、再拆组件、用插槽复用卡片,最后把 ECharts 和 vue-echarts 的配置、容器高度、空数据渲染这些调试点逐一排掉。

先造首页假接口:Mock 数据 ​

为什么需要:首页图表依赖接口数据,但后端接口还没好;没有数据就无法联调渲染。
怎么想:用 mockjs 造一个和真实接口同结构的假接口,先让前端跑通“请求—拿数据—渲染”链路。
怎么做:把 home.json 放进 src/mock,写 mock/index.ts,再在入口文件引入一次,让 Mock 代码执行。

ts
// src/mock/index.ts
// @ts-ignore
import Mock from 'mockjs'
import homeData from './home.json'

Mock.mock('/mock/home', {
  code: 200,
  data: homeData
})
ts
// src/main.ts
import './mock'

易错点:JSON 文件不能写 export;Mock 接口基础路径与真实接口不同,要单独封装一个 requestMock,baseURL 指向 /mock,不要混用真实请求实例。

首页拆成上中下,顶部再拆卡片 ​

为什么需要:首页结构多,全塞一个组件难维护。
怎么想:先按区域拆 Top、Middle、Bottom;顶部四个卡片结构相似,只封装一个 Card,用插槽传内容。
怎么做:Home 中引入三个子组件;Top 中用 el-row/el-col 或 flex 排四个卡片。

vue
<!-- Home.vue -->
<template>
  <Top :data="chartObj" />
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { reqChart } from '@/api/home'
import Top from './components/top/index.vue'

const chartObj = ref<any>({})
const getData = async () => {
  const res = await reqChart()
  chartObj.value = res.data
}
onMounted(() => getData())
</script>

易错点:四个卡片不要拆成四个几乎相同的组件,具名插槽更合适;Card 根节点是块元素,默认一行一个,要用栅格或 flex 控制一行四个。

具名插槽完成第一个卡片 ​

为什么需要:卡片头部、数字、图表、底部结构固定,内容由父组件决定。
怎么想:子组件留四个具名插槽:header、number、chart、bottom。
怎么做:

vue
<!-- Card.vue -->
<template>
  <div class="card">
    <div class="header"><slot name="header" /></div>
    <div class="number"><slot name="number" /></div>
    <div class="chart"><slot name="chart" /></div>
    <div class="bottom"><slot name="bottom" /></div>
  </div>
</template>

父组件使用 #header、#number 等写法。数据 key 若含特殊字符,必须用中括号取值,不能写 data.--。
易错点:Vue3 具名插槽写 #header,不是 Vue2 的 v-slot:header 混写;默认插槽一般只有一个,具名插槽可多个。

第一个卡片接折线图并调试 ​

为什么需要:卡片中间要展示趋势图。
怎么想:ECharts 三步:引包、准备有高度的容器、初始化实例并 setOption。
怎么做:

vue
<!-- Line.vue -->
<template>
  <div ref="chartRef" class="line"></div>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue'
import * as echarts from 'echarts'

const chartRef = ref<HTMLDivElement>()
let chart: echarts.ECharts

const initChart = () => {
  chart = echarts.init(chartRef.value!)
  chart.setOption({
    xAxis: { type: 'category', boundaryGap: false, show: false },
    yAxis: { show: false },
    grid: { left: 0, right: 0, top: 0, bottom: 0 },
    tooltip: { trigger: 'axis', confine: true },
    series: [{
      type: 'line',
      smooth: true,
      symbol: 'none',
      data: [20, 18, 30, 40, 50, 60],
      areaStyle: { color: '#409eff' }
    }]
  })
}
onMounted(() => initChart())
window.addEventListener('resize', () => chart?.resize())
</script>

<style scoped>
.line { width: 100%; height: 80px; }
</style>

调试时若图表不显示,先查容器高度;若子组件拿不到数据,是父组件初始值 {} 导致首次渲染为空,可在图表外包 v-if="data && data['字段名']",等接口回来再渲染。
易错点:ECharts 容器必须有高度;隐藏坐标轴后要用 grid 贴边;tooltip 需要拐点或坐标轴指示器才能触发;resize 只监听窗口变化,不监听容器自身变化。

课堂画面 ​

课堂画面 1

课堂画面 2

课堂画面 3

课堂画面 4

课堂画面 5

代码示例 ​

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <title>Mock 首页接口</title>
</head>
<body>
  <div id="app"></div>

  <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
  <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/mockjs@1.1.0/dist/mock.min.js"></script>

  <script>
    const { createApp, ref, onMounted } = Vue;

    // 模拟首页接口:地址 /mock/home,返回 code + data
    Mock.mock('/mock/home', 'get', {
      code: 200,
      data: {
        '//1': {
          today: 303,
          lastDay: '--',
          dayRatio: '19%',
          monthRatio: '56.67%'
        }
      }
    });

    // mock 接口的基础路径和真实接口不同,所以单独复制一份 request
    const mockRequest = axios.create({
      baseURL: '/mock'
    });

    const App = {
      setup() {
        const result = ref(null);

        onMounted(async () => {
          // baseURL + '/home' => /mock/home
          const res = await mockRequest.get('/home');
          result.value = res.data;
          console.log(res.data);
        });

        return { result };
      },
      template: `
        <pre>{{ result ? JSON.stringify(result, null, 2) : '请求中...' }}</pre>
      `
    };

    createApp(App).mount('#app');
  </script>
</body>
</html>

要点解释

  • Mock.mock('/mock/home', 'get', {...}):第一个参数是假接口地址,第二个参数是请求方式,第三个参数是返回数据;地址必须和请求地址一致。
  • mockRequest 的 baseURL 是 /mock,所以 mockRequest.get('/home') 实际请求 /mock/home;真实接口基础路径不同,不能直接混用。
  • 特殊字段名 '//1' 不能写成 data.//1,必须用中括号:data['//1']。
  • MockJS 文件必须在入口执行一次,否则假接口不会生效;JSON 文件不能写 export。
  • 请求成功后先把数据存到 result,再渲染;否则首次渲染拿不到数据。
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <title>首页顶部卡片拆分与第一个卡片</title>
  <style>
    * { box-sizing: border-box; }
    body {
      margin: 0;
      padding: 20px;
      background: #f5f7fa;
      font-family: Arial, sans-serif;
    }
    .top {
      display: flex;
      gap: 20px;
    }
    .card {
      flex: 1;
      background: #fff;
      border: 1px solid #eee;
      padding: 16px;
    }
    .card .header {
      font-size: 16px;
      font-weight: 900;
      margin-bottom: 10px;
    }
    .card .number {
      font-size: 24px;
      font-weight: 800;
      animation: numberIn 0.5s ease 1;
    }
    .card .chart {
      min-height: 80px;
      border-bottom: 1px dashed #ddd;
      margin: 10px 0;
    }
    .card .bottom {
      margin-top: 10px;
      color: #666;
    }
    .ratio {
      display: flex;
      gap: 20px;
    }
    .ratio-item {
      display: flex;
      align-items: center;
      gap: 4px;
    }
    @keyframes numberIn {
      from {
        transform: translateY(10px);
        opacity: 0;
      }
      to {
        transform: translateY(0);
        opacity: 1;
      }
    }
  </style>
</head>
<body>
  <div id="app"></div>

  <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>

  <script>
    const { createApp } = Vue;

    // 卡片子组件:预留 header / number / chart / bottom 四个具名插槽
    const Card = {
      template: `
        <div class="card">
          <div class="header"><slot name="header"></slot></div>
          <div class="number"><slot name="number"></slot></div>
          <div class="chart"><slot name="chart"></slot></div>
          <div class="bottom"><slot name="bottom"></slot></div>
        </div>
      `
    };

    const Top = {
      components: { Card },
      setup() {
        const homeData = {
          '//1': {
            today: 303,
            lastDay: '--',
            dayRatio: '19%',
            monthRatio: '56.67%'
          }
        };

        return { homeData };
      },
      template: `
        <div class="top">
          <Card>
            <template #header>今日销售额</template>
            <template #number>{{ homeData['//1'].today }}</template>
            <template #chart>
              <div class="ratio">
                <div class="ratio-item">
                  <span>日同比</span>
                  <span>{{ homeData['//1'].dayRatio }}</span>
                  <span>↓</span>
                </div>
                <div class="ratio-item">
                  <span>月同比</span>
                  <span>{{ homeData['//1'].monthRatio }}</span>
                  <span>↑</span>
                </div>
              </div>
            </template>
            <template #bottom>
              昨日销售额:{{ homeData['//1'].lastDay }}
            </template>
          </Card>

          <Card>
            <template #header>待接入</template>
            <template #number>--</template>
            <template #chart>图表容器必须有高度</template>
            <template #bottom>--</template>
          </Card>

          <Card>
            <template #header>待接入</template>
            <template #number>--</template>
            <template #chart>图表容器必须有高度</template>
            <template #bottom>--</template>
          </Card>

          <Card>
            <template #header>待接入</template>
            <template #number>--</template>
            <template #chart>图表容器必须有高度</template>
            <template #bottom>--</template>
          </Card>
        </div>
      `
    };

    createApp({
      components: { Top },
      template: `<Top />`
    }).mount('#app');
  </script>
</body>
</html>

要点解释

  • Card 组件里用 <slot name="header"></slot> 等预留具名插槽,父组件通过 <template #header>...</template> 填结构。
  • Vue3 具名插槽简写是 #header,不是 Vue2 的 slot="header";这是易错点。
  • 第一个卡片数据字段是特殊 key '//1',模板中必须写 homeData['//1'].today,不能写 homeData.//1。
  • 四个卡片用 display:flex 排成一行,flex:1 平均分配宽度;如果直接放块级 div,默认会一人占一行。
  • .chart 区域如果将来放 ECharts,必须有高度;转写稿里反复强调“图表容器没高度就看不见”。
  • number 上的动画由 animation: numberIn 0.5s ease 1; 使用,配合 @keyframes 定义;只定义动画不使用不会生效。

常见错误 ​

  1. 现象:引入 mockjs 后出现红色波浪线,项目仍能运行。
    原因:mockjs 是 JS 包,缺少 TS 类型声明。
    修法:不影响运行可忽略;不想看到报错则用 // @ts-ignore 或补充声明文件。

  2. 现象:在 .json 文件里写 export default 后报错。
    原因:JSON 不是 JS/TS 模块,不能写 export。
    修法:JSON 只保留纯数据;需要导出时在 .ts/.js 中 import 后使用。

  3. 现象:Mock 接口已写好,但请求 404 或拿不到数据。
    原因:Mock 文件没有被入口文件引入执行。
    修法:在入口文件如 main.ts 中 import './mock',确保 Mock 注册执行一次。

  4. 现象:真实接口请求正常,Mock 接口请求失败。
    原因:Axios 封装的基础路径是 /api,Mock 地址却是 /mock。
    修法:为 Mock 单独复制一份请求封装,基础路径改为 /mock,不要在同一个实例上混用。

  5. 现象:具名插槽内容不显示或控制台提示插槽名不匹配。
    原因:Vue3 具名插槽写法仍用旧写法,或 #name 与子组件 name 不一致。
    修法:父组件用 #插槽名,子组件用 <slot name="插槽名" />,两边名称严格一致。

  6. 现象:ECharts 图表区域空白,容器有宽但看不到图。
    原因:图表容器没有明确高度,或初始化时 ref 容器尚未拿到。
    修法:给图表容器设置固定高度或 height: 100% 且父级有高度;在 onMounted 后初始化。

  7. 现象:接口数据已返回,但图表首次渲染报错或无数据。
    原因:初始值是空对象,子组件首次渲染直接取深层字段。
    修法:使用可选链/默认值,或用 v-if="有数据" 等数据到位后再渲染图表。

  8. 现象:vue-echarts 注册后仍不显示,或 option 写法混乱。
    原因:组件未全局/局部注册,或 option 直接塞在模板里且父容器无高度。
    修法:正确注册 v-chart,option 用计算属性/函数返回对象,并保证父容器有高度。

练习 ​

  1. 在入口文件接入 Mock,新增 /mock/home 接口,返回 { code: 200, data: {...} },并在首页 onMounted 中打印请求结果。
  2. 将首页拆成 Top / Middle / Bottom 三个子组件,并在父组件中完成引入、注册和使用。
  3. 用“一个卡片组件 + 多个具名插槽”复用四个顶部卡片,要求标题、数字、图表区、底部说明都能由父组件传入。
  4. 封装一个折线图子组件:父组件传入数据,子组件完成 ECharts 初始化、配置项设置和窗口 resize 自适应。
  5. 用 vue-echarts 完成柱状图卡片,要求配置项独立成函数/计算属性,并验证容器尺寸变化时图表能自适应。

原视频对照 ​

#视频时长
1mock首页的接口15:51
2搭建首页顶部的静态与组件的拆分25:59
3首页顶部第一个卡片的完成21:32
4首页顶部的第二个卡片完成43:34
5首页顶部第三个卡片-vue-echarts27:58

小结 ​

  1. Mock 接口的核心是“先注册执行,再按独立基础路径发请求”,不要和真实接口封装混用。
  2. 首页顶部优先做结构拆分,再抽卡片组件;复用场景优先考虑插槽,而不是复制四份结构。
  3. 具名插槽适合“子组件定布局、父组件填内容”的卡片类组件,插槽名必须成对维护。
  4. ECharts 图表能否显示,首先检查容器高度和初始化时机,其次检查配置项和数据。
  5. 接口数据未返回时要有兜底:默认值、可选链或条件渲染,避免首屏渲染直接取深层字段。
  6. vue-echarts 的优势是组件化、配置项集中、事件和自适应更省心,但同样依赖父容器高度。
  7. 图表自适应要区分“窗口变化”和“容器变化”;后台管理系统里容器变化很常见,不能只监听 window.resize。
  8. 样式调整优先用 Flex/Grid 等布局手段解决对齐和间距,图表贴边问题重点检查 grid 配置。