小合金 發表於 2025-5-28 11:11:00

基于vue3项目开发+MonacoEditor实现外部引入依赖,界面化所见即所得

<h2 id="最近一个项目中基于vue3开发想开发一个在线管理组件库的功能具体业务实现">最近一个项目中,基于vue3开发,想开发一个在线管理组件库的功能,具体业务实现:</h2>
<h3 id="1-在私库nexus上传组件包">1. 在私库Nexus上传组件包;</h3>
<h3 id="2-然后用unpkg实现路径访问在线解压文件">2. 然后用UNPKG实现路径访问在线解压文件;</h3>
<h3 id="3-解压文件上传到gitee组件库中查看">3. 解压文件上传到gitee组件库中查看;</h3>
<h3 id="4-然后通过页面配置填写需要引入的依赖地址直接通过unpkg读取包内文件内容页面中填写dist文件夹中的文件路径支持在当前组件中引入多个外部依赖cssjs">4. 然后通过页面配置填写需要引入的依赖地址(直接通过UNPKG读取包内文件内容),页面中填写dist文件夹中的文件路径,支持在当前组件中引入多个外部依赖(css、js);</h3>
<h3 id="5-可以读取readememd文件并在页面中展示">5. 可以读取reademe.md文件并在页面中展示;</h3>
<h3 id="6-输入并引入依赖后在编辑器中输入样例代码切换preview实现预览">6. 输入并引入依赖后,在编辑器中输入样例代码,切换preview实现预览;</h3>
<h4 id="主要实现逻辑用monacoeditor作为编辑器组件然后用importmap的方式在html页面的head中引入页面中配置的依赖地址在编辑器中编辑代码然后通过响应式传入并渲染到html中进行功能展示">主要实现逻辑:用MonacoEditor作为编辑器组件,然后用importmap的方式在html页面的head中引入页面中配置的依赖地址,在编辑器中编辑代码然后通过响应式传入并渲染到html中进行功能展示。</h4>
<h3 id="代码片段截图">代码片段截图</h3>
<h5 id="tab切换编辑器和视图展示">tab切换编辑器和视图展示:</h5>
<p><img src="https://img2024.cnblogs.com/blog/2686952/202505/2686952-20250528104807577-1036865389.png"></p>
<h4 id="监听切换tab切换到视图界面获取编辑器中代码">监听切换tab,切换到视图界面获取编辑器中代码:</h4>
<p><img src="https://img2024.cnblogs.com/blog/2686952/202505/2686952-20250528104838296-1323552286.png"></p>
<h4 id="根据unpkg地址获取md内容并用marked展示">根据UNPKG地址获取md内容并用marked展示:</h4>
<p><img src="https://img2024.cnblogs.com/blog/2686952/202505/2686952-20250528104901936-407467178.png"><br>
<img src="https://img2024.cnblogs.com/blog/2686952/202505/2686952-20250528105005154-1338935605.png"></p>
<h4 id="根据页面上填写的js和css文件地址合并数组传入preview组件">根据页面上填写的js和css文件地址合并数组传入preview组件:</h4>
<p><img src="https://img2024.cnblogs.com/blog/2686952/202505/2686952-20250528105144745-2145280774.png"></p>
<h4 id="preview组件中监听监听propsstate做数据更新propsresources监听更新html头部的importmap引入依赖">preview组件中监听监听props.state做数据更新,props.resources监听更新html头部的importmap引入依赖:</h4>
<p><img src="https://img2024.cnblogs.com/blog/2686952/202505/2686952-20250528105440001-99207310.png"></p>
<h4 id="创建沙盒拼接html中head内的importmap沙盒加载以及销毁部分通过postmessage传递渲染内容">创建沙盒,拼接html中head内的importmap,沙盒加载以及销毁部分,通过postMessage传递渲染内容:</h4>
<p><img src="https://img2024.cnblogs.com/blog/2686952/202505/2686952-20250528105530829-1911421061.png"></p>
<h4 id="html页面中监听postmessage传递的内容然后用handleeval方法添加加载脚本">html页面中监听postMessage传递的内容,然后用handleEval方法添加加载脚本:</h4>
<p><img src="https://img2024.cnblogs.com/blog/2686952/202505/2686952-20250528105836350-1397930037.png"></p>
<h4 id="monacoeditor编辑器配置">MonacoEditor编辑器配置</h4>
<p><img src="https://img2024.cnblogs.com/blog/2686952/202505/2686952-20250528110124973-1823263468.png"></p>
<h2 id="功能页面展示如下">功能页面展示如下:</h2>
<h5 id="代码编辑和readmemd引入展示">代码编辑和readme.md引入展示:</h5>
<p><img src="https://img2024.cnblogs.com/blog/2686952/202505/2686952-20250528102550008-683984462.png"></p>
<h4 id="示例代码preview展示">示例代码preview展示:</h4>
<p><img src="https://img2024.cnblogs.com/blog/2686952/202505/2686952-20250528102615847-979925910.png"></p>
<h3 id="全部功能代码如下">全部功能代码如下:</h3>
<h4 id="表单">表单</h4>
<details>
<summary>点击查看代码</summary>
<pre><code>&lt;template&gt;
&lt;div class="crud-page" v-loading="loading"&gt;
    &lt;el-form
      ref="ruleFormRef"
      :inline="true"
      :model="crudForm"
      class="crudForm"
      :rules="rules"
      label-width="150px"
    &gt;
      &lt;el-row class="componentMsg"&gt;
      &lt;p&gt;组件信息&lt;/p&gt;
      &lt;el-col :span="12"&gt;
          &lt;el-form-item label="组件名称:" prop="name"&gt;
            &lt;el-input
            v-model="crudForm.name"
            :disabled="disabledStatus"
            placeholder="请输入组件名称"
            clearable
            /&gt;
          &lt;/el-form-item&gt;
      &lt;/el-col&gt;
      &lt;el-col :span="12"&gt;
          &lt;el-form-item label="组件分类:" prop="type"&gt;
            &lt;el-tree-select
            v-model="crudForm.type"
            :disabled="disabledStatus"
            :props="treeprops"
            check-strictly
            filterable
            :data="typeOptions"
            :render-after-expand="false"
            /&gt;
            &lt;!-- &lt;el-select v-model="crudForm.type" placeholder="请选择组件分类"&gt;
            &lt;el-option v-for="item in typeOptions" :key="item.value" :label="item.label" :value="item.value" /&gt;
            &lt;/el-select&gt; --&gt;
            &lt;!-- &lt;el-cascader v-model="editypeList" :options="typeOptions" :show-all-levels="false" @change="editType"/&gt; --&gt;
          &lt;/el-form-item&gt;
      &lt;/el-col&gt;
      &lt;el-col :span="12"&gt;
          &lt;el-form-item label="组件标签:" prop="tagList"&gt;
            &lt;!-- &lt;el-select v-model="crudForm.tag" multiple placeholder="请选择组件标签"&gt;
            &lt;el-option label="标签一" value="tag1" /&gt;
            &lt;el-option label="标签二" value="tag2" /&gt;
            &lt;/el-select&gt; --&gt;
            &lt;el-select
            v-model="tagList"
            multiple
            filterable
            allow-create
            default-first-option
            :reserve-keyword="false"
            placeholder="请选择组件标签"
            @change="createTagFun"
            :disabled="disabledStatus"
            &gt;
            &lt;el-option
                v-for="item in tagOptions"
                :key="item.value"
                :label="item.label"
                :value="item.value"
            /&gt;
            &lt;/el-select&gt;
          &lt;/el-form-item&gt;
      &lt;/el-col&gt;
      &lt;el-col :span="12"&gt;
          &lt;el-form-item label="版本号:"&gt;
            &lt;el-input
            v-model="crudForm.version"
            placeholder="请输入版本号"
            clearable
            :disabled="disabledStatus"
            /&gt;
          &lt;/el-form-item&gt;
      &lt;/el-col&gt;
      &lt;!-- &lt;el-col :span="12"&gt;
          &lt;el-form-item label="所属任务:" prop="task"&gt;
            &lt;el-select v-model="crudForm.task" placeholder="请选择所属任务"&gt;
            &lt;el-option label="所属任务" value="suoshurenwu" /&gt;
            &lt;/el-select&gt;
          &lt;/el-form-item&gt;
      &lt;/el-col&gt; --&gt;
      &lt;el-col :span="12"&gt;
          &lt;el-form-item label="组件描述:" prop="description"&gt;
            &lt;el-input
            type="textarea"
            v-model="crudForm.description"
            :rows="5"
            :max-rows="5"
            placeholder="请输入组件描述"
            :disabled="disabledStatus"
            &gt;&lt;/el-input&gt;
          &lt;/el-form-item&gt;
      &lt;/el-col&gt;
      &lt;el-col :span="12"&gt;
          &lt;el-form-item label="发布日志:" prop="publishLog"&gt;
            &lt;el-input
            type="textarea"
            v-model="crudForm.publishLog"
            :rows="5"
            :max-rows="5"
            placeholder="请输入发布日志"
            :disabled="disabledStatus"
            &gt;&lt;/el-input&gt;
          &lt;/el-form-item&gt;
      &lt;/el-col&gt;

      &lt;el-col :span="12" v-show="!disabledStatus"&gt;
          &lt;el-form-item label="仓库地址:"&gt;
            &lt;el-input
            v-model="crudForm.warehouseAddress"
            placeholder="gitee仓库地址,例如:/packages/vue3/xxx"
            clearable
            :disabled="importDisabled"
            /&gt;
          &lt;/el-form-item&gt;
      &lt;/el-col&gt;
      &lt;el-col :span="12" v-show="disabledStatus"&gt;
          &lt;el-form-item label="仓库地址:" prop="publishLog"&gt;
            &lt;el-link
            :href="
                'https://gitee.com/ksbpump/ki-components/tree/develop' + crudForm.warehouseAddress
            "
            target="_blank"
            &gt;
            去仓库查看
            &lt;el-icon&gt;&lt;Right /&gt;&lt;/el-icon&gt;
            &lt;/el-link&gt;
          &lt;/el-form-item&gt;
      &lt;/el-col&gt;
      &lt;el-col :span="12" v-show="!disabledStatus"&gt;
          &lt;el-button
            :icon="Search"
            style="float: left; margin-left: 10px"
            @click="viewMd"
            type="primary"
          &gt;
            查看README.md
          &lt;/el-button&gt;
      &lt;/el-col&gt;

      &lt;el-col :span="12" v-show="!disabledStatus"&gt;
          &lt;el-form-item
            v-for="(ssJsConfig, index) in crudForm.ssJsConfig"
            :key="index"
            :label="index === 0 ? '组件js依赖:' : ' '"
          &gt;
            &lt;el-input
            v-model="ssJsConfig.dataKey"
            placeholder="组件别名"
            clearable
            :disabled="importDisabled"
            style="width: 20%"
            /&gt;
            &lt;el-input
            v-model="ssJsConfig.dataValue"
            placeholder="组件依赖路径,例如:/dist/index.ems.js"
            clearable
            :disabled="importDisabled"
            style="width: 75%"
            /&gt;
            &lt;el-icon
            size="16px"
            color="#f56c6c"
            style="margin-left: 5px; cursor: pointer"
            @click="deleteSsJs(ssJsConfig)"
            v-show="index != 0"
            &gt;
            &lt;RemoveFilled /&gt;
            &lt;/el-icon&gt;
          &lt;/el-form-item&gt;
      &lt;/el-col&gt;
      &lt;el-col :span="12" v-show="!disabledStatus" style="vertical-align: middle"&gt;
          &lt;el-button
            :icon="Plus"
            style="float: left; margin-left: 10px"
            @click="addSsJs"
            type="success"
            :disabled="importDisabled"
          &gt;
            添加组件js依赖
          &lt;/el-button&gt;
      &lt;/el-col&gt;
      &lt;el-col :span="12" v-show="!disabledStatus"&gt;
          &lt;el-form-item
            v-for="(ssCssConfig, index) in crudForm.ssCssConfig"
            :key="index"
            :label="index === 0 ? '组件css依赖:' : ' '"
          &gt;
            &lt;el-input
            v-model="ssCssConfig.dataValue"
            placeholder="组件依赖路径,例如:/dist/index.min.css"
            clearable
            :disabled="importDisabled"
            style="width: 95%"
            /&gt;
            &lt;el-icon
            size="16px"
            color="#f56c6c"
            style="margin-left: 5px; cursor: pointer"
            @click="deleteSsCss(ssCssConfig)"
            v-show="index != 0"
            &gt;
            &lt;RemoveFilled /&gt;
            &lt;/el-icon&gt;
          &lt;/el-form-item&gt;
      &lt;/el-col&gt;
      &lt;el-col :span="12" v-show="!disabledStatus"&gt;
          &lt;el-button
            :icon="Plus"
            style="float: left; margin-left: 10px"
            @click="addSsCss"
            type="success"
            :disabled="importDisabled"
          &gt;
            添加组件css依赖
          &lt;/el-button&gt;
      &lt;/el-col&gt;

      &lt;!-- 全局部分 --&gt;
      &lt;el-col :span="12" v-show="!disabledStatus"&gt;
          &lt;el-form-item
            v-for="(ssJsConfig, index) in crudForm.ssAllJsConfig"
            :key="index"
            :label="index === 0 ? '全局组件js依赖:' : ' '"
          &gt;
            &lt;el-input
            v-model="ssJsConfig.dataKey"
            placeholder="组件别名"
            clearable
            :disabled="importDisabled"
            style="width: 20%"
            /&gt;
            &lt;el-input
            v-model="ssJsConfig.name"
            placeholder="组件名称"
            clearable
            :disabled="importDisabled"
            style="width: 20%"
            /&gt;
            &lt;el-input
            v-model="ssJsConfig.versionNum"
            placeholder="组件版本号"
            clearable
            :disabled="importDisabled"
            style="width: 20%"
            /&gt;
            &lt;el-input
            v-model="ssJsConfig.dataValue"
            placeholder="组件依赖路径,例如:/dist/index.ems.js"
            clearable
            :disabled="importDisabled"
            style="width: 35%"
            /&gt;
            &lt;el-icon
            size="16px"
            color="#f56c6c"
            style="margin-left: 5px; cursor: pointer"
            @click="deleteSsAllJs(ssJsConfig)"
            v-show="index != 0"
            &gt;
            &lt;RemoveFilled /&gt;
            &lt;/el-icon&gt;
          &lt;/el-form-item&gt;
      &lt;/el-col&gt;
      &lt;el-col :span="12" v-show="!disabledStatus" style="vertical-align: middle"&gt;
          &lt;el-button
            :icon="Plus"
            style="float: left; margin-left: 10px"
            @click="addSsAllJs"
            type="success"
            :disabled="importDisabled"
          &gt;
            添加全局组件js依赖
          &lt;/el-button&gt;
      &lt;/el-col&gt;
      &lt;el-col :span="12" v-show="!disabledStatus"&gt;
          &lt;el-form-item
            v-for="(ssCssConfig, index) in crudForm.ssAllCssConfig"
            :key="index"
            :label="index === 0 ? '全局组件css依赖:' : ' '"
          &gt;
            &lt;el-input
            v-model="ssCssConfig.name"
            placeholder="组件名称"
            clearable
            :disabled="importDisabled"
            style="width: 20%"
            /&gt;
            &lt;el-input
            v-model="ssCssConfig.versionNum"
            placeholder="组件版本号"
            clearable
            :disabled="importDisabled"
            style="width: 20%"
            /&gt;
            &lt;el-input
            v-model="ssCssConfig.dataValue"
            placeholder="组件依赖路径,例如:/dist/index.min.css"
            clearable
            :disabled="importDisabled"
            style="width: 55%"
            /&gt;
            &lt;el-icon
            size="16px"
            color="#f56c6c"
            style="margin-left: 5px; cursor: pointer"
            @click="deleteSsAllCss(ssCssConfig)"
            v-show="index != 0"
            &gt;
            &lt;RemoveFilled /&gt;
            &lt;/el-icon&gt;
          &lt;/el-form-item&gt;
      &lt;/el-col&gt;
      &lt;el-col :span="12" v-show="!disabledStatus"&gt;
          &lt;el-button
            :icon="Plus"
            style="float: left; margin-left: 10px"
            @click="addSsAllCss"
            type="success"
            :disabled="importDisabled"
          &gt;
            添加全局组件css依赖
          &lt;/el-button&gt;
      &lt;/el-col&gt;

      &lt;el-col class="install-picture" :span="12"&gt;
          &lt;el-form-item label="效果图:" prop="images"&gt;
            &lt;SingleImageUpload
            v-model="crudForm.images"
            :style="{ width: '64px', height: '64px' }"
            :disabled="disabledStatus"
            /&gt;
          &lt;/el-form-item&gt;
      &lt;/el-col&gt;
      &lt;el-col :span="12" v-show="!disabledStatus"&gt;
          &lt;el-button
            :icon="BottomLeft"
            style="float: left; margin-left: 10px"
            @click="importModel"
            type="info"
            :disabled="importDisabled"
          &gt;
            引入全部依赖
          &lt;/el-button&gt;
      &lt;/el-col&gt;
      &lt;/el-row&gt;
      &lt;el-row class="componentExample"&gt;
      &lt;p&gt;使用示例&lt;/p&gt;
      &lt;el-col class="install-example" :span="24"&gt;
          &lt;span&gt;使用示例:&lt;/span&gt;
          &lt;div class="exampleDiv" v-if="showExample"&gt;
            &lt;el-tooltip class="box-item" effect="dark" content="复制" placement="top"&gt;
            &lt;el-icon
                style="position: absolute; right: 25px; top: 23px; cursor: pointer; z-index: 100"
            &gt;
                &lt;DocumentCopy /&gt;
            &lt;/el-icon&gt;
            &lt;/el-tooltip&gt;
            &lt;el-tabs type="border-card" v-model="tab"&gt;
            &lt;el-tab-pane name="code"&gt;
                &lt;template #label&gt;
                  &lt;span class="custom-tabs-label"&gt;
                  &lt;el-icon&gt;&lt;postcard /&gt;&lt;/el-icon&gt;
                  &lt;span&gt;Code&lt;/span&gt;
                  &lt;/span&gt;
                &lt;/template&gt;
                &lt;MonacoEditor
                  ref="editorRef"
                  v-model="crudForm.installExample"
                  :visible="props.componentModel"
                  :style="{ height: '415px' }"
                  :options="editorOptions"
                  @update:modelValue="getCode"
                /&gt;
            &lt;/el-tab-pane&gt;
            &lt;el-tab-pane name="preview"&gt;
                &lt;template #label&gt;
                  &lt;span class="custom-tabs-label"&gt;
                  &lt;el-icon&gt;&lt;Monitor /&gt;&lt;/el-icon&gt;
                  &lt;span&gt;Preview&lt;/span&gt;
                  &lt;/span&gt;
                &lt;/template&gt;
                &lt;Preview
                  v-if="showExample"
                  :state="state"
                  :tab="tab"
                  :resources="resources"
                &gt;&lt;/Preview&gt;
            &lt;/el-tab-pane&gt;
            &lt;/el-tabs&gt;
          &lt;/div&gt;
          &lt;div v-else class="exampleText"&gt;
            请填写组件在仓库中的地址和组件依赖信息后,点击引入依赖按钮后进行示例填写
          &lt;/div&gt;
      &lt;/el-col&gt;
      &lt;/el-row&gt;
      &lt;el-row class="componentShuo" v-show="componentShuoShow"&gt;
      &lt;p&gt;使用说明&lt;/p&gt;
      &lt;div id="preview"&gt;&lt;/div&gt;
      &lt;/el-row&gt;
    &lt;/el-form&gt;
    &lt;el-row class="history-table"&gt;
      &lt;p&gt;版本历史&lt;/p&gt;
      &lt;el-table :data="tableData" style="width: 100%"&gt;
      &lt;el-table-column prop="version" label="版本号" /&gt;
      &lt;el-table-column prop="publishLog" label="更新日志" /&gt;
      &lt;el-table-column prop="updateTime" label="更新时间" /&gt;
      &lt;el-table-column prop="address" label="操作" width="80"&gt;
          &lt;template #default="scope"&gt;
            &lt;el-button link type="primary" size="small" @click="viewLogs(scope.row)"&gt;
            查看
            &lt;/el-button&gt;
          &lt;/template&gt;
      &lt;/el-table-column&gt;
      &lt;/el-table&gt;
    &lt;/el-row&gt;
    &lt;el-drawer
      v-model="innerDrawer"
      title="版本历史"
      :append-to-body="true"
      :before-close="handleClose"
      size="800"
      class="innerDrawer"
    &gt;
      &lt;el-form
      ref="ruleFormRef"
      :inline="true"
      :model="histroyForm"
      class="histroyForm"
      label-width="100px"
      &gt;
      &lt;el-row&gt;
          &lt;el-col :span="12"&gt;
            &lt;el-form-item label="组件名称:" prop="name"&gt;
            &lt;el-input
                v-model="histroyForm.name"
                :disabled="true"
                placeholder="请输入组件名称"
                clearable
            /&gt;
            &lt;/el-form-item&gt;
          &lt;/el-col&gt;
          &lt;el-col :span="12"&gt;
            &lt;el-form-item label="组件分类:" prop="type"&gt;
            &lt;el-tree-select
                v-model="histroyForm.type"
                :disabled="true"
                :props="treeprops"
                check-strictly
                filterable
                :data="typeOptions"
                :render-after-expand="false"
            /&gt;
            &lt;/el-form-item&gt;
          &lt;/el-col&gt;
          &lt;el-col :span="12"&gt;
            &lt;el-form-item label="组件标签:" prop="historyTagList"&gt;
            &lt;el-select
                v-model="historyTagList"
                multiple
                filterable
                allow-create
                default-first-option
                :reserve-keyword="false"
                placeholder="请选择组件标签"
                :disabled="true"
            &gt;
                &lt;el-option
                  v-for="item in tagOptions"
                  :key="item.value"
                  :label="item.label"
                  :value="item.value"
                /&gt;
            &lt;/el-select&gt;
            &lt;/el-form-item&gt;
          &lt;/el-col&gt;
          &lt;el-col :span="12"&gt;
            &lt;el-form-item label="版本号:" prop="version"&gt;
            &lt;el-input
                v-model="histroyForm.version"
                placeholder="请输入版本号"
                clearable
                :disabled="true"
            /&gt;
            &lt;/el-form-item&gt;
          &lt;/el-col&gt;
          &lt;el-col :span="12"&gt;
            &lt;el-form-item label="更新时间:" prop="updateTime"&gt;
            &lt;el-date-picker
                style="width: 100%"
                v-model="histroyForm.updateTime"
                type="datetime"
                placeholder="请选择时间"
                value-format="YYYY-MM-DD HH:mm:ss"
                :disabled="true"
            /&gt;
            &lt;/el-form-item&gt;
          &lt;/el-col&gt;
          &lt;el-col :span="24"&gt;
            &lt;el-form-item label="组件描述:" prop="description"&gt;
            &lt;el-input
                type="textarea"
                v-model="histroyForm.description"
                :rows="5"
                :max-rows="5"
                placeholder="请输入组件描述"
                :disabled="true"
            &gt;&lt;/el-input&gt;
            &lt;/el-form-item&gt;
          &lt;/el-col&gt;
          &lt;el-col :span="24"&gt;
            &lt;el-form-item label="更新日志:" prop="publishLog"&gt;
            &lt;el-input
                type="textarea"
                v-model="histroyForm.publishLog"
                :rows="5"
                :max-rows="5"
                placeholder="请输入更新日志"
                :disabled="true"
            &gt;&lt;/el-input&gt;
            &lt;/el-form-item&gt;
          &lt;/el-col&gt;
      &lt;/el-row&gt;
      &lt;/el-form&gt;
    &lt;/el-drawer&gt;
&lt;/div&gt;
&lt;/template&gt;
&lt;script setup lang="ts"&gt;
import { ref, reactive, onMounted, nextTick, watch } from "vue";
import type { FormInstance, FormRules } from "element-plus";
import Preview from "@/components/VueSfcEditor/preview/index.vue";
import {
Postcard,
Monitor,
Search,
Download,
Right,
CirclePlusFilled,
RemoveFilled,
BottomLeft,
} from "@element-plus/icons-vue";
import ComponentManageAPI from "@/api/modules/componentManage.api";
import { parse, compileScript, compileTemplate, compileStyle } from "@vue/compiler-sfc";
import { ElMessage } from "element-plus";

import { marked } from "marked";
import "highlight.js/styles/github.css";
import hljs from "highlight.js";

const loading = ref(false);
const props = defineProps({
componentForm: {
    type: Object,
    default: () =&gt; ({}),
},
componentModel: {
    type: Boolean,
    default: false,
},
});

const tab = ref("code");

const editStatus = ref("");

watch(
() =&gt; props.componentModel,
(val) =&gt; {
    if (val) {
      tab.value = "code";
      getCode("");
    }
}
);

const disabledStatus = ref(false);

watch(
() =&gt; tab.value,
(val) =&gt; {
    if (val === "preview") {
      getCode(editorRef.value.getEditValue());
    }
}
);

marked.use({
async: false,
highlight: (code, lang) =&gt; {
    const validLang = hljs.getLanguage(lang) ? lang : "plaintext";
    return hljs.highlight(code, { language: validLang }).value;
},
});

marked.setOptions({
gfm: true, // 支持 GitHub 风格语法
breaks: false, // 换行符处理
pedantic: false, // 避免严格模式导致解析异常
});

interface RuleForm {
id: string;
name: string;
type: string;
tagStr: string;
updateTime: string;
warehouseAddress: string;
// task: string;
installType: string;
install: string;
description: string;
installExample: string;
version: string;
images: string;
publishLog: string;
status: string;
readme: string;
ssJsConfig: {
    dataKey: string;
    dataValue: string;
}[];
ssCssConfig: {
    dataValue: string;
}[];
ssAllJsConfig: {
    dataKey: string;
    name: string;
    versionNum: string;
    dataValue: string;
}[];
ssAllCssConfig: {
    name: string;
    versionNum: string;
    dataValue: string;
}[];
}
const ruleFormRef = ref&lt;FormInstance&gt;();
const crudForm = reactive&lt;RuleForm&gt;({
id: "",
// name: "ksb-vis-timeline",
name: "",
type: "",
tagStr: "",
updateTime: "",
// warehouseAddress: "/packages/vue3/ui-data",
warehouseAddress: "",
// task: "",
installType: "NPM",
install: "",
description: "",
installExample: "",
// version: "0.0.0-no-version",
version: "",
images: "",
publishLog: "",
status: "0",
readme: "",
ssJsConfig: [
    // {
    //   dataKey: "vis",
    //   dataValue: "/dist/vis-timeline-graph2d.esm.js",
    // },
    {
      dataKey: "",
      dataValue: "",
    },
],
ssCssConfig: [
    // {
    //   dataValue: "/dist/vis-timeline-graph2d.min.css",
    // },
    {
      dataValue: "",
    },
],
ssAllJsConfig: [
    // {
    //   dataKey: "vue",
    //   name: "vue",
    //   versionNum: "3.4.19",
    //   dataValue: "/dist/vue.esm-browser.js",
    // },
    {
      dataKey: "",
      name: "",
      versionNum: "",
      dataValue: "",
    },
],
ssAllCssConfig: [
    // {
    //   name: "",
    //   versionNum: "",
    //   dataValue: "",
    // },
    {
      name: "",
      versionNum: "",
      dataValue: "",
    },
],
});
const rules = reactive&lt;FormRules&lt;RuleForm&gt;&gt;({
name: [{ required: true, message: "组件名称不能为空", trigger: "blur" }],
type: [{ required: true, message: "请选择组件分类", trigger: "change" }],
description: [{ required: true, message: "请输入组件描述", trigger: "blur" }],
});

// const installTypes = ["NPM", "pnpm", "bun", "yarn"];
const installTypes = ["NPM"];

import { Delete, Download, Plus, ZoomIn } from "@element-plus/icons-vue";

import type { UploadFile } from "element-plus";
import edit from "@/views/demo/curd/config/edit";
import { c } from "vite/dist/node/moduleRunnerTransport.d-DJ_mE5sf";
import { editor } from "monaco-editor";
import func from "vue-temp/vue-editor-bridge";

const disabled = ref(false);

const editorRef = ref&lt;any&gt;(null);

// 添加 Monaco Editor 配置
const editorOptions = {
value: "",
language: "html",
theme: "vs-dark",
automaticLayout: true,
minimap: {
    enabled: false,
},
scrollBeyondLastLine: false,
fontSize: 14,
tabSize: 2,
wordWrap: "on",
formatOnPaste: true,
formatOnType: true,
suggestOnTriggerCharacters: true,
acceptSuggestionOnEnter: "on",
quickSuggestions: true,
parameterHints: {
    enabled: true,
},
// 添加自定义配置以处理内存文件
model: {
    uri: "inmemory://model/1",
    language: "html",
},
};

const getCode = (code?: string) =&gt; {
crudForm.installExample = code ?? "";
state.code = code ?? "";
};

const state = reactive({
// sfc 源代码
code: "",
// code: props?.componentData?.content || DefaultCode.trim(),
updateCode(code) {
    state.code = code;
},

// 编译过程
compile(code) {
    // 直接返回代码内容,不进行 Vue 编译
    return code;
},
});
provide("store", state);
// console.log(state);

const treeprops = {
label: "className",
children: "childrenList",
value: "id",
};

interface Tree {
id: string;
className: string;
parentId: string;
childrenList?: Tree[];
classDesc: string;
classCode: string;
}

const typeOptions = ref([]);
function getTypeListFun() {
ComponentManageAPI.getTypeList({}).then((res) =&gt; {
    const dataTree: Tree[] = JSON.parse(JSON.stringify(res));
    typeOptions.value = dataTree;
});
}

const tagOptions = ref([]);
function getTagListFun() {
ComponentManageAPI.getTagList({}).then((res) =&gt; {
    tagOptions.value = [];
    res.forEach((item) =&gt; {
      tagOptions.value.push({
      value: item.tagName,
      label: item.tagName,
      });
    });
});
}

const tagList = ref([]);

function createTagFun() {
// 提取B的value集合,使用Set提高检索效率
const BValueSet = new Set(tagOptions.value.map((item) =&gt; item.value));
// 筛选A中存在但B中缺失的值
const missingInB = tagList.value.filter((item) =&gt; !BValueSet.has(item));
if (missingInB.length &gt; 0) {
    ComponentManageAPI.createTag(missingInB.toString()).then((res) =&gt; {
      getTagListFun();
    });
}
crudForm.tagStr = tagList.value.toString();
}

function selectInstallTypeFun(type: string) {
crudForm.installType = type;
}

let tableData = ref([]);
const historyTagList = ref([]);

const histroyForm = reactive({
name: "",
type: "",
tagStr: "",
description: "",
installType: "",
install: "",
version: "",
updateTime: "",
publishLog: "",
});
const innerDrawer = ref(false);
function viewLogs(row) {
innerDrawer.value = true;

histroyForm.name = row.name;
histroyForm.type = row.type;
histroyForm.tagStr = row.tagStr;
histroyForm.description = row.description;
histroyForm.installType = row.installType;
histroyForm.install = row.install;
histroyForm.version = row.version;
histroyForm.updateTime = row.updateTime;
histroyForm.publishLog = row.publishLog;

historyTagList.value = row.tagStr.split(",");
}
function handleClose() {
innerDrawer.value = false;
}

const getEditorCode = () =&gt; {
crudForm.installExample = editorRef.value.getEditValue();
};

const componentShuoShow = ref(false);

const UNPKGAddress = "http://10.22.0.120:4000";
// 查看md
function viewMd() {
if (crudForm.name == "" || crudForm.version == "") {
    ElMessage({
      type: "error",
      message: "请填写组件名称和版本号后操作",
    });
    return;
}

fetchReadme(crudForm.name, crudForm.version).then((content) =&gt; {
    if (content) {
      nextTick(() =&gt; {
      const previewElement = document.getElementById("preview");
      if (previewElement) {
          previewElement.innerHTML = marked.parse(content);
          // 添加代码块样式
          previewElement.querySelectorAll("pre code").forEach((block) =&gt; {
            hljs.highlightElement(block);
          });
          componentShuoShow.value = true;
      } else {
          console.error("Preview element not found");
      }
      });
    }
});
}

async function fetchReadme(packageName: string, version = "latest") {
const url = UNPKGAddress + `/${packageName}@${version}/README.md`;
try {
    const response = await fetch(url, { mode: "cors" });
    if (!response.ok) {
      ElMessage({
      type: "error",
      message: "HTTP错误" + ` ${response.status}`,
      });
    }
    const readmeContent = await response.text();
    console.log(readmeContent); // 输出或处理内容
    return readmeContent;
} catch (error) {
    ElMessage({
      type: "error",
      message: "文件获取失败",
    });
    return null;
}
}

const importDisabled = ref(false);
interface ResourceItem {
type: string;
name?: string;
url: string;
}
const showExample = ref(false);
const resources = ref&lt;ResourceItem[]&gt;([]);
function importModel() {
if (crudForm.name == "" || crudForm.version == "") {
    ElMessage({
      type: "error",
      message: "请填写组件名称和版本号后操作",
    });
    return;
}
if (
    !validateConfigs(
      crudForm.ssJsConfig,
      crudForm.ssCssConfig,
      crudForm.ssAllJsConfig,
      crudForm.ssAllCssConfig
    )
) {
    ElMessage({
      type: "error",
      message: "请正确填写依赖后操作",
    });
    return;
}

resources.value = [];
let list: any[] = [];
crudForm.ssJsConfig.map((item) =&gt; {
    if (item.dataKey &amp;&amp; item.dataValue) {
      list.push({
      type: "js",
      name: item.dataKey,
      url: UNPKGAddress + `/${crudForm.name}@${crudForm.version}${item.dataValue}`,
      });
    }
});
crudForm.ssAllJsConfig.map((item) =&gt; {
    if (item.dataKey &amp;&amp; item.name &amp;&amp; item.versionNum &amp;&amp; item.dataValue) {
      list.push({
      type: "js",
      name: item.dataKey,
      url: UNPKGAddress + `/${item.name}@${item.versionNum}${item.dataValue}`,
      });
    }
});
crudForm.ssCssConfig.map((item) =&gt; {
    if (item.dataValue) {
      list.push({
      type: "css",
      url: UNPKGAddress + `/${crudForm.name}@${crudForm.version}${item.dataValue}`,
      });
    }
});
crudForm.ssAllCssConfig.map((item) =&gt; {
    if (item.name &amp;&amp; item.versionNum &amp;&amp; item.dataValue) {
      list.push({
      type: "css",
      url: UNPKGAddress + `/${item.name}@${item.versionNum}${item.dataValue}`,
      });
    }
});

resources.value = list;
showExample.value = true;
}

async function fetchJsCss(packageName: string, version = "latest", dataValue: string) {
const url = UNPKGAddress + `/${packageName}@${version}${dataValue}`;
try {
    const response = await fetch(url, { mode: "cors" });
    if (!response.ok) {
      ElMessage({
      type: "error",
      message: "HTTP错误" + ` ${response.status}`,
      });
    }
    const readmeContent = await response.text();
    console.log(readmeContent); // 输出或处理内容
    return readmeContent;
} catch (error) {
    ElMessage({
      type: "error",
      message: "文件获取失败",
    });
    return null;
}
}

function validateConfigs(
ssJsConfig: any,
ssCssConfig: any,
ssAllJsConfig: any,
ssAllCssConfig: any
): boolean {
// ====================== 防御性校验 ======================
// 检查输入是否为数组
if (
    !Array.isArray(ssJsConfig) ||
    !Array.isArray(ssCssConfig) ||
    !Array.isArray(ssAllJsConfig) ||
    !Array.isArray(ssAllCssConfig)
) {
    throw new Error("配置必须为数组类型");
}

// ====================== 核心校验逻辑 ======================
// 校验规则1:检查 ssJsConfig 中的每条数据是否符合互斥规则
const isJsConfigValid = ssJsConfig.every((item) =&gt; {
    // 确保处理字符串类型 (防御非字符串输入)
    const dataKey = String(item?.dataKey ?? "").trim();
    const dataValue = String(item?.dataValue ?? "").trim();

    // 互斥规则:两个字段要么同时为空,要么同时有值
    return (dataKey === "" &amp;&amp; dataValue === "") || (dataKey !== "" &amp;&amp; dataValue !== "");
});

const isAllJsConfigValid = ssAllJsConfig.every((item) =&gt; {
    // 确保处理字符串类型 (防御非字符串输入)
    const dataKey = String(item?.dataKey ?? "").trim();
    const name = String(item?.name ?? "").trim();
    const versionNum = String(item?.versionNum ?? "").trim();
    const dataValue = String(item?.dataValue ?? "").trim();

    // 互斥规则:两个字段要么同时为空,要么同时有值
    return (
      (dataKey === "" &amp;&amp; name === "" &amp;&amp; versionNum === "" &amp;&amp; dataValue === "") ||
      (dataKey !== "" &amp;&amp; name !== "" &amp;&amp; versionNum !== "" &amp;&amp; dataValue !== "")
    );
});

// 规则1不通过:直接返回错误
if (!isJsConfigValid || !isAllJsConfigValid) {
    return false;
}

// ====================== 最终结果 ======================
return true; // 或根据业务需求返回其他标识
}

// 组件内增删
function deleteSsJs(ssJsConfig: any) {
let index = crudForm.ssJsConfig.indexOf(ssJsConfig);
if (index !== -1) {
    crudForm.ssJsConfig.splice(index, 1);
}
}

function addSsJs() {
crudForm.ssJsConfig.push({
    dataKey: "",
    dataValue: "",
});
}

function deleteSsCss(ssCssConfig: any) {
let index = crudForm.ssCssConfig.indexOf(ssCssConfig);
if (index !== -1) {
    crudForm.ssCssConfig.splice(index, 1);
}
}

function addSsCss() {
crudForm.ssCssConfig.push({
    dataValue: "",
});
}

// 全局组件增删
function deleteSsAllJs(ssJsConfig: any) {
let index = crudForm.ssAllJsConfig.indexOf(ssJsConfig);
if (index !== -1) {
    crudForm.ssAllJsConfig.splice(index, 1);
}
}

function addSsAllJs() {
crudForm.ssAllJsConfig.push({
    dataKey: "",
    name: "",
    versionNum: "",
    dataValue: "",
});
}

function deleteSsAllCss(ssCssConfig: any) {
let index = crudForm.ssAllCssConfig.indexOf(ssCssConfig);
if (index !== -1) {
    crudForm.ssAllCssConfig.splice(index, 1);
}
}

function addSsAllCss() {
crudForm.ssAllCssConfig.push({
    name: "",
    versionNum: "",
    dataValue: "",
});
}

onMounted(() =&gt; {
getTypeListFun();
getTagListFun();
});

function getVersionListFun() {
ComponentManageAPI.getVersionList({ componentId: crudForm.id }, 999, 1).then((res) =&gt; {
    tableData.value = res.records;
});
}
// 暴露给父组件的方法和属性
defineExpose({
ruleFormRef,
crudForm,
state,
getEditorCode,
setFormData: (data: any) =&gt; {
    if (data) {
      crudForm.id = data.id || "";
      crudForm.name = data.name || "";
      crudForm.type = data.type || "";
      crudForm.tagStr = data.tagStr || "";
      crudForm.installType = data.installType || "";
      crudForm.install = data.install || "";
      crudForm.description = data.description || "";
      crudForm.installExample = data.installExample || "";
      crudForm.version = data.version || "";
      crudForm.images = data.images || "";
      crudForm.publishLog = data.publishLog || "";
      crudForm.status = "0";
      crudForm.updateTime = data.updateTime || "";

      crudForm.warehouseAddress = data.warehouseAddress || "";
      crudForm.ssJsConfig = data.ssJsConfig || [{ dataKey: "", dataValue: "" }];
      crudForm.ssCssConfig = data.ssCssConfig || [{ dataValue: "" }];
      crudForm.ssAllJsConfig = data.ssAllJsConfig || [
      { dataKey: "", name: "", versionNum: "", dataValue: "" },
      ];
      crudForm.ssAllCssConfig = data.ssAllCssConfig || [
      { name: "", versionNum: "", dataValue: "" },
      ];

      tagList.value = [];
      historyTagList.value = [];

      // if (data.type) {
      //   editypeList.value = ;
      // }

      if (data.tagStr) {
      tagList.value = data.tagStr.split(",");
      }

      if (data.status == "view") {
      editStatus.value = "view";
      disabledStatus.value = true;
      viewMd();
      getVersionListFun();
      importModel();
      } else if (data.status == "add") {
      editStatus.value = "add";
      disabledStatus.value = false;
      componentShuoShow.value = false;
      resources.value = [];
      importDisabled.value = false;
      showExample.value = false;
      } else {
      editStatus.value = "edit";
      getVersionListFun();
      importModel();
      disabledStatus.value = false;
      componentShuoShow.value = false;
      importDisabled.value = true;
      showExample.value = true;
      }
    }
},
});
&lt;/script&gt;
&lt;style scoped lang="scss"&gt;
.crud-page {
position: relative;
height: 100%;
}
::v-deep .crudForm {
.el-form-item {
    width: 100%;
    margin-right: 0;
    margin-bottom: 18px;
}
.el-cascader {
    width: 100%;
}
}

.install-instructions,
.install-example {
display: flex;
justify-content: center;
margin-bottom: 18px;
position: relative;
span {
    width: 100px;
    text-align: right;
    font-size: 14px;
    display: inline-block;
    padding-right: 12px;
    color: #606266;
}
}
::v-deep .install-picture {
display: flex;
justify-content: center;
margin-bottom: 0;
.el-input__inner {
    cursor: pointer;
}
}
.installDiv {
border: 1px solid #dcdfe6;
border-radius: 5px;
display: inline-block;
width: calc(100% - 100px);
height: 110px;
padding: 10px;
.el-form-item {
    margin-bottom: 0;
}
}
::v-deep .exampleDiv {
border: 1px solid #dcdfe6;
border-radius: 5px;
display: inline-block;
width: calc(100% - 100px);
height: 500px;
padding: 10px;
.el-tabs__content {
    height: 435px;
    padding: 10px;
    overflow: auto;
}
}
.exampleText {
display: inline-block;
width: calc(100% - 100px);
color: #606266;
}
::v-deep textarea {
resize: none; /* 禁止调整大小 */
}

.exampleDiv &gt; .el-tabs__content {
padding: 32px;
color: #6b778c;
font-size: 32px;
font-weight: 600;
}
.exampleDiv .custom-tabs-label .el-icon {
vertical-align: middle;
}
.exampleDiv .custom-tabs-label span {
vertical-align: middle;
margin-left: 4px;
width: auto;
}

::v-deep .viewShowClass {
p {
    padding-left: 20px;
    margin: 0 0 20px;
    font-size: 16px;
    font-weight: bold;
    color: #202020;
}
.el-input {
    width: 100%;
}
}
::v-deep .history-table {
// width: 100%;
padding: 0 20px;
margin: 20px 0;
p {
    // padding-left:20px;
    margin: 0 0 20px;
    font-size: 16px;
    font-weight: bold;
    color: #202020;
}
.el-table__header th {
    background-color: #f6f6f6;
}
}
.innerDrawer {
.el-form-item {
    margin: 0 0 18px 0;
    width: 100%;
    .el-input {
      width: 100%;
    }
    .el-select {
      width: 100%;
    }
}
.install-instructions {
    .el-input {
      width: 100%;
    }
}
}
::v-deep .componentMsg {
p {
    padding-left: 20px;
    margin: 0 0 20px;
    font-size: 16px;
    font-weight: bold;
    color: #202020;
    width: 100%;
}
.el-input {
    width: 100%;
}
}
::v-deep .componentExample {
p {
    padding-left: 20px;
    margin: 0 0 20px;
    font-size: 16px;
    font-weight: bold;
    color: #202020;
    width: 100%;
}
.el-input {
    width: 100%;
}
}
::v-deep .componentShuo {
p {
    padding-left: 20px;
    margin: 0 0 20px;
    font-size: 16px;
    font-weight: bold;
    color: #202020;
    width: 100%;
}
.el-input {
    width: 100%;
}
}
.el-tab-pane {
height: 100%;
overflow-y: auto;
}
#preview {
padding: 20px;
border: 1px solid #dcdfe6;
}
&lt;/style&gt;
</code></pre>
</details>
<h4 id="previewindexvue">preview/index.vue</h4>
<details>
<summary>点击查看代码</summary>
<pre><code>&lt;template&gt;
&lt;div class="preview" ref="preview"&gt;&lt;/div&gt;
&lt;/template&gt;

&lt;script setup&gt;
import { ref, onMounted, watch, inject, onUnmounted } from "vue";
import PreviewTemplate from "./preview-template.html?raw";

const preview = ref();

let proxy = ref(null);

// 注入store
const store = inject("store");

const template = ref(null)

const props = defineProps({
state: {
    type: Object,
    default: () =&gt; ({}),
},
// 修改为更通用的资源引入配置
resources: {
    type: Array,
    default: () =&gt; [
      // { type: 'js', name: 'vue', url: 'https://unpkg.com/vue@3.4.21/dist/vue.esm-browser.js' },
      // { type: 'js', name: 'element-plus', url: 'https://unpkg.com/element-plus@2.4.1/dist/index.full.mjs' },
      // { type: 'css', url: 'https://unpkg.com/element-plus@2.4.1/dist/index.css' }
    ]
}
});

watch(
() =&gt; props.state,
(newVal) =&gt; {
    // console.log('444444444444444444444')
    // console.log(template)
    setTimeout(() =&gt; {
      proxy.value = createProxy(template.value);
    }, 100)
},
{ deep: true, immediate: true}
);

// 监听resources变化,重新创建沙盒
watch(
() =&gt; props.resources,
async () =&gt; {
    await createSandbox();
    // 重新创建代理
    proxy.value = createProxy(template.value);
},
{ deep: true }
);

// 创建沙盒
function createSandbox () {
// 清理旧的 iframe
if (template.value) {
    template.value.remove();
    template.value = null;
}

template.value = document.createElement("iframe");
template.value.setAttribute("frameborder", "0");
template.value.style = "width: 100%; height:100%";

// 创建基础HTML内容
const baseHtml = PreviewTemplate;

// 动态生成importmap
const jsResources = props.resources.filter(resource =&gt; resource.type === 'js');
const importMapScript = `&lt;script type="importmap"&gt;
    {
      "imports": {
      ${jsResources.map(resource =&gt; `"${resource.name}": "${resource.url}"`).join(',\n      ')}
      }
    }
&lt;\/script&gt;`;

// 动态生成CSS链接
const cssResources = props.resources.filter(resource =&gt; resource.type === 'css');
const cssLinks = cssResources.map(resource =&gt;
    `&lt;link href="${resource.url}" rel="stylesheet" type="text/css" /&gt;`
).join('\n');

// 组合最终的HTML内容
template.value.srcdoc = baseHtml.replace('&lt;/head&gt;', `${importMapScript}\n${cssLinks}\n&lt;/head&gt;`);

preview.value.appendChild(template.value);

// 等待iframe加载完成
return new Promise((resolve) =&gt; {
    template.value.onload = () =&gt; {
      resolve();
    };
});
}

// 创建代理,用于监听code 变化,告诉沙盒重新渲染
function createProxy (iframe) {
let _iframe = iframe;

const stopWatch = watch(() =&gt; store?.code, (newCode) =&gt; {
    if (newCode) {
      compile(newCode);
    }
}, { immediate: true });

function compile (code) {
    if (!code?.trim()) {
      code = "&lt;script setup&gt; // &lt;\/script&gt;"
    }

    const compiledCode = store?.compile(code);

    if (_iframe?.contentWindow) {
      _iframe.contentWindow.postMessage(
      { type: "eval", code: compiledCode },
      "*"
      );
    }
}

// 销毁沙盒
function destory () {
    _iframe?.remove();
    _iframe = null;
    stopWatch?.();
}

return {
    compile,
    destory,
};
}

onMounted(async () =&gt; {
await createSandbox();
proxy.value = createProxy(template.value);
});

onUnmounted(() =&gt; proxy.value?.destory());
&lt;/script&gt;

&lt;style scoped&gt;
.preview {
width: 100%;
height: 100%;
overflow: hidden;
}
&lt;/style&gt;
</code></pre>
</details>
<h4 id="html预览html">html预览html</h4>
<details>
<summary>点击查看代码</summary>
<pre><code>&lt;!doctype html&gt;
&lt;html lang="en"&gt;
&lt;head&gt;
    &lt;meta charset="UTF-8" /&gt;
    &lt;meta name="viewport" content="width=device-width, initial-scale=1.0" /&gt;
    &lt;style&gt;
      body {
      margin: 0;
      padding: 20px;
      }
      #preview-content {
      width: 100%;
      height: 100%;
      }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;div id="preview-content"&gt;&lt;/div&gt;

    &lt;script&gt;
      // 监听 message,preview/index.vue 通过 postmessage 传递需要执行的代码
      window.addEventListener("message", ({ data }) =&gt; {
      const { type, code } = data;
      if (type === "eval") {
          handleEval(code);
      }
      });

      // 处理需要执行的代码
      function handleEval(code) {
      const previewContent = document.getElementById('preview-content');
      if (!previewContent) return;

      try {
          // 清空之前的内容
          previewContent.innerHTML = '';

          // 分离 HTML 和 JavaScript 代码
          const htmlMatch = code.match(/&lt;div[^&gt;]*&gt;[\s\S]*?&lt;\/div&gt;/);
          const scriptMatches = code.matchAll(/&lt;script[^&gt;]*&gt;([\s\S]*?)&lt;\/script&gt;/g);
          const scriptSrcMatches = code.matchAll(/&lt;script[^&gt;]*src="([^"]*)"[^&gt;]*&gt;/g);

          // 创建一个临时的容器来处理脚本
          const tempContainer = document.createElement('div');
          tempContainer.style.display = 'none';
          document.body.appendChild(tempContainer);

          // 先添加所有外部脚本
          const externalScripts = [];
          for (const match of scriptSrcMatches) {
            const script = document.createElement('script');
            script.src = match;
            externalScripts.push(script);
            tempContainer.appendChild(script);
          }

          // 等待外部脚本加载完成
          Promise.all(externalScripts.map(script =&gt; {
            return new Promise((resolve, reject) =&gt; {
            script.onload = resolve;
            script.onerror = reject;
            });
          })).then(() =&gt; {
            // 渲染 HTML 内容
            if (htmlMatch) {
            previewContent.innerHTML = htmlMatch;
            } else {
            // 如果没有找到 HTML 内容,直接设置整个代码
            previewContent.innerHTML = code;
            }

            // 添加并执行所有内联脚本
            for (const match of scriptMatches) {
            const scriptContent = match;
            const isModule = match.includes('type="module"');
            const hasImport = scriptContent.includes('import ');

            if (isModule || hasImport) {
                // 对于 ES 模块,创建新的脚本标签
                const script = document.createElement('script');
                script.type = 'module';
                // 如果使用全局 Vue,需要修改 import 语句
                const modifiedContent = scriptContent.replace(
                  /import\s*{\s*([^}]+)\s*}\s*from\s*['"]vue['"]/g,
                  'const { $1 } = Vue'
                );
                script.textContent = modifiedContent;
                previewContent.appendChild(script);
            } else {
                // 对于全局脚本,使用 Function 构造器执行
                try {
                  new Function(scriptContent)();
                } catch (error) {
                  console.error('Error executing script:', error);
                  previewContent.innerHTML = `&lt;pre&gt;Error executing script: ${error.message}&lt;/pre&gt;`;
                }
            }
            }

            // 清理临时容器
            tempContainer.remove();
          }).catch(error =&gt; {
            console.error('Error loading scripts:', error);
            previewContent.innerHTML = `&lt;pre&gt;Error loading scripts: ${error.message}&lt;/pre&gt;`;
            tempContainer.remove();
          });
      } catch (error) {
          console.error('Error displaying content:', error);
          previewContent.innerHTML = `&lt;pre&gt;Error: ${error.message}&lt;/pre&gt;`;
      }
      }
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;

</code></pre>
</details>
<h4 id="monacoeditor配置">MonacoEditor配置</h4>
<details>
<summary>点击查看代码</summary>
<pre><code>&lt;template&gt;
&lt;div v-show="visible" class="youlai-editor-wrapper" ref="monacoEdit"&gt;&lt;/div&gt;
&lt;/template&gt;

&lt;script setup&gt;
import { toRaw, onUnmounted, onMounted, watch } from "vue";
// 导入monaco编辑器
import * as monaco from "monaco-editor/esm/vs/editor/editor.main.js";
import "monaco-editor/esm/vs/basic-languages/javascript/javascript.contribution";

// 编辑器容器div
const monacoEdit = ref(null);
// 编辑器实列
const editor = ref(null);

const emits = defineEmits(["update:modelValue"]);
const props = defineProps({
modelValue: {
    type: String,
    default: "",
},
visible: {
    type: Boolean,
    default: false,
},
});

// 注册 Vue 语言和语法高亮
const registerVueLanguage = () =&gt; {
monaco.languages.register({ id: "vue" });
monaco.languages.setMonarchTokensProvider("vue", {
    tokenizer: {
      root: [
      [/&lt;template&gt;/, "keyword"],
      [/&lt;script&gt;/, "keyword"],
      [/&lt;style&gt;/, "keyword"],
      [/&lt;\/template&gt;/, "keyword"],
      [/&lt;\/script&gt;/, "keyword"],
      [/&lt;\/style&gt;/, "keyword"],
      [/@\w+/, "decorator"],
      [/{{[^}]+}}/, "variable"],
      ,
      [/:+/, "binding"],
      [/@+/, "event"],
      ],
    },
});
};

// 配置 TypeScript 编译选项
const configureTypeScript = () =&gt; {
monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
    target: monaco.languages.typescript.ScriptTarget.ES2020,
    module: monaco.languages.typescript.ModuleKind.ESNext,
    moduleResolution: monaco.languages.typescript.ModuleResolutionKind.NodeJs,
    strict: true,
    esModuleInterop: true,
    skipLibCheck: true,
    allowSyntheticDefaultImports: true,
    sourceMap: true,
    jsx: monaco.languages.typescript.JsxEmit.React,
    baseUrl: ".",
    paths: {
      "@/*": ["src/*"],
    },
});
// monaco.languages.typescript.javascriptDefaults.setCompilerOptions({
//   allowNonTsExtensions: true,
// })
};

// 添加 Vue 3 类型定义
const addVueTypeDefinitions = () =&gt; {
monaco.languages.typescript.typescriptDefaults.addExtraLib(
    `
    // Vue 3 核心类型定义
    declare module 'vue' {
      import { ComponentOptions } from 'vue';
      
      export declare const createApp: (rootComponent: ComponentOptions&lt;any&gt;) =&gt; any;
      
      export interface DefineComponent&lt;
      PropsOrPropOptions = {},
      RawBindings = {},
      D = {},
      C extends ComputedOptions = ComputedOptions,
      M extends MethodOptions = MethodOptions,
      Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
      Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
      E extends EmitsOptions = EmitsOptions,
      EE extends string = string,
      PropsDefaults = PropsDefaultsType&lt;PropsOrPropOptions&gt;
      &gt; {
      // Vue 组件类型定义
      }
      
      // 其他 Vue 3 类型...
    }
   
    // Vue Router 类型定义
    declare module 'vue-router' {
      import { Router, RouteLocationNormalized, RouteRecordRaw } from 'vue-router';
      
      export declare const createRouter: (options: any) =&gt; Router;
      export declare const createWebHistory: () =&gt; any;
      
      // 其他 Vue Router 类型...
    }
`,
    "node_modules/@types/vue/index.d.ts"
);
};

const initEdit = () =&gt; {
setTimeout(() =&gt; {
    if (monacoEdit.value) {
      // 注册 Vue 语言支持
      registerVueLanguage();
      // 配置 TypeScript
      configureTypeScript();
      // 添加 Vue 类型定义
      addVueTypeDefinitions();

      // 创建模型
      const model = monaco.editor.createModel(
      props.modelValue,
      "html",
      monaco.Uri.parse("inmemory://model/1")
      );

      // 创建编辑器实列
      editor.value = monaco.editor.create(monacoEdit.value, {
      model,
      theme: "vs-light", // 官方自带三种主题vs, hc-black, or vs-dark
      autoIndex: true,
      language: "html", // 语言类型
      tabCompletion: "on",
      cursorSmoothCaretAnimation: true,
      minimap: {
          enabled: true,
      },
      formatOnPaste: false,
      mouseWheelZoom: true,
      folding: true, //代码折叠
      selectOnLineNumbers: true, // 显示行号
      wordWrap: "on", // 代码超出换行
      overviewRulerBorder: false, // 不要滚动条的边框
      foldingHighlight: true, // 折叠等高线
      foldingStrategy: "indentation", // 折叠方式auto | indentation
      showFoldingControls: "always", // 是否一直显示折叠 always | mouseover
      disableLayerHinting: true, // 等宽优化
      emptySelectionClipboard: false, // 空选择剪切板
      selectionClipboard: false, // 选择剪切板
      automaticLayout: true, // 自动布局
      codeLens: false, // 代码镜头
      scrollBeyondLastLine: false, // 滚动完最后一行后再滚动一屏幕
      colorDecorators: true, // 颜色装饰器
      accessibilitySupport: "off", // 辅助功能支持"auto" | "off" | "on"
      lineNumbers: "on", // 行号 取值: "on" | "off" | "relative" | "interval" | function
      lineNumbersMinChars: 5, // 行号最小字符   number
      enableSplitViewResizing: false,
      readOnly: false, //是否只读取值 true | false
      scrollbar: {
          useShadows: false,
          verticalHasArrows: false,
          horizontalHasArrows: false,
          vertical: "visible",
          horizontal: "visible",
          verticalScrollbarSize: 10,
          horizontalScrollbarSize: 10,
          arrowSize: 30,
          mouseWheelScrollSensitivity: 1,
          alwaysConsumeMouseWheel: false,
      },
      });

      // 编辑器内容变更时回调
      editor.value.onDidChangeModelContent(() =&gt; {
      let code = toRaw(editor.value).getValue();
      emits("update:modelValue", code);
      });

      // 添加被动事件监听器
      const editorElement = monacoEdit.value;
      if (editorElement) {
      editorElement.addEventListener("wheel", () =&gt; {}, { passive: true });
      editorElement.addEventListener("touchstart", () =&gt; {}, { passive: true });
      editorElement.addEventListener("touchmove", () =&gt; {}, { passive: true });
      }
    }
}, 100);
};

const getEditValue = () =&gt; {
return toRaw(editor.value).getValue();
};

defineExpose({ getEditValue });

// 添加对 visible prop 的监听
watch(
() =&gt; props.visible,
(newVal) =&gt; {
    if (newVal) {
      // 当对话框显示时,确保编辑器已初始化
      // if (!editor.value) {
      //   initEdit();
      // }
      if (editor.value) {
      const rawEditor = toRaw(editor.value);
      const model = rawEditor.getModel();

      // 分步释放资源
      rawEditor.dispose();
      if (model &amp;&amp; !model.isDisposed()) {
          model.dispose();
      }

      initEdit();
      }else{
      initEdit();
      }
    }
},
{ immediate: true }
);


onUnmounted(() =&gt; {
if (editor.value) {
    const rawEditor = toRaw(editor.value);
    const model = rawEditor.getModel();

    // 分步释放资源
    rawEditor.dispose();
    if (model &amp;&amp; !model.isDisposed()) {
      model.dispose();
    }
}
});
&lt;/script&gt;

&lt;style scoped&gt;
.youlai-editor-wrapper {
width: 100%;
}
&lt;/style&gt;

</code></pre>
</details><br><br>
来源:https://www.cnblogs.com/jqCode/p/18900177
頁: [1]
查看完整版本: 基于vue3项目开发+MonacoEditor实现外部引入依赖,界面化所见即所得