TkAstral3D/packages/editor/src/components/code/CodeEditor.vue
plum e8615c6796 feat(sdk): 迁移脚本执行系统与编辑器热更新对齐 Astral
- Viewer.ts: enableScript setter 修复写回 options,脚本状态变量改为实例级
- Viewer.ts: 新增 InstallScriptsOptions、reinstallObjectScripts、invokeInstalledScriptEvent
- Viewer.ts: installScripts 支持 loadedScriptNames 精准触发 loaded,补充 bindDataChange 分发
- Viewer.ts: unInstallScripts 移除错误的 enableScript 提前返回守卫
- Signals.ts: 脚本信号统一改用 reinstallObjectScripts 热更新
- Signals.ts: sceneCleared 先卸载脚本再清场
- Signals.ts: registerSignal/dispose 模式防止信号泄漏
- CodeEditor.vue: Monaco 忽略 TS80002(构造函数转 class 建议)
2026-04-09 00:43:15 +08:00

232 lines
9.1 KiB
Vue

<template>
<div ref="monacoEditorRef" class="h-full"></div>
</template>
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount, nextTick } from 'vue';
import * as monaco from 'monaco-editor';
import EditorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
import JsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker';
import CssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker';
import HtmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker';
import TsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker';
const props = withDefaults(defineProps<{
source: string,
mode: string,
config?: {
[key: string]: any
}
}>(), {
source: "",
mode: "javascript",
config: () => ({})
})
const emits = defineEmits(["update:source"]);
const monacoEditorRef = ref();
// 编辑器实列
let editor: monaco.editor.IStandaloneCodeEditor | null = null;
// 导入的sqlLanguage
let sqlLanguage:any = null;
// sql语法提示示例
let sqlProvider:monaco.IDisposable | null = null;
// 语法错误信息
let errorMarkers: monaco.editor.IMarker[] = [];
onMounted(async () => {
await nextTick();
window.MonacoEnvironment = {
getWorker(_, label) {
if (label === 'json') {
return new JsonWorker();
}
if (label === 'css' || label === 'scss' || label === 'less') {
return new CssWorker();
}
if (label === 'html' || label === 'handlebars' || label === 'razor') {
return new HtmlWorker();
}
if (label === 'typescript' || label === 'javascript') {
return new TsWorker();
}
return new EditorWorker();
},
};
await initMonaco();
})
onBeforeUnmount(() => {
if(sqlLanguage){
sqlLanguage = null;
}
if(sqlProvider){
sqlProvider.dispose();
sqlProvider = null;
}
if (editor) {
editor.dispose();
editor = null;
}
});
async function initMonaco() {
monaco.languages.typescript.javascriptDefaults.setDiagnosticsOptions({
noSemanticValidation: false,
noSyntaxValidation: false,
diagnosticCodesToIgnore: [80002],
});
// 如果使用 Webpack 或其他打包工具,可以使用 importScripts
monaco.languages.typescript.javascriptDefaults.setCompilerOptions({
target: monaco.languages.typescript.ScriptTarget.ES2016,
noImplicitAny: false, // 允许隐式 any 类型
strict: false, // 禁用所有严格类型检查。
allowJs: true,
checkJs: false, // 禁用对 .js 文件的类型检查(仅解析语法)
allowNonTsExtensions: true, // 允许非 .ts 文件的语法检查
});
// 添加 THREE 的全局声明,以便在 Monaco Editor 中使用而不报错
// const response = await fetch('/libs/@types/three/index.d.ts');
// const content = await response.text();
// monaco.languages.typescript.javascriptDefaults.addExtraLib(
// content,
// 'three.d.ts'
// );
if (props.mode === "sql") {
if(!sqlLanguage){
const { language } = await import("monaco-editor/esm/vs/basic-languages/sql/sql.js");
sqlLanguage = language;
}
sqlProvider = monaco.languages.registerCompletionItemProvider('sql', {
provideCompletionItems: (model, position) => {
let suggestions:any = []
const { lineNumber, column } = position
const textBeforePointer = model.getValueInRange({
startLineNumber: lineNumber,
startColumn: 0,
endLineNumber: lineNumber,
endColumn: column,
})
const contents = textBeforePointer.trim().split(/\s+/)
const lastContents = contents[contents?.length - 1];
if (lastContents) {
const sqlConfigKey = ['builtinFunctions', 'keywords', 'operators'];
sqlConfigKey.forEach(key => {
sqlLanguage[key].forEach(sql => {
suggestions.push(
{
label: sql, // 显示的提示内容;
insertText: sql, // 应插入到文档中的内容
kind: monaco.languages.CompletionItemKind.Keyword
}
)
})
})
}
return {
suggestions,
}
}
})
}
editor = monaco.editor.create(monacoEditorRef.value, {
value: props.source,
language: props.mode,
...Object.assign({
theme: 'vs-dark', //官方自带三种主题vs, hc-black, or vs-dark
readOnly: false, // 是否只读内容不可编辑
readOnlyMessage: { value: "不可以修改哦", supportThemeIcons: true, supportHtml: true }, // 为只读时编辑内日提示词
codeLens: true, // 代码透镜
folding: true, // 代码折叠
snippetSuggestions: 'inline' as 'inline', // 代码提示
tabCompletion: 'on' as 'on', // 代码提示按tab完成
foldingStrategy: 'auto' as 'auto', // 折叠策略
smoothScrolling: true, // 滚动动画
// acceptSuggestionOnCommitCharacter: true, // 接受关于提交字符的建议
// acceptSuggestionOnEnter: 'on', // 接受输入建议 "on" | "off" | "smart"
// accessibilityPageSize: 10, // 辅助功能页面大小 Number 说明:控制编辑器中可由屏幕阅读器读出的行数。警告:这对大于默认值的数字具有性能含义。
// accessibilitySupport: 'on', // 辅助功能支持 控制编辑器是否应在为屏幕阅读器优化的模式下运行。
// autoClosingBrackets: 'always', // 是否自动添加结束括号(包括中括号) "always" | "languageDefined" | "beforeWhitespace" | "never"
// autoClosingDelete: 'always', // 是否自动删除结束括号(包括中括号) "always" | "never" | "auto"
// autoClosingOvertype: 'always', // 是否关闭改写 即使用insert模式时是覆盖后面的文字还是不覆盖后面的文字 "always" | "never" | "auto"
// autoClosingQuotes: 'always', // 是否自动添加结束的单引号 双引号 "always" | "languageDefined" | "beforeWhitespace" | "never"
// automaticLayout: true, // 自动布局
// codeLensFontFamily: '', // codeLens的字体样式
// codeLensFontSize: 13, // codeLens的字体大小
// colorDecorators: true, // 呈现内联色彩装饰器和颜色选择器
// comments: {
// ignoreEmptyLines: true, // 插入行注释时忽略空行。默认为真。
// insertSpace: true, // 在行注释标记之后和块注释标记内插入一个空格。默认为真。
// }, // 注释配置
contextmenu: true, // 启用上下文菜单
// columnSelection: true, // 启用列编辑 按下shift键位然后按↑↓键位可以实现列选择 然后实现列编辑
// autoSurround: 'never', // 是否应自动环绕选择
// copyWithSyntaxHighlighting: true, // 是否应将语法突出显示复制到剪贴板中 即 当你复制到word中是否保持文字高亮颜色
// cursorBlinking: 'smooth', // 光标动画样式
// cursorSmoothCaretAnimation: 'on', // 是否启用光标平滑插入动画 当你在快速输入文字的时候 光标是直接平滑的移动还是直接"闪现"到当前文字所处位置
// cursorStyle: 'line', // "Block"|"BlockOutline"|"Line"|"LineThin"|"Underline"|"UnderlineThin" 光标样式
// cursorSurroundingLines: 0, // 光标环绕行数 当文字输入超过屏幕时 可以看见右侧滚动条中光标所处位置是在滚动条中间还是顶部还是底部 即光标环绕行数 环绕行数越大 光标在滚动条中位置越居中
// cursorSurroundingLinesStyle: 'all', // "default" | "all" 光标环绕样式
// cursorWidth: 2, // <=25 光标宽度
minimap: {
enabled: props.mode !== 'sql', // 是否启用预览图
},
// scrollbar: {
// verticalScrollbarSize: 5,
// horizontalScrollbarSize: 5,
// arrowSize: 10,
// alwaysConsumeMouseWheel: false,
// },
// links: true, // 是否点击链接
// overviewRulerBorder: true, // 是否应围绕概览标尺绘制边框
// renderLineHighlight: 'gutter', // 当前行突出显示方式
// scrollBeyondLastLine: false, // 设置编辑器是否可以滚动到最后一行之后
// lineNumbers: 'on',
// lineNumbersMinChars: 0,
// fontSize: 13,
// roundedSelection: false, // 右侧不显示编辑器预览框
// autoIndent: 'full',
// formatOnType: true,
// formatOnPaste: true
}, props.config)
})
editor.onDidChangeModelContent(() => {
editor && emits('update:source', editor.getValue())
})
monaco.editor.onDidChangeMarkers(([uri]) => {
errorMarkers = monaco.editor.getModelMarkers({ resource: uri });
})
}
function getErrors() {
const markers: string[] = [];
const ignoreError = ["All destructured elements are unused", "is declared but its value is never read"];
errorMarkers.forEach(marker => {
const { message, startLineNumber, startColumn, endLineNumber, endColumn } = marker;
const isIgnore = ignoreError.some(ignore => message.indexOf(ignore) >= 0);
if (isIgnore) return;
markers.push(`${message} [${startLineNumber}${startColumn}列 - ${endLineNumber}${endColumn}列]`);
});
return markers;
}
defineExpose({
getErrors
})
</script>