溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

怎么使用Vue3開發(fā)Fimga插件

發(fā)布時(shí)間:2022-04-11 13:38:31 來源:億速云 閱讀:261 作者:iii 欄目:編程語言

本篇內(nèi)容介紹了“怎么使用Vue3開發(fā)Fimga插件”的有關(guān)知識(shí),在實(shí)際案例的操作過程中,不少人都會(huì)遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

怎么使用Vue3開發(fā)Fimga插件

用 Vue 3 開發(fā) Figma 插件

Figma 是一款當(dāng)下流行的設(shè)計(jì)工具,越來越多的設(shè)計(jì)團(tuán)隊(duì)開始從 Sketch 轉(zhuǎn)向 Figma。Figma 最大的特點(diǎn)是使用Web技術(shù)開發(fā),實(shí)現(xiàn)了完全的跨平臺(tái)。 Figma 插件也是使用 Web 技術(shù)開發(fā),只要會(huì)  htmljs 、 css 就能動(dòng)手寫一個(gè) Figma 插件。

Figma 插件原理

Figma 架構(gòu)簡(jiǎn)介

介紹 Fimga 插件之前,我們先來了解一下 Fimga 的技術(shù)架構(gòu)。

Figma 整體是用 React 開發(fā)的,核心的畫布區(qū)是一塊 canvas ,使用WebGL來渲染。并且畫布引擎部分使用的是WebAssembly,這就是 Figma 能夠如此流暢的原因。桌面端的Figma App 使用了 Electron——一個(gè)使用Web技術(shù)開發(fā)桌面應(yīng)用的框架。Electron 類似于一個(gè)瀏覽器,內(nèi)部運(yùn)行的其實(shí)還是一個(gè)Web應(yīng)用。

Figma 插件原理

在Web端開發(fā)一套安全、可靠的插件系統(tǒng), iframe 無疑是最直接的方案。 iframe 是標(biāo)準(zhǔn)的W3C規(guī)范,在瀏覽器上已經(jīng)經(jīng)過多年應(yīng)用,它的特點(diǎn)是:

  • 安全,天然沙箱隔離環(huán)境,iframe內(nèi)頁面無法操作主框架;

  • 可靠,兼容性非常好,且經(jīng)過了多年市場(chǎng)的檢驗(yàn);

但是它也有明顯的缺點(diǎn):與主框架通信只能通過 postMessage(STRING) 的方式,通信效率非常低。如果要在插件里操作一個(gè)畫布元素,首先要將元素的節(jié)點(diǎn)信息從主框架拷貝到 iframe 中,然后在  iframe 中操作完再更新節(jié)點(diǎn)信息給主框架。這涉及到大量通信,而且對(duì)于復(fù)雜的設(shè)計(jì)稿節(jié)點(diǎn)信息是非常巨大的,可能超過通信的限制。

為了保證操作畫布的能力,必須回到主線程。插件在主線程運(yùn)行的問題主要在于安全性上,于是Figma的開發(fā)人員在主線程實(shí)現(xiàn)了一個(gè) js 沙箱環(huán)境,使用了Realm API。沙箱中只能運(yùn)行純粹的 js 代碼和Figma提供的API,無法訪問瀏覽器API(例如網(wǎng)絡(luò)、存儲(chǔ)等),這樣就保證了安全性。

怎么使用Vue3開發(fā)Fimga插件

感興趣的同學(xué)推薦閱讀官方團(tuán)隊(duì)寫的《How to build a plugin system on the web and also sleep well at night》,詳細(xì)介紹了 Figma 插件方案的選擇過程,讀來獲益良多。

經(jīng)過綜合考慮,F(xiàn)igma 將插件分成兩個(gè)部分:插件UI運(yùn)行在 iframe 中,操作畫布的代碼運(yùn)行在主線程的隔離沙箱中。UI線程和主線程通過  postMessage 通信。

插件配置文件 manifest.json 中分別配置 main 字段指向加載到主線程的 js 文件, ui 字段配置加載到 iframe 中的 html 文件。打開插件時(shí),主線程調(diào)用 figma.showUI() 方法加載 iframe

寫一個(gè)最簡(jiǎn)單的 Figma 插件

為了了解插件的運(yùn)行過程,我們先寫一個(gè)最簡(jiǎn)單的 Figma 插件。功能很簡(jiǎn)單:可以加減正方形色塊。

安裝Figma桌面端

首先要下載并安裝好 Figma 桌面端。

編寫插件的啟動(dòng)文件 manifest.json

新建一個(gè)代碼工程,在根目錄中新建 manifest.json 文件,內(nèi)容如下:

{
  "name": "simple-demo",
  "api": "1.0.0",
  "main": "main.js",
  "ui": "index.html",
  "editorType": [
    "figjam",
    "figma"
  ]
}
編寫UI代碼

根目錄新建 index.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Demo</title>
  <style>

    h2 {
      text-align: center;
    }
    p {
      color: red;
    }

    .buttons {
      margin-top: 20px;
      text-align: center;
    }

    .buttons button {
      width: 40px;
    }

    #block-num {
      font-size: 20px;
    }

  </style>
 
</head>
<body>
  <h2>Figma 插件 Demo</h2>
  <p>當(dāng)前色塊數(shù)量:<span id="block-num">0</span></p>
  <div>
    <button id="btn-add" onclick="addBlock()">+</button>
    <button id="btn-sub" onclick="subBlock()">-</button>
  </div>

  <script>
    console.log('ui code runs!');
    var blockNumEle = document.getElementById('block-num');
    function addBlock() {
      console.log('add');
      var num = +blockNumEle.innerText;
      num += 1;
      blockNumEle.innerText = num;
    }

    function subBlock() {
      console.log('substract');
      var num = +blockNumEle.innerText;
      if (num === 0) return;
      num -= 1;
      blockNumEle.innerText = num;
    }
  </script>
</body>
</html>
編輯main js 代碼

根目錄新建 main.js ,內(nèi)容如下:

console.log('from code 2');
figma.showUI(__html__, {
  width: 400,
  height: 400,
});
啟動(dòng)插件

Figma桌面APP,畫布任意地方右鍵打開菜單, Plugins -> Development -> Import plugin from manifest... ,選擇前面創(chuàng)建的 manifest.json 文件路徑,即可成功導(dǎo)入插件。 然后通過右鍵, Plugins -> Development -> simple-demo (插件名),就可以打開插件。

怎么使用Vue3開發(fā)Fimga插件

測(cè)試點(diǎn)擊按鈕,功能正常。只不過頁面上還未出現(xiàn)色塊(別著急)。 通過 Plugins -> Development -> Open console  可以打開調(diào)試控制臺(tái)??梢钥吹轿覀兇蛴〉娜罩?。

操作畫布

前面講了,畫布代碼是運(yùn)行在主線程的,為了執(zhí)行效率,插件要操作畫布內(nèi)容也只能在主線程執(zhí)行,即在 main.js 中。 main.js 中暴露了頂級(jí)對(duì)象 figma ,封裝了用來操作畫布的一系列API,具體可以去看官網(wǎng)文檔。我們用 figma.createRectangle() 來創(chuàng)建一個(gè)矩形。主線程需要通過 figma.ui.onmessage 監(jiān)聽來自UI線程的事件,從而做出響應(yīng)。修改后的 main.js 代碼如下:

console.log('figma plugin code runs!')

figma.showUI(__html__, {
  width: 400,
  height: 400,
});

const nodes = [];

figma.ui.onmessage = (msg) => {=
  if (msg.type === "add-block") {
    const rect = figma.createRectangle();
    rect.x = nodes.length * 150;
    rect.fills = [{ type: "SOLID", color: { r: 1, g: 0.5, b: 0 } }];
    figma.currentPage.appendChild(rect);
    nodes.push(rect);
  } else if (msg.type === "sub-block") {
    const rect = nodes.pop();
    if (rect) {
      rect.remove();
    }
  }
  figma.viewport.scrollAndZoomIntoView(nodes);
};

同時(shí)要修改  index.html 中的部分代碼,通過 parent.postMessage 給主線程發(fā)送事件:

function addBlock() {
  console.log('add');
  var num = +blockNumEle.innerText;
  num += 1;
  blockNumEle.innerText = num;
  parent.postMessage({ pluginMessage: { type: 'add-block' } }, '*')
}

function subBlock() {
  console.log('substract');
  var num = +blockNumEle.innerText;
  if (num === 0) return;
  num -= 1;
  blockNumEle.innerText = num;
  parent.postMessage({ pluginMessage: { type: 'sub-block' } }, '*')
}

重新啟動(dòng)插件,再試驗(yàn)一下,發(fā)現(xiàn)已經(jīng)可以成功加減色塊了。

怎么使用Vue3開發(fā)Fimga插件

使用 Vue 3 開發(fā) Figma 插件

通過前面的例子,我們已經(jīng)清楚 Figma 插件的運(yùn)行原理。但是用這種“原生”的 js 、 html 來編寫代碼非常低效的。我們完全可以用最新的Web技術(shù)來編寫代碼,只要打包產(chǎn)物包括一個(gè)運(yùn)行在主框架的 js 文件和一個(gè)給 iframe 運(yùn)行的 html 文件即可。我決定嘗試使用  Vue 3 來開發(fā)插件。(學(xué)習(xí)視頻分享:vuejs教程)

關(guān)于 Vue 3 就不多做介紹了,懂的都懂,不懂的看到這里可以先去學(xué)習(xí)一下再來。這里的重點(diǎn)不在于用什么框架(改成用vue 2、react過程也差不多),而在于構(gòu)建工具。

Vite 啟動(dòng)一個(gè)新項(xiàng)目

Vite 是Vue的作者開發(fā)的新一代構(gòu)建工具,也是 Vue 3推薦的構(gòu)建工具。 我們先建一個(gè) Vue  +  TypeScript 的模板項(xiàng)目。

npm init vite@latest figma-plugin-vue3 --template vue-ts
cd figma-plugin-vue3
npm install
npm run dev

然后通過瀏覽器打開 http://localhost:3000 就能看到頁面。

移植上述demo代碼

我們把前面的插件demo移植到 Vue 3 中。 src/App.vue 代碼修改如下:

<script setup>
import { ref } from 'vue';

const num = ref(0);

console.log('ui code runs!');

function addBlock() {
  console.log('add');
  num.value += 1;
  parent.postMessage({ pluginMessage: { type: 'add-block' } }, '*')
}

function subBlock() {
  console.log('substract');
  if (num .value=== 0) return;
  num.value -= 1;
  parent.postMessage({ pluginMessage: { type: 'sub-block' } }, '*')
}
</script>

<template>
  <h2>Figma 插件 Demo</h2>
  <p>當(dāng)前色塊數(shù)量:<span id="block-num">{{ num }}</span></p>
  <div>
    <button id="btn-add" @click="addBlock">+</button>
    <button id="btn-sub" @click="subBlock">-</button>
  </div>
</template>

<style scoped>
h2 {
  text-align: center;
}
p {
  color: red;
}

.buttons {
  margin-top: 20px;
  text-align: center;
}

.buttons button {
  width: 40px;
}

#block-num {
  font-size: 20px;
}
</style>

我們?cè)?src/worker 目錄存放運(yùn)行在主線程沙箱中的js代碼。新建 src/worker/code.ts ,內(nèi)容如下:

console.log('figma plugin code runs!')

figma.showUI(__html__, {
  width: 400,
  height: 400,
});

const nodes: RectangleNode[] = [];

figma.ui.onmessage = (msg) => {
  
  if (msg.type === "add-block") {
    const rect = figma.createRectangle();
    rect.x = nodes.length * 150;
    rect.fills = [{ type: "SOLID", color: { r: 1, g: 0.5, b: 0 } }];
    figma.currentPage.appendChild(rect);
    nodes.push(rect);
  } else if (msg.type === "sub-block") {
    const rect = nodes.pop();
    if (rect) {
      rect.remove();
    }
  }

  figma.viewport.scrollAndZoomIntoView(nodes);

};

上述代碼中缺少 figma 的 ts 類型聲明,所以我們需要安裝一下。

npm i -D @figma/plugin-typings

修改 tsconfig.json ,添加 typeRoots ,這樣 ts 代碼就不會(huì)報(bào)錯(cuò)了。同時(shí)也要加上 "skipLibCheck": true ,解決類型聲明沖突問題。

{
  "compilerOptions": {
    // ...
"skipLibCheck": true,
    "typeRoots": [
      "./node_modules/@types",
      "./node_modules/@figma"
    ]
  },
}

修改構(gòu)建配置

Figma 插件需要的構(gòu)建產(chǎn)物有:

  • manifest.json  文件作為插件配置

  • index.html 作為UI代碼

  • code.js 作為主線程js代碼

在 public 目錄中添加 manifest.json 文件

public 目錄中的文件都會(huì)負(fù)責(zé)到構(gòu)建產(chǎn)物 dist 目錄下。

{
  "name": "figma-plugin-vue3",
  "api": "1.0.0",
  "main": "code.js",
  "ui": "index.html",
  "editorType": [
    "figjam",
    "figma"
  ]
}
vite.config.ts  中增加構(gòu)建入口

默認(rèn)情況下 vite 會(huì)用 index.html 作為構(gòu)建入口,里面用到的資源會(huì)被打包構(gòu)建。我們還需要一個(gè)入口,用來構(gòu)建主線程 js 代碼。

執(zhí)行  npm i -D @types/node ,安裝  Node.js  的類型聲明,以便在 ts 中使用  Node.js  API。 vite.config.ts  的  build.rollupOptions  中增加  input 。默認(rèn)情況下輸出產(chǎn)物會(huì)帶上文件 hash ,所以也要修改 output 配置:

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path';

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  build: {
    sourcemap: 'inline',
    rollupOptions: {
      input:{
            main: resolve(__dirname, 'index.html'),
            code: resolve(__dirname, 'src/worker/code.ts'),
          },
      output: {
        entryFileNames: '[name].js',
      },
    },
  },
})
運(yùn)行構(gòu)建

執(zhí)行 npm run build , dist 目錄會(huì)有構(gòu)建產(chǎn)物。然后我們按照前面的步驟,將  dist  目錄添加為 Figma 插件。 Plugins -> Development -> Import plugin from manifest... ,選擇 dist/manifest.json 文件路徑。

啟動(dòng)插件......怎么插件里一片空白?好在 Figma 里面有 devtools 調(diào)試工具,我們打開瞧一瞧。

怎么使用Vue3開發(fā)Fimga插件

可以看到,我們的 index.html 已經(jīng)成功加載,但是 js 代碼沒加載所以頁面空白。js、css 等資源是通過相對(duì)路徑引用的,而我們的 iframe 中的 src 是一個(gè) base64 格式內(nèi)容,在尋找 js 資源的時(shí)候因?yàn)闆]有域名,所以找不到資源。

解決辦法也很簡(jiǎn)單,我們給資源加上域名,然后本地起一個(gè)靜態(tài)資源服務(wù)器就行了。修改  vite.config.ts ,加上 base: 'http://127.0.0.1:3000'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path';

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  base: 'http://127.0.0.1:3000',
  build: {
    sourcemap: 'inline',
    rollupOptions: {
      input: {
        main: resolve(__dirname, 'index.html'),
        code: resolve(__dirname, 'src/worker/code.ts'),
      },
      output: {
        entryFileNames: '[name].js',
      },
    },
  },
  preview: {
    port: 3000,
  },
})

重新構(gòu)建代碼 npm run build 。然后啟動(dòng)靜態(tài)資源服務(wù)器 npm run preview 。通過瀏覽器訪問 http://localhost:3000/ 可以看到內(nèi)容。

然后重新打開 Figma 插件看看。果然,插件已經(jīng)正常了!

怎么使用Vue3開發(fā)Fimga插件

Figma 加載插件只需要  index.html  和  code.js ,其他資源都可以通過網(wǎng)絡(luò)加載。這意味著我們可以將 js、css 資源放在服務(wù)端,實(shí)現(xiàn)插件的熱更?不知道發(fā)布插件的時(shí)候會(huì)不會(huì)有限制,這個(gè)我還沒試過。

開發(fā)模式

我們已經(jīng)能成功通過 Vue 3 來構(gòu)建 Figma 插件了,但是我不想每次修改代碼都要構(gòu)建一遍,我們需要能夠自動(dòng)構(gòu)建代碼的開發(fā)模式。

vite 自動(dòng)的 dev 模式是啟動(dòng)了一個(gè)服務(wù),沒有構(gòu)建產(chǎn)物(而且沒有類似webpack里面的  writeToDisk 配置),所以無法使用。

watch 模式

vite 的 build 命令有watch模式,可以監(jiān)聽文件改動(dòng)然后自動(dòng)執(zhí)行  build 。我們只需要修改 package.jsonscripts  里新增  "watch": "vite build --watch" 。

npm run watch

# 同時(shí)要在另一個(gè)終端里啟動(dòng)靜態(tài)文件服務(wù)
npm run preview

這種方式雖然修改代碼后會(huì)自動(dòng)編譯,但是每次還是要關(guān)閉插件并重新打開才能看到更新。這樣寫UI還是太低效了,能不能在插件里實(shí)現(xiàn)  HMR  (模塊熱重載)功能呢?

dev 模式

vite dev 的問題在于沒有構(gòu)建產(chǎn)物。 code.js  是運(yùn)行在 Fimga 主線程沙箱中的,這部分是無法熱重載的,所以可以利用 vite build --watch 實(shí)現(xiàn)來編譯。需要熱重載的是 index.html  以及相應(yīng)的 js 、css 資源。 先來看一下 npm run dev 模式下的 html 資源有什么內(nèi)容:

怎么使用Vue3開發(fā)Fimga插件

理論上來說,我們只需要把這個(gè) html 手動(dòng)寫入到  dist  目錄就行,熱重載的時(shí)候 html 文件不需要修改。直接寫入的話會(huì)遇到資源是相對(duì)路徑的問題,所以要么把資源路徑都加上域名( http://localhost:3000 ),或者使用 <base>標(biāo)簽。

手動(dòng)生成 html 文件

對(duì)比上面的 html 代碼和根目錄的 index.html  文件,發(fā)現(xiàn)只是增加了一個(gè) <script type="module" src="/@vite/client"></script> 。所以我們可以自己解析  index.html ,然后插入相應(yīng)這個(gè)標(biāo)簽,以及一個(gè)  <base> 標(biāo)簽。解析 HTML 我們用  jsdom  。

const JSDOM = require('jsdom');
const fs = require('fs');

// 生成 html 文件
function genIndexHtml(sourceHTMLPath, targetHTMLPath) {
  const htmlContent = fs.readFileSync(sourceHTMLPath, 'utf-8');
  const dom = new JSDOM(htmlContent);
  const { document } = dom.window;
  
  const script = document.createElement('script');
  script.setAttribute('type', 'module');
  script.setAttribute('src', '/@vite/client');
  dom.window.document.head.insertBefore(script, document.head.firstChild);
  
  const base = document.createElement('base');
  base.setAttribute('href', 'http://127.0.0.1:3000/');
  dom.window.document.head.insertBefore(base, document.head.firstChild);

  const result = dom.serialize();
  fs.writeFileSync(targetHTMLPath, result);
}

同時(shí) vite 提供了 JavaScript API,所以我們可以代碼組織起來,寫一個(gè) js 腳本來啟動(dòng)開發(fā)模式。新建文件 scripts/dev.js ,完整內(nèi)容如下:

const { JSDOM } = require('jsdom');
const fs = require('fs');
const path = require('path');
const vite = require('vite');

const rootDir = path.resolve(__dirname, '../');

function dev() {
  const htmlPath = path.resolve(rootDir, 'index.html');
  const targetHTMLPath = path.resolve(rootDir, 'dist/index.html');
  genIndexHtml(htmlPath, targetHTMLPath);

  buildMainCode();

  startDevServer();
}

// 生成 html 文件
function genIndexHtml(sourceHTMLPath, targetHTMLPath) {
  const htmlContent = fs.readFileSync(sourceHTMLPath, 'utf-8');
  const dom = new JSDOM(htmlContent);
  const {
    document
  } = dom.window;

  const script = document.createElement('script');
  script.setAttribute('type', 'module');
  script.setAttribute('src', '/@vite/client');
  dom.window.document.head.insertBefore(script, document.head.firstChild);

  const base = document.createElement('base');
  base.setAttribute('href', 'http://127.0.0.1:3000/');
  dom.window.document.head.insertBefore(base, document.head.firstChild);

  const result = dom.serialize();
  fs.writeFileSync(targetHTMLPath, result);
}

// 構(gòu)建 code.js 入口
async function buildMainCode() {
  const config = vite.defineConfig({
    configFile: false, // 關(guān)閉默認(rèn)使用的配置文件
    build: {
      emptyOutDir: false, // 不要清空 dist 目錄
      lib: { // 使用庫模式構(gòu)建
        entry: path.resolve(rootDir, 'src/worker/code.ts'),
        name: 'code',
        formats: ['es'],
        fileName: (format) => `code.js`,
      },
      sourcemap: 'inline',
      watch: {},
    },
  });
  return vite.build(config);
}

// 開啟 devServer
async function startDevServer() {
  const config = vite.defineConfig({
    configFile: path.resolve(rootDir, 'vite.config.ts'),
    root: rootDir,
    server: {
      hmr: {
        host: '127.0.0.1', // 必須加上這個(gè),否則 HMR 會(huì)報(bào)錯(cuò)
      },
      port: 3000,
    },
    build: {
      emptyOutDir: false, // 不要清空 dist 目錄
      watch: {}, // 使用 watch 模式
    }
  });
  const server = await vite.createServer(config);
  await server.listen()

  server.printUrls()
}

dev();

執(zhí)行  node scripts/dev.js ,然后在 Figma 中重啟插件。試試修改一下 Vue 代碼,發(fā)現(xiàn)插件內(nèi)容自動(dòng)更新了!

怎么使用Vue3開發(fā)Fimga插件

最后在  package.json  中新建一個(gè)修改一下dev的內(nèi)容為 "dev": "node scripts/dev.js" 就可以了。

通過請(qǐng)求來獲取 HTML

前面通過自己生產(chǎn)  index.html  的方式有很大的弊端:萬一后續(xù) vite 更新后修改了默認(rèn) html 的內(nèi)容,那我們的腳本也要跟著修改。有沒有更健壯的方式呢?我想到可以通過請(qǐng)求 devServer 來獲取 html 內(nèi)容,然后寫入本地。話不多說,修改后代碼如下:

const { JSDOM } = require('jsdom');
const fs = require('fs');
const path = require('path');
const vite = require('vite');
const axios = require('axios');

const rootDir = path.resolve(__dirname, '../');

async function dev() {
  // const htmlPath = path.resolve(rootDir, 'index.html');
  const targetHTMLPath = path.resolve(rootDir, 'dist/index.html');

  await buildMainCode();

  await startDevServer();
  
  // 必須放到 startDevServer 后面執(zhí)行
  await genIndexHtml(targetHTMLPath);
}

// 生成 html 文件
async function genIndexHtml(/* sourceHTMLPath,*/ targetHTMLPath) {
  const htmlContent = await getHTMLfromDevServer();
  const dom = new JSDOM(htmlContent);
  
  // ...

  const result = dom.serialize();
  fs.writeFileSync(targetHTMLPath, result);
}

// ...

// 通過請(qǐng)求 devServer 獲取HTML
async function getHTMLfromDevServer () {
  const rsp = await axios.get('http://localhost:3000/index.html');
  return rsp.data;
}

dev();

“怎么使用Vue3開發(fā)Fimga插件”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

vue
AI