文档:更新编辑器项目说明

This commit is contained in:
sunlei 2026-05-13 19:06:37 +08:00
parent ab3a1aecc3
commit 712e26a13c

284
README.md
View File

@ -1,204 +1,140 @@
# @vue/repl
# KT Template Online Playground
Vue SFC REPL as a Vue 3 component.
`kt-template-online-playground` 是 KT Template Online 的在线编辑器。项目基于 `@vue/repl` 改造,负责编辑 Vue SFC 模板、预览运行效果、读取前台传入的组件信息,并在保存时截图上传 MinIO 后写入后端组件数据。
## Basic Usage
## 技术栈
**Note: `@vue/repl` >= 2 now supports Monaco Editor, but also requires explicitly passing in the editor to be used for tree-shaking.**
- Vue 3 + TypeScript
- Vite
- @vue/repl
- Monaco Editor / CodeMirror
- Axios
- MinIO 上传接口
- pnpm
```ts
// vite.config.ts
import { defineConfig } from 'vite'
export default defineConfig({
optimizeDeps: {
exclude: ['@vue/repl'],
},
// ...
})
## 功能概览
- 在线编辑 Vue 单文件组件。
- 实时编译并在 iframe 预览运行结果。
- 支持从 URL hash 恢复编辑器文件状态。
- 读取 URL query 初始化组件 `id`、`name`、`type`、`componentType`。
- 保存时自动截取预览区图片,上传到 MinIO并把图片地址写入组件 `image` 字段。
- 根据是否存在 `id` 自动调用新增或编辑接口。
## 目录结构
```text
src
api/ # axios 封装和组件/字典/MinIO 接口
editor/ # 文件列表、编辑器容器、编辑器适配
output/ # 预览、沙箱、SSR 输出
monaco/ # Monaco 语言服务和资源加载
codemirror/ # CodeMirror 编辑器
template/ # 默认模板
PlaygroundHeader.vue # 组件表单、截图上传、保存入口
Repl.vue # 编辑器主体
store.ts # REPL 文件状态、序列化和反序列化
index.ts # 包导出入口
test/main.ts # 本地 Playground 页面入口
```
### With CodeMirror Editor
## 环境变量
Basic editing experience with no intellisense. Lighter weight, fewer network requests, better for embedding use cases.
仓库只提交 `.env.example`,真实 `.env.development``.env.production` 保留在本地。
```vue
<script setup>
import { Repl } from '@vue/repl'
import CodeMirror from '@vue/repl/codemirror-editor'
// import '@vue/repl/style.css'
// ^ no longer needed after 3.0
</script>
<template>
<Repl :editor="CodeMirror" />
</template>
```env
VITE_APP_API_BASE=/api
VITE_APP_PROXY=http://localhost:48085/
```
### With Monaco Editor
关键变量:
With Volar support, autocomplete, type inference, and semantic highlighting. Heavier bundle, loads dts files from CDN, better for standalone use cases.
| 变量 | 说明 |
| --- | --- |
| `VITE_APP_API_BASE` | 前端请求前缀,默认 `/api` |
| `VITE_APP_PROXY` | 后端服务地址Vite dev server 会把 `/api` 代理到这里 |
```vue
<script setup>
import { Repl } from '@vue/repl'
import Monaco from '@vue/repl/monaco-editor'
// import '@vue/repl/style.css'
// ^ no longer needed after 3.0
</script>
## 启动
<template>
<Repl :editor="Monaco" />
</template>
项目 `.node-version``lts/*`。Windows 下如别名不可用,先用 `nvm ls` 查看已安装 LTS再切到具体版本。
```bash
pnpm install
pnpm dev
```
## Advanced Usage
常用命令:
Customize the behavior of the REPL by manually initializing the store.
See [v4 Migration Guide](https://github.com/vuejs/repl/releases/tag/v4.0.0)
```vue
<script setup>
import { watchEffect, ref } from 'vue'
import { Repl, useStore, useVueImportMap } from '@vue/repl'
import Monaco from '@vue/repl/monaco-editor'
// retrieve some configuration options from the URL
const query = new URLSearchParams(location.search)
const {
importMap: builtinImportMap,
vueVersion,
productionMode,
} = useVueImportMap({
// specify the default URL to import Vue runtime from in the sandbox
// default is the CDN link from jsdelivr.com with version matching Vue's version
// from peerDependency
runtimeDev: 'cdn link to vue.runtime.esm-browser.js',
runtimeProd: 'cdn link to vue.runtime.esm-browser.prod.js',
serverRenderer: 'cdn link to server-renderer.esm-browser.js',
})
const store = useStore(
{
// pre-set import map
builtinImportMap,
vueVersion,
// starts on the output pane (mobile only) if the URL has a showOutput query
showOutput: ref(query.has('showOutput')),
// starts on a different tab on the output pane if the URL has a outputMode query
// and default to the "preview" tab
outputMode: ref(query.get('outputMode') || 'preview'),
},
// initialize repl with previously serialized state
location.hash,
)
// persist state to URL hash
watchEffect(() => history.replaceState({}, '', store.serialize()))
// use a specific version of Vue
vueVersion.value = '3.2.8'
// production mode is enabled
productionMode.value = true
</script>
<template>
<Repl :store="store" :editor="Monaco" :showCompileOutput="true" />
</template>
```bash
pnpm dev # 本地 Playground 页面
pnpm typecheck # 类型检查
pnpm build # 构建库产物
pnpm build-preview
pnpm lint
```
Use only the Preview without the editor
## URL 数据约定
```vue
<script setup>
import { ref } from 'vue'
import { Sandbox, useStore } from '@vue/repl'
Playground 同时使用 query 和 hash
// retrieve some configuration options from the URL
const query = new URLSearchParams(location.search)
- `query` 保存组件业务信息:`id`、`name`、`type`、`componentType`。
- `hash` 保存 `store.serialize()` 生成的编辑器文件状态。
- 保存成功后会用 `history.replaceState` 同步 query 和最新 hash。
const store = useStore(
{
// custom vue version
vueVersion: ref(query.get('vue')),
},
// initialize repl with previously serialized state
location.hash,
)
</script>
前台跳转示例:
<template>
<Sandbox :store="store" />
</template>
```text
http://localhost:5173/?id=xxx&name=基础折线图&type=1&componentType=1#...
```
<details>
<summary>Configuration options for resource links. (replace CDN resources)</summary>
## 保存流程
```ts
export type ResourceLinkConfigs = {
/** URL for ES Module Shims. */
esModuleShims?: string
/** Function that generates the Vue compiler URL based on the version. */
vueCompilerUrl?: (version: string) => string
/** Function that generates the TypeScript library URL based on the version. */
typescriptLib?: (version: string) => string
`PlaygroundHeader.vue` 是保存入口:
/** [monaco] Function that generates a URL to fetch the latest version of a package. */
pkgLatestVersionUrl?: (pkgName: string) => string
/** [monaco] Function that generates a URL to browse a package directory. */
pkgDirUrl?: (pkgName: string, pkgVersion: string, pkgPath: string) => string
/** [monaco] Function that generates a URL to fetch the content of a file from a package. */
pkgFileTextUrl?: (
pkgName: string,
pkgVersion: string | undefined,
pkgPath: string,
) => string
}
1. 从 query 读取组件信息。
2. 请求 `/dict/getDictByKey``/dict/getComponentDictByType` 初始化类型下拉。
3. 通过当前 iframe 预览内容生成截图。
4. 调用 `/minio/upload` 上传截图,获取返回的 `url`
5. 组装 `name`、`type`、`componentType`、`image`、`template`。
6. 有 `id` 时调用 `/component/update`,没有 `id` 时调用 `/component/save`
7. 新增成功后把后端返回的 `id` 写回 query。
## 接口约定
接口集中在 `src/api`
- `request.ts`axios 实例,统一处理 `code !== 200` 的错误。
- `component.ts`:新增和编辑组件。
- `dict.ts`:组件类型字典。
- `minio.ts`:截图文件上传。
当前主要接口:
| 方法 | 地址 | 用途 |
| --- | --- | --- |
| `GET` | `/dict/getDictByKey` | 查询一级类型 |
| `GET` | `/dict/getComponentDictByType` | 查询二级类型 |
| `POST` | `/minio/upload` | 上传预览截图 |
| `POST` | `/component/save` | 新增组件 |
| `POST` | `/component/update` | 编辑组件 |
## 开发约定
- 保持 query 和 hash 分工,不要让 `store.serialize()` 覆盖 `location.search`
- 保存前必须先上传截图,`image` 字段使用 MinIO 返回的 URL。
- 业务请求新增时统一放到 `src/api`,组件里调用封装后的函数。
- 编辑器核心逻辑来自 `@vue/repl`,调整时优先保持原有 Store、Preview、Editor 分层。
## 轻量验证
常规改动优先执行:
```bash
pnpm typecheck
```
**unpkg**
涉及保存或预览交互时再启动开发服务检查明显报错:
```ts
const store = useStore({
resourceLinks: ref({
esModuleShims:
'https://unpkg.com/es-module-shims@1.5.18/dist/es-module-shims.wasm.js',
vueCompilerUrl: (version) =>
`https://unpkg.com/@vue/compiler-sfc@${version}/dist/compiler-sfc.esm-browser.js`,
typescriptLib: (version) =>
`https://unpkg.com/typescript@${version}/lib/typescript.js`,
pkgLatestVersionUrl: (pkgName) =>
`https://unpkg.com/${pkgName}@latest/package.json`,
pkgDirUrl: (pkgName, pkgVersion, pkgPath) =>
`https://unpkg.com/${pkgName}@${pkgVersion}/${pkgPath}/?meta`,
pkgFileTextUrl: (pkgName, pkgVersion, pkgPath) =>
`https://unpkg.com/${pkgName}@${pkgVersion || 'latest'}/${pkgPath}`,
}),
})
```bash
pnpm dev
```
**npmmirror**
```ts
const store = useStore({
resourceLinks: ref({
esModuleShims:
'https://registry.npmmirror.com/es-module-shims/1.5.18/files/dist/es-module-shims.wasm.js',
vueCompilerUrl: (version) =>
`https://registry.npmmirror.com/@vue/compiler-sfc/${version}/files/dist/compiler-sfc.esm-browser.js`,
typescriptLib: (version) =>
`https://registry.npmmirror.com/typescript/${version}/files/lib/typescript.js`,
pkgLatestVersionUrl: (pkgName) =>
`https://registry.npmmirror.com/${pkgName}/latest/files/package.json`,
pkgDirUrl: (pkgName, pkgVersion, pkgPath) =>
`https://registry.npmmirror.com/${pkgName}/${pkgVersion}/files/${pkgPath}/?meta`,
pkgFileTextUrl: (pkgName, pkgVersion, pkgPath) =>
`https://registry.npmmirror.com/${pkgName}/${pkgVersion || 'latest'}/files/${pkgPath}`,
}),
})
```
</details>