Vue3+NodeJS 接入文心一言, 發(fā)布一個 VSCode 大模型問答插件
大廠技術(shù) 高級前端 Node進(jìn)階
點擊上方 程序員成長指北,關(guān)注公眾號
回復(fù)1,加入高級Node交流群
隨著大模型能力越來越卷,在垂直領(lǐng)域的落地也在加快,對于大模型代碼生成能力而言,最簡潔高效的方式就是集成為常用IDE的插件,在vscode的插件戰(zhàn)場中,比較知名的就有 GitHub Copilot, 智譜清言的codegeex, 訊飛星火的iFlyCode。
那么我們就以開發(fā)一個簡易的大模型對話插件,來探究一下vscode插件開發(fā)到發(fā)布的流程,研究一下文心一言大模型api的接入
跟著操作大約30-60分鐘,你需要
-
安裝vscode,npm/yarn等,node版本 >12.0 -
最好有時間提前看一看 vscode 官網(wǎng)api開發(fā)文檔[1]
萬字長文 Action!
一:首先明確插件開發(fā)方式
-
如果你的插件只提供原生vscode能力,沒有復(fù)雜的UI需求,只需要在 vscode插件項目上開發(fā)即可,類似插件比如VolarGit HistoryEslint -
如果提供復(fù)雜UI交互,定制化界面,就需要在vscode插件內(nèi)嵌iframe頁面(用iframe展示線上web地址與使用vscode提供的一套UI組件皆可,詳見第三節(jié)),我這里選擇訪問線上地址,因此需要 開發(fā)一個vscode插件項目與一個vue3項目(其他框架亦可),類似的復(fù)雜插件比如CodeGeeXiFlyCode,會將web頁面展示在側(cè)邊欄中。
本文主要講解 如何在vscode插件中通過iframe展示web頁面,獲得更好的拓展性與可維護(hù)性
二:新建一個Vscode 插件項目
1. 官網(wǎng)教程地址
開始你的第一個插件項目[2]
2. 一步一步來創(chuàng)建
-
找到一個比較舒服的文件夾,打開cmd,通過以下命令安裝 vscode項目腳手架,取的是 registry.npmjs.org[3] 鏡像源,因此可能會有科學(xué)問題
npm install -g yo generator-code
-
安裝完成后,直接用命令創(chuàng)建新的插件項目
yo code
-
進(jìn)入配置頁面,默認(rèn)就選擇 NewExtension(TypeScript),后面的按照圖中來就可
-
然后會自動創(chuàng)建好項目,并執(zhí)行npm i,然后用 vscode 打開項目
3. 分析目錄結(jié)構(gòu)以及運行插件
目錄結(jié)構(gòu)就很清晰了,我們主要涉及修改 extension.ts 以及 package.json文件
上圖中,extension.ts 中 activate() 方法就是插件的入口函數(shù),每次插件啟動都會執(zhí)行此函數(shù),當(dāng)前代碼是注冊了一個hello world命令,當(dāng)你在vscode中通過 ctrl+shift+p 調(diào)出輸入框并輸入hello world,就會執(zhí)行此注冊命令的回調(diào),彈出一個message框,下面我們來試一下
在當(dāng)前項目中,直接按F5,會啟動一個擴展開發(fā)宿主,你的插件就運行在這個vscode窗口上啦 下面我們調(diào)出命令輸入框ctrl+shift+p ,輸入 hello world, 會提示命令,選中執(zhí)行,右下角會發(fā)現(xiàn)彈了一個message?。?!
什么?你的沒彈出?那你豈不是和我當(dāng)時一樣倒霉,但你不需要花時間去挖這個奇怪的~bug !
首先看一下你的vscode版本
當(dāng)前vscode版本不能低于 package.json 中的最低版本要求!
這樣寫表示最低支持到1.83.0版本!改一下重新reload一下宿主插件,再試試命令就可以彈出啦!到此我們的插件側(cè)項目就搭建好了,下面我們簡單建一個vue項目,嵌入到側(cè)邊欄中
三:新建一個Vue3 項目,在側(cè)邊欄中展示,實現(xiàn)vscode插件 <=> vue項目 雙向消息傳遞
文章開頭我們提到,插件內(nèi)展示豐富的UI,既可以用iframe展示線上web網(wǎng)頁,也可以在插件內(nèi)部用vsode ui實現(xiàn)。下面我主要演示用iframe的方式,另一種嵌入方式推薦大家去看一下 CodeGeeX 插件[4]源碼如何做的,引入了一套vscode風(fēng)格的UI組件@vscode/webview-ui-toolkit,源碼里面的webviewUI文件夾與translationWebviewProvider.ts文件都是相關(guān)代碼。
1. 新建vue3+vite+ts項目
找一個舒服的文件夾,打開cmd
npm init vite
執(zhí)行后按需選擇自己的框架與開發(fā)環(huán)境,然后run dev一下子,拿到地址, 比如 http://localhost:5173/
2. 將web頁面展示在vscode側(cè)邊欄
(1) 插件項目修改,把視圖注冊到側(cè)邊欄,完成消息傳遞
第一步當(dāng)然是先建一個iframe把我們的web項目的地址填進(jìn)去唄,開始。
vscode 提供了兩種創(chuàng)建iframe的方法,WebviewViewProvider 和 createWebviewPanel,選其一即可,這里我們介紹一下WebviewViewProvider如何使用
首先在extension.ts 同級目錄下新建 chatWebview.ts
-
WebviewViewProvider 是一個接口,因此建一個自己的類實現(xiàn)它的方法即可
下面我們創(chuàng)建一個實現(xiàn)WebviewViewProvider接口的類:ChatWebviewchatWebview.ts 文件: (可直接運行)
具體代碼作用看注釋
import { window, Position, WebviewView, WebviewViewProvider } from "vscode";
export class ChatWebview implements WebviewViewProvider {
// 寫一個public變量,方便對象引用創(chuàng)建后的webview實例,但是可能存在還未完全解析完成時,訪問值為null
// 看了vscode api發(fā)現(xiàn),resolveWebView 返回一個 Thenable,可以在解析完成后拿到webview實例
// 但是這個函數(shù)是在webview容器第一次顯示時自動執(zhí)行,不需要手動調(diào)用,不知道怎么拿到Thenable
public webview: WebviewView | null = null;
resolveWebviewView(webviewView: WebviewView): void | Thenable<void> {
this.webview = webviewView;
webviewView.webview.options = {
enableScripts: true,
};
// 監(jiān)聽web端傳來的消息
webviewView.webview.onDidReceiveMessage((message) => {
switch (message.command) {
case "WebSendMesToVscode":
// 實現(xiàn)一個簡單的功能,將web端傳遞過來的消息插入到當(dāng)前活動編輯器中
let editor = window.activeTextEditor;
editor?.edit((edit) => {
let position = editor?.selection
? editor?.selection.start
: new Position(0, 0);
edit.insert(position, message.data);
});
return;
}
}, undefined);
// webview 展示的內(nèi)容本身就是嵌套在一個iframe中,因此在此html中再嵌套一個iframe時,需要傳遞兩次postMessage
webviewView.webview.html = `
<!DOCTYPE html>
<html lang="en">
<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">
<style>
html,
body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
background-color:#000000;
overflow:hidden;
}
.webView_iframe {
width: 100%;
height: 100%;
border: none;
}
.outer{
width: 100%;
height: 100%;
overflow: hidden;
}
</style>
</head>
<body>
<script>
console.log('Hello from the webview!');
// 向vscode 傳遞消息的固定寫法, vscode 為我們封裝好了postMessage
const vscode = acquireVsCodeApi();
// 接收來自web頁面的消息
window.addEventListener('message', event => {
const message = event.data;
switch (message.command) {
// 插件傳遞消息給web端
case 'vscodeSendMesToWeb':
let iframe = document.getElementById('WebviewIframe')
WebviewIframe.contentWindow.postMessage(message, "*")
console.log("fromWebViewIframe: "+message.data)
break;
// web端發(fā)送消息給插件
case 'WebSendMesToVscode':
vscode.postMessage(message);
break;
}
});
</script>
<div class="outer">
<iframe id='WebviewIframe' class="webView_iframe" sandbox="allow-scripts allow-same-origin allow-forms allow-pointer-lock allow-downloads" allow="cross-origin-isolated; clipboard-read; clipboard-write;" src="http://localhost:5173/"></iframe>
</div>
</body>
</html>
`;
}
}
提供webview視圖的類創(chuàng)建好了,然后我們需要在入口函數(shù)中實例化一個webview,然后把這個視圖注冊到vscode側(cè)邊欄中
打開extension.ts文件,修改如下 (代碼可直接運行)
// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
import * as vscode from "vscode";
import { ChatWebview } from "./chatWebview";
// This method is called when your extension is activated
// vscode 插件入口函數(shù),當(dāng)插件第一次加載時會執(zhí)行activate
export function activate(context: vscode.ExtensionContext) {
console.log('Congratulations, your extension "Chat" is now active!');
// 實現(xiàn)側(cè)邊欄的初始化
// 實例化一個chatWebview
const chatWebview = new ChatWebview();
// 注冊webview 到id為 Chat-sidebar 的views中,這個id為 Chat-sidebar 的視圖我們稍后會在
// package.json 中聲明,先理解為我們要把iframe渲染在那個地方(側(cè)邊欄還是標(biāo)簽頁)需要在
// packagea.json 中控制
context.subscriptions.push(
vscode.window.registerWebviewViewProvider("Chat-sidebar", chatWebview, {
webviewOptions: {
// 這是一個比較有用的配置項,可以確保你的插件在不可見時不會被銷毀,建議開啟,否側(cè)每次打開都會重新加載一次插件
retainContextWhenHidden: true,
},
})
);
// 這里實現(xiàn)了一個簡單的功能,在vscode打開的文件中,選中代碼時會實時展示在web頁面上
// 監(jiān)聽用戶選中文本事件
vscode.window.onDidChangeTextEditorSelection((event) => {
const editor = event.textEditor;
let document = editor.document;
let selection = editor.selection;
// 獲取當(dāng)前窗口的文本
let text = document.getText(selection);
// 上文提到chatWebview可能為null 因此需要可選鏈寫法,所以這里存在不穩(wěn)定性,不過測試沒問題~
chatWebview?.webview?.webview.postMessage({
// 第一次postMessage,下一次在chatWebview文件的iframe中
command: "vscodeSendMesToWeb",
data: text,
});
});
}
// This method is called when your extension is deactivated
export function deactivate() {}
至此,我們實例化了ChatWebview,并將其與視圖Chat-siderbar綁定
下面我們需要在package.json中將視圖注冊到側(cè)邊欄中,并指定名字,圖標(biāo)等 打開package.json 文件,修改如下 將原本的 contributes 字段替換一下
確保activitybar 中的id,在views中有對應(yīng)的視圖,我們這里id是Chat-sidebar-view,在views就要有對應(yīng)名字的視圖, 并且該視圖 Chat-sidebar-view 的id為我們 ChatWebview 綁定的視圖id
"contributes": {
"commands": [],
"viewsContainers": {
"activitybar": [
{
"id": "Chat-sidebar-view",
"title": "Chat",
"icon": "images/vite.svg"
}
]
},
"views": {
"Chat-sidebar-view": [
{
"type": "webview",
"id": "Chat-sidebar",
"name": " Chat",
"icon": "images/vite.svg",
"contextualTitle": "Chat"
}
]
}
},
至此!我們的視圖和雙向通訊在插件側(cè)已經(jīng)完成了,我們試一下!直接F5運行,打開拓展開發(fā)宿主
點擊左側(cè)欄圖標(biāo),會看見我們deweb頁面加載出來啦!
再試一下選中文本的事件和postMessage通訊,點擊上方help,選擇倒數(shù)第三個Toggle developer tools或者按ctrl+shift+i 可以打開谷歌開發(fā)者工具,調(diào)試vscode
隨便打開一個項目文件,在窗口中選中文本,會發(fā)現(xiàn)控制臺一直在輸出 fromWebViewIframe: ...... ,我們第一步通訊通了,下面在vue項目中加一下消息接收和發(fā)送。
(2) web項目修改,增加事件監(jiān)聽
打開index.html,增加message的監(jiān)聽,收到消息時插入到container中
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Vue + TS</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
<script type="module">
window.addEventListener("message", (event) => {
const message = event.data;
switch (message.command) {
case "vscodeSendMesToWeb":
const div = document.getElementById("container");
div.innerHTML = message.data;
break;
}
});
</script>
</body>
</html>
我們試一下,在拓展開發(fā)宿主中選中文本,會實時展示在頁面上!
下面我們發(fā)送消息試一下 簡單修改一下 HellowWord.vue 組件,增加一個sendMessage 方法
<script setup lang="ts">
import { ref } from "vue";
defineProps<{ msg: string }>();
const count = ref(0);
const sendMessage = () => {
window.parent.postMessage(
{
command: "WebSendMesToVscode",
data: "this message is from vue3",
},
"*"
);
};
</script>
<template>
<h1>{{ msg }}</h1>
<div class="card">
<button type="button" @click="sendMessage">click</button>
<p>
Edit
<code>components/HelloWorld.vue</code> to test HMR
</p>
</div>
<p>
Check out
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
>create-vue</a
>, the official Vue + Vite starter
</p>
<p>
Install
<a href="https://github.com/vuejs/language-tools" target="_blank">Volar</a>
in your IDE for a better DX
</p>
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
</template>
<style scoped>
.read-the-docs {
color: #888;
}
</style>
我們點擊一下按鈕,會發(fā)現(xiàn)在當(dāng)前文件光標(biāo)處插入了一條信息。this message is from vue3!
到此我們的小插件展示出來了,也實現(xiàn)了數(shù)據(jù)互通。下面我們實現(xiàn)一個簡單對話UI,并接入百度文心一言大模型,做一個自己的插件小助手,如果他能記住我們之前問過的代碼,并幫我們舉一反三,并提醒我們查漏補缺就好了。
四:接入大模型對話能力,實現(xiàn)ChatUI
1. 大模型接入準(zhǔn)備
我分別注冊了智譜清言(chatGLM)與文心一言(ERNIE-Bot),發(fā)現(xiàn)兩者都有基礎(chǔ)的免費額度,前者相對于后者代碼能力貌似更強一些,我們這里做一個簡單類似于代碼錯題本的對話助手,就接入文心一言吧
首先我們要去官網(wǎng)[5],注冊一下開發(fā)者賬號,并且實名認(rèn)證 整個過程很簡單,然后我們看一下api 文檔[6]
下面我把主要步驟說一下 首先我們要創(chuàng)建一個自己的應(yīng)用,獲取到Secret Key和API Key
進(jìn)入下面頁面,點擊創(chuàng)建應(yīng)用,輸入應(yīng)用名稱和應(yīng)用描述直接確定即可,然后會有一個應(yīng)用生成,里面就有我們的Secret Key和API Key
我們要拿這兩個key,去獲取 access_token 和 refresh_token, 用于JWT鑒權(quán),有兩種方式,其一我們可以在網(wǎng)頁中訪問一下拿到一次性30天的access_token用于臨時測試,其二最好在項目http請求前自動用refresh_token去獲取access_token
下面我們訪問一下這個地址,當(dāng)然你要把雙key換成你自己的應(yīng)用的~!https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=你的key&client_secret=你的key
直接用瀏覽器訪問一下呢,然后就在請求里拿到了access_token (不能有小可愛找不到吧~) 拿到token了我們來這里測試一下 測試地址[7]
填入標(biāo)出的這兩項,第二項示例如下
[
{
"role": "user",
"content": "介紹一下自己"
},
]
可以看到接口調(diào)用結(jié)果
2. nodejs調(diào)用api
首先找一個舒服的文件夾,新建一個node項目,我們這里選用express框架,可以參考我這里的命令行
npm init 后會生成一個package.json,然后我們安裝一下常用包npm install express sequelize mysql2 axios body-parser cors --save 之后就可以在vscode中打開我們的項目了,我們先新建一個server.js,作為我們的入口文件,再建一個chat.js 作為我們的大模型調(diào)用文件
兩個文件代碼如下,具體解析見注釋,可以直接復(fù)制過去,然后在控制臺執(zhí)行node server.js 直接啟動服務(wù)~
//server.js
const Conversation = require("./chat.js");
const express = require("express");
const bodyParser = require("body-parser");
const cors = require("cors");
const app = express();
// 暫時允許所有跨域請求
let corsOptions = {
origin: "*",
};
app.use(cors(corsOptions));
// content-type:application/json
app.use(bodyParser.json());
// content-type:application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: true }));
// 對話類
const conversation = new Conversation();
// 定義/chat路由處理POST請求
app.post("/chat", async (req, res) => {
const { messages = "" } = req.body || {};
if (typeof messages !== "string") {
return res.status(400).send({ error: "Invalid messages type" });
}
try {
// 調(diào)用ask方法獲取大模型結(jié)果
const response = await conversation.ask(messages);
return res.status(200).send({ message: response });
} catch (error) {
return res.status(500).send({ error: error.messages });
}
});
// 設(shè)置監(jiān)聽端口
const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
console.log(`服務(wù)器運行端口: ${PORT}.`);
});
chat.js
// 訪問模型服務(wù)
const axios = require("axios");
// 這里就是你的accessToken,我改了兩個數(shù),所以你得替換成自己的嘍~
const accessToken =
"24.88635a1444105db00bb6684c0598a9a3.2542000.1741590285.281335-42231960";
const ERNIEB4 =
"https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions_pro";
const ERNIEB =
"https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions";
class Conversation {
constructor() {
// 上下文數(shù)據(jù)存在這里,文心的調(diào)用是需要把所有的歷史對話數(shù)據(jù)全部傳過去,所以上下文窗口大小得注意
this.messages = [];
}
async ask(prompt) {
// 問句push進(jìn)去
this.messages.push({ role: "user", content: prompt });
console.log("message" + this.messages[0]);
try {
const res = await axios.post(
ERNIEB,
{ messages: this.messages },
{ params: { access_token: accessToken } }
);
const { data } = res;
console.log(data);
// 答案也放進(jìn)去
this.messages.push({ role: "assistant", content: data.result });
return data.result;
} catch (error) {
console.log("調(diào)用模型失敗" + error);
}
}
}
// 導(dǎo)出函數(shù)
module.exports = Conversation;
所以我們的服務(wù)起了嗎?網(wǎng)頁試一下8080唄,通了就可以
下面我們在前端代碼中加一下接口調(diào)用,就大功告成啦!
想必看到這里你也累了,我們?nèi)フ{(diào)戲一下 Sydney
看來一時半會我們還是不可替代的 o_O
3. 前端接口調(diào)試
言歸正傳,我們來增加接口調(diào)用吧,順便畫一個看得過去的UI界面
先裝一下 npm install @ant-design/icons-vue
然后把App.vue刪一下
// app.vue
<script setup lang="ts">
import ChatUI from "./components/chatUI.vue";
</script>
<template>
<div class="container">
<ChatUI />
</div>
</template>
<style scoped>
.container {
width: 100%;
height: 100%;
}
</style>
無需多言,**chatUI.vue代碼**奉上(主要界面gpt畫的,我加了接口調(diào)用)\
// chatUI.vue
<template>
<div class="chat-container">
<div class="messages">
<div
v-for="(item, index) in chatList"
:key="index"
:class="['message', item.type]"
>
<div class="bubble">{{ item.content }}</div>
<div class="avatar">
<component
:is="item.type === 'question' ? UserOutlined : RobotOutlined"
/>
</div>
</div>
</div>
<div class="input-area">
<a-input
v-model:value="inputValue"
placeholder="Type a message..."
@pressEnter="handleSend"
/>
<a-button type="primary" @click="handleSend">send</a-button>
</div>
</div>
</template>
<script setup lang="ts">
import axios from "axios";
import { ref } from "vue";
import { UserOutlined, RobotOutlined } from "@ant-design/icons-vue";
const inputValue = ref("");
let chatList = ref<any[]>([]);
const handleSend = () => {
const question = inputValue.value.trim();
if (question) {
getAnswer(question);
chatList.value.push({ type: "question", content: question });
inputValue.value = ""; // 清空輸入框
}
};
function getAnswer(question: string) {
const URL = "http://localhost:8080/chat";
const payload = {
messages: question,
};
sendPost(
URL,
payload,
{},
(res: any) => {
console.log(res.data.message);
chatList.value.push({ type: "answer", content: res.data.message });
},
(err: any) => {
console.log(err);
}
);
}
//post方法
function sendPost(
url: string,
data: any,
headers = {},
funcSuccess: any,
funcError: any
) {
const headerTem = {
"content-Type": "application/json;charset=UTF-8",
};
if (JSON.stringify(headers) != "{}") {
Object.assign(headerTem, headers);
}
axios
.post(url, data, {
headers: headerTem,
})
.then(function (res) {
console.log("sendPost res info :", res);
funcSuccess(res);
})
.catch((err) => {
console.log("sendPost err info :" + err);
if (funcError) {
funcError(err);
}
});
}
</script>
<style scoped>
.chat-container {
min-width: 300px;
height: 100%;
display: flex;
flex-direction: column;
/* background-color: #1e1e1e; */
border: 1px solid #999;
border-radius: 8px;
}
.messages {
height: 650px;
overflow-y: auto;
padding: 10px;
display: flex;
flex-direction: column;
gap: 10px;
}
.bubble {
color: #333;
text-align: right;
margin-right: 8px;
}
.input-area {
display: flex;
}
.message {
display: flex;
align-items: center;
}
.question {
justify-content: flex-end;
}
.answer {
justify-content: flex-start;
}
</style>
這個就是簡單的調(diào)用接口,我就不注釋了,我們試一下接口
我們在vscode中看一下當(dāng)前效果
還可以吧,也不能要求AI太高,哈哈,我們問幾個問題試試
啊?文心一言還挺強,緊跟時事哦
所以你的接口通了嗎?通了的話點個贊吧,好人一生平安~
沒通的話原因有點多,代碼是沒問題的,其他的可以評論區(qū)討論下
至此我們的聊天小插件算是開發(fā)完成了,我們學(xué)習(xí)了如何創(chuàng)建一個vscode插件,隨后搭建了一個vue3項目展示在了側(cè)邊欄里,然后我們用nodejs接入了文心一言api,前端調(diào)用接口簡單實現(xiàn)了對話功能,希望你看完這篇文章有所收獲,有所感想!
五:注冊開發(fā)者賬號并發(fā)布插件
1. 推薦教程
插件開發(fā)手冊[8]
根據(jù)教程注冊賬號,拿到自己的Token,通過vsce publish 1.0.x 來更新版本
2. 增加插件商店圖標(biāo)
插件商店的圖標(biāo)是通過讀取package.json中的icon來展示的,該字段與publisher同級
"icon": "images/icon.png",
3. 前端資源的緩存策略會影響插件web頁面的實時更新
因為插件實時訪問的前端服務(wù),當(dāng)我們更新前端資源時,當(dāng)然希望插件能同步更新,此時要注意前端資源的緩存策略,最好是配置為 cache-control:no-store no-cache
六:實戰(zhàn)能力探討(會持續(xù)更新,歡迎探討)
1. 行內(nèi)提示功能的設(shè)計與實現(xiàn)(InlineCompletionItemProvider[9])
先說一個思路,就是用InlineCompletionItemProvider實現(xiàn)行內(nèi)提示
2. SSH 遠(yuǎn)程打開文件能力(使用 remote-ssh 插件提供的命令)
先說一個思路,就是通過在控制臺執(zhí)行 remote-ssh 的命令 :code --reuse-window vscode-remote://ssh-remote+${hostname}${path} 來實現(xiàn)打開遠(yuǎn)程ssh地址的文件,需要安裝remote-ssh插件
3. Json 文件可視化編輯(JsonToHtml)
先說一個思路,就是監(jiān)聽用戶打開文件夾時的事件,然后再窗口中打開一個新的webview,試用了一些jsonToHtml的包不如自己手動格式化,將bool格式化為checkbox等
作者:零念
原文鏈接:https://juejin.cn/post/7298160530291376140
Node 社群
我組建了一個氛圍特別好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你對Node.js學(xué)習(xí)感興趣的話(后續(xù)有計劃也可以),我們可以一起進(jìn)行Node.js相關(guān)的交流、學(xué)習(xí)、共建。下方加 考拉 好友回復(fù)「Node」即可。
“分享、點贊、在看” 支持一下
