Skip to content

大屏项目搭建与图表(一) ​

本页对应 大屏项目搭建与图表,共 7 段视频,约 1 小时 12 分。

学习目标 ​

  • 能使用 create-vue 按指定选项(启用 Router、ESLint,不启用 TS、Pinia)创建 Vue3 大屏项目,并清理默认组件、保留 RouterView 使项目可正常启动与路由切换。
  • 能封装与主应用一致的 token cookie 存取模块和 Axios 请求实例,正确配置 baseURL、请求头 token 注入及响应 data 返回,并说明 401 的排查与跨域 cookie 共享条件。
  • 能新增大屏路由并配置重定向,使用完整 .vue 路径引入路由组件并验证 /big-screen 可渲染;能引入全局样式并配置 SCSS(安装 sass、指定 lang="scss")完成样式初始化。
  • 能基于接口渲染园区概况数据:声明响应式对象、封装请求方法并在 onMounted 中调用赋值,在模板中绑定数据;能使用可选链避免首次渲染空对象导致的 undefined 访问错误。
  • 能按 ECharts 基础流程在 Vue 组件中完成图表初始化:通过 ref 获取 DOM、在 onMounted 中调用 init、准备 option 并调用 setOption 渲染柱状图,并能排查 DOM 未就绪导致的初始化错误。
  • 能定位并修复本页常见问题:SCSS 未指定 lang、接口路径错误导致 404、setup 拼写错误导致组件无法解析,并说明各自的报错特征与修复方式。

知识点 ​

这一节在准备什么 ​

这一节的目标不是马上把图表画出来,而是先把“园区产业分布”这一块的静态模板搭好。为什么需要先做静态模板?因为大屏页面通常由多个区块拼成,先有稳定的 DOM 结构和尺寸,后面的 ECharts 初始化才有容器可挂。怎么想?把这一块拆成两部分:上方是标题图片,下方是图表容器。怎么做?把模板放进大屏页面的 template 内部,并配套复制样式;注意不要破坏原有页面结构。易错点:模板复制时容易放到 script 或别的区块旁边,导致结构不对;样式也要一起复制,否则容器高度会塌。

vue
<template>
<!-- 园区产业分布 -->
<div class="section-three">
  <img class="img-header"
    src="https://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/smartPark/%E5%A4%A7%E5%B1%8F%E5%88%87%E5%9B%BE/%E5%9B%AD%E5%8C%BA%E4%BA%A7%E4%B8%9A%E5%88%86%E5%B8%83%402x.png"
    alt="园区产业分布标题图" />
  <div class="pie-chart" ref="pieChart"></div>
</div>
</template>

<style>
  .section-two {
    flex-basis: 35%;
    .bar-chart {
      width: 100%;
      height: calc(100% - 90px);
    }
  }
  .section-three {
    flex-basis: 40%;

    .pie-chart {
      position: relative;
      margin: 0 auto;
      padding-bottom: 20px;
      width: 80%;
      height: calc(100% - 40px);
    }
  }
</style>

结构:标题图与图表容器 ​

为什么需要 img-header 和 pie-chart 两个节点?标题图负责视觉头部,pie-chart 负责后续图表渲染。怎么想?图表库初始化时需要真实 DOM,所以先用一个 div 占位,并给它 ref="pieChart"。怎么做?模板里保留 ref,后续在 script setup 中声明同名 ref,等组件挂载后再拿 DOM。易错点:ref 名字必须和脚本里一致;不能在 DOM 还没挂载时直接初始化图表。转写稿里反复强调,ECharts 初始化有前置依赖,必须保证容器 DOM 可用,通常放到 mounted 里执行。本节虽然还没写脚本,但这里先把容器准备好,就是为后续初始化做铺垫。

样式:布局比例与高度计算 ​

为什么 .section-three 要写 flex-basis: 40%?大屏通常是横向分区,用 flex 控制各区块占比。怎么想?这一块占 40%,旁边区块按整体布局分配。怎么做?保留 .section-three 和 .pie-chart 的样式:容器居中、宽度 80%、高度用 calc(100% - 40px) 给头部图片留空间。易错点:calc 里减号两边要有空格;百分比高度依赖父级高度,父级没高度时图表会塌。转写稿里还提醒,如果项目使用 SCSS 嵌套写法,style 上应指定 lang="scss",需要局部生效时加 scoped;否则可能出现解析或高亮异常。本节代码保留了原样,实际项目要按工程配置确认。

资源与调试:图片 404 怎么排查 ​

为什么要注意图片资源?静态模板复制后最常见的问题就是图片 404。转写稿里提到,复制模板后图标不显示,打开检查面板能看到多个 404,原因是 assets 里的图片没有一起拷过来;把资源补到项目对应目录后刷新就恢复。本节用的是在线 OSS 图片,仍要确认 URL 可访问、网络正常。怎么做?页面刷新后看标题图是否出现,打开 Network 看图片请求是否 200;如果后续换成本地图片,要检查路径和文件名。易错点:只复制模板不复制资源;alt 留空不利于排查和无障碍,这里改为中文“园区产业分布标题图”。

交付标准 ​

完成这一节后,页面上应出现“园区产业分布”的标题图和一块空白的图表容器,控制台没有资源 404;pie-chart 的 ref 已准备好,后续再接入 ECharts 初始化逻辑。这样静态模板和布局就稳了,下一步才是图表实例化与数据渲染。

先看目标:园区概况为什么要封装接口 ​

为什么需要:园区概况要展示楼宇总数、入驻企业总数、车位总数、一体杆总数。这些数字来自后端统计接口,不能写死在模板里;同时请求逻辑不应散落在组件中,否则后续换地址、加拦截器都会很麻烦。
怎么想:把“发请求”放到 apis/park 里,组件只负责拿数据、存状态、渲染页面。项目里已有统一的 request,接口层只描述 URL。
怎么做:新建园区接口文件,导出 getParkInfoAPI。
易错点:request 的导入路径要与项目一致;接口函数要 export,组件才能按名导入;不要在组件里直接写完整请求配置,保持接口层统一。

javascript
// 获取园区信息

import request from '@/utils/request'

/**
 * 获取园区信息
 * @returns 
 */
export function getParkInfoAPI () {
  return request({
    url: "/park/statistics/info"
  })
}

组件里怎么拿到数据:ref、异步函数与初始化调用 ​

为什么需要:接口是异步的,页面必须先有一个响应式状态,等数据回来后自动触发视图更新。
怎么想:接口返回的是对象,初始值就用空对象 {};在 async 函数里 await 接口,拿到 res.data 后赋给 parkInfo.value。组件初始化时调用一次获取函数。
怎么做:在 <script setup> 中导入 ref 和接口,定义 parkInfo 与 getParkInfo。
易错点:初始值要和接口返回格式匹配,这里不是数组;赋值时别漏 .value;接口层若已经处理过响应,组件里仍要按项目约定取 res.data。

vue
<script setup>
  import { ref } from 'vue'
  import { getParkInfoAPI } from '@/apis/park'
  const parkInfo = ref({})
  const getParkInfo = async () => {
    const res = await getParkInfoAPI()
    parkInfo.value = res.data
  }
  getParkInfo()
</script>

模板渲染与可选链:为什么首次渲染会报错 ​

为什么需要:模板要读取 parkInfo.base 下的四个统计字段。但异步数据不会让组件“等着不渲染”,组件会先用初始值渲染一次,数据回来后再渲染一次。
怎么想:首次渲染时 parkInfo 是 {},parkInfo.base 得到 undefined,再访问 undefined.buildingTotal 就会报错。解决方式是可选链 ?.:只有问号前面的值既不是 null 也不是 undefined,才继续往后取属性。
怎么做:模板里统一写成 parkInfo.base?.buildingTotal 等;保留园区概况头部图片,并把 alt 改成中文。
易错点:不要写成 parkInfo.base.buildingTotal;可选链只保护属性访问,不代表数据一定存在;四个字段都要按接口返回的 base 层级取。

vue
<template>
  <div class="all-charts">
    <!-- 园区概况 -->
    <div class="section-one">
      <img class="img-header"
        src="https://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/smartPark/%E5%A4%A7%E5%B1%8F%E5%88%87%E5%9B%BE/%E5%9B%AD%E5%8C%BA%E6%A6%82%E5%86%B5%402x.png"
        alt="园区概况头部装饰图" />
      <div class="icons-container">
        <div class="item">
          <div class="icons-item building-icon">
            <span class="number">
              <!-- 
                首次渲染(后端数据没有返回)
                parkInfo - {} - {}.base -> undefined -> undefined.buidingTotal
                
                二次渲染(后端数据返回之后 响应式状态发生变化 再次引起模版渲染)

                场景:访问一个空对象多层次的属性
                解决办法:可选链?.   保证必须前面有值才会继续往后进行点运算
               -->
              {{ parkInfo.base?.buildingTotal }}
            </span>
          </div>
          <span class="title">楼宇总数</span>
          <span class="unity">(栋)</span>
        </div>
        <div class="item">
          <div class="icons-item enterprise-icon">
            <span class="number">
              {{ parkInfo.base?.enterpriseTotal }}
            </span>
          </div>
          <span class="title">入驻企业总数</span>
          <span class="unity">(家)</span>
        </div>
        <div class="item">
          <div class="icons-item car-icon">
            <span class="number">
              {{ parkInfo.base?.parkingTotal }}
            </span>
          </div>
          <span class="title">车位总数</span>
          <span class="unity">(个)</span>
        </div>
        <div class="item">
          <div class="icons-item rod-icon">
            <span class="number">
              {{ parkInfo.base?.chargePoleTotal }}
            </span>
          </div>
          <span class="title">一体杆总数</span>
          <span class="unity">(个)</span>
        </div>
      </div>
    </div>
  </div>
</template>

调试补充:401 与 token 共享 ​

为什么需要:大屏子应用调接口时,请求头里需要主应用登录后保存的 token。
怎么想:token 存在 cookie 中,子应用通过工具模块读取并注入请求拦截器。若接口报 401,优先查请求头是否带上 token。
怎么做:先在主应用登录,确认 cookie 中有 token;再让子应用与主应用使用一致的主域(如都使用 localhost),这样不同端口下 cookie 也可共享;刷新子应用后重新请求。
易错点:127.0.0.1 与 localhost 是两个不同的域,不能直接共享 cookie;token 过期要重新登录;接口导入名、导出名、请求方法名都要对齐,路径写错常见表现是 404。

渲染结果检查 ​

为什么需要:确认接口数据真正进入模板,而不是只在控制台看到。
怎么想:用 Vue 调试工具看 parkInfo 是否包含 base,再看 Network 中接口是否 200、响应里四个统计字段是否存在。
怎么做:刷新页面,模板四个数字应分别对应接口返回的 buildingTotal、enterpriseTotal、parkingTotal、chargePoleTotal。
易错点:图标资源缺失会报 404,需补齐静态资源;样式和作用域要一起引入;不要因为首次渲染报 undefined 就怀疑接口,先检查是否加了可选链。

一、先明确:静态模板是图表的“落脚点” ​

为什么需要: 大屏里的 2D 可视化通常交给 ECharts 这类图表库完成,但图表不是凭空出现的,必须先有一个稳定的 DOM 容器。静态模板的作用,就是先把标题图、图表区域和布局尺寸搭好,后面再接入数据和图表配置。转写稿里也强调,做图表前要先“准备静态模板”,复制模板和配套样式,先让页面有结构。

怎么想: 本节只做“园区产业分布”这一块的骨架:外层 section-three 负责在页面中占位,头部图片负责视觉标题,内部 pie-chart 作为后续饼图的容器。容器上要提前放 ref="pieChart",因为后面初始化图表时需要拿到真实 DOM。

怎么做: 保留笔记中的模板和样式,图片 alt 改成中文,便于可访问性和排查问题:

vue
<template>
<!-- 园区产业分布 -->
<div class="section-three">
  <img class="img-header"
    src="https://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/smartPark/%E5%A4%A7%E5%B1%8F%E5%88%87%E5%9B%BE/%E5%9B%AD%E5%8C%BA%E4%BA%A7%E4%B8%9A%E5%88%86%E5%B8%83%402x.png"
    alt="园区产业分布标题图" />
  <div class="pie-chart" ref="pieChart"></div>
</div>
</template>

<style>
  .section-two {
    flex-basis: 35%;
    .bar-chart {
      width: 100%;
      height: calc(100% - 90px);
    }
  }
  .section-three {
    flex-basis: 40%;

    .pie-chart {
      position: relative;
      margin: 0 auto;
      padding-bottom: 20px;
      width: 80%;
      height: calc(100% - 40px);
    }
  }
</style>

易错点: 复制模板时不要把 section-three 放到 template 外面;图片地址是远程 OSS 地址,如果资源失效会出现图片 404;alt 不要长期留空;ref 名称要和后续脚本中取 DOM 的名称一致,否则图表初始化时拿不到容器。

二、图表容器:为什么一定要有宽高和 ref ​

为什么需要: ECharts 初始化时需要读取容器的宽高。如果容器没有高度,图表可能渲染不出来,或者只出现一条线。转写稿里也提到,图表容器要“定义宽高达”,否则初始化没有依据。

怎么想: 外层 .section-three 用 flex-basis: 40% 参与大屏横向布局;内部 .pie-chart 用 width: 80% 控制宽度,用 height: calc(100% - 40px) 给图表区留出高度。padding-bottom: 20px 和 margin: 0 auto 负责底部留白和水平居中。

怎么做: 先保证 .pie-chart 有明确高度,再在后续脚本中通过 ref 获取节点。转写稿中初始化图表的流程是:准备节点 → 初始化实例 → 准备配置项 → 调用 setOption。其中“准备节点”就是本节静态模板要完成的事。

易错点: 只写 width 不写 height,图表容器高度为 0;calc(100% - 40px) 写成 calc(100%-40px) 可能不生效;样式复制后没有引入或没有作用到当前组件;后续用 ref 取 DOM 时,脚本里要写 .value,模板里不要写 .value。转写稿里就出现过 ref 相关报错,本质是没拿到真实 DOM。

三、复制模板和样式后的调试顺序 ​

为什么需要: 从已有页面复制模板和样式,最容易出现“结构有了但样式不对”“图标 404”“图表容器没尺寸”等问题。转写稿里复制完模板后页面“只有一个框框”,复制样式后又出现资源 404,这些都是正常调试过程。

怎么想: 先看结构是否在正确位置,再看样式是否生效,最后看静态资源是否缺失。本节图片是远程地址,若远程可用就不需要本地资源;如果换成项目内图标,则要检查路径。转写稿中处理图标 404 的方式,是把准备好的 assets 资源复制到项目对应目录,再刷新验证。

怎么做: 复制模板后先刷新页面,确认 .section-three 出现;再检查 .pie-chart 是否有可见高度;最后检查控制台 Network 是否有 404。后续初始化图表时,要把初始化代码放到 mounted 中,保证 DOM 已可用。转写稿明确说:“初始化代码不能裸写,必须在一个合适的时机下调起来”,这个时机就是 mounted。

易错点: 看到 404 先怀疑接口,其实静态资源路径也会 404;看到图表不显示就改配置,可能只是容器没高度;初始化代码写在 setup 顶层,DOM 还没挂载,拿不到节点;样式里用了 SCSS 语法但没写 lang="scss",或者项目没装 sass,也会导致样式不生效。转写稿里还提醒过,style 块最好明确语法,避免解析器按 CSS 解析 SCSS。

四、本节边界:只准备静态模板,不提前写图表逻辑 ​

为什么需要: 静态模板和数据渲染要分开做。先把结构、尺寸、ref 准备好,后面接 ECharts 时只需要关心“拿容器、初始化、传配置、填数据”,排查问题也更清晰。

怎么想: 本节笔记没有给出饼图配置和数据接口,所以不能提前编造图表参数。这里只确认:section-three 是园区产业分布区域,img-header 是标题图,pie-chart 是后续图表容器。

怎么做: 完成模板和样式复制后,刷新页面确认布局占位正确;后续再在 mounted 中通过 pieChart 这个 ref 获取 DOM,按 ECharts 流程初始化。若数据首次渲染出现 undefined,转写稿中的经验是用可选链 ?. 做保护,避免空对象继续点属性报错。

易错点: 把静态模板和接口请求混在一起写,出错时不好定位;容器 ref 名称写错;复制样式时漏掉外层选择器;没有确认项目是否支持 SCSS 就大量写嵌套语法。按转写稿的调试思路:先让静态结构可见,再让图表容器可初始化,最后才接真实数据。

先明确图表渲染的三步 ​

为什么需要:园区产业分布不能靠手写 HTML 拼出来,ECharts 图表需要先拿到容器、准备配置,再把配置应用到实例上。转写稿里反复强调的流程可以归纳为三步:实例化图表、准备配置项参数、调用实例的 setOption。

怎么想:不要一上来就写业务配置,先把“图表能出来”这条链路跑通,再把 demo 里的数据替换成 parkInfo.value.parkIndustry。本节代码就是把这三步封装进 initPieChart。

怎么做:先用 ref 拿到 DOM 容器,再组织 pieOption,最后 echarts.init(pieChart.value) 并 setOption。

易错点:初始化不能裸写在组件顶层,因为此时 DOM 还不一定可用;按转写稿的调试经验,初始化要放到 mounted 之后执行。另一个高频错误是 ref 忘记加 .value,会报“get 不是函数”一类错误。

准备容器、数据和初始化入口 ​

为什么需要:饼图必须挂到一个真实 DOM 节点上,数据也必须来自园区概况接口,而不是写死。

怎么想:笔记里用 const pieChart = ref(null) 表示图表容器,用 const { parkIndustry } = parkInfo.value 取产业分布数据。这样模板里通过 ref="pieChart" 绑定,脚本里通过 pieChart.value 拿到节点。

怎么做:在 initPieChart 内部先解构数据,再定义 pieOption,最后初始化。调用时机上,要保证 parkInfo 已经有数据、容器已经渲染。

易错点:parkInfo.value 为空时直接解构会报错;parkIndustry 不是数组时饼图数据不合法;容器没有宽高时图表可能看不见。转写稿里“先跑通基础 demo,再改业务数据”的思路在这里同样适用。

配置图例、提示框和环形饼图 ​

为什么需要:大屏图表不只是“有图”,还要让图例、悬浮提示、环形结构符合视觉要求。

怎么想:legend 放到底部,tooltip 用 item 触发,series.type 设为 pie,用 radius: ['55%', '60%'] 形成环形,用 center: ['50%', '40%'] 调整整体位置。

怎么做:legend 里设置 itemGap、bottom: '0'、icon: 'rect'、itemHeight、itemWidth 和文字颜色;tooltip.formatter 中用 params.seriesName、params.marker、params.name、params.percent 拼接展示内容;label.show: false 隐藏默认标签,避免和中心区域冲突。

易错点:legend.bottom 和 series.center 要配合,否则图例可能挤压饼图;formatter 返回的是 HTML 字符串,字段名要写对;params.percent 是 ECharts 计算出的百分比,不要误当成原始数据。

实例化并 setOption ​

为什么需要:pieOption 只是配置对象,必须交给 ECharts 实例才会渲染。

怎么想:先 echarts.init(pieChart.value) 得到实例,再 myPieChart.setOption(pieOption)。这正好对应转写稿里的“实例化 → 配置项 → setOption”。

怎么做:完整代码如下,保留本节笔记代码:

javascript
// 渲染园区产业分布图表
const pieChart = ref(null)
const initPieChart = () => {
  const { parkIndustry } = parkInfo.value
  const pieOption = {
    color: [
      '#00B2FF', '#2CF2FF', '#892CFF', '#FF624D', '#FFCF54', '#86ECA2'],
    legend: {
      itemGap: 20,
      bottom: '0',
      icon: 'rect',
      itemHeight: 10, // 图例icon高度
      itemWidth: 10, // 图例icon宽度
      textStyle: {
        color: '#c6d1db',
      },
    },
    tooltip: {
      trigger: 'item'
    },
    series: [
      {
        name: '园区产业分析',
        type: 'pie',
        radius: ['55%', '60%'], // 设置内圈与外圈的半径使其呈现为环形
        center: ['50%', '40%'], // 圆心位置, 用于调整整个图的位置
        tooltip: {
          trigger: 'item',
          formatter: (params) => {
            return `${params.seriesName}</br><div style='display:flex;justify-content: space-between;'><div>${params.marker}${params.name}</div><div>${params.percent}%</div></div>`
          }
        },
        label: {
          show: false,
          position: 'center',
        },
        data: parkIndustry,
      },
    ],

  }
  const myPieChart = echarts.init(pieChart.value)
  myPieChart.setOption(pieOption)
}

易错点:myPieChart 现在是函数内局部变量,后续如果想更新图表拿不到实例;同一容器重复初始化前要先处理已有实例,避免重复 init。调试时先看 pieChart.value 是否拿到、parkIndustry 是否有值,再看 setOption 是否执行,顺序不要乱。

为什么先准备静态模板 ​

大屏项目里,图表不是一上来就写配置项。转写稿里反复强调一个顺序:先把静态结构、样式和资源位置准备好,再考虑数据接口和图表实例。因为 2D 可视化通常由前端独立完成,前端先要跟后端要数据,但页面骨架、标题图、图表容器必须先存在,否则后面 ECharts 初始化时连“往哪里渲染”都没有。本节笔记只做“园区产业分布”这一块的静态模板:一个标题图片,一个饼图容器。先不写饼图数据逻辑,目的是让后续图表代码有稳定的 DOM 落点。

静态结构怎么放 ​

为什么需要:模板位置放错,页面不会按预期渲染,甚至会把模板代码和逻辑代码混在一起。怎么想:这一块是页面中的一个区块,应该和已有区块并列放在 template 根节点下。怎么做:保留笔记中的结构,把 section-three 作为园区产业分布区块,内部放 img-header 和 pie-chart。转写稿里提醒过,复制模板时不要放到和 onMounted 并列的位置,模板要放在 template 里面。易错点:只复制了内部 div,漏掉外层 section-three;或者把 section-three 放到 script 区域,导致编译不认。

vue
<template>
<!-- 园区产业分布 -->
<div class="section-three">
  <img class="img-header"
    src="https://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/smartPark/%E5%A4%A7%E5%B1%8F%E5%88%87%E5%9B%BE/%E5%9B%AD%E5%8C%BA%E4%BA%A7%E4%B8%9A%E5%88%86%E5%B8%83%402x.png"
    alt="园区产业分布标题图" />
  <div class="pie-chart" ref="pieChart"></div>
</div>
</template>

图片和图表容器分别做什么 ​

为什么需要:img-header 是大屏切图里的标题图,属于静态视觉资源;pie-chart 是后续饼图的挂载容器。怎么想:标题图直接使用在线地址,不需要自己再做一张;图表容器先留空,等图表逻辑阶段再通过 ref 拿到它。怎么做:ref="pieChart" 是给后续脚本获取 DOM 用的,命名要和脚本里的 ref 变量保持一致。转写稿里讲 ECharts 基础渲染时强调过:初始化图表实例前,必须保证 DOM 可用,所以后面初始化要放到 onMounted 里,并且通过 .value 拿节点。易错点:ref 名字写错;在 setup 里直接裸写初始化;忘记 .value。这些都会导致“拿不到节点”或初始化失败。本节只准备容器,不提前编造饼图配置。

样式布局的关键点 ​

为什么需要:大屏是分栏布局,区块占比和图表容器高度不稳定,图表就会溢出或显示很小。怎么想:section-three 用 flex-basis: 40% 表示它在 flex 布局中占约 40% 的主轴空间;pie-chart 用 width: 80%、margin: 0 auto 居中,用 height: calc(100% - 40px) 给标题图留出高度。怎么做:保留笔记样式,并注意 section-two 是相邻区块的样式,不要误删。易错点:父级高度没有确定时,calc(100% - 40px) 可能算不出预期高度;position: relative 是给图表内部定位留基础;padding-bottom: 20px 会影响实际内容区高度。

vue
<style>
  .section-two {
    flex-basis: 35%;
    .bar-chart {
      width: 100%;
      height: calc(100% - 90px);
    }
  }
  .section-three {
    flex-basis: 40%;

    .pie-chart {
      position: relative;
      margin: 0 auto;
      padding-bottom: 20px;
      width: 80%;
      height: calc(100% - 40px);
    }
  }
</style>

易错点与调试过程 ​

转写稿里常见排查思路可以直接套到这里:第一,图片不显示就看控制台是否 404,确认资源地址和 assets 路径;第二,样式不生效先检查类名是否和模板一致,使用 SCSS 时按转写稿提醒写 lang="scss",需要组件隔离时加 scoped;第三,后续图表不出现,先确认 pieChart 这个 ref 是否拿到,初始化是否放在 onMounted;第四,异步数据渲染时注意可选链,避免初始空对象继续点属性报错。本节先把静态模板放稳,后面接饼图时只需要围绕这个容器继续写。

先明确:这一节要解决什么问题 ​

园区产业分布需要一张可视化图表来展示各产业占比。笔记里选择的是 ECharts 的环形饼图:先准备一个 DOM 容器,再初始化 ECharts 实例,最后把配置项 setOption 进去。转写稿里把 ECharts 的流程概括为三步:实例化、准备配置项参数、调用实例的 setOption 方法。这个顺序不能乱,因为 echarts.init 依赖一个已经存在的 DOM 节点。

为什么需要:如果直接把配置对象写进模板,浏览器并不知道怎么把它画成图表;ECharts 需要先拿到容器,再创建实例。
怎么想:先问“图表画在哪个 DOM 上”,再问“这个 DOM 什么时候可用”,最后才问“配置项怎么写”。
怎么做:用 ref 绑定容器,在 onMounted 后初始化,调用 setOption。
易错点:在 setup 阶段直接 echarts.init(pieChart.value),此时 DOM 还没挂载,pieChart.value 可能是 null。转写稿里特别强调:初始化代码不能裸写,必须放到 onMounted 里,保证 DOM 可用。

图表容器与 ref:先拿到可渲染的 DOM ​

笔记代码开头是:

javascript
const pieChart = ref(null)

它对应模板里一个用于承载图表的节点。转写稿里讲柱状图时也提到,原来 Demo 用 document 原生操作拿 DOM,在 Vue 里可以改成 ref 方式拿节点;拿到后还要注意 .value,否则会报“不是函数”之类的错误。

为什么需要:ECharts 初始化时必须传入真实 DOM 元素,不能传 ref 对象本身。
怎么想:ref(null) 只是声明一个响应式引用,模板挂载后它的 .value 才是 DOM。
怎么做:模板中写 <div ref="pieChart"></div>,脚本中 echarts.init(pieChart.value)。
易错点:忘记 .value;ref 名字和模板不一致;容器没有宽高导致图表看不见。

初始化 ECharts 实例:不要在 setup 里裸写 ​

笔记里核心初始化是:

javascript
const myPieChart = echarts.init(pieChart.value)
myPieChart.setOption(pieOption)

为什么需要:init 负责创建图表实例,setOption 负责把配置和数据交给实例渲染。
怎么想:转写稿里说得很直白:第一步实例化,第二步准备配置项,第三步调用 setOption。
怎么做:把初始化封装成 initPieChart,在 onMounted 中调用;如果数据是异步回来的,要确保 parkIndustry 已经有值再初始化,或在数据更新后重新 setOption。
易错点:在 setup 顶层直接调用 initPieChart();容器还没挂载就 init;重复初始化同一容器。

环形饼图配置:从业务数据到视觉结构 ​

笔记中的配置项包括颜色数组、图例、提示框和 series。type: 'pie' 表示饼图,radius: ['55%', '60%'] 让内外圈形成环形,center: ['50%', '40%'] 调整整个图表位置,label.show: false 隐藏默认标签,data: parkIndustry 接入园区产业数据。

为什么需要:环形比实心饼图更轻,适合大屏;图例放底部、颜色统一,能让产业分类更清楚。
怎么想:先确定图表类型,再定视觉结构:颜色、图例位置、提示框、圆心和半径。
怎么做:按笔记保留 legend.bottom: '0'、icon: 'rect'、itemHeight/itemWidth: 10、textStyle.color: '#c6d1db';series 中写 radius 和 center。
易错点:radius 写成单值会变实心;center 不改时图表可能偏下;图例和饼图区域重叠;parkIndustry 为 undefined 时图表空白。

tooltip formatter:把默认提示改成业务需要的两列 ​

笔记里 tooltip.formatter 返回:

javascript
return `${params.seriesName}</br><div style='display:flex;justify-content: space-between;'><div>${params.marker}${params.name}</div><div>${params.percent}%</div></div>`

为什么需要:默认提示信息不够贴合业务,需要显示系列名、产业名、颜色标记和百分比。
怎么想:params 里有 seriesName、marker、name、percent,直接拼成 HTML 字符串即可。
怎么做:在 series.tooltip.formatter 中返回模板字符串。
易错点:formatter 要返回字符串;字段名拼错会显示 undefined;marker 是 ECharts 给的颜色标记,不要自己另写。

数据来源与异步渲染:parkIndustry 为空时怎么办 ​

笔记中:

javascript
const { parkIndustry } = parkInfo.value

转写稿里园区概况数据是异步获取的,首次渲染时 parkInfo.value 可能还是空对象,继续访问属性会出现 undefined。转写稿用可选链解决模板渲染中的空值问题,但图表这里同样要意识到:数据没回来时,parkIndustry 可能没有值。

为什么需要:接口数据不会在组件第一次渲染前就保证到位。
怎么想:先保证数据赋值,再初始化图表;如果先初始化,至少要处理空数据。
怎么做:在获取园区概况数据并完成 parkInfo.value = ... 后再调用 initPieChart(),或用 watch 监听数据变化后重新 setOption。
易错点:把 initPieChart() 写在数据请求之前;以为模板加了可选链,图表配置里就不会遇到空值。

完整代码保留 ​

javascript
// 渲染园区产业分布图表
const pieChart = ref(null)
const initPieChart = () => {
  const { parkIndustry } = parkInfo.value
  const pieOption = {
    color: [
      '#00B2FF', '#2CF2FF', '#892CFF', '#FF624D', '#FFCF54', '#86ECA2'],
    legend: {
      itemGap: 20,
      bottom: '0',
      icon: 'rect',
      itemHeight: 10, // 图例icon高度
      itemWidth: 10, // 图例icon宽度
      textStyle: {
        color: '#c6d1db',
      },
    },
    tooltip: {
      trigger: 'item'
    },
    series: [
      {
        name: '园区产业分析',
        type: 'pie',
        radius: ['55%', '60%'], // 设置内圈与外圈的半径使其呈现为环形
        center: ['50%', '40%'], // 圆心位置, 用于调整整个图的位置
        tooltip: {
          trigger: 'item',
          formatter: (params) => {
            return `${params.seriesName}</br><div style='display:flex;justify-content: space-between;'><div>${params.marker}${params.name}</div><div>${params.percent}%</div></div>`
          }
        },
        label: {
          show: false,
          position: 'center',
        },
        data: parkIndustry,
      },
    ],

  }
  const myPieChart = echarts.init(pieChart.value)
  myPieChart.setOption(pieOption)
}

实际调用时,把 initPieChart() 放到 onMounted 中,或在数据就绪后调用。这样既符合转写稿强调的“DOM 可用后再初始化”,也能避免 parkIndustry 还没赋值就渲染图表。

代码示例 ​

示例一:封装园区信息接口(src/apis/park.js) ​

js
// src/apis/park.js
import request from '@/utils/request'

/**
 * 获取园区信息
 * @returns
 */
export function getParkInfoAPI () {
  return request({
    url: "/park/statistics/info"
  })
}

要点解释 / 易错点:

  • request 来自本项目的 @/utils/request(内部已完成 baseURL、请求拦截器注入 token、响应拦截器 return res.data),导入路径必须和本地实际文件名一致;如果工具文件叫 app.js,这里写成 @/utils/request 就会报"模块找不到"。
  • 控制台出现 GET ... 404 / 500 且路径后面带个"对象"样式的报错,几乎一定是路径写错(如把 @/apis 写成 @/api s、apis/park 写错),顺着报错行号去检查 import 即可。
  • 接口无任何参数,是纯 GET,不要多写 params 或 data。

示例二:园区概况组件(完整 SFC,含可选链渲染) ​

vue
<script setup>
import { ref } from 'vue'
import { getParkInfoAPI } from '@/apis/park'

// 响应式数据:初值要与接口返回格式保持一致(接口返回对象,就写对象)
const parkInfo = ref({})

// 封装单独的方法,把接口调起来
const getParkInfo = async () => {
  const res = await getParkInfoAPI()
  parkInfo.value = res.data
}

// setup 顶层直接调用即可(也可放进 onMounted)
getParkInfo()
</script>

<template>
  <div class="all-charts">
    <!-- 园区概况 -->
    <div class="section-one">
      <img class="img-header"
        src="https://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/smartPark/%E5%A4%A7%E5%B1%8F%E5%88%87%E5%9B%BE/%E5%9B%AD%E5%8C%BA%E6%A6%82%E5%86%B5%402x.png"
        alt="" />
      <div class="icons-container">
        <div class="item">
          <div class="icons-item building-icon">
            <span class="number">{{ parkInfo.base?.buildingTotal }}</span>
          </div>
          <span class="title">楼宇总数</span>
          <span class="unity">(栋)</span>
        </div>
        <div class="item">
          <div class="icons-item enterprise-icon">
            <span class="number">{{ parkInfo.base?.enterpriseTotal }}</span>
          </div>
          <span class="title">入驻企业总数</span>
          <span class="unity">(家)</span>
        </div>
        <div class="item">
          <div class="icons-item car-icon">
            <span class="number">{{ parkInfo.base?.parkingTotal }}</span>
          </div>
          <span class="title">车位总数</span>
          <span class="unity">(个)</span>
        </div>
        <div class="item">
          <div class="icons-item rod-icon">
            <span class="number">{{ parkInfo.base?.chargePoleTotal }}</span>
          </div>
          <span class="title">一体杆总数</span>
          <span class="unity">(个)</span>
        </div>
      </div>
    </div>
  </div>
</template>

要点解释 / 易错点:

  • 两次渲染问题:首次渲染时后端数据还没回来,parkInfo 是 {},{}.base 得到 undefined,再 .buildingTotal 就会抛 "Cannot read properties of undefined"。解决方式是可选链 ?.:只有 ?. 前面的值不是 undefined/null,才会继续往后做点运算。
  • 不要把初值写成 ref(null) 后再 null.base 同理也会炸;按接口返回类型给初值(对象 → {},数组 → [])。
  • 模板里图片路径、icons-item 图标(building-icon 等)依赖 assets/sence 里的图标资源,资源缺失时页面不显示图标且控制台一堆 404,需要把 sence 图片目录拷进项目并保证样式里引用路径正确。
  • 若请求返回 401,是 token 没被注入到请求头:先确认 utils/token.js 里导出的方法名与 utils/request.js 中 import 的名字一致(如 getToken ≠ getCookie),再把子应用主域改成 localhost 与主应用一致,才能共享 cookie 里的 token。

示例三:园区产业分布饼图组件(完整 SFC) ​

vue
<script setup>
import { ref, onMounted } from 'vue'
import * as echarts from 'echarts'
import { getParkInfoAPI } from '@/apis/park'

const parkInfo = ref({})
const pieChart = ref(null)

const getParkInfo = async () => {
  const res = await getParkInfoAPI()
  parkInfo.value = res.data
}

// 渲染园区产业分布图表
const initPieChart = () => {
  const { parkIndustry } = parkInfo.value
  const pieOption = {
    color: [
      '#00B2FF', '#2CF2FF', '#892CFF', '#FF624D', '#FFCF54', '#86ECA2'],
    legend: {
      itemGap: 20,
      bottom: '0',
      icon: 'rect',
      itemHeight: 10, // 图例icon高度
      itemWidth: 10, // 图例icon宽度
      textStyle: {
        color: '#c6d1db',
      },
    },
    tooltip: {
      trigger: 'item'
    },
    series: [
      {
        name: '园区产业分析',
        type: 'pie',
        radius: ['55%', '60%'], // 设置内圈与外圈的半径使其呈现为环形
        center: ['50%', '40%'], // 圆心位置,用于调整整个图的位置
        tooltip: {
          trigger: 'item',
          formatter: (params) => {
            return `${params.seriesName}</br><div style='display:flex;justify-content: space-between;'><div>${params.marker}${params.name}</div><div>${params.percent}%</div></div>`
          }
        },
        label: {
          show: false,
          position: 'center',
        },
        data: parkIndustry,
      },
    ],
  }
  const myPieChart = echarts.init(pieChart.value)
  myPieChart.setOption(pieOption)
}

// 必须在 onMounted 中:DOM 可用 + 数据已回来
onMounted(async () => {
  await getParkInfo()
  initPieChart()
})
</script>

<template>
  <!-- 园区产业分布 -->
  <div class="section-three">
    <img class="img-header"
      src="https://yjy-teach-oss.oss-cn-beijing.aliyuncs.com/smartPark/%E5%A4%A7%E5%B1%8F%E5%88%87%E5%9B%BE/%E5%9B%AD%E5%8C%BA%E4%BA%A7%E4%B8%9A%E5%88%86%E5%B8%83%402x.png"
      alt="" />
    <div class="pie-chart" ref="pieChart"></div>
  </div>
</template>

<style lang="scss" scoped>
.section-three {
  flex-basis: 40%;

  .pie-chart {
    position: relative;
    margin: 0 auto;
    padding-bottom: 20px;
    width: 80%;
    height: calc(100% - 40px);
  }
}
</style>

要点解释 / 易错点:

  • echarts.init(pieChart.value) 有前置依赖:目标节点必须已经渲染成真实 DOM,所以初始化不能裸写在 setup 里,必须放进 onMounted;同时 option 里用到了 parkIndustry,因此要先 await getParkInfo() 再 initPieChart(),否则 data 是 undefined,饼图空白。
  • 忘记写 .value 会报 Cannot read properties of null (reading 'getAttribute') 之类的错——ref 拿 DOM 一定要 .value。
  • 容器必须有明确宽高(这里 width: 80%; height: calc(100% - 40px)),否则 ECharts 初始化后看不到图。
  • 环形效果靠 radius: ['55%', '60%'](内圈/外圈半径),center: ['50%', '40%'] 用于把圆心整体上移,给底部图例腾位置。
  • 样式块要写 lang="scss",否则嵌套写法的 .pie-chart 不会被正确解析(项目需已安装 sass 依赖),部分环境不写连高亮和编译都会出问题。
  • 图例文字颜色、icon 尺寸都在 legend 里控制,label.show: false 是关掉饼图外部的文字标签,让信息只通过 tooltip 展示。

常见错误 ​

  1. 现象:模板报 Cannot read properties of undefined (reading 'buildingTotal')。
    原因:接口未返回时 parkInfo 仍是 {},首次渲染访问 parkInfo.base.buildingTotal 得到 undefined.buildingTotal。
    修法:使用可选链 parkInfo.base?.buildingTotal,或给 base 合理初始值/条件渲染。

  2. 现象:删除组件后页面报组件找不到、模块 404。
    原因:只删了文件,未同步清理 import、路由配置或模板引用。
    修法:按报错文件与行号反向清理所有引用,删文件后全局搜索组件名/路径。

  3. 现象:接口返回 401。
    原因:请求头未注入 token,或子应用与主应用域名/cookie key 不一致导致取不到 token。
    修法:确认主应用已登录、cookie key 一致、请求拦截器已注入 token;本地调试统一域名。

  4. 现象:ECharts 初始化报错或图表不显示。
    原因:在 DOM 尚未挂载、ref.value 为空时执行 echarts.init。
    修法:把初始化放到 onMounted 中,确认 ref 已绑定且容器有宽高。

  5. 现象:控制台出现 GET ... 404 或模块路径解析失败。
    原因:接口路径、导入路径大小写/拼写错误,如 api 写成 apis。
    修法:根据报错行号定位导入语句,逐段核对路径与文件名。

  6. 现象:script setup 内数据/方法在模板中不可用,或页面异常。
    原因:setup 拼写错误,等价于未启用语法糖,也未按旧写法导出。
    修法:统一写成 <script setup>,避免拼写错误。

  7. 现象:SCSS 样式不生效或提示缺少 sass。
    原因:未安装 sass,或 style 标签未声明 lang="scss"。
    修法:安装 sass -D,并使用 <style lang="scss" scoped>。

练习 ​

  1. 构造 parkInfo = ref({}) 的场景,复现空对象多层属性访问报错,并用两种方式修复:可选链、初始值兜底。
  2. 模拟子应用请求返回 401,按“登录主应用→检查 cookie key→检查请求头→检查域名”的顺序完成排查。
  3. 将 ECharts 柱状图初始化代码从裸写改为 onMounted 中执行,并说明为什么不能提前初始化。
  4. 给项目新增一个仅子应用使用的大屏路由,并配置根路径重定向到该路由。
  5. 检查当前项目 SCSS 配置,补齐依赖与 lang="scss",并验证样式是否生效。

课堂画面 ​

课堂画面 01

课堂画面 02

课堂画面 03

课堂画面 04

课堂画面 05

课堂画面 06

原视频对照 ​

#视频时长
1大屏项目-使用create-vue初始化项目12:21
2大屏项目-准备utils模块05:39
3大屏项目-准备基础路由05:48
4大屏项目-初始化样式05:12
5大屏项目-园区管理数据渲染24:43
6常见问题说明04:24
7大屏项目-年度收入统计-图表基础渲染14:36

小结 ​

  1. 子应用不是孤立系统,token、cookie key、请求封装要与主应用保持一致。
  2. 大屏路由应保留一个子应用内唯一路径,便于后续微前端接入匹配。
  3. 异步数据渲染至少经历“初始值渲染”和“数据返回后渲染”,模板访问深层属性要防御空值。
  4. ECharts 使用可归纳为:准备容器、初始化实例、设置 option,关键是初始化时机必须在 DOM 可用后。
  5. 404 优先查路径,401 优先查 token,图表空白优先查容器、时机和数据。
  6. 删文件必须同步删引用,否则残留导入会直接导致编译或运行错误。
  7. SCSS 生效依赖依赖安装与 lang 声明,样式初始化应在入口统一引入。