Skip to content

Commit 9720d5e

Browse files
committed
feat: playground
1 parent 58ac87c commit 9720d5e

34 files changed

+1812
-24
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
"preview": "pnpm vite ./test"
1919
},
2020
"dependencies": {
21-
"mermaid": "^11.8.0"
21+
"mermaid": "^11.8.0",
22+
"monaco-editor": "^0.52.2"
2223
},
2324
"devDependencies": {
2425
"@antfu/eslint-config": "catalog:",

playground/.gitignore

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
yarn-debug.log*
6+
yarn-error.log*
7+
pnpm-debug.log*
8+
lerna-debug.log*
9+
10+
node_modules
11+
dist
12+
dist-ssr
13+
*.local
14+
15+
# Editor directories and files
16+
.vscode/*
17+
!.vscode/extensions.json
18+
.idea
19+
.DS_Store
20+
*.suo
21+
*.ntvs*
22+
*.njsproj
23+
*.sln
24+
*.sw?

playground/.vscode/extensions.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"recommendations": ["Vue.volar"]
3+
}

playground/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Vue 3 + TypeScript + Vite
2+
3+
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
4+
5+
Learn more about the recommended Project Setup and IDE Support in the [Vue Docs TypeScript Guide](https://vuejs.org/guide/typescript/overview.html#project-setup).

playground/index.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<title>Vite + Vue + TS</title>
8+
</head>
9+
<body>
10+
<div id="app"></div>
11+
<script type="module" src="/src/main.ts"></script>
12+
</body>
13+
</html>

playground/package.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"name": "@sciux/playground",
3+
"type": "module",
4+
"version": "0.0.0",
5+
"private": true,
6+
"scripts": {
7+
"dev": "vite",
8+
"build": "vue-tsc -b && vite build",
9+
"preview": "vite preview"
10+
},
11+
"dependencies": {
12+
"@sciux/theme-default": "workspace:^",
13+
"sciux": "workspace:^",
14+
"vue": "^3.5.17",
15+
"vue-router": "^4.5.1"
16+
},
17+
"devDependencies": {
18+
"@vitejs/plugin-vue": "^6.0.0",
19+
"@vue/tsconfig": "^0.7.0",
20+
"typescript": "~5.8.3",
21+
"vite": "^7.0.4",
22+
"vue-tsc": "^2.2.12"
23+
}
24+
}

playground/public/vite.svg

Lines changed: 1 addition & 0 deletions
Loading

playground/src/App.vue

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<template>
2+
<RouterView />
3+
</template>

playground/src/assets/vue.svg

Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
<script setup lang="ts">
2+
import { ref, onMounted, onUnmounted, watch, nextTick } from 'vue'
3+
import * as monaco from 'monaco-editor'
4+
5+
// 定义组件props
6+
interface Props {
7+
modelValue: string
8+
language?: string
9+
theme?: string
10+
options?: monaco.editor.IStandaloneEditorConstructionOptions
11+
debounceTime?: number
12+
}
13+
14+
const props = withDefaults(defineProps<Props>(), {
15+
language: 'html',
16+
theme: 'vs-dark',
17+
debounceTime: 300,
18+
options: () => ({
19+
automaticLayout: true,
20+
minimap: { enabled: false },
21+
fontSize: 14,
22+
fontFamily: 'Monaco, Menlo, "Ubuntu Mono", monospace',
23+
lineNumbers: 'on',
24+
folding: true,
25+
wordWrap: 'on',
26+
scrollBeyondLastLine: false,
27+
renderWhitespace: 'boundary',
28+
tabSize: 2,
29+
insertSpaces: true,
30+
})
31+
})
32+
33+
// 定义emits
34+
interface Emits {
35+
'update:modelValue': [value: string]
36+
'change': [value: string, event: monaco.editor.IModelContentChangedEvent]
37+
'ready': [editor: monaco.editor.IStandaloneCodeEditor]
38+
'render-request': [content: string]
39+
}
40+
41+
const emit = defineEmits<Emits>()
42+
43+
// 组件状态
44+
const editorContainer = ref<HTMLDivElement>()
45+
let editor: monaco.editor.IStandaloneCodeEditor | null = null
46+
let debounceTimer: number | null = null
47+
48+
// 防抖渲染函数
49+
const debounceRender = (content: string) => {
50+
if (debounceTimer) {
51+
clearTimeout(debounceTimer)
52+
}
53+
debounceTimer = window.setTimeout(() => {
54+
emit('render-request', content)
55+
}, props.debounceTime)
56+
}
57+
58+
// 初始化Monaco Editor
59+
const initEditor = async () => {
60+
if (!editorContainer.value) return
61+
62+
// 配置Monaco Editor的worker (简化版本)
63+
self.MonacoEnvironment = {
64+
getWorkerUrl: function (moduleId, label) {
65+
// 使用CDN版本以确保兼容性
66+
const BASE_URL = 'https://cdn.jsdelivr.net/npm/monaco-editor@0.52.2/min/vs'
67+
68+
if (label === 'json') {
69+
return `${BASE_URL}/language/json/json.worker.js`
70+
}
71+
if (label === 'css' || label === 'scss' || label === 'less') {
72+
return `${BASE_URL}/language/css/css.worker.js`
73+
}
74+
if (label === 'html' || label === 'handlebars' || label === 'razor') {
75+
return `${BASE_URL}/language/html/html.worker.js`
76+
}
77+
if (label === 'typescript' || label === 'javascript') {
78+
return `${BASE_URL}/language/typescript/ts.worker.js`
79+
}
80+
return `${BASE_URL}/editor/editor.worker.js`
81+
}
82+
}
83+
84+
// 创建editor实例
85+
editor = monaco.editor.create(editorContainer.value, {
86+
value: props.modelValue,
87+
language: props.language,
88+
theme: props.theme,
89+
...props.options,
90+
})
91+
92+
// 监听内容变化
93+
editor.onDidChangeModelContent((event) => {
94+
const value = editor!.getValue()
95+
emit('update:modelValue', value)
96+
emit('change', value, event)
97+
98+
// 触发防抖渲染
99+
debounceRender(value)
100+
})
101+
102+
// 设置HTML语言的特殊配置
103+
if (props.language === 'html') {
104+
monaco.languages.html.htmlDefaults.setOptions({
105+
format: {
106+
tabSize: 2,
107+
insertSpaces: true,
108+
wrapLineLength: 80,
109+
unformatted: 'default',
110+
indentInnerHtml: false,
111+
preserveNewLines: true,
112+
maxPreserveNewLines: null,
113+
indentHandlebars: false,
114+
endWithNewline: false,
115+
extraLiners: 'head, body, /html',
116+
wrapAttributes: 'auto'
117+
},
118+
suggest: {
119+
html5: true,
120+
angular1: false,
121+
ionic: false
122+
}
123+
})
124+
}
125+
126+
// 触发ready事件
127+
emit('ready', editor)
128+
129+
// 初始渲染
130+
if (props.modelValue) {
131+
debounceRender(props.modelValue)
132+
}
133+
}
134+
135+
// 监听modelValue变化
136+
watch(() => props.modelValue, (newValue) => {
137+
if (editor && editor.getValue() !== newValue) {
138+
editor.setValue(newValue)
139+
}
140+
})
141+
142+
// 监听语言变化
143+
watch(() => props.language, (newLanguage) => {
144+
if (editor) {
145+
const model = editor.getModel()
146+
if (model) {
147+
monaco.editor.setModelLanguage(model, newLanguage)
148+
}
149+
}
150+
})
151+
152+
// 监听主题变化
153+
watch(() => props.theme, (newTheme) => {
154+
if (editor) {
155+
monaco.editor.setTheme(newTheme)
156+
}
157+
})
158+
159+
// 暴露方法给父组件
160+
const focus = () => {
161+
editor?.focus()
162+
}
163+
164+
const getEditor = () => {
165+
return editor
166+
}
167+
168+
const setValue = (value: string) => {
169+
editor?.setValue(value)
170+
}
171+
172+
const getValue = () => {
173+
return editor?.getValue() || ''
174+
}
175+
176+
const triggerRender = () => {
177+
const content = getValue()
178+
emit('render-request', content)
179+
}
180+
181+
defineExpose({
182+
focus,
183+
getEditor,
184+
setValue,
185+
getValue,
186+
triggerRender
187+
})
188+
189+
// 生命周期
190+
onMounted(async () => {
191+
await nextTick()
192+
await initEditor()
193+
})
194+
195+
onUnmounted(() => {
196+
if (debounceTimer) {
197+
clearTimeout(debounceTimer)
198+
}
199+
if (editor) {
200+
editor.dispose()
201+
editor = null
202+
}
203+
})
204+
</script>
205+
206+
<template>
207+
<div ref="editorContainer" class="monaco-editor-container" />
208+
</template>
209+
210+
<style scoped>
211+
.monaco-editor-container {
212+
width: 100%;
213+
height: 100%;
214+
min-height: 200px;
215+
}
216+
217+
/* 修复Monaco Editor在玻璃效果容器中的显示问题 */
218+
:deep(.monaco-editor) {
219+
background: transparent !important;
220+
}
221+
222+
:deep(.monaco-editor .margin) {
223+
background: rgba(0, 0, 0, 0.2) !important;
224+
}
225+
226+
:deep(.monaco-editor .monaco-editor-background) {
227+
background: rgba(0, 0, 0, 0.3) !important;
228+
}
229+
230+
:deep(.monaco-editor .current-line) {
231+
background: rgba(255, 255, 255, 0.05) !important;
232+
}
233+
234+
:deep(.monaco-editor .line-numbers) {
235+
color: var(--text-muted) !important;
236+
}
237+
238+
:deep(.monaco-editor .monaco-placeholder) {
239+
color: var(--text-secondary) !important;
240+
}
241+
</style>

0 commit comments

Comments
 (0)