vue3项目搭建基础

element-plus

安装依赖(确保版本适配 Vue 3)

npm install element-plus --save

plugins/element.js 实现按需引入组件

javascript 复制代码
import { ElButton } from 'element-plus'  // 导入 Element Plus 的 Button(Vue 3 版本)
import 'element-plus/dist/index.css' //全局引入样式,避免找不到

 // 接收 main.js 传入的 app 实例
export default function setupElement(app) {
 // 注册 Button 组件(Vue 3 用 app.component 注册单个组件)
  app.component('ElButton', ElButton)
}

main.js

javascript 复制代码
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import setupElement from './plugins/element.js'

//创建应用实例
const app = createApp(App)
//注册 Element Plus 插件
setupElement(app)
app.use(router)
//挂载应用
app.mount('#app')

App.vue

xml 复制代码
<template>
  <div id="app">
        <el-button type="danger">Danger</el-button>
  </div>
</template>

<script>
export default {
  name: 'app'
}
</script>

<style>
        html,body,#app{
                width: 100%;
                height: 100%;
                padding: 0;
                margin: 0;
        }
</style>

代码是从上往下一行一行执行的

  • 执行 main.js中引入代码

  • import { createApp } from 'vue' → 从 Vue 库导入 createApp 方法;

  • import App from './App.vue' → 导入根组件 App.vue(仅加载,未渲染);

  • import router from './router' → 导入路由(如果有配置);

  • 当执行到import setupElement from './plugins/element.js'会立即进入 element.js 执行里面的代码 ,但注意:element.js 中只有「导入组件 + 定义函数」的逻辑会执行,app.component 注册组件的逻辑要等 main.js 中调用 setupElement(app) 时才执行。

从 Element Plus 加载按钮组件的代码;

执行 import 'element-plus/dist/index.css' → 加载 Element Plus 的全局样式(CSS 生效);

执行 export default function setupElement(app) {...} → 定义 setupElement 函数(仅定义,不执行函数内部代码);

执行完 element.js 后,将 setupElement 函数作为返回值,赋值给 main.js 中的 setupElement 变量

  • 执行 main.js 中的实例创建和注册逻辑

  • const app = createApp(App) → 创建 Vue 应用实例(此时还未挂载组件);

  • setupElement(app)调用 element.js 中定义的函数

    • 将 Vue 实例 app 传入函数,执行 app.component('ElButton', ElButton) → 全局注册 ElButton 组件;
  • app.use(router) → 注册路由(如果有);

  • app.mount('#app') → 将 Vue 实例挂载到页面的 #app 元素上:

    • 渲染 App.vue 组件;
    • 解析 App.vue 中的 <el-button> 标签 → 匹配到全局注册的 ElButton 组件 → 渲染出带样式的危险按钮。
javascript 复制代码
import setupElement from './plugins/element.js'

//创建应用实例
const app = createApp(App)
//注册 Element Plus 插件
setupElement(app)

相当于这里要引用setupElement这个方法,创建实例时,调用setupElement这个方法,并将创建的实例当成参数传入,实现vue组件的注册。

简单说:setupElement 是一个 "组件注册工具函数",调用它并传入 Vue 实例 app,本质就是把 ElButton 组件挂载到 Vue 应用实例上 ,这样整个项目的所有组件(比如 App.vue)都能使用 <el-button> 标签。

element.js

javascript 复制代码
import { ElCard,ElCol,ElRow } from 'element-plus'
import 'element-plus/dist/index.css'

export default function setupElement(app) {
  app.component('ElCard', ElCard)
  app.component('ElCol', ElCol)
  app.component('ElRow', ElRow)

}

进行优化

javascript 复制代码
import { ElCard,ElCol,ElRow } from 'element-plus'
import 'element-plus/dist/index.css'

export default function setupElement(app) {

    //批量注册
    const components = { ElCard, ElCol, ElRow }
    Object.entries(components).forEach(([name, component]) => {
      app.component(name, component)
    })
}

将需要注册的组件放入对象中,这里是简写语法。 const components = { ElCard, ElCol, ElRow }

const components = { ElCard: ElCard, ElCol: ElCol, ElRow: ElRow }

Object.entries(components)把对象转成「键值对数组」:转换成「二维数组」,每个子数组包含「键、值」

css 复制代码
[     ['ElCard', ElCard], 
    ['ElCol', ElCol],
    ['ElRow', ElRow]
   ]

.forEach(...) → 遍历这个二维数组

([name, component]):ES6 数组解构,把遍历到的子数组 ['ElCard', ElCard] 拆成两个变量:

  • name = 'ElCard'(组件注册名);
  • component = ElCard(组件对象)

app.component(name, component):调用 Vue 3 的组件注册方法,等价于 app.component('ElCard', ElCard)

echarts

安装依赖

ESLint 相关的配置包版本太旧,和新版的 Vue ESLint 插件不兼容,连带导致 echarts 安装失败.直接在安装命令后加 --legacy-peer-deps,强制忽略版本冲突,快速安装 echarts

css 复制代码
npm i -S echarts --legacy-peer-dep

正常安装

css 复制代码
npm i -S echarts

彻底解决依赖冲突

如果想从根本上修复版本冲突,执行以下步骤(不影响已安装的 Element Plus):

步骤 1:卸载旧的 ESLint 配置包

bash

运行

lua 复制代码
npm uninstall @vue/eslint-config-standard --save-dev
步骤 2:安装兼容新版 eslint-plugin-vue@8.x 的配置包

bash

运行

sql 复制代码
npm install @vue/eslint-config-standard@latest --save-dev --legacy-peer-deps
步骤 3:重新安装 echarts

bash

运行

css 复制代码
npm i -S echarts

main.js

javascript 复制代码
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import setupElement from './plugins/element.js'
import * as echarts from 'echarts'

const app = createApp(App)
setupElement(app)
app.config.globalProperties.$echarts = echarts
app.use(router)
app.mount('#app')

Vue 3 中移除 Vue.prototype,改用 app.config.globalProperties 的本质是:Vue 3 不再基于构造函数原型链扩展,而是基于应用实例的全局配置来挂载全局属性 / 方法 ,更贴合 Vue 3 的「实例化」设计(每个 createApp() 都是独立的应用实例)。app.config.globalProperties 挂载到这个对象上的属性 / 方法,会被注入到所有组件的「选项式 API」上下文(即 this)中,效果和 Vue 2 的 Vue.prototype 完全一致,但作用域仅限当前应用实例。

arduino 复制代码
// 创建应用实例 
const app = createApp(App) 
// 挂载全局属性/方法 
app.config.globalProperties.自定义属性名 = 要挂载的内容
特性 Vue 2 Vue 3
核心载体 全局构造函数 Vue(所有组件共享同一个原型链) 应用实例 appcreateApp() 创建,多实例隔离)
全局挂载 Vue.prototype.$xxx = xxx(挂载到构造函数原型,全局共享) app.config.globalProperties.$xxx = xxx(挂载到单个应用实例,实例隔离)
  • 多实例隔离 :Vue 3 支持一个页面创建多个独立的 Vue 应用实例(比如 app1 = createApp(), app2 = createApp()),如果用原型链,会导致多个实例的全局属性互相污染;而 globalProperties 是绑定到单个 app 实例的,不同实例的全局属性互不影响。

  • 更符合模块化 :Vue 3 推崇「模块化」「按需使用」,原型链扩展是侵入式的(修改全局构造函数),而 globalProperties 是配置式的(仅修改当前应用实例),更灵活。

import * as echarts from 'echarts'

import * as echarts from 'echarts' 的作用是:把 echarts 库中所有导出的内容,整体导入并挂载到 echarts 这个变量上 。这么写的根本原因是:echarts v5+ 采用「命名导出」(Named Export),而非「默认导出」(Default Export),所以不能用 import echarts from 'echarts' 这种默认导入方式

导出方式 语法示例(库作者写的代码) 导入方式(你写的代码) 适用场景
默认导出(Default Export) export default { init: () => {} } import echarts from 'echarts' 库只有一个核心导出(比如 Vue、React)
命名导出(Named Export) export const init = () => {}``export const dispose = () => {} import * as echarts from 'echarts'import { init } from 'echarts' 库有多个独立导出(比如 echarts、lodash)

echarts 源码的核心导出逻辑类似这样(简化版):

javascript 复制代码
// echarts 源码中的导出逻辑(模拟)
export const init = (dom) => { /* 初始化图表 */ }
export const dispose = (instance) => { /* 销毁图表 */ }
export const registerMap = (name, data) => { /* 注册地图 */ }
// ... 还有上百个命名导出的方法/对象

:echarts 没有写 export default,只有一堆 export const/function 的「命名导出」------ 这就是为什么你用 import echarts from 'echarts' 会报错(找不到默认导出)

import * as echarts from 'echarts' 语法拆解

语法片段 含义
import * 导入目标模块中所有 命名导出的内容(init/dispose/registerMap 等)
as echarts 把这些导入的内容,统一挂载到一个名为 echarts命名空间对象
from 'echarts' echarts 这个模块导入

相当于给 echarts 库的所有导出内容做了一个 "收纳箱",箱子名字叫 echarts

  • 原本分散的 init 方法 → 现在是 echarts.init
  • 原本分散的 dispose 方法 → 现在是 echarts.dispose
  • 所有 echarts 的功能,都可以通过 echarts.xxx 访问,既整洁又不会污染全局变量。

如果想只导入部分方法,也可以写:

csharp 复制代码
// 只导入 init 方法(命名导出的精准导入) 
import { init } from 'echarts'
// 使用时直接写 init(),而非 echarts.init() 
const chart = init(document.getElementById('chart'))

echarts 极致按需导入

javascript 复制代码
// 极致按需导入(推荐生产环境用)
import { init } from 'echarts/core'
import { BarChart } from 'echarts/charts'
import { TitleComponent, TooltipComponent } from 'echarts/components'
import { CanvasRenderer } from 'echarts/renderers'

// 注册需要的模块
init.use([BarChart, TitleComponent, TooltipComponent, CanvasRenderer])

// 使用 init 方法创建图表
const chart = init(document.getElementById('chart'))

echarts使用

php 复制代码
<div ref="totalOrderRef" :style="{width:'100%',height:'100px'}" />

<script setup>
        import { onMounted,ref,getCurrentInstance } from 'vue'
        // 1. 定义 ref 变量,和模板中的 ref="totalOrderRef" 对应
        const totalOrderRef = ref(null)
        // 2. 在 onMounted 中获取元素(DOM 渲染完成)
        onMounted(() => {
          // 通过 ref 获取元素(Vue3 推荐方式)
          const chartDom = totalOrderRef.value
          if (chartDom) {
                 const instance = getCurrentInstance()
                 const $echarts = instance.appContext.config.globalProperties.$echarts
                 const chart = $echarts.init(chartDom)
                 chart.setOption({
                         xAxis: {
                                 type: 'value',
                                 show: false
                         },
                         yAxis: {
                                 type: 'category',
                                 show: false
                         },
                         series: [{
                                name: '上月平台用户数',
                                type: 'bar',
                                stack: '总量',	
                                data: [300],
                                barWidth: 10,
                                itemStyle: {
                                        color: '#45c946'
                                }
                         },{
                                name: '今日平台用户数',
                                type: 'bar',
                                stack: '总量',
                                data: [200],
                                itemStyle: {
                                        color: '#ddd'
                                }
                         },{
                                 type: 'custom',
                                 stack: '总量',
                                 data: [300],
                                 renderItem: (params,api) => {
                                        const value = api.value(0)
                                        const point = api.coord([value,0])
                                        return {
                                                type: 'group',
                                                position: [point[0] - 10, point[1]] ,
                                                children: [{
                                                        type: 'path',
                                                        shape: {
                                                                d: 'M0 767.909l512.029-511.913L1024 767.909 0 767.909z',
                                                                x: 0,
                                                                y: 2,
                                                                width: 20,
                                                                height: 20
                                                        },
                                                        style: {
                                                                fill: '#45c946'
                                                        }
                                                },{
                                                        type: 'path',
                                                        shape: {
                                                                d: 'M1024 255.996 511.971 767.909 0 255.996 1024 255.996z',
                                                                x: 0,
                                                                y: -22,
                                                                width: 20,
                                                                height: 20
                                                        },
                                                        style: {
                                                                fill: '#45c946'
                                                        }
                                                }]
                                        }
                                 }
                         }],
                         grid: {
                                top: 0,
                                bottom: 0,
                                left: 0,
                                right: 0
                         }
                 })
          }
        })
</script>

getCurrentInstance()

<script setup> 中 "接触" 到组件底层实例的唯一官方入口(<script setup> 是封闭作用域,无法直接访问 this,所以无法像 Vue2 那样用 this.$echarts,只能通过 getCurrentInstance() 获取 appContext(应用上下文),进而访问全局挂载的 $echarts。必须在组件生命周期内调用,onMountedonCreated<script setup> 顶层调用。异步回调(如 setTimeout、接口请求回调)中调用可能获取不到实例。

创建 ECharts 实例

$echarts.init() 方法会创建一个独立的 ECharts 实例 ,并返回给变量 chart。这个实例是操作图表的 "唯一入口"

  • 每个 DOM 容器对应一个独立的 ECharts 实例(避免多个图表冲突);

  • 实例包含图表的所有配置、数据、渲染状态等核心信息。

setOption

向 ECharts 实例传入图表的配置项(Option),让 ECharts 根据配置渲染 / 更新图表

php 复制代码
    chart.setOption({
      xAxis: { type: 'value', show: false }, // x轴配置
      yAxis: { type: 'category', show: false }, // y轴配置
      series: [/* 柱状图/自定义图形系列配置 */], // 图表数据和样式
      grid: { top: 0, bottom: 0 } // 网格布局
    })

配置合并规则

arduino 复制代码
   // 完全替换旧配置,重新渲染图表 
   chart.setOption(newOption, true)            
  • 默认:setOption合并新旧配置(新配置覆盖旧配置,未修改的保留);

  • 强制替换:如需完全替换配置(而非合并),可传第二个参数 true

renderItem

它是 ECharts 「自定义系列(custom series)」的核心渲染函数,作用是「告诉 ECharts 如何手动绘制每一个数据项的图形」

renderItem 是自定义系列(type: 'custom')的必填配置,ECharts 渲染自定义系列时,会对每一个数据项调用一次 renderItem,你需要在这个函数中返回「图形描述对象」,ECharts 会根据这个描述画出对应的图形。

当 ECharts 渲染 type: 'custom' 的系列时,会遍历该系列的 data 数组(比如写的 data: [200]),对每一个数据项(这里是 200)执行一次 renderItem 函数。

通过 renderItem,你可以突破 ECharts 内置图表(如柱状图、折线图)的限制,实现:

  • 自定义形状(如三角形、五角星、不规则路径);
  • 精准控制图形位置(如定位到 200 数值处);
  • 组合多个图形(如一个三角形 + 一个文本标签);
  • 动态调整图形样式(如根据数据值改变颜色 / 大小)。

api对象提供 ECharts 内置的「坐标转换 / 数据获取」工具方法:

api.value(dimIndex):获取当前数据项指定维度的值(如 api.value(0) 取 200);

api.coord([x, y]):把逻辑坐标(如 [200, 0])转换成画布像素坐标;

api.size([width, height]):把逻辑尺寸转换成像素尺寸;

api.style():获取系列默认样式

params对象包含当前数据项的上下文信息:

params.dataIndex:当前数据项的索引(如 0)

params.value:当前数据项的原始值(如 200);

params.seriesIndex:当前系列的索引;

params.coordSys:坐标系信息(如 x/y 轴类型)

renderItem 必须返回一个「图形描述对象」(或数组),ECharts 会根据这个对象绘制图形

arduino 复制代码
    return {
     type: 'group', // 图形类型:组合图形(可包含多个子图形)
     position: point, // 图形的基准位置(像素坐标)
     children: [{ // 子图形列表
       type: 'path', // 图形类型:路径(自定义形状)
       shape: { // 形状配置
         d: 'M0 767.909...', // 路径指令(三角形的绘制路径)
         x: 0,//路径的偏移
         y: 0, //路径的偏移
         width: 20, // 尺寸
         height: 20 // 尺寸
       },
       style: { fill: 'red' } // 样式:填充红色
     }]
   }
类型 作用 示例场景
path 绘制自定义路径(如三角形、不规则图形) 你画红色三角形的核心
rect 绘制矩形 自定义柱状图
circle 绘制圆形 自定义散点图
text 绘制文本 给图形加标签
group 组合多个图形 三角形 + 文本标签

和前面用 type: 'bar' 画柱状图,ECharts 会自动渲染;而 type: 'custom' 则是把渲染权完全交给你,核心差异

维度 内置系列(bar/line) 自定义系列(custom)
渲染逻辑 ECharts 自动绘制(固定形状) 你通过 renderItem 手动定义
灵活性 低(只能改样式,不能改形状) 高(可画任意形状)
复杂度 简单(只需配置 data/style) 稍高(需手动计算坐标 / 形状)
适用场景 标准图表(柱状图、折线图) 非标准图形(自定义标记、组合图形)

配置

stack

它是 ECharts 中用于实现「堆叠式图表」的关键配置,作用是「将多个同系列类型(如 bar)、同 stack 值的系列,在同一类目下按数值累加堆叠显示」 ------ 两个柱状图系列因为都配置了 stack: '总量',才会从 0 开始依次累加(200+260),形成绿色 + 灰色的分段堆叠效果

组件引用

把导入的 TopView 组件 "注册" 到 Home 组件的作用域中,只有注册后,才能在 <template> 中使用;

xml 复制代码
  <template>
      <div class="home">
              <!-- 引用组件 -->
              <top-view/>	  
      </div>
</template>

<script>
        import TopView from '../components/TopView'
        export default {
                name: 'Home',
                components: {
                        TopView,// 局部注册组件
                }
        }
</script>

<style>
        .home{
                width: 100%;
                height: 100%;
                padding: 0 20px;
                background: #eee;
                box-sizing: border-box;
        }
</style>

仅在当前 Home 组件内可用,其他组件(如 About.vue)不能直接用 <top-view>,需重新导入 + 注册。不会污染全局作用域,适合只在单个页面使用的组件

公共组件提取

如CommonCard这个组件很多地方用到,进行提取

创建一个mixins文件夹,创建对应的文件名card.js

javascript 复制代码
import CommonCard from '../components/CommonCard/index'
export default {
        components: {
                CommonCard
        }
}

需要调用时引入card.js

xml 复制代码
<template>
        <common-card/>
</template>

<script>
        import CommonCard from '../../mixins/card'
        export default {
                mixins: [CommonCard]
        }
</script>

<style>
</style>

vue3调用公共组件

xml 复制代码
<template>
        <common-card
                title="累计订单量"
                value="2,124,223"	
        >
        </common-card>	
</template>

<script setup>
        import { onMounted, ref,defineOptions } from 'vue'
        import commonCardMixin from '../../mixins/commonCardMixin'
        defineOptions({
          mixins: [commonCardMixin]
        })

</script>

需要使用defineOptions

Mixin 是 Vue2 的经典写法,Vue3 更推荐用 "组件导入函数""全局注册组件" 替代 mixin(减少隐式依赖)

javascript 复制代码
// utils/importComponents.js
export const useCommonCard = () => {
  const CommonCard = defineAsyncComponent(() => import('../components/CommonCard/index'))
  return { CommonCard }
}

业务组件中使用:

xml 复制代码
 <script setup>
    import { useCommonCard } from '../../utils/importComponents'
    const { CommonCard } = useCommonCard() // 显式获取组件
</script>

全局注册组件(适合全项目高频使用的组件)

javascript 复制代码
// main.js
import { createApp } from 'vue'
import CommonCard from './components/CommonCard/index'

const app = createApp(App)
app.component('CommonCard', CommonCard) // 全局注册,所有组件可直接使用

插槽

引用子组件common-card时,自定义两个插槽的内容及样式。下面代码存在有误地方,

xml 复制代码
    <template>
      <div class="compare-wrapper">
        <div class="compare">
          <span>同比</span>
          <span class="emphasis">7.3%</span>
        </div>	
      </div> 
    </template>

这段代码无法正常显示出默认插槽中的内容,<template> 无任何指令(如 v-slot)直接包裹默认插槽 → 编译时会被忽略,内容不渲染.

xml 复制代码
父组件:
<template>
  <common-card 
    title="销售额"
  >

    <template>
      <div class="compare-wrapper">
        <div class="compare">
          <span>同比</span>
          <span class="emphasis">7.3%</span>
        </div>	
      </div> 
    </template>
    <!-- footer 具名插槽 -->
    <template v-slot:footer>
      <span>昨日销售额:</span>
      <span class="money">¥40,123</span>
    </template> 
  </common-card>
</template>

子组件:
<template>
<div class="common-card">
	<div class="title">{{title}}</div>
	<div>
                <slot></slot>
	</div>
	<div class="total">
	    <slot name="footer"></slot>
	</div>
</div>
</template>

<template>...</template>,Vue 不知道这是 "默认插槽",就不会把内容插入到 <slot></slot> 位置;加 v-slot 指令后,Vue 才会识别并渲染。

xml 复制代码
    <template v-slot>
      <div class="compare-wrapper">
        <div class="compare">
          <span>同比</span>
          <span class="emphasis">7.3%</span>
        </div>	
      </div> 
    </template>
    

或者不需要template包裹

xml 复制代码
        <div class="compare-wrapper">
        <div class="compare">
          <span>同比</span>
          <span class="emphasis">7.3%</span>
        </div>	
      </div> 
      
写法 是否生效 原因
<template>内容</template> ❌ 不生效 Vue 无法识别这是默认插槽,编译时忽略该 template
<template v-slot>内容</template> ✅ 生效 v-slot 指令明确标识这是「默认插槽」,Vue 会把内容插入到 <slot></slot> 位置
直接写内容(无 template) ✅ 生效 Vue 自动把未包裹的内容识别为默认插槽,是最简洁的写法

Vue2 和 Vue3 在「<template> 包裹默认插槽」的语法上确实有差别 ------Vue2 中 <template> 包裹默认插槽无需加 v-slot 就能生效,而 Vue3 必须显式加 v-slot 指令

场景 Vue2 写法(生效) Vue3 写法(生效) Vue3 错误写法(不生效)
template 包裹默认插槽 <template>内容</template> <template v-slot>内容</template> <template>内容</template>
直接写默认插槽内容 直接写内容(生效) 直接写内容(生效) -
具名插槽 <template slot="footer"> / <template v-slot:footer> <template #footer> / <template v-slot:footer> -

vue3这样都通过 v-slot 指令管理,逻辑更清晰,也避免了 "无指令 template 被误解析" 的问题。

slot作用

Vue3 中的插槽,本质是组件对外暴露的 "自定义渲染接口" ------ 组件开发者给组件预留 "空白位置"(<slot>),组件使用者可以往这个位置插入任意内容(HTML、子组件、逻辑渲染的内容),实现「组件固定结构复用 + 自定义内容灵活定制」。

插槽就像你买的 "定制化蛋糕胚",蛋糕胚的大小、形状(组件的基础 UI / 逻辑)是固定的,但你可以往上面加水果、奶油、巧克力(插槽内容),做出不同样式的蛋糕,而不用重新做蛋糕胚。

复用性:

没有插槽的组件,只能显示固定内容

xml 复制代码
<!-- 无插槽的 CommonCard 组件 -->
<template>
  <div class="card">
    <div class="title">累计销售额</div>
    <div class="content">¥1,211,312</div>
  </div>
</template>

有插槽,复用性拉满

xml 复制代码
<!-- 有插槽的 CommonCard 组件 -->
<template>
  <div class="card">
    <div class="title">{{ title }}</div>
    <!-- 插槽:预留自定义位置 -->
    <div class="content">
      <slot></slot>
    </div>
  </div>
</template>
<script>
export default { props: ['title'] }
</script>

可自定义内容,同一个组件适配多场景

xml 复制代码
<!-- 场景1:显示销售额 -->
<common-card title="累计销售额">
  <div>¥1,211,312</div>
</common-card>

<!-- 场景2:显示订单数 -->
<common-card title="累计订单数">
  <div>123,456 单</div>
</common-card>

<!-- 场景3:显示复杂内容(同比数据+箭头) -->
<common-card title="累计销售额">
  <div class="compare">
    <span>日同比 7.3%</span>
    <div class="arrow-up"></div>
  </div>
</common-card>

逻辑与 UI 解耦,降低维护成本,比如修改卡片的基础样式只需改 CommonCard,修改销售额的显示格式只需改插槽内容。

插槽可以插入任意内容:

  • 普通 HTML 标签(<div>/<span>);
  • 其他 Vue 组件(<el-button>/<chart>);
  • 带逻辑的渲染内容(v-if/v-for/{{ 变量 }});
  • 甚至是 JSX/TSX(复杂场景)。

Vue3 把插槽分为 3 类,覆盖所有自定义场景,且废弃了 Vue2 的 slot 属性,统一用 v-slot 指令(简写 #

1. 默认插槽(匿名插槽)

  • 定义 :无 name 属性的 <slot>,是组件的 "默认自定义位置";

  • 用法

    • 简洁写法(推荐):直接把内容写在组件标签内,无需 <template>
    • 完整写法:用 <template v-slot> 包裹(Vue3 必须加 v-slot)。

2. 具名插槽

  • 定义 :有 name 属性的 <slot>,用于组件内多位置自定义(比如卡片的 header、body、footer);

  • 用法 :用 <template #插槽名>(或 <template v-slot:插槽名>)包裹内容,和组件内的 name 一一对应。

    xml 复制代码
    <!-- 组件内定义多个具名插槽 -->
    <template>
      <div class="card">
        <div class="header">
          <slot name="header"></slot> <!-- 具名插槽:header -->
        </div>
        <div class="body">
          <slot></slot> <!-- 默认插槽 -->
        </div>
        <div class="footer">
          <slot name="footer"></slot> <!-- 具名插槽:footer -->
        </div>
      </div>
    </template>
    
    <!-- 使用具名插槽 -->
    <common-card>
      <template #header>
        <h3>累计销售额</h3> <!-- 对应 header 插槽 -->
      </template>
      <div>¥1,211,312</div> <!-- 对应默认插槽 -->
      <template #footer>
        <span>昨日销售额:¥40,123</span> <!-- 对应 footer 插槽 -->
      </template>
    </common-card>

3. 作用域插槽(带数据的插槽)

  • 定义:组件可以给插槽传递数据(作用域数据),使用者可以接收并基于这些数据自定义渲染 ------ 这是插槽的 "高级玩法",实现「组件传数据 + 使用者自定义渲染」;

  • 用法

    1. 组件内:给 <slot> 绑定属性(:数据名="数据值");

    2. 使用者:用 <template v-slot="插槽变量"> 接收数据,在插槽内使用。

      xml 复制代码
      <!-- 组件内定义作用域插槽(给插槽传数据) -->
      <template>
        <div class="card">
          <!-- 给默认插槽传数据:salesData -->
          <slot :salesData="sales"></slot>
          <!-- 给 footer 插槽传数据:yesterdaySales -->
          <slot name="footer" :yesterdaySales="yesterday"></slot>
        </div>
      </template>
      <script>
      export default {
        data() {
          return {
            sales: { total: 1211312, dayRatio: 7.3 }, // 组件内部数据
            yesterday: 40123
          }
        }
      }
      </script>
      
      <!-- 使用作用域插槽(接收并使用数据) -->
      <common-card>
        <!-- 接收默认插槽的 data,自定义渲染 -->
        <template v-slot="slotProps">
          <div>
            累计销售额:¥{{ slotProps.salesData.total.toLocaleString() }}
            <span>日同比:{{ slotProps.salesData.dayRatio }}%</span>
          </div>
        </template>
        <!-- 接收 footer 插槽的 data,自定义渲染 -->
        <template #footer="footerProps">
          <span>昨日销售额:¥{{ footerProps.yesterdaySales.toLocaleString() }}</span>
        </template>
      </common-card>

Vue3 支持对插槽变量解构,让代码更简洁:

xml 复制代码
        <!-- 解构默认插槽数据 -->
            <template v-slot="{ salesData }">
              <div>累计销售额:¥{{ salesData.total }}</div>
            </template>

            <!-- 解构具名插槽数据 + 重命名 -->
            <template #footer="{ yesterdaySales: ys }">
              <span>昨日销售额:¥{{ ys }}</span>
            </template>  
            

4.支持动态插槽名

可以用变量作为插槽名

xml 复制代码
<template #[dynamicSlotName]>
  <div>动态插槽内容</div>
</template>
<script>
export default {
  data() {
    return { dynamicSlotName: 'footer' } // 动态指定插槽名
  }
}
</script>

插槽的实战场景

数据可视化组件:封装图表组件时,用作用域插槽传递图表数据,使用者自定义 tooltip、图例的显示样式

列表渲染组件:装通用列表组件时,用作用域插槽传递列表项数据,使用者自定义列表项的渲染样式

通用组件封装:封装卡片、表格、弹窗、导航栏等通用组件时,用插槽预留自定义位置

vue-echarts

vue-echarts 完全兼容 Vue3,且专门为 Vue3 做了适配(支持 <script setup>、组合式 API 等),相比直接使用原生 ECharts,它的优势是:

  • 自动处理 ECharts 实例的创建 / 销毁,避免内存泄漏;
  • 响应式更新配置,数据变化时自动重绘图表;
  • 无需手动获取 DOM 元素,直接通过组件属性传参。
markdown 复制代码
    npm install echarts vue-echarts -S

css 复制代码
    npm install vue-echarts echarts --save

全局注册

javascript 复制代码
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import setupElement from './plugins/element.js'

// 1. 核心补充:按需导入 ECharts 核心模块(必填)
import { use } from 'echarts/core'
// 导入你需要的图表类型(根据业务场景添加,比如柱状图、自定义系列)
import { BarChart, CustomChart, LineChart } from 'echarts/charts'
// 导入渲染器(必须,推荐 CanvasRenderer)
import { CanvasRenderer } from 'echarts/renderers'
// 可选:导入交互组件(如提示框、图例,按需添加)
import { TooltipComponent, LegendComponent } from 'echarts/components'

// 2. 核心补充:注册 ECharts 模块(必填)
use([
  BarChart, CustomChart, LineChart, // 图表类型
  CanvasRenderer,                  // 渲染器(必须)
  TooltipComponent, LegendComponent // 可选交互组件
])

// 3. 导入 vue-echarts 组件(你的原有代码)
import VueECharts from 'vue-echarts'

const app = createApp(App)
setupElement(app)

// 4. 全局注册 <v-chart> 组件(你的原有代码,正确)
app.component('v-chart', VueECharts)

app.use(router)
app.mount('#app')    

v-echarts

css 复制代码
npm i v-charts echarts -S

或安装(直接执行以下命令,纠正包名 + 忽略 peer 依赖冲突

css 复制代码
npm install vue-echarts echarts -S --legacy-peer-deps

vue2和vue3支持状态

维度 v-charts(第三方) vue-echarts(官方)
Vue3 适配 ❌ 无官方支持 ✅ 完全适配(v6+ 版本专为 Vue3 设计)
维护状态 ❌ 停止维护 ✅ 持续更新,和 ECharts 版本同步
打包体积 ❌ 内置全量 ECharts,体积大 ✅ 按需导入模块,体积可控
功能完整性 ❌ 仅封装部分 ECharts 功能 ✅ 支持 ECharts 所有功能(包括自定义系列、交互)
文档 / 社区 ❌ 文档陈旧,无社区支持 ✅ 官方文档完善,问题可在 ECharts 社区解决

v-charts(第三方)和 vue-echarts(官方),前者是 Vue2 专属,后者是 Vue3 首选

Element Plus

el-menu

<el-menu> 组件的基础使用方式,用于实现水平导航菜单(如页面顶部的销售额 / 访问量切换)

ini 复制代码
<el-menu 
  mode="horizontal" 
  :default-active="'1'"
  @select="pnSelect"
>
配置项 类型 作用 & 意义
mode="horizontal" 字符串 定义菜单的布局模式: horizontal:水平布局(顶部导航); vertical:垂直布局(侧边栏); inline:内嵌布局(侧边栏子菜单展开)
:default-active="'1'" 字符串 / 数字(响应式绑定) 设置菜单的默认选中项 ,值需和 <el-menu-item>index 匹配;注意:index 本质是字符串,所以这里用 '1'(加引号)更规范(也可写 1,Element Plus 会自动转换) 默认选中 "销售额"(index="1"),页面加载后该菜单项会高亮
@select="pnSelect" 事件绑定 监听菜单选中事件:点击 <el-menu-item> 时触发,回调函数会接收当前选中项的 index 点击 "销售额"/"访问量" 时,pnSelect 方法会拿到 index(1/2),用于后续业务逻辑(如切换图表数据)
相关推荐
却尘1 小时前
一个 ERR_SSL_PROTOCOL_ERROR 让我们排查了三层问题,最后发现根本不是 SSL 的锅
前端·后端·网络协议
用户83040713057011 小时前
如何处理axios请求中post请求的坑
前端
sudo_明天上线1 小时前
React 核心深度解析:调度、协调与提交的闭环全解
前端
广州华水科技1 小时前
单北斗GNSS在变形监测中的应用与发展新趋势
前端
宁雨桥2 小时前
详解Web服务部署:IP+端口 vs IP+端口+目录 实战指南
前端·网络协议·tcp/ip
chengbo_eva2 小时前
大前端全栈实践-基于nodejs实现服务端内核引擎
前端
一颗奇趣蛋2 小时前
哨兵模式-无限滚动
前端
爱分享的鱼鱼2 小时前
Element Plus 日期选择器(DatePicker)深度解析:从基础用法到高级定制
前端
evle2 小时前
从 Recoil 的兴衰看前端状态管理的技术选型
前端·react.js