Przeglądaj źródła

更换canvas_editor编辑器并插入内容

yangg 6 miesięcy temu
rodzic
commit
b2f23c7bfc

+ 9 - 0
ui/src/api/document.js

@@ -149,3 +149,12 @@ export function searchlistDoc(data) {
     data
   })
 }
+
+/* 获取codeJson */
+
+export function getInfoDCPPNO(productId) {
+  return request({
+    url: `/product/getInfoDCPPNO/${productId}`,
+    method: 'get'
+  })
+}

+ 22 - 1
ui/src/components/CanvasEditor/index.vue

@@ -531,14 +531,17 @@ export default {
     // 编辑模式
     editMode: {
       type: String,
+      default: 'edit'
     },
     // html数据
     htmlData: {
       type: String,
+      default: ''
     },
     // 后端传过来的保存html的JSON数据(用于回显)
     docJson: {
-      type: String,
+      type: [Object, String],
+      default: null
     },
   },
   watch: {
@@ -579,6 +582,24 @@ export default {
         }, delay);
       };
     },
+    getValue() {
+      // 返回当前编辑器的内容
+      return this.editor?.getData() || '';
+    },
+    
+    insertHtml(html) {
+      // 插入 HTML 内容
+      if (this.editor) {
+        this.editor.insertHtml(html);
+      }
+    },
+    
+    setContent(content) {
+      // 设置编辑器内容
+      if (this.editor) {
+        this.editor.setData(content);
+      }
+    }
   },
   mounted() {
     const isApple =

+ 230 - 30
ui/src/views/system/document/com/components/TextArea/index.vue

@@ -2,7 +2,7 @@
   <div class="template-textarea">
     <template v-if="isEdit == 1">
       <div class="editor-area sticky-editor">
-        <ckeditor
+        <!-- <ckeditor
           ref="editor"
           v-model="com.content"
           @focus="onFocus"
@@ -11,16 +11,16 @@
           @ready="onEditorReady"
           :config="editorConfig"
           :editorUrl="editorUrl"
-        ></ckeditor>
-        <!-- <CanvasEditor
+        ></ckeditor> -->
+        <CanvasEditor
           ref="wordEditor"
           :edit-mode="Modetype"
-          :html-data="htmlData"
+          :html-data="com.content"
           :doc-json="docJson"
           :key="keys"
           @save="save"
           @is-save="getSave"
-        ></CanvasEditor> -->
+        ></CanvasEditor>
       </div>
     </template>
 
@@ -30,6 +30,7 @@
         ref="richEditor"
         v-html="content"
         @click="handleImageClick"
+        :class="{'view-mode': isEdit == 2}"
       ></div>
     </template>
     <div v-if="loading" class="overlay">
@@ -85,15 +86,28 @@ export default {
   watch: {
     isEdit: {
       handler(val) {
-        console.log(val);
+        console.log("isEdit:",val);
         let _this = this;
-        if (_this.com != null) return;
-        _this.replaceData(_this.com.content).then((res) => {
-          _this.content = res;
-          _this.$nextTick(() => {
-            _this.bindEvents();
+        if (val == '2') {
+          _this.save()
+          /* if (_this.com && _this.com.content) {
+            _this.replaceData(_this.com.content).then((res) => {
+              _this.content = res;
+              _this.htmlData = res;
+              _this.$nextTick(() => {
+                _this.bindEvents();
+                _this.initializeInputWidths();
+              });
+            });
+          } */
+        } else if (_this.com != null) {
+          _this.replaceData(_this.com.content).then((res) => {
+            _this.content = res;
+            _this.$nextTick(() => {
+              _this.bindEvents();
+            });
           });
-        });
+        }
       },
       immediate: true,
       deep: true,
@@ -105,7 +119,7 @@ export default {
         if (val.content == undefined || val.content == null) return;
         try {
           let res = await _this.replaceData(val.content);
-         /*  _this.htmlData=res */
+          _this.htmlData = res;
           _this.content = res;
           _this.$nextTick(() => {
             _this.bindEvents();
@@ -121,21 +135,87 @@ export default {
     insertCmd: {
       handler(val) {
         if (val == null || this.isEdit != 1) return;
-        let data = this.$refs.editor.instance.getSelection().getSelectedText();
-        if (val.content.indexOf("Directory", 0) >= 0) {
-          //执行的是目录
-          this.$emit(
-            "onUpdateAttr",
-            this.currentIndex,
-            this.com.attrs.length - 1,
-            data
-          );
-          this.$refs.editor.instance.execCommand("delete");
-        }
-        this.$refs.editor.instance.insertHtml(val.content);
+        
+        this.$nextTick(() => {
+          try {
+            if (!this.$refs.wordEditor) {
+              console.warn('编辑器未就绪');
+              return;
+            }
+
+            const editor = this.$refs.wordEditor;
+            
+            // 获取当前内容和新内容
+            let currentContent = this.com.content || '';
+            let insertContent = val.content;
+
+            // 确保变量两侧有空格
+            if (insertContent.includes('{{') && insertContent.includes('}}')) {
+              insertContent = ` ${insertContent} `;
+            }
+
+            // 构建新内容
+            let newContent;
+            
+            // 如果编辑器支持选区
+            if (editor.getSelection) {
+              const selection = editor.getSelection();
+              if (selection) {
+                // 替换选中的内容
+                const start = selection.start || 0;
+                const end = selection.end || start;
+                newContent = currentContent.substring(0, start) + 
+                           insertContent + 
+                           currentContent.substring(end);
+              } else {
+                // 在末尾添加
+                newContent = currentContent + insertContent;
+              }
+            } else {
+              // 在末尾添加
+              newContent = currentContent + insertContent;
+            }
+
+            // 更新内容
+            this.com.content = newContent;
+
+            // 更新编辑器显示
+            if (editor.setHtmlData) {
+              editor.setHtmlData(newContent);
+            }
+
+            // 强制重新渲染
+            this.keys = new Date().getTime();
+
+            // 调用保存方法
+            this.save({
+              main: newContent
+            });
+
+            // 确保视图更新
+            this.$forceUpdate();
+            
+            // 同步到预览模式
+            this.syncContent();
+
+            // 记录操作日志
+            console.log('内容插入成功:', {
+              原内容: currentContent,
+              插入内容: insertContent,
+              新内容: newContent
+            });
+
+          } catch (error) {
+            console.error('插入内容时出错:', error);
+            console.error('错误详情:', {
+              编辑器: this.$refs.wordEditor,
+              内容: val.content,
+              当前内容: this.com.content
+            });
+          }
+        });
       },
-      immediate: true,
-      deep: true,
+      deep: true
     },
   },
   data() {
@@ -163,6 +243,14 @@ export default {
   mounted() {
     this.$nextTick(() => {
       this.initializeInputWidths();
+      this.syncContent();
+    });
+  },
+  updated() {
+    this.$nextTick(() => {
+      if (this.isEdit == '2') {
+        this.syncContent();
+      }
     });
   },
   // 记得在组件销毁时移除事件监听器
@@ -174,9 +262,18 @@ export default {
   methods: {
     /*canvas 添加 */
     save(data) {
-      // 处理保存逻辑
-      console.log("保存数据:", data);
-      // 这里可以添加具体的保存实现
+      // 如果没有传入data参数,使用当前编辑器的内容
+      const content = data ? data.main : this.com.content;
+      console.log(112);
+      // 更新内容
+      this.com.content = content;
+      this.syncContent(); // 同步到预览模式
+      
+      // 触发更新事件
+      this.$emit('onUpdate', this.currentIndex, this.com.content);
+      
+      // 返回保存成功状态
+      return true;
     },
     getSave(isSaved) {
       // 处理保存状态
@@ -1016,6 +1113,49 @@ export default {
       //// console.log(' onBlur e',e);
       // this.$emit("onUploadAttr",this.dataIndex,this.dataAttr);
     }, */
+
+    syncContent() {
+      if (this.isEdit == '2' && this.com && this.com.content) {
+        const formattedContent = this.formatContent(this.com.content);
+        this.replaceData(formattedContent).then((res) => {
+          this.content = res;
+          this.$nextTick(() => {
+            this.bindEvents();
+            this.initializeInputWidths();
+            
+            // 确保变量和空格都正确显示
+            if (this.com.attrs) {
+              this.com.attrs.forEach((attr, index) => {
+                const element = this.$el.querySelector(`#${attr.id}`);
+                if (element) {
+                  element.value = attr.content || '';
+                  this.adjustInputWidth({ target: element });
+                }
+              });
+            }
+          });
+        });
+      }
+    },
+
+    getCursorPosition() {
+      const editor = this.$refs.wordEditor;
+      if (!editor) return 0;
+
+      // 尝试不同的方法获取光标位置
+      if (editor.getCursorPosition) {
+        return editor.getCursorPosition();
+      } else if (editor.getSelection) {
+        const selection = editor.getSelection();
+        return selection ? selection.start || 0 : 0;
+      }
+      return 0;
+    },
+
+    formatContent(content) {
+      // 保留空格和换行
+      return content.replace(/\s+/g, ' ').replace(/\n/g, '<br>');
+    },
   },
 };
 </script>
@@ -1109,4 +1249,64 @@ export default {
   font-weight: normal;
   margin-left: 100px;
 }
+
+.rich-editor.view-mode {
+  // 添加与编辑模式一致的样式
+  ::v-deep {
+    // 继承编辑器的基本样式
+    font-family: inherit;
+    line-height: 1.6;
+    color: inherit;
+    
+    // 标题样式
+    h1, h2, h3, h4, h5, h6 {
+      margin: 1em 0 0.5em;
+      font-weight: bold;
+    }
+    
+    // 段落样式
+    p {
+      margin: 1em 0;
+    }
+    
+    // 列表样式
+    ul, ol {
+      padding-left: 2em;
+      margin: 1em 0;
+    }
+    
+    // 表格样式
+    table {
+      border-collapse: collapse;
+      width: 100%;
+      margin: 1em 0;
+      
+      td, th {
+        border: 1px solid #ddd;
+        padding: 8px;
+      }
+    }
+    
+    // 图片样式
+    img {
+      max-width: 100%;
+      height: auto;
+    }
+    
+    // 保持输入框样式一致
+    .text-input-box,
+    .text-input-boxs {
+      display: inline-block;
+      border: 1px solid transparent;
+      background: transparent;
+      padding: 2px 4px;
+      min-width: 20px;
+      outline: none;
+      
+      &:hover {
+        border-color: #ddd;
+      }
+    }
+  }
+}
 </style>

+ 174 - 0
ui/src/views/system/document/com/components/codeJson/index.vue

@@ -0,0 +1,174 @@
+<template>
+  <div>
+    <div class="product-content">
+      <div class="content-header">
+        <div class="search-area">
+          <el-form :inline="true" :model="searchForm" ref="searchForm">
+            <el-form-item label="产品应用编号">
+              <el-input
+                v-model="searchForm.dcp_p_no"
+                placeholder="请输入产品应用编号"
+                clearable
+              ></el-input>
+            </el-form-item>
+            <el-form-item label="产品名称">
+              <el-input
+                v-model="searchForm.dcp_name"
+                placeholder="请输入产品名称"
+                clearable
+              ></el-input>
+            </el-form-item>
+            <el-form-item label="产品类型">
+              <el-input
+                v-model="searchForm.dcp_model"
+                placeholder="请输入产品类型"
+                clearable
+              ></el-input>
+            </el-form-item>
+            <el-form-item label="OA编号">
+              <el-input
+                v-model="searchForm.dcp_oa_code"
+                placeholder="请输入OA编号"
+                clearable
+              ></el-input>
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" @click="handleSearch">搜索</el-button>
+              <el-button @click="resetForm">重置</el-button>
+            </el-form-item>
+          </el-form>
+        </div>
+        <!--   <el-button type="primary" @click="handleAddProduct">新建产品</el-button> -->
+      </div>
+
+      <el-table :data="productList">
+        <el-table-column prop="dcp_oa_code" label="OA编号" />
+        <el-table-column
+          prop="dcp_p_no"
+          label="产品应用编号"
+          show-overflow-tooltip
+          :min-width="120"
+        >
+          <template slot-scope="scope">
+            <div class="ellipsis">{{ scope.row.dcp_p_no }}</div>
+          </template>
+        </el-table-column>
+        <el-table-column prop="dcp_name" label="产品名称" />
+        <el-table-column prop="dcp_model" label="产品类型" />
+        <!--<el-table-column prop="screenMaterial" label="屏幕材质" /> -->
+        <el-table-column label="操作" width="200">
+          <template slot-scope="scope">
+            <el-button size="mini" @click="handleUseProduct(scope.row)"
+              >使用</el-button
+            >
+            <!-- <el-button size="mini" @click="handleEditProduct(scope.row)"
+              >编辑</el-button
+            >
+            <el-button
+              size="mini"
+              type="danger"
+              @click="handleDeleteProduct(scope.row)"
+              >删除</el-button
+            > -->
+          </template>
+        </el-table-column>
+      </el-table>
+      <!-- 添加分页组件 -->
+      <div class="pagination-container">
+        <el-pagination
+          @current-change="handleCurrentChange"
+          :current-page="pagination.currentPage"
+          :page-size="pagination.pageSize"
+          :total="pagination.total"
+          layout="total, prev, pager, next"
+        >
+        </el-pagination>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+import { productList, deleteProduct } from "@/api/ProductMent/product";
+import { getInfoDCPPNO } from "@/api/document";
+export default {
+  name: "CodeJson",
+  /*组件参数 接收来自父组件的数据*/
+  props: {},
+  /*局部注册的组件*/
+  components: {},
+  /*组件状态值*/
+  data() {
+    return {
+      searchForm: {
+        dcp_p_no: "",
+        dcp_name: "",
+        dcp_model: "",
+        dcp_oa_code: "",
+      },
+      typeList: [],
+      dcpInfo: null,
+      // 分页参数
+      pagination: {
+        currentPage: 1,
+        pageSize: 10,
+        total: 0,
+      },
+      productList: [],
+    };
+  },
+
+  /*计算属性*/
+  computed: {},
+
+  /*侦听器*/
+  watch: {},
+
+  /*
+   * el 被新创建的 vm.$ el 替换,并挂载到实例上去之后调用该钩子。
+   * 如果 root 实例挂载了一个文档内元素,当 mounted 被调用时 vm.$ el 也在文档内。
+   */
+  mounted() {
+    this.fetchProductList();
+  },
+
+  /*组件方法*/
+  methods: {
+    // 搜索和分页
+    handleSearch() {
+      this.pagination.currentPage = 1; // 重置到第一页
+      this.fetchProductList();
+    },
+    // 获取产品列表
+    async fetchProductList() {
+      try {
+        const params = {
+          pageNum: this.pagination.currentPage,
+          pageSize: this.pagination.pageSize,
+          ...this.searchForm, // 展开所有搜索条件
+        };
+
+        productList(params).then((res) => {
+          this.productList = res.rows;
+          this.pagination.total = res.total;
+        });
+      } catch (error) {
+        this.$message.error("获取产品列表失败");
+      }
+    },
+    /* 使用 */
+    handleUseProduct() {},
+    /*  getDCPPNOInfo() {
+      getInfoDCPPNO(this.queryForm.type).then((res) => {
+        this.dcpInfo = res;
+      });
+    },
+
+    onBtnSearch() {
+      this.getDCPPNOInfo();
+    }, */
+  },
+};
+</script>
+<style lang='css' scoped>
+</style>

+ 14 - 1
ui/src/views/system/document/com/editor.vue

@@ -158,6 +158,7 @@
             <div class="main" @click="onSetActive(index)">
               <component
                 :is="it.type"
+                :ref="`component_${index}`"
                 :com="it"
                 :coms="comList"
                 :currentIndex="index"
@@ -672,6 +673,18 @@ export default {
       this.comList = this.comList.map((it, index) => {
         it.isEdit = 2;
 
+        // 当切换到预览状态时(state === 2),触发保存
+        if (index === e && state === 2) {
+          // 找到对应的TextArea组件实例并调用save方法
+          this.$nextTick(() => {
+            console.log(111);
+            const componentRef = this.$refs[`component_${index}`];
+            if (componentRef && componentRef.save) {
+              componentRef.save();
+            }
+          });
+        }
+
         // 当切换到编辑状态时,对当前项进行 attrs 过滤
         if (index === e && state === 1) {
           this.filterAttrs(it);
@@ -684,7 +697,7 @@ export default {
         this.comList[e].isEdit = 1;
         this.onSetActive(e);
       }
-      console.log(this.comList);
+      
       this.$emit("onRebuild", this.comList);
     },
 

+ 37 - 15
ui/src/views/system/document/com/menus.vue

@@ -17,11 +17,12 @@
               :class="{
                 disabled:
                   (typeMenu === 'document' && item.key !== 'ai') ||
-                  (item.key === 'article' && (typeMenu === 'module' || typeMenu === undefined)),
+                  (item.key === 'article' &&
+                    (typeMenu === 'module' || typeMenu === undefined)),
               }"
             >
               <div class="icon">
-                <img 
+                <img
                   v-if="item.icon"
                   :src="getIconPath(item.icon)"
                   class="menu-icon"
@@ -89,7 +90,7 @@
       title="AI"
     >
       <!-- <sourceAi @onPicked="onPickedAi"></sourceAi> -->
-     <!--  <ChatBox /> -->
+      <!--  <ChatBox /> -->
     </el-dialog>
 
     <!-- ai -->
@@ -133,6 +134,19 @@
         >
       </span>
     </el-dialog>
+    <!-- 选择 codeJson -->
+    <el-dialog
+      :visible.sync="codeJsonData"
+      append-to-body
+      v-el-drag-dialog
+      width="300"
+      :close-on-click-modal="false"
+      top="20px"
+      custom-class="prod-verify"
+      title="选择"
+    >
+      <CodeJson />
+    </el-dialog>
   </div>
 </template>
 
@@ -142,6 +156,7 @@ import sourceData from "./components/SourceData";
 import sourceAi from "./components/sourceAi/index.vue";
 import sourceEs from "./components/sourceEs/index.vue";
 import Variable from "./components/Variable";
+import CodeJson from "./components/codeJson";
 import elDragDialog from "@/directive/el-drag-dialog";
 /* import ChatBox from "@/components/ai.vue"; */
 import TemList from "../temList/temList.vue";
@@ -154,7 +169,8 @@ export default {
     sourceData,
     sourceAi,
     sourceEs,
-   /*  ChatBox, */
+    CodeJson,
+    /*  ChatBox, */
   },
   directives: { elDragDialog },
   props: {
@@ -167,15 +183,16 @@ export default {
     return {
       showDirectoryLevelDialog: false,
       directoryForm: {
-        level: 1
+        level: 1,
       },
+      codeJsonData: false,
       showFormula: false,
       showVariable: false,
       showSourceData: false,
       showSourceAi: false,
       showSourceEs: false,
       type: null,
-      typeMenu:null,
+      typeMenu: null,
       insertTitle: "插入变量",
       menus: [
         {
@@ -190,10 +207,11 @@ export default {
               key: "article",
               name: "图文",
               icon: "图文",
-              disabled: () =>this.typeMenu === "module" || this.typeMenu === undefined,
+              disabled: () =>
+                this.typeMenu === "module" || this.typeMenu === undefined,
             },
 
-           /*  {
+            /*  {
                 key:"SourceData",
                 name:"源数据",
                 icon:"sourceData",
@@ -216,7 +234,7 @@ export default {
             },
             {
               key: "attr",
-              name: "属性",
+              name: "产品",
               icon: "属性",
             },
             {
@@ -239,7 +257,7 @@ export default {
               name: "AI模块",
               icon: "AI",
             }, */
-             /* {
+            /* {
                 key:"searchEs",
                 name:"检索",
                 icon:"form",
@@ -345,7 +363,8 @@ export default {
     onClickMenus(e) {
       if (
         (this.typeMenu === "document" && e.key !== "ai") ||
-        (e.key === "article" && (this.typeMenu === "module" || this.typeMenu === undefined))
+        (e.key === "article" &&
+          (this.typeMenu === "module" || this.typeMenu === undefined))
       ) {
         return; // Exit the method if the item is disabled
       }
@@ -365,10 +384,13 @@ export default {
         this.showVariable = true;
       } else if (e.key == "ai") {
         this.showSourceAi = true;
-      } else if (e.key == "searchEs") {//aiEs
+      } else if (e.key == "searchEs") {
+        //aiEs
         this.showSourceEs = true;
-      } else  if (e.key === "Directory") {
+      } else if (e.key === "Directory") {
         this.showDirectoryLevelDialog = true;
+      } else if (e.key == "attr") {
+        this.codeJsonData = true;
       } else {
         this.$emit("onEvents", e);
       }
@@ -378,12 +400,12 @@ export default {
       this.$emit("onEvents", {
         type: "insert",
         key: "Directory",
-        content: { level: this.directoryForm.level }
+        content: { level: this.directoryForm.level },
       });
     },
     getIconPath(iconName) {
       return require(`@/icons/svg/${iconName}.svg`);
-    }
+    },
   },
 };
 </script>