Vue3+Vite+ts使用BabyLon.js(一)

简介

Babylon.js‌ 是一款基于 WebGL/WebGPU 的开源 3D 引擎,由微软主导开发并持续维护,专注于在浏览器中创建高性能交互式 3D/2D 图形应用。支持 JavaScript/TypeScript 开发,适用于游戏、工业可视化、VR/AR 及产品展示等场景。

官方网址:Babylon.js: Powerful, Beautiful, Simple, Open - Web-Based 3D At Its Best https://www.babylonjs.com/

渲染效果图

一、BabyLon.js 安装

1、CND方式导入

<!-- 核心库 -->

<script src="https://cdn.babylonjs.com/babylon.js"></script>

<!-- 加载器(选装) -->

<script src="https://cdn.babylonjs.com/loaders/babylonjs.loaders.min.js"></script>

2、npm安装(推荐)

npm install @babylonjs/core @babylonjs/materials

二、封装BabyLonApp类

1、创建BabylonThreeD.ts文件

在项目文件夹src内任意位置创建文件

2、构建BabyLonApp类

2.1、导入babylon核心模块及其相关模块

import * as BABYLON from 'babylonjs' // 导入核心模块
import * as GUI from 'babylonjs-gui' // 导入GUI系统
import 'babylonjs-loaders' // 导入模型加载器

3、构建BabyLonApp封装类及相关成员

3.1、构造函数及成员初始化

export class BabyLonApp {
  mEngine: BABYLON.Engine | null = null // 引擎
  mScene: BABYLON.Scene | null = null // 场景

  constructor(readonly canvas: HTMLCanvasElement) { // 构造函数
    this.mEngine = new BABYLON.Engine(canvas) // 创建引擎
    this.mScene = onCreateScene(this.mEngine, this.canvas) // 创建场景
    window.addEventListener('resize', () => { this.onResize() }) // 监听窗口大小变化
  }
}

3.2、babylon调试模式

babylon调试模式无特殊需求可不开启,默认关闭状态;

onDebug (debugOn: boolean = false) { // 调试函数
    if (this.mScene === null) return // 如果场景为空,则返回
    if (debugOn) {
        this.mScene.debugLayer.show({ overlay: true }) // 显示调试层
    } else {
        this.mScene.debugLayer.hide() // 隐藏调试层
    }
  }

3.3、babylon运行渲染

onRunBabyLon () { // 运行渲染函数
    this.onDebug(false) // 关闭调试层
    if (this.mEngine === null) return // 如果引擎为空,则返回
    this.mEngine.runRenderLoop(() => { // 渲染函数
      if (this.mScene !== null) this.mScene.render() // 渲染场景
    })
  }

3.4、babylon暂停渲染

onPauseRender () { // 暂停渲染函数
    if (this.mEngine) {
      this.mEngine.stopRenderLoop() // 停止渲染循环
    }
  }

3.5、babylon停止渲染

onStopBabyLon () { // 停止渲染函数
    if (this.mEngine && this.mScene) {
      this.mEngine.stopRenderLoop() // 停止渲染循环
      this.mEngine = null // 清空引擎
      this.mScene = null // 清空场景
      window.removeEventListener('resize', () => { this.onResize() }) // 解绑窗口大小改变事件
    }
  }

3.6、babylon重置画布尺寸

onResize() { // 重置窗口尺寸函数(窗口大小改变时重置画布大小)
    if (this.mEngine !== null) this.mEngine.resize() // 如果引擎不为空,则重置画布大小
  }

3.7、babylon加载模型文件

onImportMeshAsyncScene () { // 加载导入场景模型函数
    BABYLON.SceneLoader.ImportMeshAsync(
      null, // 定加载的模型名称为空''或为null表示加载全部
      '', // 模型文件目录路径(静态路径/网络路径皆可),如:http://xxxxx/ 或 ./(表示public文件夹下)
      '', // 模型文件名,如:xxxx.glb/xxx.obj/xxx.ply/xxx.stl/xxx.gltf...等格式
      this.mScene, // 目标场景,默认为当前场景
      (e: any) => { // 加载进度回调
        console.log('当前加载进度:', Math.round((e.loaded * 100) / e.total)) // 当前模型加载进度
      }
    ).then(result => { // 模型加载成功
      const model = result.meshes[0] // 获取导入的模型根网格
      model.renderingGroupId = 1 // 设置物体的渲染组优先级-数值越高越靠后
      this.onResize() // 调整场景尺寸
    }).catch(err => { // 模型加载失败
      console.log('模型加载失败, ', err)
    })
  }

4、创建场景

const onCreateScene = (engine: BABYLON.Engine, canvas: HTMLCanvasElement) => { // 创建场景函数
  const scene = new BABYLON.Scene(engine) // 创建场景
  const camera = new BABYLON.ArcRotateCamera(CAMERANAME, Math.PI / 4, Math.PI / 4, 100, BABYLON.Vector3.Zero(), scene) // 创建相机
  camera.inertia = 0 // 禁用惯性
  camera.attachControl(canvas, true)

  // 移除默认鼠标输入并添加自定义输入
  camera.inputs.removeByType('ArcRotateCameraPointersInput')
  const customInput = new CustomArcRotateCameraInput()
  customInput.camera = camera
  camera.inputs.add(customInput)

  scene.clearColor = new BABYLON.Color4(0.5, 0.6, 0.8, 1) // 设定场景背景色

  // 添加一个 DirectionalLight 作为太阳光
  const sunLight = new BABYLON.DirectionalLight(SUNLIGHT, new BABYLON.Vector3(2, -2, 1), scene)
  sunLight.position = new BABYLON.Vector3(20, 40, 20)
  sunLight.intensity = 1.0

  return scene // 返回场景
}

5、构建自定义相机操作类CustomArcRotateCameraInput

class CustomArcRotateCameraInput implements BABYLON.ICameraInput<BABYLON.ArcRotateCamera> { // 自定义相机操作类
  // 需要绑定的相机
  camera: BABYLON.ArcRotateCamera = null!;

  // 鼠标状态变量
  private _isPointerDown: boolean = false;
  private _isMiddlePointerDown: boolean = false;
  private _lastX: number = 0;
  private _lastY: number = 0;

  // 限制距离范围
  private _minRadius: number = 2; // 最小距离
  private _maxRadius: number = 1000; // 最大距离

  // 绑定事件监听
  attachControl(noPreventDefault?: boolean): void {
    const engine = this.camera.getEngine();
    const element = engine.getRenderingCanvas();
    if (!element) return;
    //const dt : number = engine.getDeltaTime() * 1000;

    element.addEventListener("pointerdown", (event:any) => {
      if (event.button === 0) {
          this._isPointerDown = true; // 左键拖动
      } else if (event.button === 2) {
          this._isMiddlePointerDown = true; // 右键拖动
      }
      this._lastX = event.clientX;
      this._lastY = event.clientY;

      if (!noPreventDefault) event.preventDefault();
    });

    element.addEventListener("pointermove", (event:any) => {
      const deltaX = event.clientX - this._lastX;
      const deltaY = event.clientY - this._lastY;

      if (this._isPointerDown) {
        // 左键拖动:旋转相机
        this.camera.alpha -= deltaX * 0.005; // 水平旋转速度
        this.camera.beta -= deltaY * 0.005;  // 垂直旋转速度
        this.camera.beta = BABYLON.Scalar.Clamp(this.camera.beta, -Math.PI / 2, Math.PI / 2); // 限制垂直角度
      } else if (this._isMiddlePointerDown) {
        // 右键拖动:平移焦点
        const moveSpeed = this.camera.radius * 0.0015; // 移动速度与距离成正比
        const forward = this.camera.getForwardRay().direction; // 相机的前方向
        const right = BABYLON.Vector3.Cross(forward, BABYLON.Vector3.Up()).normalize(); // 相机的右方向
        const up = BABYLON.Vector3.Cross(right, forward).normalize(); // 相机的上方向

        const translation = right.negate().scale(-deltaX * moveSpeed).add(up.scale(deltaY * moveSpeed));
        this.camera.target.addInPlace(translation); // 平移焦点
      }

      this._lastX = event.clientX;
      this._lastY = event.clientY;

      if (!noPreventDefault) event.preventDefault();
    });

    element.addEventListener("pointerup", (event:any) => {
      if (event.button === 0) {
        this._isPointerDown = false;
      } else if (event.button === 2) {
        this._isMiddlePointerDown = false;
      }
    });

    element.addEventListener("pointerleave", () => {
      this._isPointerDown = false;
      this._isMiddlePointerDown = false;
    });

    element.addEventListener("wheel", (event:any) => {
      if (!noPreventDefault) event.preventDefault();
      // 滚轮缩放
      const sensScale = 0.001;
      const scaleRatio = Math.exp(event.deltaY * sensScale);
      this.camera.radius = BABYLON.Scalar.Clamp(this.camera.radius * scaleRatio, this._minRadius, this._maxRadius); // 限制距离
    });
  }

  // 解绑事件监听
  detachControl(element?: HTMLElement): void {
    if (!element) return;
    element.removeEventListener("pointerdown", () => {});
    element.removeEventListener("pointermove", () => {});
    element.removeEventListener("pointerup", () => {});
    element.removeEventListener("pointerleave", () => {});
    element.removeEventListener("wheel", () => {});
  }

  getClassName(): string {
    return "CustomArcRotateCameraInput"
  }

  getTypeName(): string {
    return "CustomArcRotateCameraInput"
  }

  getSimpleName(): string {
    return "customMouseInput"
  }
}

三、Vue组件中使用BabyLonApp类

1、canvas元素

<canvas ref="mBabyLonCanvasRef" id="renderCanvas" class="three-d-canvas-dom"></canvas>

2、导入BabyLonApp类

import { BabyLonApp } from '@/XXX/BabyLonThreeD' // 导入BabyLonApp(路径为自己创建路径)

3、声明canvas节点及BabyLonApp成员

const mBabyLonCanvasRef = ref<HTMLCanvasElement | null>(null) // canvas节点
const mBabyLonApp = ref<BabyLonApp | null>(null) // Babylon构造器实例

4、初始化BabyLonApp对象

const onInitBabyLon = () => { // 初始化BabyLon函数
  if (mBabyLonCanvasRef.value === null || mThreeDSceneRef.value === null) return // 若canvas节点为空 则直接结束
  if (mBabyLonApp.value !== null) { // 若实例不为空 则先停止/销毁实例
    mBabyLonApp.value.onStopBabyLon() // 停止三维场景运行
    mBabyLonApp.value = null // 清空实例
  }

  mBabyLonApp.value = new BabyLonApp(mBabyLonCanvasRef.value) // 构造BabyLon对象
  mBabyLonApp.value.onRunBabyLon() // 运行BabyLon
  mBabyLonApp.value.onImportMeshAsyncScene() // 模型异步加载
}

四、BabyLonApp类完整代码

import * as BABYLON from 'babylonjs' // 导入核心模块
import * as GUI from 'babylonjs-gui' // 导入GUI系统
import 'babylonjs-loaders' // 导入模型加载器

const CAMERANAME:string = 'GS_CAMERA' // 相机名称
const SUNLIGHT:string = 'GS_SUNLIGHT' // 光源名称

export class BabyLonApp {
  mEngine: BABYLON.Engine | null = null // 引擎
  mScene: BABYLON.Scene | null = null // 场景

  constructor(readonly canvas: HTMLCanvasElement) { // 构造函数
    this.mEngine = new BABYLON.Engine(canvas) // 创建引擎
    this.mScene = onCreateScene(this.mEngine, this.canvas) // 创建场景
    window.addEventListener('resize', () => { this.onResize() }) // 监听窗口大小变化
  }
  onDebug (debugOn: boolean = false) { // 调试函数
    if (this.mScene === null) return // 如果场景为空,则返回
    if (debugOn) {
        this.mScene.debugLayer.show({ overlay: true }) // 显示调试层
    } else {
        this.mScene.debugLayer.hide() // 隐藏调试层
    }
  }

  onRunBabyLon () { // 运行渲染函数
    this.onDebug(false) // 关闭调试层
    if (this.mEngine === null) return // 如果引擎为空,则返回
    this.mEngine.runRenderLoop(() => { // 渲染函数
      if (this.mScene !== null) this.mScene.render() // 渲染场景
    })
  }

  onPauseRender () { // 暂停渲染函数
    if (this.mEngine) {
      this.mEngine.stopRenderLoop() // 停止渲染循环
    }
  }

  onStopBabyLon () { // 停止渲染函数
    if (this.mEngine && this.mScene) {
      this.mEngine.stopRenderLoop() // 停止渲染循环
      this.mEngine = null // 清空引擎
      this.mScene = null // 清空场景
      window.removeEventListener('resize', () => { this.onResize() }) // 解绑窗口大小改变事件
    }
  }

  onImportMeshAsyncScene () { // 加载导入场景模型函数
    BABYLON.SceneLoader.ImportMeshAsync(
      null, // 定加载的模型名称为空''或为null表示加载全部
      '', // 模型文件目录路径(静态路径/网络路径皆可),如:http://xxxxx/ 或 ./(表示public文件夹下)
      '', // 模型文件名,如:xxxx.glb/xxx.obj/xxx.ply/xxx.stl/xxx.gltf...等格式
      this.mScene, // 目标场景,默认为当前场景
      (e: any) => { // 加载进度回调
        console.log('当前加载进度:', Math.round((e.loaded * 100) / e.total)) // 当前模型加载进度
      }
    ).then(result => { // 模型加载成功
      const model = result.meshes[0] // 获取导入的模型根网格
      model.renderingGroupId = 1 // 设置物体的渲染组优先级-数值越高越靠后
      this.onResize() // 调整场景尺寸
    }).catch(err => { // 模型加载失败
      console.log('模型加载失败, ', err)
    })
  }

  onResize() { // 重置窗口尺寸函数(窗口大小改变时重置画布大小)
    if (this.mEngine !== null) this.mEngine.resize() // 如果引擎不为空,则重置画布大小
  }
}

const onCreateScene = (engine: BABYLON.Engine, canvas: HTMLCanvasElement) => { // 创建场景函数
  const scene = new BABYLON.Scene(engine) // 创建场景
  const camera = new BABYLON.ArcRotateCamera(CAMERANAME, Math.PI / 4, Math.PI / 4, 100, BABYLON.Vector3.Zero(), scene) // 创建相机
  camera.inertia = 0 // 禁用惯性
  camera.attachControl(canvas, true)

  // 移除默认鼠标输入并添加自定义输入
  camera.inputs.removeByType('ArcRotateCameraPointersInput')
  const customInput = new CustomArcRotateCameraInput()
  customInput.camera = camera
  camera.inputs.add(customInput)

  scene.clearColor = new BABYLON.Color4(0.5, 0.6, 0.8, 1) // 设定场景背景色

  // 添加一个 DirectionalLight 作为太阳光
  const sunLight = new BABYLON.DirectionalLight(SUNLIGHT, new BABYLON.Vector3(2, -2, 1), scene)
  sunLight.position = new BABYLON.Vector3(20, 40, 20)
  sunLight.intensity = 1.0

  return scene // 返回场景
}

class CustomArcRotateCameraInput implements BABYLON.ICameraInput<BABYLON.ArcRotateCamera> { // 自定义相机操作类
  // 需要绑定的相机
  camera: BABYLON.ArcRotateCamera = null!;

  // 鼠标状态变量
  private _isPointerDown: boolean = false;
  private _isMiddlePointerDown: boolean = false;
  private _lastX: number = 0;
  private _lastY: number = 0;

  // 限制距离范围
  private _minRadius: number = 2; // 最小距离
  private _maxRadius: number = 1000; // 最大距离

  // 绑定事件监听
  attachControl(noPreventDefault?: boolean): void {
    const engine = this.camera.getEngine();
    const element = engine.getRenderingCanvas();
    if (!element) return;
    //const dt : number = engine.getDeltaTime() * 1000;

    element.addEventListener("pointerdown", (event:any) => {
      if (event.button === 0) {
          this._isPointerDown = true; // 左键拖动
      } else if (event.button === 2) {
          this._isMiddlePointerDown = true; // 右键拖动
      }
      this._lastX = event.clientX;
      this._lastY = event.clientY;

      if (!noPreventDefault) event.preventDefault();
    });

    element.addEventListener("pointermove", (event:any) => {
      const deltaX = event.clientX - this._lastX;
      const deltaY = event.clientY - this._lastY;

      if (this._isPointerDown) {
        // 左键拖动:旋转相机
        this.camera.alpha -= deltaX * 0.005; // 水平旋转速度
        this.camera.beta -= deltaY * 0.005;  // 垂直旋转速度
        this.camera.beta = BABYLON.Scalar.Clamp(this.camera.beta, -Math.PI / 2, Math.PI / 2); // 限制垂直角度
      } else if (this._isMiddlePointerDown) {
        // 右键拖动:平移焦点
        const moveSpeed = this.camera.radius * 0.0015; // 移动速度与距离成正比
        const forward = this.camera.getForwardRay().direction; // 相机的前方向
        const right = BABYLON.Vector3.Cross(forward, BABYLON.Vector3.Up()).normalize(); // 相机的右方向
        const up = BABYLON.Vector3.Cross(right, forward).normalize(); // 相机的上方向

        const translation = right.negate().scale(-deltaX * moveSpeed).add(up.scale(deltaY * moveSpeed));
        this.camera.target.addInPlace(translation); // 平移焦点
      }

      this._lastX = event.clientX;
      this._lastY = event.clientY;

      if (!noPreventDefault) event.preventDefault();
    });

    element.addEventListener("pointerup", (event:any) => {
      if (event.button === 0) {
        this._isPointerDown = false;
      } else if (event.button === 2) {
        this._isMiddlePointerDown = false;
      }
    });

    element.addEventListener("pointerleave", () => {
      this._isPointerDown = false;
      this._isMiddlePointerDown = false;
    });

    element.addEventListener("wheel", (event:any) => {
      if (!noPreventDefault) event.preventDefault();
      // 滚轮缩放
      const sensScale = 0.001;
      const scaleRatio = Math.exp(event.deltaY * sensScale);
      this.camera.radius = BABYLON.Scalar.Clamp(this.camera.radius * scaleRatio, this._minRadius, this._maxRadius); // 限制距离
    });
  }

  // 解绑事件监听
  detachControl(element?: HTMLElement): void {
    if (!element) return;
    element.removeEventListener("pointerdown", () => {});
    element.removeEventListener("pointermove", () => {});
    element.removeEventListener("pointerup", () => {});
    element.removeEventListener("pointerleave", () => {});
    element.removeEventListener("wheel", () => {});
  }

  getClassName(): string {
    return "CustomArcRotateCameraInput"
  }

  getTypeName(): string {
    return "CustomArcRotateCameraInput"
  }

  getSimpleName(): string {
    return "customMouseInput"
  }
}

五、组件使用完整示例代码

<template>
  <div class="three-D-scene-box">
    <div class="three-D-scene">
      <canvas ref="mBabyLonCanvasRef" id="renderCanvas" class="three-d-canvas-dom"></canvas>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import { BabyLonApp } from '@/views/Home/Components/ThreeDScene/BabyLon/BabyLonThreeD' // babylon对象

const mThreeDSceneRef = ref<HTMLDivElement | null>(null) // 三维场景元素节点
const mBabyLonCanvasRef = ref<HTMLCanvasElement | null>(null) // canvas节点
const mBabyLonApp = ref<BabyLonApp | null>(null) // Babylon实例

onMounted(() =>  { // 生命周期函数-组件挂载后执行
  onInitBabyLon()
})
onUnmounted(() => { // 生命周期函数-组件卸载执行
  if (mBabyLonApp.value !== null) { // 停止三维场景
    mBabyLonApp.value.onStopBabyLon() // 停止三维场景运行
    mBabyLonApp.value = null // 清空实例
  }
})

const onInitBabyLon = async () => { // 初始化BabyLon函数
  if (mBabyLonCanvasRef.value === null) return // 若canvas节点为空 则直接结束
  if (mBabyLonApp.value !== null) { // 若实例不为空 则先停止/销毁实例
    mBabyLonApp.value.onStopBabyLon() // 停止三维场景运行
    mBabyLonApp.value = null // 清空实例
  }
  
  mBabyLonApp.value = new BabyLonApp(mBabyLonCanvasRef.value) // 构造BabyLon对象
  mBabyLonApp.value.onRunBabyLon() // 运行BabyLon
  mBabyLonApp.value.onImportMeshAsyncScene() // 加载导入场景模型函数
}
</script>

<style scoped lang="sass">
</style>

个人笔记分享,若有错误之处还望大佬纠错指教。

<think>好的,我现在需要帮助用户解决如何配置Vue3ViteTypeScript项目中的eslint.config.js文件。首先,我得回忆Vite和ESLint的基本配置方法。用户提到的是最新的配置方法,所以我得确保自己了解最近的更新,比如ESLint的扁平化配置方式,因为传统的.eslintrc可能已经被替代了。 接下来,用户的项目使用Vue3TypeScript,这意味着需要相关的插件和解析器。我记得Vue3需要eslint-plugin-vue,而TypeScript需要@typescript-eslint/parser和@typescript-eslint/eslint-plugin。此外,Vite项目可能需要配置环境,比如node和es2020,以及处理模块解析的问题。 然后,用户提到了vite.config.ts的配置,这可能涉及到与ESLint的集成。不过,ESLint通常独立于构建工具配置,但需要确保TypeScriptVue的单文件组件被正确解析。可能需要安装@types/node来避免类型错误,这点在引用[4]中提到过。 另外,用户提供的引用中有提到vue/setup-compiler-macros的设置,这是在处理Vue3的setup语法糖时需要配置的,应该放在ESLint的env或rules里。引用[2]和[3]都涉及到Vue相关的插件,比如vite-plugin-vue-setup-extend,这可能需要在Vite配置中添加,但ESLint配置中也需要相应调整。 现在,我需要整合这些信息,生成个最新的eslint.config.js示例。需要注意的是,ESLint现在可能使用导出的数组格式,而不是传统的对象。同时,确保所有依赖包都是最新版本,比如eslint-plugin-vue的版本是否支持Vue3的最新特性。 最后,检查是否需要安装其他依赖,如eslint-config-standard-with-typescript,或者prettier相关的插件,如果用户需要集成代码格式化。不过用户的问题主要围绕ESLint,所以可能不需要涉及Prettier,除非必要。 总结步骤:安装必要依赖,创建eslint.config.js,配置解析器、插件、扩展规则,设置环境,处理VueTypeScript的特定规则,确保与Vite项目的兼容性。同时,注意引用中提到的配置项,比如vue/setup-compiler-macros需要启用,以及可能的路径别名设置。</think>### Vue3 + Vite + TypeScript 的 ESLint 最新配置方法 #### 1. 安装必要依赖 ```bash npm install eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-vue @types/node --save-dev ``` #### 2. 创建 `eslint.config.js` 配置文件 ```javascript import tsParser from &#39;@typescript-eslint/parser&#39;; import vuePlugin from &#39;eslint-plugin-vue&#39;; import tsPlugin from &#39;@typescript-eslint/eslint-plugin&#39;; export default [ { files: ["**/*.vue", "**/*.ts", "**/*.tsx"], languageOptions: { parser: tsParser, ecmaVersion: 2020, sourceType: "module", globals: { defineProps: "readonly", defineEmits: "readonly", defineExpose: "readonly", withDefaults: "readonly" } }, plugins: { vue: vuePlugin, &#39;@typescript-eslint&#39;: tsPlugin }, rules: { // Vue 规则 "vue/multi-word-component-names": "off", "vue/no-multiple-template-root": "off", // TypeScript 规则 "@typescript-eslint/no-explicit-any": "warn", "@typescript-eslint/ban-ts-comment": "warn" } }, { // 处理 Vue SFC 文件 files: ["**/*.vue"], processor: vuePlugin.processors[&#39;.vue&#39;] } ]; ``` #### 3. 关键配置说明 1. **解析器配置**:使用 `@typescript-eslint/parser` 替代默认解析器,支持 TS 语法解析[^2] 2. **Vue 插件**:`eslint-plugin-vue` 提供 Vue3 单文件组件规则支持[^3] 3. **环境配置**:显式声明 `ecmaVersion: 2020` 支持最新语法 4. **组合式 API**:通过 `globals` 配置自动识别 `<script setup>` 语法糖 #### 4. 推荐的扩展配置(可选) 在 `package.json` 中添加 ESLint 执行脚本: ```json { "scripts": { "lint": "eslint . --ext .vue,.ts" } } ``` #### 5.Vite 集成 在 `vite.config.ts` 中建议添加类型声明: ```typescript import { defineConfig } from &#39;vite&#39;; import vue from &#39;@vitejs/plugin-vue&#39;; export default defineConfig({ plugins: [ vue(), VueSetupExtend() // 如果使用 setup 语法糖扩展 ] }); ``` 需要安装 `@vitejs/plugin-vue` 和 `vite-plugin-vue-setup-extend`[^4]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值