Преглед на файлове

完善项目中的操作逻辑

yangg преди 7 месеца
родител
ревизия
af603d83f3

Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
dist/index.html


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
dist/static/css/chunk-6711f3a4.f7015c45.css


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
dist/static/css/chunk-7431502d.b95ec291.css


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
dist/static/css/chunk-77e7ad00.f7015c45.css


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
dist/static/js/app.f808da03.js


+ 0 - 0
dist/static/js/chunk-10b289e2.f2c74c38.js → dist/static/js/chunk-10b289e2.d06e3c4c.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
dist/static/js/chunk-3a588088.53b8fb2e.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
dist/static/js/chunk-6711f3a4.efb6e7d1.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
dist/static/js/chunk-7431502d.a0289a62.js


Файловите разлики са ограничени, защото са твърде много
+ 0 - 0
dist/static/js/chunk-77e7ad00.624a586e.js


+ 0 - 0
dist/static/js/chunk-d4c547ca.0a2a1daa.js → dist/static/js/chunk-d4c547ca.bfb382e5.js


+ 20 - 0
src/api/project.js

@@ -96,7 +96,27 @@ export function deleteProject(data) {
   })
 }
 
+/* 关联模版 */
 
+export function relate(data) {
+  return request({
+    url: '/project/relate',
+    method: 'post',
+    data
+  })
+}
+
+
+
+/* 获取绑定数据内容  template/searchTemplateList*/
+
+export function searchTemplateList(data) {
+  return request({
+    url: '/template/searchTemplateList',
+    method: 'post',
+    data
+  })
+}
 
 
 

+ 7 - 0
src/router/index.js

@@ -105,6 +105,13 @@ export const constantRoutes = [
     hidden:true,
     meta: { title: '创建文档',  noCache: true }
   },
+  {
+    path: '/document/viewCreate',
+    component: () => import('@/views/document/viewCreate'),
+    name: 'documentViewCreate',
+    hidden:true,
+    meta: { title: '查看文档',  noCache: true }
+  },
   {
     path: '/infoList',
     component: () => import('@/views/knowledgeMenu/category/infoList'),

+ 2379 - 0
src/views/document/viewCreate.vue

@@ -0,0 +1,2379 @@
+<template>
+    <div class="doc-container">
+      <div class="editor-top">
+        <div class="left">
+          <div class="doc" v-if="type == 'document'">
+            <span>当前打开文档:</span
+            >{{ docAttr.title == "" ? "新建文档" : docAttr.title }}
+          </div>
+          <div class="doc" v-else-if="type == 'module'">
+            <span>当前打开模块:</span
+            >{{ coms[0].name == "" ? "新建模块" : coms[0].name }}
+          </div>
+          <div class="doc" v-else>
+            <span>当前打开模版:</span
+            >{{ docAttr.title == "" ? "新建模版" : docAttr.title }}
+          </div>
+          <div class="creator" v-if="userInfo != null">
+            <span>创建者:</span>{{ userInfo && userInfo.username }}
+          </div>
+        </div>
+        <div class="right">
+          <el-button
+            type="primary"
+            size="mini"
+            @click="onSaveAs"
+            v-if="
+              type !== 'module' && type == 'document' && articleId == undefined
+            "
+            >保存文档</el-button
+          ><!-- v-if="docAttr.id > 0" -->
+          <el-button
+            type="primary"
+            size="mini"
+            @click="renew"
+            v-if="
+              articleId !== undefined && type !== 'module' && type == 'document'
+            "
+            >更新文档</el-button
+          >
+          <!-- <el-popover
+              placement="bottom"
+              trigger="click"
+              v-if="type !== 'module' && type !== 'document'"
+            > -->
+          <el-button
+            type="primary"
+            v-if="
+              $store.state.user.name == 'admin' &&
+              type !== 'module' &&
+              type !== 'document' &&
+              !articleId
+            "
+            @click="onSaveUpload"
+            >保存</el-button
+          >
+          <!-- <el-button
+              type="primary"
+              v-if="
+                $store.state.user.name == 'admin' &&
+                type !== 'module' &&
+                type !== 'document' &&
+                articleId
+              "
+              @click="onUpload"
+              >更新</el-button
+            > -->
+          <!-- <el-button
+                slot="reference"
+                type="primary"
+                size="mini"
+                style="margin: 0 10px"
+                v-if="$store.state.user.name == 'admin'"
+                >保存并更新模版</el-button
+              >
+            </el-popover> -->
+          <!-- <el-button
+              type="primary"
+              size="mini"
+              v-if="$store.state.user.name == 'admin'"
+              @click="onSaveUpload"
+              >保存并更新模版</el-button v-if="type !== 'module' && type == 'document'"
+            > -->
+          <el-button
+            type="primary"
+            size="mini"
+           
+            @click="onOpenView"
+            >预览导出
+          </el-button>
+          <el-button type="primary" size="mini" @click="onClose">
+            关闭文档
+          </el-button>
+        </div>
+      </div>
+      <div class="editor-content">
+        <div class="neary-doc">
+          <div class="left-content">
+            <!--  <div class="search">
+                <el-input placeholder="搜索文档标题"></el-input>
+              </div> -->
+            <div class="new-article">
+              <div v-if="showView == 0 && type !== 'module'">
+                <el-collapse v-model="activeNames">
+                  <el-collapse-item
+                    title="最新文档"
+                    name="-1"
+                    v-if="type == 'document'"
+                  >
+                    <div
+                      v-for="(it, index) in articleList"
+                      draggable
+                      :key="it.id"
+                      :data-index="index"
+                      data-type="article"
+                      class="sub-menus"
+                    >
+                      <div
+                        @click="onLoadArticle(it.id)"
+                        :title="it.title"
+                        style="
+                          cursor: pointer;
+                          white-space: nowrap;
+                          overflow: hidden; /* 隐藏超出部分 */
+                          text-overflow: ellipsis; /* 显示省略号 */
+                          max-width: 100%;
+                        "
+                      >
+                        {{ it.title }}
+                      </div>
+                    </div>
+                  </el-collapse-item>
+                  <el-collapse-item
+                    title="模版列表"
+                    name="-2"
+                    v-if="showView == 0"
+                  >
+                    <div
+                      v-for="(it, index) in templateList"
+                      draggable
+                      :key="it.id"
+                      :data-index="index"
+                      data-type="article"
+                      class="sub-menus"
+                    >
+                      <div
+                        @click="onTemplateInfo(it.id)"
+                        :title="it.title"
+                        style="
+                          cursor: pointer;
+                          white-space: nowrap;
+                          overflow: hidden; /* 隐藏超出部分 */
+                          text-overflow: ellipsis; /* 显示省略号 */
+                          width: 100%;
+                        "
+                      >
+                        {{ it.title }}
+                      </div>
+                    </div>
+                  </el-collapse-item>
+                </el-collapse>
+              </div>
+              <div v-if="showView == 1" class="scrollable-container">
+                <!-- <el-collapse
+                    v-model="activeModel"
+                    :accordion="true"
+                    @change="handleItemClick"
+                    class="borderless-collapse"
+                  >
+                    <el-collapse-item
+                      v-for="(item, itemIndex) in categoryList"
+                      :key="itemIndex"
+                      :title="item.name"
+                      :name="item.id"
+                    >
+                      <el-collapse
+                        v-if="item.children && item.children.length"
+                        v-model="item.activeChildren"
+                        @change="handleItemVal"
+                        v-show="item.children"
+                        class="borderless-collapse"
+                      >
+                        <el-collapse-item
+                          v-for="(child, childIndex) in item.children"
+                          :key="childIndex"
+                          :title="child.name"
+                          :name="child.id"
+                        >
+                          <draggable
+                            v-model="child.dataList"
+                            :options="{
+                              group: { name: 'itxst', pull: 'clone' },
+                              sort: true,
+                            }"
+                            :disabled="type == 'module'"
+                          >
+                            <transition-group>
+                              <div
+                                class="sub-menus"
+                                v-for="(its, index) in child.dataList"
+                                :key="index"
+                              >
+                                <div>{{ its.name }}</div>
+                              </div>
+                            </transition-group>
+                          </draggable>
+                        </el-collapse-item>
+                      </el-collapse>
+                      <draggable
+                        v-model="item.dataList"
+                        :options="{
+                          group: { name: 'itxst', pull: 'clone' },
+                          sort: true,
+                        }"
+                        :disabled="type == 'module'"
+                      >
+                        <transition-group>
+                          <div
+                            class="sub-menus"
+                            v-for="(it, index) in item.dataList"
+                            :key="index"
+                          >
+                            <div>{{ it.name }}</div>
+                          </div>
+                        </transition-group>
+                      </draggable>
+                    </el-collapse-item>
+                  </el-collapse> -->
+                <el-collapse
+                  v-model="activeModel"
+                  :accordion="true"
+                  @change="handleItemClick"
+                  class="borderless-collapse"
+                >
+                  <el-collapse-item
+                    v-for="(item, itemIndex) in categoryList"
+                    :key="itemIndex"
+                    :title="item.name"
+                    :name="item.id"
+                    class="min-width-content"
+                  >
+                    <el-collapse
+                      v-if="item.children && item.children.length"
+                      v-model="item.activeChildren"
+                      @change="handleItemVal"
+                      v-show="item.children"
+                      class="borderless-collapse"
+                    >
+                      <el-collapse-item
+                        v-for="(child, childIndex) in item.children"
+                        :key="childIndex"
+                        :title="child.name"
+                        :name="child.id"
+                        class="min-width-content"
+                      >
+                        <el-collapse
+                          v-if="child.children && child.children.length"
+                          v-model="child.activeChildren"
+                          @change="handleChildItemVal"
+                          v-show="child.children"
+                          class="borderless-collapse"
+                        >
+                          <el-collapse-item
+                            v-for="(
+                              grandChild, grandChildIndex
+                            ) in child.children"
+                            :key="grandChildIndex"
+                            :title="grandChild.name"
+                            :name="grandChild.id"
+                            class="min-width-content"
+                          >
+                            <el-collapse
+                              v-if="
+                                grandChild.children && grandChild.children.length
+                              "
+                              v-model="grandChild.activeChildren"
+                              @change="handleGrandChildItemVal"
+                              v-show="grandChild.children"
+                              class="borderless-collapse"
+                            >
+                              <el-collapse-item
+                                v-for="(
+                                  greatGrandChild, greatGrandChildIndex
+                                ) in grandChild.children"
+                                :key="greatGrandChildIndex"
+                                :title="greatGrandChild.name"
+                                :name="greatGrandChild.id"
+                                class="min-width-content"
+                              >
+                                <el-collapse
+                                  v-if="
+                                    greatGrandChild.children &&
+                                    greatGrandChild.children.length
+                                  "
+                                  v-model="greatGrandChild.activeChildren"
+                                  @change="handlegreatGrandChildItemVal"
+                                  v-show="greatGrandChild.children"
+                                  class="borderless-collapse"
+                                >
+                                  <el-collapse-item
+                                    v-for="(
+                                      fiveChild, fiveIndex
+                                    ) in greatGrandChild.children"
+                                    :key="fiveIndex"
+                                    :title="fiveChild.name"
+                                    :name="fiveChild.id"
+                                    class="min-width-content"
+                                  >
+                                    <draggable
+                                      v-model="fiveChild.dataList"
+                                      :options="{
+                                        group: { name: 'itxst', pull: 'clone' },
+                                        sort: true,
+                                      }"
+                                      :disabled="type === 'module'"
+                                    >
+                                      <transition-group>
+                                        <div
+                                          class="sub-menus min-width-content"
+                                          v-for="(
+                                            fiveIts, fiveIndex
+                                          ) in fiveChild.dataList"
+                                          :key="fiveIndex"
+                                        >
+                                          <div
+                                            :title="fiveIts.name"
+                                            style="
+                                              cursor: move;
+                                              white-space: nowrap;
+                                              overflow: hidden; /* 隐藏超出部分 */
+                                              text-overflow: ellipsis; /* 显示省略号 */
+                                              width: 100%;
+                                            "
+                                          >
+                                            {{ fiveIts.name }}
+                                          </div>
+                                        </div>
+                                      </transition-group>
+                                    </draggable>
+                                  </el-collapse-item>
+                                </el-collapse>
+                                <draggable
+                                  v-model="greatGrandChild.dataList"
+                                  :options="{
+                                    group: { name: 'itxst', pull: 'clone' },
+                                    sort: true,
+                                  }"
+                                  :disabled="type == 'module'"
+                                >
+                                  <transition-group>
+                                    <div
+                                      class="sub-menus min-width-content"
+                                      v-for="(
+                                        its, index
+                                      ) in greatGrandChild.dataList"
+                                      :key="index"
+                                    >
+                                      <div
+                                        :title="its.name"
+                                        style="
+                                          cursor: move;
+                                          white-space: nowrap;
+                                          overflow: hidden; /* 隐藏超出部分 */
+                                          text-overflow: ellipsis; /* 显示省略号 */
+                                          width: 100%;
+                                        "
+                                      >
+                                        {{ its.name }}
+                                      </div>
+                                    </div>
+                                  </transition-group>
+                                </draggable>
+                              </el-collapse-item>
+                            </el-collapse>
+                            <draggable
+                              v-model="grandChild.dataList"
+                              :options="{
+                                group: { name: 'itxst', pull: 'clone' },
+                                sort: true,
+                              }"
+                              :disabled="type == 'module'"
+                            >
+                              <transition-group>
+                                <div
+                                  class="sub-menus min-width-content"
+                                  v-for="(it, index) in grandChild.dataList"
+                                  :key="index"
+                                >
+                                  <div
+                                    :title="it.name"
+                                    style="
+                                      cursor: move;
+                                      white-space: nowrap;
+                                      overflow: hidden; /* 隐藏超出部分 */
+                                      text-overflow: ellipsis; /* 显示省略号 */
+                                      width: 100%;
+                                    "
+                                  >
+                                    {{ it.name }}
+                                  </div>
+                                </div>
+                              </transition-group>
+                            </draggable>
+                          </el-collapse-item>
+                        </el-collapse>
+                        <draggable
+                          v-model="child.dataList"
+                          :options="{
+                            group: { name: 'itxst', pull: 'clone' },
+                            sort: true,
+                          }"
+                          :disabled="type == 'module'"
+                        >
+                          <transition-group>
+                            <div
+                              class="sub-menus min-width-content"
+                              v-for="(it, index) in child.dataList"
+                              :key="index"
+                            >
+                              <div
+                                :title="it.name"
+                                style="
+                                  cursor: move;
+                                  white-space: nowrap;
+                                  overflow: hidden; /* 隐藏超出部分 */
+                                  text-overflow: ellipsis; /* 显示省略号 */
+                                  width: 100%;
+                                "
+                              >
+                                {{ it.name }}
+                              </div>
+                            </div>
+                          </transition-group>
+                        </draggable>
+                      </el-collapse-item>
+                    </el-collapse>
+                    <draggable
+                      v-model="item.dataList"
+                      :options="{
+                        group: { name: 'itxst', pull: 'clone' },
+                        sort: true,
+                      }"
+                      :disabled="type == 'module'"
+                    >
+                      <transition-group>
+                        <div
+                          class="sub-menus min-width-content"
+                          v-for="(it, index) in item.dataList"
+                          :key="index"
+                        >
+                          <div
+                            :title="it.name"
+                            style="
+                              cursor: move;
+                              white-space: nowrap;
+                              overflow: hidden; /* 隐藏超出部分 */
+                              text-overflow: ellipsis; /* 显示省略号 */
+                              width: 100%;
+                            "
+                          >
+                            {{ it.name }}
+                          </div>
+                        </div>
+                      </transition-group>
+                    </draggable>
+                  </el-collapse-item>
+                </el-collapse>
+              </div>
+            </div>
+          </div>
+          <div class="right-sidebarew">
+            <div
+              v-if="type !== 'module'"
+              class="sidebar-item"
+              :class="showView == 0 ? 'active-item' : ''"
+              @click="viewDocument"
+            >
+              <span v-if="type == 'document'">查看文档</span>
+              <span v-else>查看模版</span>
+            </div>
+            <div
+              v-if="type !== 'document'"
+              class="sidebar-item"
+              :class="showView == 1 ? 'active-item' : ''"
+              @click="viewModule"
+            >
+              模块列表
+            </div>
+          </div>
+        </div>
+  
+        <div class="editor-main">
+          <div class="menus-box">
+            <menus
+              @onEvents="onInsert"
+              @onVariable="uptadeVariable"
+              :comArr="coms"
+            ></menus>
+          </div>
+          <div
+            class="editor-box"
+            ref="tabHtml"
+            v-loading="loading"
+            element-loading-text="加载中..."
+            element-loading-spinner="el-icon-loading"
+          >
+            <editor
+              :coms="coms"
+              :comIndex="comIndex"
+              :isAdmin="isAdmin"
+              :templateCate="categoryList"
+              :insertCmd="insertCmd"
+              @onSetActiveIndex="onSetActiveIndex"
+              @onInsert="onInsert"
+              @onLoadArticle="onLoadArticle"
+              @onRebuild="onRebuild"
+              @onDelete="onRemove"
+              @onSetComs="onSetComs"
+            ></editor>
+          </div>
+        </div>
+  
+        <div class="editor-option">
+          <div class="editor-menu">
+            <div
+              v-if="type !== 'module' && type !== 'document'"
+              class="item"
+              @click="onSetActiveIndexs(3)"
+              :class="docuComIndex == 3 ? 'active-item' : ''"
+            >
+              <span class="name">模版属性</span>
+            </div>
+            <div
+              v-if="type !== 'module'"
+              class="item"
+              @click="onSetActiveIndex(9998)"
+              :class="comIndex == 9998 ? 'active-item' : ''"
+            >
+              <span class="name">模块管理</span>
+            </div>
+            <div
+              class="item"
+              @click="onCatalogIndex(9997)"
+              :class="catalogIndex == 9997 ? 'active-item' : ''"
+            >
+              <span class="name">目录信息</span>
+            </div>
+            <div
+              class="item"
+              @click="onAttributeIndex(1)"
+              :class="AttributeIndex == 1 ? 'active-item' : ''"
+            >
+              <span class="name">属性变量</span>
+            </div>
+          </div>
+  
+          <template
+            v-if="docuComIndex == 3 && type !== 'module' && type !== 'document'"
+          >
+            <docAttr :attrs="docAttr"></docAttr>
+          </template>
+          <template v-if="comIndex == 9998">
+            <comList
+              :coms="coms"
+              @onRemove="onRemove"
+              @onSetActive="onSetActiveIndex"
+              @onRebuild="onRebuild"
+            ></comList>
+          </template>
+          <template v-if="catalogIndex == 9997">
+            <Directory :coms="coms" />
+          </template>
+          <!-- comIndex != 9999 && comIndex != 9998 && comIndex != 9997 AttributeIndex==1-->
+          <!-- <template
+              v-if="
+                comIndex != 9999 &&
+                comIndex != 9998 &&
+                catalogIndex != 9997 &&
+                AttributeIndex != 1
+              "
+            >
+              <Attribute
+                :com="coms[comIndex]"
+                @onRefresh="initCategoryList"
+              ></Attribute>
+            </template> -->
+          <template v-if="AttributeIndex == 1">
+            <attributeVar
+              :com="coms[comIndex]"
+              @onRefresh="initCategoryList"
+            ></attributeVar>
+          </template>
+          <!-- <Directory :coms="coms" /> -->
+        </div>
+      </div>
+  
+      <el-dialog
+        :visible.sync="showViewForm"
+        :close-on-click-modal="false"
+        append-to-body
+        v-el-drag-dialog
+        width="850px"
+        custom-class="prod-verify"
+        title="文档预览"
+      >
+        <ViewForm :coms="coms" :docAttr="docAttr"></ViewForm>
+      </el-dialog>
+  
+      <el-dialog
+        :visible.sync="showProductAttr"
+        append-to-body
+        v-el-drag-dialog
+        :close-on-click-modal="false"
+        width="850px"
+        custom-class="prod-verify"
+        title="插入产品属性"
+      >
+        <ProductAttr
+          :docAttr="docAttr"
+          @onInsertProductAttr="onInsertProductAttr"
+        ></ProductAttr>
+      </el-dialog>
+      <el-dialog
+        :visible.sync="docVisible"
+        append-to-body
+        v-el-drag-dialog
+        :close-on-click-modal="false"
+        width="500px"
+        custom-class="prod-verify"
+        title="文档信息"
+      >
+        <el-form
+          :model="docForm"
+          :rules="docRules"
+          ref="docRef"
+          label-position="left"
+          label-width="100"
+        >
+          <el-form-item label="文档名称:" prop="title">
+            <el-input
+              v-model="docForm.title"
+              style="width: 60%"
+              class="input-item"
+              placeholder="请填写文档名称"
+              :disabled="docAttr.id > 0"
+            />
+          </el-form-item>
+  
+          <el-form-item label="文档分类:" prop="category_id">
+            <el-select
+              v-model="docForm.category_id"
+              style="width: 60%"
+              placeholder="请选择所属分类"
+              :disabled="docAttr.id > 0"
+            >
+              <el-option
+                v-for="(item, index) in articleCategoryList"
+                :key="index"
+                :label="item.name"
+                :value="item.id"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="创建人:">
+            {{ userInfo && userInfo.username }}
+          </el-form-item>
+        </el-form>
+        <span slot="footer" class="dialog-footer">
+          <el-button @click="closeDoc">取 消</el-button>
+          <el-button type="primary" @click="submitDoc">确 定</el-button>
+        </span>
+      </el-dialog>
+    </div>
+  </template>
+    
+    <script>
+  import { findData } from "@/api/sourceData";
+  import utils from "@/utils/fun"; // 方法类
+  import { searchDocument, pageDocument } from "@/api/document";
+  import menus from "./com/menus";
+  import editor from "./com/editor";
+  import Attribute from "./com/components/Attribute";
+  import attributeVar from "./com/components/Attribute/attributeVar/index.vue";
+  import ProductAttr from "./com/components/ProductAttr";
+  import {
+    searchTemplateCategory,
+    searchTemplate,
+    getTemplateInfo,
+    getAllCategory,
+  } from "@/api/template";
+  import docAttr from "./com/components/Attribute/docAttr";
+  import comList from "./com/components/Attribute/comList";
+  import Directory from "./com/components/Attribute/Directory";
+  import { getCurrentUserInfo } from "@/api/api";
+  import {
+    createDocument,
+    updateDocument,
+    getDocumentInfo,
+    exportDocument,
+    searchDocumentCategory,
+  } from "@/api/document";
+  import ViewForm from "./com/view";
+  import elDragDialog from "@/directive/el-drag-dialog";
+  import draggable from "vuedraggable";
+  import htmlDocx from "html-docx-js/dist/html-docx";
+  import { searchFormula } from "@/api/formula";
+  import axios from "axios";
+  import settings from "@/settings";
+  // import htmlDocx from "htmlDocx";
+  export default {
+    name: "create",
+    components: {
+      menus,
+      editor,
+      Attribute,
+      comList,
+      docAttr,
+      ViewForm,
+      ProductAttr,
+      Directory,
+      draggable,
+      attributeVar,
+    },
+    directives: {
+      elDragDialog,
+    },
+    data() {
+      return {
+        activeNames: "-1",
+        nestedActiveNames: [],
+        articleList: [],
+        activeModel: "",
+        templateList: [],
+        categoryList: [],
+        //组件相关
+        coms: [],
+        comIndex: 9999, //当前组件索引
+        showProductAttr: false,
+        comList: [],
+        //文档属性
+        docAttr: {
+          id: 0,
+          category_id: "",
+          title: "",
+          content: "",
+          status: 5,
+          links: "",
+          linkProduct: [],
+          linkProject: [],
+          projects: "",
+        },
+        insertCmd: null,
+        showViewForm: false,
+        isAdmin: 2, //1是管理员,2非管理员
+        //当前选中表格
+        tabExt: {
+          row: 0,
+          col: 0,
+        },
+        userInfo: null,
+        articleId: 0,
+        loading: false, // 增加 loading 状态
+        showView: 0, //查看 文档/模块
+        childrenList: "",
+        value: [],
+        AttributeIndex: 0,
+        catalogIndex: 0, //目录信息
+        docuComIndex: 3,
+        type: "",
+        docVisible: false,
+        docForm: {
+          title: "",
+          category_id: "",
+        },
+        docRules: {
+          title: [{ required: true, message: "请输文档名称", trigger: "blur" }],
+          category_id: [
+            { required: true, message: "请选择文档分类", trigger: "change" },
+          ],
+        },
+        articleCategoryList: [],
+      };
+    },
+    watch: {
+      coms: {
+        handler(newComs, oldComs) {
+          this.updateAttrs(newComs, oldComs);
+        },
+        immediate: true, //立即执行
+        deep: true,
+      },
+      articleId: {
+        handler(val) {
+          if (val == null || val == undefined || val <= 0) return;
+          if (this.$route.query.type == "document") {
+            this.loadArticle(val);
+          } else {
+            this.onTemplateInfo(val);
+          }
+          //
+        },
+        immediate: true,
+        deep: true,
+      },
+    },
+  
+    async mounted() {
+      console.log(this.$store.state.user);
+      this.articleId = this.$route.query.articleId;
+      this.type = this.$route.query.type;
+      if (this.type == "module") {
+        this.showView = 1;
+      }
+  
+      this.searchArticle();
+      await this.initCategoryList();
+      await this.initCurrentUser();
+      let templateId = this.$route.query.templateId;
+      if (templateId > 0) {
+        this.loadTemplateInfo(templateId);
+      }
+      this.isAdmin = this.userInfo ? this.userInfo.roleInfo.is_admin : 2;
+      this.initArticleCategoryList();
+      if (this.type !== "module" && this.type !== "document") {
+        document.title = "新建模版-" + settings.title;
+      }
+      if (
+        this.type !== "module" &&
+        this.type !== "document" &&
+        this.articleId !== undefined
+      ) {
+        document.title = this.docAttr.title + settings.title;
+      }
+      this.$nextTick(() => {
+        const arrows = document.querySelectorAll(".el-collapse-item__arrow");
+        arrows.forEach((arrow) => {
+          arrow.style.margin = "0";
+        });
+      });
+    },
+  
+    methods: {
+      handleItemVal(value) {
+        this.$forceUpdate();
+        this.loadChildren(value);
+      },
+      handleChildItemClick(child) {
+        if (!child.children) {
+          this.loadChildren(child);
+        }
+      },
+      handleChildItemVal(value) {
+        this.$forceUpdate();
+        this.loadChildren(value);
+      },
+      handleGrandChildItemClick(grandChild) {
+        if (!grandChild.children) {
+          this.loadChildren(grandChild);
+        }
+      },
+      handleGrandChildItemVal(value) {
+        this.loadChildren(value);
+        this.$forceUpdate();
+      },
+      handlegreatGrandChildItemVal(greatGrandChild) {
+        this.loadChildren(greatGrandChild);
+        this.$forceUpdate();
+      },
+      async loadChildren(category) {
+        const category_id = category[category.length - 1];
+  
+        try {
+          const res = await searchTemplateCategory({ parent_id: category_id });
+          if (res.status != 200 || res.data.length == 0) return;
+          const newChildren = res.data;
+          for (let i = 0; i < newChildren.length; i++) {
+            newChildren[i].dataList = await this.getTemplateList(
+              newChildren[i].id
+            );
+          }
+          this.insertChildrenById(this.categoryList, category_id, newChildren);
+          console.log(this.categoryList);
+          this.$forceUpdate(); // Force Vue to re-render the component
+        } catch (error) {
+          console.error("Error fetching template categories:", error);
+        }
+      },
+  
+      insertChildrenById(list, id, newChildren) {
+        for (let item of list) {
+          if (item.id === id) {
+            this.$set(item, "children", newChildren); // Use Vue.set to ensure reactivity
+            return true; // Stop searching once the item is found
+          }
+          if (item.children && item.children.length > 0) {
+            const found = this.insertChildrenById(item.children, id, newChildren);
+            if (found) return true; // Stop searching if the item is found in deeper levels
+          }
+        }
+        return false; // Continue searching
+      },
+      /* 取消新增 */
+      closeDoc() {
+        this.docVisible = false;
+        this.docForm = {
+          title: "",
+          category_id: "",
+        };
+      },
+      /* 确定新增模块*/
+      submitDoc() {
+        this.$refs.docRef.validate((valid) => {
+          if (valid) {
+            this.docAttr.title = this.docForm.title;
+            this.docAttr.category_id = this.docForm.category_id;
+            this.onSave();
+          }
+        });
+      },
+      //加载文档分类
+      initArticleCategoryList() {
+        let _this = this;
+        searchDocumentCategory({ page: 1, pageSize: 99, status: 5 }).then(
+          (res) => {
+            if (res.status != 200) return;
+            _this.articleCategoryList = res.data.dataList;
+          }
+        );
+      },
+      updateAttrs(newComs, oldComs) {
+        console.log(newComs);
+        newComs.forEach((newCom, comIndex) => {
+          const oldCom = oldComs[comIndex];
+          if (oldCom) {
+            newCom.attrs.forEach((newAttr, attrIndex) => {
+              const oldAttr = oldCom.attrs[attrIndex];
+              if (oldAttr && newAttr.content !== oldAttr.content) {
+                // 内容发生变化,进行全局更新
+                this.updateGlobalAttr(newAttr.name, newAttr.content);
+              }
+            });
+          }
+        });
+      },
+  
+      updateGlobalAttr(attrId, newContent) {
+        // 遍历所有组件,更新匹配的属性
+        this.coms.forEach((com) => {
+          com.attrs.forEach((attr) => {
+            // 只更新非 variableNull 类型的属性
+            if (attr.name === attrId && attr.type !== "variableNull") {
+              attr.content = newContent;
+            }
+          });
+  
+          // 如果是文本区域,更新内容中的占位符
+          if (com.type === "TextArea") {
+            com.content = com.content.replace(
+              new RegExp(`{{${attrId}}}`, "g"),
+              (match) => {
+                const attr = com.attrs.find((a) => a.id === attrId);
+                if (attr && attr.type !== "variableNull") {
+                  return newContent;
+                }
+                return match; // 保留 variableNull 类型的原始占位符
+              }
+            );
+          }
+        });
+  
+        // 触发视图更新
+        this.replaceData(this.coms);
+        this.$forceUpdate();
+      },
+  
+      async replaceData(data) {
+        for (let item of this.coms) {
+          for (let el of item.attrs) {
+            let attrId = el.id;
+            if (el.type === "formual") {
+              try {
+                let formual = await this.analysisFormual(el);
+                formual = await this.getRemote(formual);
+                el.content = eval(formual);
+              } catch (error) {
+                console.error("处理公式时出错:", error);
+              }
+            } else if (el.type === "variableNull") {
+              // 保留 variableNull 类型的原始内容
+              el.content = el.content || "请输入";
+            }
+          }
+        }
+        return data;
+      },
+      //分析公式
+      async analysisFormual(attrs) {
+        let _this = this;
+        // console.log("开始分析公式:", attrs.formula);
+        var pattern = /(\[.*?\]){3}/;
+        var formual = attrs.formula;
+        var reg = new RegExp(pattern);
+        while (true) {
+          var items = formual.match(reg);
+          if (items == null) break;
+          let itemName = items[0];
+          // console.log("处理项:", itemName);
+          try {
+            let data = await _this.getFormualData(itemName);
+            // console.log(`获取到的数据: ${itemName} = ${data}`);
+            if (data === null || data === undefined || isNaN(data)) {
+              console.warn(`获取到的数据无效: ${itemName}`);
+              formual = formual.replace(itemName, "(0)");
+            } else {
+              formual = formual.replace(itemName, `(${parseFloat(data)})`);
+            }
+          } catch (error) {
+            console.error(`处理 ${itemName} 时出错:`, error);
+            formual = formual.replace(itemName, "(0)");
+          }
+        }
+        // console.log("分析后的公式:", formual);
+        return formual;
+      },
+      async getFormualData(formualItem) {
+        let _this = this;
+        // console.log("开始获取公式数据:", formualItem);
+        var pattern = /\[(.*?)\]\[(.*?)\]\[(.*?)\]/;
+        var reg = new RegExp(pattern);
+        let items = formualItem.match(reg);
+        // console.log("解析结果:", items);
+        let calResult = 0;
+        if (items && items[1] == "T") {
+          calResult = await _this.getModuleData(items[2], items[3]);
+        } else {
+          console.warn("无法解析公式项:", formualItem);
+        }
+        // console.log("计算结果:", calResult);
+        return calResult;
+      },
+      //获取本地文档模块数据
+      async getModuleData(code, attrName) {
+        let _this = this;
+        // console.log(`尝试获取模块数据: code=${code}, attrName=${attrName}`);
+        // console.log("当前 coms:", _this.coms);
+  
+        let item = _this.coms.filter((o) => o.name == code);
+        // console.log(`找到的模块:`, item);
+  
+        if (item.length > 0) {
+          let attr = item[0].attrs.filter((o) => o.name == attrName);
+          // console.log(`找到的属性:`, attr);
+  
+          if (attr.length > 0) {
+            // console.log(`返回的值:`, attr[0].content);
+            return parseFloat(attr[0].content);
+          }
+        }
+        // console.log("未找到匹配的模块或属性,返回 0");
+        return 0;
+      },
+  
+      async getRemote(formual) {
+        let _this = this;
+        var pattern = /\[(.*?)\]\[(.*?)\]\[(.*?)\]\[(.*?)\]/;
+        var reg = new RegExp(pattern);
+        while (true) {
+          let items = formual.match(reg);
+          if (items == null) break;
+          if (items[1] == "R") {
+            //表示获取远程数据
+            var dataIndex = items[4];
+            dataIndex = items[4].split(",");
+            let calResult = await _this.getRemoteData(
+              items[2],
+              items[3],
+              dataIndex[0],
+              dataIndex[1]
+            );
+            let itemName = items[0];
+            // 检查calResult是否为数字
+            if (!isNaN(calResult)) {
+              formual = formual.replace(
+                itemName,
+                "(" + parseFloat(calResult) + ")"
+              );
+            } else {
+              formual = formual.replace(itemName, `"${calResult}"`);
+            }
+            formual = formual.replace(
+              itemName,
+              "(" + parseFloat(calResult) + ")"
+            );
+          }
+        }
+        return formual;
+      },
+      async getRemoteData(code, sheet, row, col) {
+        let _this = this;
+        let result = 0;
+        let par = {
+          code: code,
+          sheetName: sheet,
+          row: row,
+          col: col,
+        };
+  
+        /*  if (!this.isEditing) {
+            this.loading = true;
+            this.progress = 0;
+    
+            try {
+              const updateProgress = setInterval(() => {
+                if (this.progress < 90) {
+                  this.progress += 10;
+                }
+              }, 100);
+    
+              let res = await findData(par);
+              if (res.status == 200) {
+                result = res.data.result;
+              }
+    
+              this.progress = 100;
+              clearInterval(updateProgress);
+            } catch (error) {
+              console.error("Error fetching remote data:", error);
+            } finally {
+              setTimeout(() => {
+                this.loading = false;
+                this.progress = 0;
+              }, 1000);
+            }
+          } else { */
+        // 如果正在编辑,直接获取数据而不显示进度条
+        try {
+          let res = await findData(par);
+          if (res.status == 200) {
+            result = res.data.result;
+            // 添加数据类型检查和转换
+            if (typeof result === "string" && !isNaN(result)) {
+              result = parseFloat(result);
+            }
+          }
+        } catch (error) {
+          console.error("获取远程数据错误:", error);
+        }
+        /*   } */
+  
+        return result;
+      },
+      /* 
+        handleItemVal(value) {
+          console.log(value);
+          this.$forceUpdate();
+        }, */
+      async handleItemClick(item) {
+        const category = this.categoryList.find((cat) => cat.id === item);
+        if (!category) return;
+  
+        if (!category.children) {
+          try {
+            const res = await searchTemplateCategory({ parent_id: item });
+            if (res.status != 200 || res.data.length == 0) return;
+            category.children = res.data;
+            for (let i = 0; i < category.children.length; i++) {
+              category.children[i].dataList = await this.getTemplateList(
+                category.children[i].id
+              );
+            }
+          } catch (error) {
+            console.error("Error fetching template categories:", error);
+          }
+        }
+  
+        // 触发视图更新
+        this.$set(category, "activeChildren", category.activeChildren || []);
+      },
+      /* 切换标签 */
+      viewDocument() {
+        this.showView = 0;
+      },
+      viewModule() {
+        this.showView = 1;
+      },
+      /* 更新变量 */
+      /*  uptadeVariable(value) {
+          for (const item of this.coms) {
+            for (const el of item.attrs) {
+              if (el.type === "variable") {
+                if (el.data.id == value.id) {
+                  el.content = value.value;
+                  el.data = value;
+                }
+              } else if (el.type === "constant") {
+                if (el.data.id == value.id) {
+                  el.content = value.value;
+                  el.data = value;
+                }
+              }
+            }
+          }
+        } */
+      uptadeVariable(value) {
+        const { id, value: newValue } = value;
+        this.coms.forEach((item) => {
+          item.attrs.forEach((el) => {
+            if (
+              (el.type === "variable" || el.type === "constant") &&
+              el.data.id === id
+            ) {
+              el.content = newValue;
+              el.data = value;
+            }
+          });
+        });
+      },
+      /* 更新检索内容 */
+      async uptadeSearch() {
+        for (const item of this.coms) {
+          for (const el of item.attrs) {
+            if (el.type === "ai") {
+              // await this.retrieval(el);
+            }
+          }
+        }
+      },
+      /* 初始化检索 */
+      async retrieval(el) {
+        this.loading = true; // 开始加载动画
+        try {
+          const response = await axios.post(
+            "http://58.246.234.210:7860/api/v1/run/3f84a841-cefd-44b3-9555-568cc3b6c2d2?stream=false",
+            {
+              input_value: el.search,
+              output_type: "chat",
+              input_type: "chat",
+              tweaks: {
+                "ChatInput-em6qC": {},
+                "ParseData-yO3YQ": {},
+                "Prompt-Wj75b": {},
+                "ChatOutput-zy9na": {},
+                "SplitText-O1knk": {},
+                "File-4j6Zd": {},
+                "OllamaEmbeddings-xbYXX": {},
+                "Chroma-OIejP": {},
+                "OllamaModel-04mEO": {},
+                "OllamaEmbeddings-UGvLP": {},
+                "Chroma-HzukO": {},
+              },
+            },
+            {
+              headers: {
+                "Content-Type": "application/json",
+              },
+            }
+          );
+  
+          if (response.status === 200) {
+            // 假设返回的数据在 response.data.outputs[0].outputs[0].results.message.data.text 中
+            const resultText =
+              response.data.outputs[0].outputs[0].results.message.data.text;
+            // 将搜索词和生成的内容存储在 searchResult 对象中
+            el.content = resultText; // 重新赋值给原数组的对应 content
+          }
+        } catch (error) {
+          console.error("Error during retrieval:", error);
+          this.$message.error("检索失败,请稍后重试");
+        } finally {
+          this.loading = false; // 结束加载动画
+        }
+      },
+      async loadTemplateInfo(id) {
+        try {
+          // 获取模板信息
+          const { data } = await getTemplateInfo({ id });
+  
+          // 设置默认值
+          if (!data.attrs || !data.type) {
+            Object.assign(data, {
+              attrs: "[]",
+              content: "请填写内容",
+              lay_id: "textArea",
+              type: "TextArea",
+            });
+          }
+  
+          // 设置文档标题
+          document.title = data.name
+            ? `${data.name}-${settings.title}`
+            : "新建模块-" + settings.title;
+  
+          // 解析属性
+          data.attrs = JSON.parse(data.attrs);
+  
+          // 获取公式并更新属性
+          const {
+            data: { dataList },
+          } = await searchFormula({ page: 1, pageSize: 999 });
+          console.log(data);
+          data.attrs = data.attrs.map((item) => {
+            const formula = dataList.find((el) => el.id === item.id);
+            return formula
+              ? { ...item, formula: formula.formula, data: formula }
+              : item;
+          });
+          // 从 content 中提取 {{}} 包裹的 ID
+          const contentIds = (data.content.match(/{{([^}]+)}}/g) || []).map(
+            (match) => match.slice(2, -2).trim()
+          );
+  
+          // 过滤 attrs,只保留在 content 中出现的 ID
+          data.attrs = data.attrs.filter((item) => contentIds.includes(item.id));
+  
+          this.coms = [data];
+        } catch (error) {
+          console.error("加载模板信息失败:", error);
+        }
+      },
+      onExport(e) {
+        let _this = this;
+        _this.comIndex = -1;
+        let content = _this.$refs.tabHtml.innerHTML;
+      },
+  
+      onOpenView(e) {
+        console.log("this.coms", this.coms);
+        this.showViewForm = true;
+      },
+      onClose() {
+        /* this.$router.back(); */
+        window.close();
+      },
+  
+      loadArticle(e) {
+        let _this = this;
+        _this.onLoadArticle(e);
+        /* getDocumentInfo({
+            id: e,
+          }).then((res) => {
+            if (res.status != 200) return;
+            _this.onLoadArticle(res.data);
+          }); */
+      },
+  
+      handleDragStart(e) {
+        let data = {};
+        if (e.target.dataset.type == "article") {
+          let index = e.target.dataset.index;
+          data = {
+            optType: e.target.dataset.type,
+            item: this.articleList[index],
+          };
+        } else {
+          let index = e.target.dataset.index;
+          let subIndex = e.target.dataset.subIndex;
+          let item = this.categoryList[index].dataList[subIndex];
+          delete item.category;
+          data = {
+            ...item,
+            optType: e.target.dataset.type,
+            key: "addNew",
+            attrs: JSON.parse(item.attrs),
+          };
+        }
+        e.dataTransfer.setData("item", JSON.stringify(data));
+        this.operation = "drag";
+      },
+  
+      onRebuild(updatedComList) {
+        this.coms = [...updatedComList];
+        this.$nextTick(() => {
+          this.$forceUpdate();
+        });
+      },
+      //初始化当前用户信息
+      async initCurrentUser() {
+        let _this = this;
+        let res = await getCurrentUserInfo();
+        if (res.status != 200) return;
+        _this.userInfo = res.data;
+      },
+      //加载文档信息
+      async onLoadArticle(id) {
+        try {
+          this.loading = true;
+          const res = await getDocumentInfo({ id });
+  
+          if (res.status !== 200) {
+            throw new Error("Invalid response status");
+          }
+  
+          this.docAttr = {
+            id: res.data.id,
+            category_id: Number(res.data.category_id),
+            title: res.data.title,
+            content: "",
+            status: res.data.status,
+            is_template: res.data.is_template,
+            linkProduct: res.data.linkProduct,
+            links: res.data.links,
+            linkProject: res.data.linkProject,
+            projects: res.data.projects,
+          };
+  
+          const templateData =
+            typeof res.data.data === "string"
+              ? JSON.parse(res.data.data)
+              : res.data.data;
+  
+          const updatedComs = await Promise.all(
+            templateData.map(async (el) => {
+              /* let templateInfo = { data: {} };
+                if (this.type !== "document") {
+                  templateInfo = await getTemplateInfo({ id: el.id });
+                } */
+              const formulaData = await searchFormula({ page: 1, pageSize: 999 });
+  
+              const attrs = el.attrs;
+              /* typeof templateInfo.data.attrs === "string"
+                    ? JSON.parse(templateInfo.data.attrs)
+                    : templateInfo.data.attrs || []; // Add a fallback empty array */
+  
+              attrs.forEach((item) => {
+                if (item && item.data && item.data.id) {
+                  const matchedFormula = formulaData.data.dataList.find(
+                    (ite) => ite.id === item.data.id
+                  );
+                  if (matchedFormula) {
+                    item.data = matchedFormula;
+                    item.formula = matchedFormula.formula;
+                  }
+                }
+              });
+  
+              return { ...el /* , ...templateInfo.data, attrs */ };
+            })
+          );
+  
+          this.coms = updatedComs;
+          console.log("coms", updatedComs);
+  
+          if (this.type === "document" && this.articleId !== undefined) {
+            document.title = `${this.docAttr.title}-${settings.title}`;
+          }
+  
+          this.uptadeSearch();
+        } catch (error) {
+          console.error("Error in onLoadArticle:", error);
+        } finally {
+          this.loading = false;
+        }
+      },
+      /* 加载模版信息 */
+      async onTemplateInfo(id) {
+        try {
+          this.loading = true;
+          // 获取文档信息
+          const res = await getDocumentInfo({ id });
+  
+          if (res.status !== 200 || !res.data) {
+            throw new Error("无效的响应或未收到数据");
+          }
+  
+          // 设置文档属性
+          this.docAttr = {
+            id: this.docAttr.id,
+            category_id: Number(this.docAttr.category_id),
+            title: res.data.title,
+            content: "",
+            status: res.data.status,
+            is_template: res.data.is_template,
+            linkProduct: res.data.linkProduct || [],
+            links: res.data.links || "",
+            linkProject: res.data.linkProject || [],
+            projects: res.data.projects || "",
+          };
+  
+          // 解析文档数据
+          const documentData = res.data.data ? JSON.parse(res.data.data) : [];
+          console.log("解析后的文档数据:", documentData);
+  
+          // 获取公式数据
+          const formulaData = await searchFormula({ page: 1, pageSize: 999 });
+  
+          // 处理组件数据
+          const updatedComs = documentData.map((docEl) => {
+            if (!docEl || !docEl.id) {
+              console.warn("无效的文档项:", docEl);
+              return null;
+            }
+  
+            // 确保 attrs 是数组
+            let attrs = Array.isArray(docEl.attrs) ? docEl.attrs : [];
+            if (typeof docEl.attrs === "string") {
+              try {
+                attrs = JSON.parse(docEl.attrs);
+              } catch (e) {
+                console.warn("解析 attrs 失败:", e);
+                attrs = [];
+              }
+            }
+  
+            // 确保 content 是字符串
+            let content = docEl.content || "";
+            if (typeof content !== "string") {
+              content = String(content);
+            }
+  
+            // 处理目录项
+            const contentItems = (content.match(/{{([^}]+)}}/g) || []).map(
+              (match) => match.slice(2, -2)
+            );
+  
+            // 确保所有目录项存在
+            contentItems.forEach((itemId) => {
+              if (
+                itemId.startsWith("Directory_") &&
+                !attrs.some((attr) => attr.id === itemId)
+              ) {
+                const [, moduleIndex, level] = itemId.split("_");
+                attrs.push({
+                  type: "Directory",
+                  id: itemId,
+                  name: "目录信息",
+                  intro: "目录信息",
+                  number: "",
+                  content: "",
+                  level: parseInt(level) || 1,
+                  moduleIndex: parseInt(moduleIndex) || 0,
+                });
+              }
+            });
+  
+            // 处理公式数据
+            attrs = attrs.map((item) => {
+              if (item.type === "formual" && item.data && item.data.id) {
+                const matchedFormula = formulaData.data.dataList.find(
+                  (ite) => ite.id === item.data.id
+                );
+                if (matchedFormula) {
+                  return {
+                    ...item,
+                    data: matchedFormula,
+                    formula: matchedFormula.formula,
+                  };
+                }
+              }
+              return item;
+            });
+  
+            // 返回完整的组件数据结构
+            return {
+              id: docEl.id,
+              content: content,
+              attrs: attrs,
+              title: docEl.title || "",
+              type: docEl.type || "normal",
+              status: docEl.status || 1,
+              sort: docEl.sort || 0,
+              is_template: docEl.is_template || 0,
+              created_at: docEl.created_at,
+              updated_at: docEl.updated_at,
+            };
+          });
+  
+          // 过滤并设置组件
+          this.coms = updatedComs.filter(Boolean);
+          console.log("处理后的组件数据:", this.coms);
+  
+          if (this.articleId !== undefined) {
+            document.title = `${this.docAttr.title}-${settings.title}`;
+          }
+          this.uptadeSearch();
+        } catch (error) {
+          console.error("加载文档信息时出错:", error);
+          this.$message.error("加载文档信息时出错,请稍后重试");
+        } finally {
+          this.loading = false;
+        }
+      },
+      onRemove(index) {
+        this.coms.splice(index, 1);
+      },
+      /* 目录信息 */
+      onCatalogIndex(e) {
+        this.catalogIndex = e;
+        if (
+          (this.docuComIndex =
+            3 || this.comIndex == 9998 || this.AttributeIndex == 1)
+        ) {
+          this.comIndex = 0;
+          this.AttributeIndex = 0;
+          this.docuComIndex = 0;
+        }
+        this.$nextTick(() => {
+          this.$forceUpdate();
+        });
+      },
+      onSetActiveIndexs(e) {
+        this.docuComIndex = e;
+        if (
+          this.AttributeIndex == 1 ||
+          this.comIndex == 9998 ||
+          this.catalogIndex == 9997
+        ) {
+          this.catalogIndex = 0;
+          this.AttributeIndex = 0;
+          this.comIndex = 0;
+        }
+      },
+      //设置当前索引
+      onSetActiveIndex(e) {
+        this.comIndex = e;
+        if (
+          this.AttributeIndex == 1 ||
+          this.docuComIndex == 3 ||
+          this.catalogIndex == 9997
+        ) {
+          this.catalogIndex = 0;
+  
+          this.docuComIndex = 0;
+        }
+      },
+      /* 属性变量 */
+      onAttributeIndex(e) {
+        this.AttributeIndex = e;
+        if (
+          (this.docuComIndex =
+            3 || this.comIndex == 9998 || this.catalogIndex == 9997)
+        ) {
+          this.catalogIndex = 0;
+          this.comIndex = 0;
+          this.docuComIndex = 0;
+        }
+      },
+      /* 更新 */
+      renew() {
+        let _this = this;
+        _this.docVisible = true;
+        this.docForm.title = _this.docAttr.title;
+        this.docForm.category_id = _this.docAttr.category_id;
+      },
+      /* 保存 */
+      onSaveAs(e) {
+        let _this = this;
+        if (_this.coms.length <= 0) {
+          _this.$alert("请填写内容");
+          return;
+        }
+        _this.docAttr.id = "";
+        _this.docVisible = true;
+        this.docForm.title = _this.docAttr.title;
+        this.docForm.category_id = _this.docAttr.category_id;
+      },
+      //保存文档
+      onSave(e) {
+        let _this = this;
+        if (_this.coms.length <= 0) {
+          _this.$alert("增加组件");
+          return;
+        }
+        if (_this.docAttr.category_id == "" || _this.docAttr.category_id <= 0) {
+          _this.$alert("请选择文档分类");
+          return;
+        }
+        if (_this.docAttr.title == "") {
+          _this.$alert("请填写文档标题");
+          return;
+        }
+  
+        _this.docAttr.links = JSON.stringify(_this.docAttr.linkProduct);
+        _this.docAttr.projects = JSON.stringify(_this.docAttr.linkProject);
+        _this.docAttr.data = JSON.stringify(_this.coms);
+        _this.docAttr.is_template = 0;
+        _this.docAttr.user_name = _this.$store.state.user.name;
+        _this.docAttr.user_id = _this.$store.state.user.id;
+        if (_this.docAttr.id > 0) {
+          updateDocument(_this.docAttr).then((res) => {
+            if (res.status != 200) return; //更新文档
+            _this.docAttr.id = res.data;
+            _this.$alert("文档更新成功");
+            _this.docVisible = false;
+            _this.docForm = {
+              title: "",
+              category_id: "",
+            };
+            _this.searchArticle();
+          });
+        } else {
+          createDocument(_this.docAttr).then((res) => {
+            if (res.status != 200) return; //保存文档
+            _this.docAttr.id = res.data;
+            _this.articleId = res.data;
+            _this.$alert("文档保存成功");
+            _this.docVisible = false;
+            _this.docForm = {
+              title: "",
+              category_id: "",
+            };
+            _this.searchArticle();
+          });
+        }
+      },
+      /*更新  */
+      onUpload() {
+        let _this = this;
+        if (_this.coms.length <= 0) {
+          _this.$alert("增加组件");
+          return;
+        }
+        /* if (_this.docAttr.category_id == "" || _this.docAttr.category_id <= 0) {
+            _this.$alert("请选择分类");
+            return;
+          } */
+        if (_this.docAttr.title == "") {
+          _this.$alert("请填写模版标题");
+          return;
+        }
+        _this.docAttr.links = JSON.stringify(_this.docAttr.linkProduct);
+        _this.docAttr.projects = JSON.stringify(_this.docAttr.linkProject);
+        _this.docAttr.data = JSON.stringify(_this.coms);
+        _this.docAttr.is_template = 1;
+        _this.docAttr.id = this.$route.query.articleId;
+        _this.docAttr.user_name = _this.$store.state.user.name;
+        _this.docAttr.user_id = _this.$store.state.user.id;
+        updateDocument(_this.docAttr).then((res) => {
+          if (res.status != 200) return; //更新文档
+          _this.docAttr.id = res.data;
+          _this.$alert("模版更新成功");
+          _this.searchArticle();
+        });
+      },
+      /* 保存并更新模版 */
+      onSaveUpload(e) {
+        let _this = this;
+        if (_this.coms.length <= 0) {
+          _this.$alert("增加组件");
+          return;
+        }
+        /* if (_this.docAttr.category_id == "" || _this.docAttr.category_id <= 0) {
+            _this.$alert("请选择分类");
+            return;
+          } */
+        if (_this.docAttr.title == "") {
+          _this.$alert("请填写模版标题");
+          return;
+        }
+  
+        _this.docAttr.links = JSON.stringify(_this.docAttr.linkProduct);
+        _this.docAttr.projects = JSON.stringify(_this.docAttr.linkProject);
+        _this.docAttr.data = JSON.stringify(_this.coms);
+        _this.docAttr.is_template = 1;
+        _this.docAttr.user_name = _this.$store.state.user.name;
+        _this.docAttr.user_id = _this.$store.state.user.id;
+        /* if (_this.docAttr.id > 0) {
+            updateDocument(_this.docAttr).then((res) => {
+              if (res.status != 200) return; //更新文档
+              _this.docAttr.id = res.data;
+              _this.$alert("模版更新成功");
+              _this.searchArticle();
+            });
+          } else { */
+        createDocument(_this.docAttr).then((res) => {
+          if (res.status != 200) return; //保存文档
+          _this.docAttr.id = res.data;
+          _this.articleId = res.data;
+          _this.$alert("模版保存成功");
+          _this.searchArticle();
+        });
+        /*   } */
+      },
+  
+      //设置组件值
+      onSetComs(e) {
+        let _this = this;
+        _this.coms = [...e];
+      },
+      onAddLayer(cateIndex, dataIndex) {
+        let _this = this;
+        let item = _this.categoryList[cateIndex].dataList[dataIndex];
+        let data = {
+          ...item,
+          attrs: JSON.parse(item.attrs),
+        };
+        delete data.category;
+        _this.coms.push(data);
+        _this.comIndex = _this.coms.length - 1;
+      },
+      //处理插入事件
+      onInsert(e) {
+        let _this = this;
+        switch (e.key) {
+          case "article": //插入图文
+            _this.insertArticle();
+            break;
+          case "table": //插入图表
+            _this.insertTable();
+            break;
+          case "sourceData": //插入原数据
+            _this.insertSourceData(e);
+            break;
+          case "formual": //插入公式
+            _this.insertFormual(e);
+            break;
+          case "constant": //插入常量
+            _this.insertConstant(e);
+            break;
+          case "variable": //插入变量
+            _this.insertVariable(e);
+            break;
+          case "pager": //插入分页符
+            _this.insertPager();
+            break;
+          case "attr":
+            this.insertProductAttr();
+            break;
+          case "Directory": //插入主题
+            _this.insertDirectory(e);
+            break;
+          case "addNew": //插入新记录
+            _this.insertNew(e);
+            break;
+          case "InsertNull":
+            _this.insertVal();
+            break;
+          case "ai": //插入ai
+            _this.insertAI(e);
+            break;
+        }
+      },
+      /* 插入空值 */
+      insertVal() {
+        let _this = this;
+        if (_this.comIndex < 0) {
+          _this.$alert("请选择插入图层");
+          return false;
+        }
+        let com = _this.coms[_this.comIndex];
+        let uniqueId = "val" + Date.now(); // 使用时间戳创建唯一ID
+        let data = {
+          type: "variableNull",
+          id: uniqueId,
+          dataId: "",
+          name: "非变量",
+          intro: "",
+          content: "请输入",
+          data: "",
+        };
+        com.attrs.push(data);
+        if (com.type == "TextArea") {
+          //如果是图文,更新内容
+          _this.insertCmd = {
+            content: "{{" + data.id + "}}",
+          };
+        } else {
+          //仅对图表中插入有效
+          if (_this.comIndex >= 0) {
+            _this.setTableData(data.id);
+          }
+        }
+      },
+      /* 插入ai */
+      insertAI(e) {
+        let _this = this;
+        if (_this.comIndex < 0) {
+          _this.$alert("请选择插入图层");
+          return false;
+        }
+        let com = _this.coms[_this.comIndex];
+  
+        let data = {
+          type: "ai",
+          id: "ai" + (com.attrs.length + 1),
+          dataId: "",
+          name: "AI",
+          intro: "插入AI",
+          content: e.content.result,
+          search: e.content.searchTerm,
+        };
+        com.attrs.push(data);
+        if (com.type == "TextArea") {
+          //如果是图文,更新内容
+          _this.insertCmd = {
+            content: "{{" + data.id + "}}",
+          };
+        }
+      },
+      insertProductAttr() {
+        if (this.docAttr.linkProduct.length <= 0) {
+          this.$alert("请选择文档关联商品信息");
+          return false;
+        }
+        this.showProductAttr = true;
+      },
+      //插入目录
+      insertDirectory(e) {
+        let _this = this;
+        if (_this.comIndex < 0) {
+          _this.$alert("请选择插入图层");
+          return false;
+        }
+  
+        let com = _this.coms[_this.comIndex];
+        let uniqueId = `Directory_${_this.comIndex}_${com.attrs.length + 1}`;
+  
+        // 获取当前层级
+        let currentLevel = e.content.level || 1;
+  
+        // 生成带有模块索引和层级的内容
+        let { number, content } = this.generateNumberedContent(
+          _this.comIndex,
+          currentLevel
+        );
+  
+        let data = {
+          type: "Directory",
+          id: uniqueId,
+          name: "目录信息",
+          intro: "目录信息",
+          number: number,
+          content: content,
+          level: currentLevel,
+          moduleIndex: _this.comIndex,
+        };
+  
+        // 使用 $set 来确保响应式更新
+        this.$set(com.attrs, com.attrs.length, data);
+  
+        // 强制更新视图
+        this.$forceUpdate();
+  
+        // 使用 nextTick 确保 DOM 更新后再检查
+        this.$nextTick(() => {
+          if (!com.attrs[com.attrs.length - 1].content) {
+            this.$set(com.attrs[com.attrs.length - 1], "content", content);
+            this.$forceUpdate();
+          }
+        });
+  
+        // 使用新的唯一 ID 在插入命令中
+        _this.insertCmd = {
+          content: `{{${uniqueId}}}`,
+        };
+      },
+  
+      generateNumberedContent(moduleIndex, level) {
+        let number = this.generateLevelPrefix(moduleIndex, level);
+        let content = this.coms[moduleIndex].type === "TextArea" ? "" : "";
+  
+        return { number, content };
+      },
+  
+      generateLevelPrefix(moduleIndex, level) {
+        let prefix = [];
+        let currentModule = this.coms[moduleIndex];
+  
+        // 获取当前模块中已存在的目录项
+        let existingDirectories = currentModule.attrs.filter(
+          (attr) => attr.type === "Directory"
+        );
+  
+        // 计算当前层级的序号
+        let currentLevelCount =
+          existingDirectories.filter((dir) => dir.level === level).length + 1;
+  
+        // 构建多级序号
+        for (let i = 1; i <= level; i++) {
+          if (i === 1) {
+            prefix.push(moduleIndex + 1);
+          } else if (i === level) {
+            prefix.push(currentLevelCount);
+          } else {
+            // 对于中间层级,找到最近的上级目录项
+            let parentLevel = existingDirectories
+              .filter((dir) => dir.level === i - 1)
+              .pop();
+            prefix.push(parentLevel ? parentLevel.number.split(".")[i - 1] : 1);
+          }
+        }
+  
+        return prefix.join(".");
+      },
+  
+      onInsertProductAttr(e) {
+        let _this = this;
+        this.showProductAttr = false;
+        if (_this.comIndex < 0) {
+          _this.$alert("请选择插入图层");
+          return false;
+        }
+  
+        let com = _this.coms[_this.comIndex];
+  
+        let data = {
+          type: "ProductAttr",
+          id: "ProductAttr" + (com.attrs.length + 1),
+          dataId: e.id,
+          name: "商品属性",
+          intor: "商品属性",
+          content: "",
+          attrs: e,
+        };
+        com.attrs.push(data);
+        _this.insertCmd = {
+          content: "{{" + data.id + "}}",
+        };
+      },
+  
+      //插入新记录
+      insertNew(e) {
+        let _this = this;
+        _this.coms.push(e);
+      },
+  
+      //插入分页
+      insertPager() {
+        let _this = this;
+        if (_this.comIndex < 0) {
+          _this.$alert("请选择插入图层");
+          return false;
+        }
+        let com = _this.coms[_this.comIndex];
+        let data = {
+          type: "pager",
+          id: "pager" + (com.attrs.length + 1),
+          name: "分页符",
+          intor: "强制分页",
+          content: "",
+        };
+        com.attrs.push(data);
+        if (com.type == "TextArea") {
+          //如果是图文,更新内容
+          _this.insertCmd = {
+            content: "<div>{{" + data.id + "}}</div>",
+          };
+        }
+      },
+  
+      //插入图文
+      insertArticle() {
+        /*   let _this = this;
+          _this.coms.push({
+            type: "TextArea",
+            lay_id: "textArea",
+            code: "模块名",
+            name: "图文",
+            intro: "图文介绍",
+            isEdit: 2,
+            content: "请填写内容",
+            attrs: [],
+          });
+          _this.comIndex = _this.coms.length - 1; */
+        let _this = this;
+        let newArticle = {
+          type: "TextArea",
+          lay_id: "textArea",
+          code: "模块名",
+          name: "图文",
+          intro: "图文介绍",
+          isEdit: 2,
+          content: "请填写内容",
+          attrs: [],
+        };
+        _this.coms.push(newArticle);
+        _this.comIndex = _this.coms.length - 1;
+  
+        // 添加一些日志来帮助调试
+        console.log("New article added:", newArticle);
+        console.log("Current coms:", _this.coms);
+        console.log("New comIndex:", _this.comIndex);
+      },
+  
+      //插入表格
+      insertTable() {
+        let _this = this;
+        _this.coms.push({
+          type: "Table",
+          lay_id: "Table",
+          code: "模块名",
+          name: "图表名称",
+          intro: "图表介绍",
+          isEdit: 2,
+          tableHeader: this.initTableHeader(),
+          tableData: [{}],
+          htmlData: "",
+          attrs: [],
+        });
+        _this.comIndex = _this.coms.length - 1;
+      },
+      initTableHeader() {
+        var start = 65;
+        var data = [];
+        for (var i = start; i <= start + 25; i++) {
+          data.push(String.fromCharCode(i));
+        }
+        return data;
+      },
+      //插入变量
+      insertConstant(e) {
+        let _this = this;
+        // console.log("insertconstant");
+        if (_this.comIndex < 0) {
+          _this.$alert("请选择插入图层");
+          return false;
+        }
+        let com = _this.coms[_this.comIndex];
+        let data = {
+          type: "constant",
+          id: e.content.code + (com.attrs.length + 1),
+          dataId: e.id,
+          name: e.content.code,
+          intro: e.content.intro,
+          content: e.content.value,
+          data: e.content,
+        };
+  
+        com.attrs.push(data);
+        if (com.type == "TextArea") {
+          //如果是图文,更新内容
+          _this.insertCmd = {
+            content: "{{" + data.id + "}}",
+          };
+        } else {
+          //仅对图表中插入有效
+          if (_this.comIndex >= 0) {
+            _this.setTableData(data.id);
+          }
+        }
+      },
+  
+      //插入变量
+      insertVariable(e) {
+        let _this = this;
+        if (_this.comIndex < 0) {
+          _this.$alert("请选择插入图层");
+          return false;
+        }
+        let com = _this.coms[_this.comIndex];
+        let data = {
+          type: "variable",
+          id: e.content.code + (com.attrs.length + 1),
+          dataId: e.id,
+          name: e.content.code,
+          intro: e.content.intro,
+          content: e.content.value,
+          data: e.content,
+        };
+        com.attrs.push(data);
+        if (com.type == "TextArea") {
+          //如果是图文,更新内容
+          _this.insertCmd = {
+            content: "{{" + data.id + "}}",
+          };
+        } else {
+          //仅对图表中插入有效
+          if (_this.comIndex >= 0) {
+            _this.setTableData(data.id);
+          }
+        }
+      },
+      //插入原数据
+      insertSourceData(e) {
+        let _this = this;
+        if (_this.comIndex < 0) {
+          _this.$alert("请选择插入图层");
+          return false;
+        }
+        let com = _this.coms[_this.comIndex];
+        let data = {
+          type: "sourceData",
+          id: "sourceData" + (com.attrs.length + 1),
+          dataId: "",
+          name: "源数据",
+          intro: "插入源数据",
+          formula:
+            "[R][" +
+            e.content.tb +
+            "][" +
+            e.content.sheet +
+            "][" +
+            e.content.r +
+            "," +
+            e.content.c +
+            "]",
+          content: e.content.value,
+          data: e.content,
+        };
+        com.attrs.push(data);
+        if (com.type == "TextArea") {
+          //如果是图文,更新内容
+          _this.insertCmd = {
+            content: "{{" + data.id + "}}",
+          };
+        }
+      },
+      //插入公式
+      insertFormual(e) {
+        let _this = this;
+        if (_this.comIndex < 0) {
+          _this.$alert("请选择插入图层");
+          return false;
+        }
+        let com = _this.coms[_this.comIndex];
+        let data = {
+          type: "formual",
+          id: "formual" + (com.attrs.length + 1),
+          dataId: e.id,
+          name: e.content.name,
+          intro: e.content.intro,
+          formula: e.content.formula,
+          content: 0,
+          data: e.content,
+        };
+        com.attrs.push(data);
+        if (com.type == "TextArea") {
+          //如果是图文,更新内容
+          _this.insertCmd = {
+            content: "{{" + data.id + "}}",
+          };
+        } else {
+          //仅对图表中插入有效
+          if (_this.comIndex >= 0) {
+            _this.setTableData(data.id);
+          }
+        }
+      },
+  
+      //获取模版分类信息
+      async initCategoryList() {
+        let _this = this;
+        /*  let res = await getAllCategory({list:1 });
+            if(res.status!=200)return;
+            let dataList=res.data;
+    
+            for(var i=0;i<dataList.length;i++){
+               for (var j=0;j<dataList[i].children.length;j++){
+                 let dataId=dataList[i].children[j].id;
+    
+                 for(var k=0;k<dataList[i].children[j].dataList.length;k++){
+                   dataList[i].children[j].dataList[k].attrs=JSON.parse(dataList[i].children[j].dataList[k].attrs)
+                 }
+                 // dataList[i].children[j].dataList=await _this.getTemplateList(dataId)
+               }
+            }
+    
+            // console.log("dataList",dataList)
+           _this.categoryList=dataList; */
+        let res = await searchTemplateCategory({
+          page: 1,
+          pageSize: 99,
+          parent_id: 0,
+          status: 5,
+        });
+        if (res.status != 200) return;
+        _this.categoryList = res.data.dataList;
+        for (var i = 0; i < _this.categoryList.length; i++) {
+          _this.categoryList[i].dataList = await _this.getTemplateList(
+            _this.categoryList[i].id
+          );
+        }
+      },
+      //获取模版列表
+  
+      async getTemplateList(categoryId) {
+        let _this = this;
+        let res = await searchTemplate({
+          page: 1,
+          pageSize: 999,
+          category_id: categoryId,
+          status: 5,
+        });
+        if (res.status != 200) return [];
+  
+        let dataList = res.data.dataList.map((item) => {
+          // 添加空值检查
+          if (!item.attrs) {
+            item.attrs = "[]";
+          }
+          if (!item.content) {
+            item.content = "";
+          }
+  
+          try {
+            // 解析 attrs,添加错误处理
+            item.attrs =
+              typeof item.attrs === "string"
+                ? JSON.parse(item.attrs)
+                : item.attrs;
+  
+            // 从 content 中提取所有 {{}} 包裹的 ID,添加空值检查
+            const contentIds = (
+              (item.content || "").match(/{{([^}]+)}}/g) || []
+            ).map((match) => match.slice(2, -2).trim());
+  
+            // 确保 attrs 是数组
+            item.attrs = Array.isArray(item.attrs) ? item.attrs : [];
+  
+            // 过滤 attrs,只保留在 content 中出现的 ID
+            item.attrs = item.attrs.filter(
+              (attr) => attr && attr.id && contentIds.includes(attr.id)
+            );
+          } catch (error) {
+            console.error("Error processing template item:", error);
+            item.attrs = [];
+          }
+  
+          return item;
+        });
+  
+        return dataList;
+      },
+  
+      //获取文档列表
+      searchArticle() {
+        let _this = this;
+        pageDocument({
+          page: 1,
+          pageSize: 99,
+        }).then((res) => {
+          if (res.status != 200) return;
+          _this.articleList = res.data.dataList.filter(
+            (el) => el.is_template == 0
+          );
+          _this.templateList = res.data.dataList.filter(
+            (el) => el.is_template == 1
+          );
+        });
+      },
+    },
+  };
+  </script>
+    
+    <style lang="scss">
+  @import "./create.scss";
+  
+  .el-collapse,
+  .el-collapse-item__wrap {
+    border: none;
+  }
+  .el-collapse,
+  .el-collapse-item__header {
+    border: none;
+    .el-collapse-item__arrow {
+      margin: 0;
+    }
+  }
+  .borderless-collapse {
+    border: none;
+    ::v-deep .el-collapse-item__header,
+    ::v-deep .el-collapse-item__wrap,
+    ::v-deep .el-collapse-item__content,
+    ::v-deep .el-collapse-item {
+      border: none;
+    }
+  
+    ::v-deep .el-collapse-item__wrap {
+      border-bottom: none !important;
+    }
+  }
+  .scrollable-container {
+    overflow-x: auto;
+    white-space: nowrap;
+    width: 350px;
+    padding-bottom: 10px;
+  }
+  
+  .min-width-content {
+    /*   display: inline-block;
+      vertical-align: top; */
+    min-width: 150px;
+    white-space: normal;
+  }
+  
+  /*.el-collapse-item {
+      display: inline-block;
+      vertical-align: top;
+    } */
+  </style>
+    

+ 548 - 17
src/views/project/components/dataList.vue

@@ -41,6 +41,11 @@
             </span>
             <template #dropdown>
               <el-dropdown-menu>
+                <el-dropdown-item
+                  :command="{ type: 'viewTemplates', row: scope.row }"
+                >
+                  已绑定模版
+                </el-dropdown-item>
                 <el-dropdown-item
                   :command="{ type: 'template', row: scope.row }"
                   >选择模版</el-dropdown-item
@@ -102,13 +107,106 @@
     >
       <dataInfo :id="currentDataId" @onClose="onClose"></dataInfo>
     </el-dialog>
+    <!-- 选择模版 -->
+    <el-dialog
+      title="选择模版"
+      :visible.sync="templateDialogVisible"
+      width="50%"
+      append-to-body
+    >
+      <el-table
+        :data="templateList"
+        style="width: 100%"
+        @selection-change="handleTemplateSelect"
+        ref="templateTable"
+      >
+        <el-table-column type="selection" width="55"></el-table-column>
+        <el-table-column prop="id" label="ID" width="80" align="center" />
+        <el-table-column prop="title" label="模版名称" align="left" />
+        <el-table-column prop="create_time" label="创建时间" align="center" />
+      </el-table>
+      <div class="pagination-container">
+        <el-pagination
+          :current-page="templateQueryForm.page"
+          :page-size="templateQueryForm.pageSize"
+          :total="templateTotal"
+          background
+          layout="prev, pager, next"
+          @current-change="handleTemplatePageChange"
+        >
+        </el-pagination>
+      </div>
+      <div class="dialog-footer" slot="footer">
+        <el-button @click="templateDialogVisible = false">取 消</el-button>
+        <el-button type="primary" @click="confirmTemplateSelection"
+          >确 定</el-button
+        >
+      </div>
+    </el-dialog>
+    <el-dialog
+      title="已绑定模版"
+      :visible.sync="boundTemplatesDialogVisible"
+      width="50%"
+      append-to-body
+    >
+      <el-table :data="boundTemplateList" style="width: 100%">
+        <el-table-column prop="id" label="ID" width="80" align="center" />
+        <el-table-column prop="title" label="模版名称" align="left" />
+        <el-table-column prop="create_time" label="绑定时间" align="center" />
+        <el-table-column label="操作" align="center" width="200">
+          <template #default="scope">
+            <el-button type="primary" @click="previewTemplate(scope.row)"
+              >预览</el-button
+            >
+            <el-button type="primary" @click="replaceTemplate(scope.row)"
+              >替换</el-button
+            >
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-dialog>
+    <el-dialog
+      title=""
+      :visible.sync="replaceVisible"
+      width="50%"
+      append-to-body
+    >
+      <!-- 显示匹配到的结果 -->
+      <div v-if="matchResults.length" class="match-results">
+        <div
+          v-for="(item, index) in matchResults"
+          :key="index"
+          class="match-item"
+        >
+          <div class="match-content">
+            <span>原始值: {{ item }}</span>
+            <!-- <span>替换后: {{ item  }}</span> -->
+          </div>
+          <div class="actions">
+            <el-button size="small" @click="replaceItem(item)">替换</el-button>
+          </div>
+        </div>
+        <div style="display: flex; justify-content: flex-end; margin-top: 20px">
+          <el-button type="primary" @click="replaceAll">批量替换</el-button>
+        </div>
+      </div>
+    </el-dialog>
   </div>
 </template>
 
 <script>
-import { searchProject, deleteProject, searchCategory } from "@/api/project";
+import {
+  searchProject,
+  deleteProject,
+  searchCategory,
+  relate,
+  searchTemplateList,
+} from "@/api/project";
+import { searchList, updateDocument } from "@/api/document";
+/* import {searchTemplate} from "@/api/template" */
 import dataInfo from "./dataInfo.vue";
 import elDragDialog from "@/directive/el-drag-dialog";
+import axios from "axios";
 export default {
   components: {
     dataInfo,
@@ -139,6 +237,11 @@ export default {
   },
   data() {
     return {
+      templateQueryForm: {
+        page: 1,
+        pageSize: 10,
+      },
+      templateTotal: 0, // 模版总数
       dialogVisible: false, //控制产品审核
       currentDataId: 0,
       recordCount: 0,
@@ -146,6 +249,22 @@ export default {
       dataList: [],
       currentData: {},
       categoryList: [],
+      templateDialogVisible: false,
+      templateList: [],
+      selectedTemplates: [], // 改为数组以支持多选
+      boundTemplatesDialogVisible: false,
+      previewDialogVisible: false,
+      boundTemplateList: [],
+      templatePreviewContent: "",
+      currentTemplateId: null,
+      replaceVisible: false,
+      loading: false, // 增加 loading 状态
+      searchResult: {},
+      matchResults: [], // 存储匹配到的结果
+      TemList: [],
+      docAttr: {},
+      templateLoading: false, // 添加模版加载状态
+      boundTemplateLoading: false, // 添加已绑定模版加载状态
     };
   },
   mounted() {
@@ -154,23 +273,414 @@ export default {
   },
   methods: {
     handleCommand(command) {
-    switch (command.type) {
-      case 'template':
-        this.selTemplate(command.row);
-        break;
-      case 'enter':
-        this.enterPro(command.row);
-        break;
-      case 'edit':
-        this.btnEdit(command.row);
-        break;
-      case 'delete':
-        this.btnDelete(command.row.id);
-        break;
-    }
-  },
+      switch (command.type) {
+        case "template":
+          this.selTemplate(command.row);
+          break;
+        case "enter":
+          this.enterPro(command.row);
+          break;
+        case "edit":
+          this.btnEdit(command.row);
+          break;
+        case "delete":
+          this.btnDelete(command.row.id);
+          break;
+        case "viewTemplates":
+          this.viewBoundTemplates(command.row);
+          break;
+      }
+    },
+    // Add new methods
+    // 查看已绑定模版
+    viewBoundTemplates(row) {
+      this.currentData = row;
+      this.boundTemplateLoading = true;
+      searchTemplateList({ ids: row.documentIds.join(",") })
+        .then((res) => {
+          if (res.status === 200) {
+            this.boundTemplateList = res.data;
+            this.boundTemplatesDialogVisible = true;
+          }
+        })
+        .finally(() => {
+          this.boundTemplateLoading = false;
+        });
+    },
+
+    previewTemplate(template) {
+      /*  this.currentTemplateId = template.id; */
+      let _this = this;
+      let a = document.createElement("a");
+      a.href = "#/document/viewCreate?articleId=" + template.id;
+      a.target = "_blank";
+      a.click();
+      /* getTemplateContent(template.id).then((res) => {
+        if (res.status === 200) {
+          this.templatePreviewContent = res.data.content;
+          this.previewDialogVisible = true;
+        }
+      }); */
+    },
+
+    replaceTemplate(template) {
+      this.docAttr = template;
+      const TemList = JSON.parse(template.data);
+      /* this.$confirm("确定要替换此模版吗?", "提示", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning",
+      }).then(() => { */
+      this.replaceVisible = true;
+      this.searchEx(TemList);
+      /* this.closeTemplateDialog();
+        this.selTemplate(this.currentData); */
+      /* }); */
+    },
+
+    searchEx(val) {
+      let allMatches = [];
+      this.TemList = val;
+      this.TemList.forEach((el) => {
+        // 创建临时 div 来解析 HTML 内容
+        const tempDiv = document.createElement("div");
+        tempDiv.innerHTML = el.content;
+        const plainText = tempDiv.textContent || tempDiv.innerText;
+
+        // 分别匹配中文括号和英文括号
+        const patterns = [
+          /【([A-Z]{1,4}[0-9]{0,2})-([A-Z]{1,4}[0-9]{0,2})-([0-9]{2})-([0-9]{2,3})】/g,
+          /\[([A-Z]{1,4}[0-9]{0,2})-([A-Z]{1,4}[0-9]{0,2})-([0-9]{2})-([0-9]{2,3})\]/g,
+        ];
+
+        patterns.forEach((pattern) => {
+          let match;
+          while ((match = pattern.exec(plainText)) !== null) {
+            const fullMatch = match[0];
+            const segments = [match[1], match[2], match[3], match[4]];
+
+            // 验证每个段落的格式
+            const isValid = segments.every((segment) => segment.length > 0);
+
+            if (isValid) {
+              allMatches.push(fullMatch);
+            }
+          }
+        });
+      });
+
+      this.matchResults = [...new Set(allMatches)];
+      console.log("匹配结果:", this.matchResults);
+
+      // 调试用:打印每个匹配的详细信息
+      this.matchResults.forEach((match) => {
+        console.log("找到匹配:", match);
+        // 根据括号类型选择不同的切片方式
+        const content = match.startsWith("【")
+          ? match.slice(1, -1)
+          : match.slice(1, -1);
+        const parts = content.split("-");
+        console.log("分段:", parts);
+      });
+    },
+
+    // 添加新的辅助方法来判断文档类型
+    getDocumentType(code) {
+      if (code.includes("-RP-")) return "template_location";
+      if (code.includes("-IR-")) return "middle_layer_location";
+      if (code.includes("-WE-")) return "tech_report_location";
+      return "other_location";
+    },
+
+    // 修改格式化请求参数的方法
+    formatRequestParams(locations) {
+      const locationArray = Array.isArray(locations) ? locations : [locations];
+
+      // 初始化请求参数对象
+      const params = {
+        template_location: [],
+        middle_layer_location: [],
+        tech_report_location: [],
+        other_location: [],
+      };
+
+      // 根据不同类型分类存储
+      locationArray.forEach((location) => {
+        const cleanLocation = location.replace(/[\[\]]/g, "");
+        const type = this.getDocumentType(cleanLocation);
+        console.log("params", type);
+        // 为每种类型创建对应长度的空数组
+        Object.keys(params).forEach((key) => {
+          if (type === key) {
+            params[key].push(cleanLocation);
+          } else {
+            params[key].push("");
+          }
+        });
+      });
+
+      return params;
+    },
+
+    // 单个替换
+    replaceItem(item) {
+      // 去掉方括号
+      const cleanItem = item.replace(/[\[\]]/g, "");
+      this.$confirm(`确认替换 ${item}?`, "提示", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning",
+      })
+        .then(async () => {
+          try {
+            const params = this.formatRequestParams(cleanItem);
+            const response = await axios.post(
+              `${process.env.VUE_APP_BASE_API}/project-raw-data/batch-content`,
+              params,
+              {
+                headers: {
+                  "Content-Type": "application/json",
+                },
+              }
+            );
+
+            if (response.status === 200 && response.data) {
+              console.log(response.data.data);
+              // 修改获取替换值的逻辑
+              const replacementValue =
+                response.data.data.input_item_contents[0]?.content;
+
+              if (replacementValue) {
+                this.TemList.forEach((el, index) => {
+                  this.$set(
+                    this.TemList[index],
+                    "content",
+                    el.content.replace(
+                      new RegExp(this.escapeRegExp(item), "g"),
+                      replacementValue
+                    )
+                  );
+                });
+
+                this.matchResults = this.matchResults.filter(
+                  (match) => match !== item
+                );
+                this.onUpload();
+                this.$message.success("替换成功");
+                //this.$emit("close"); // 成功后触发关闭事件
+              } else {
+                this.$message.warning("未获取到替换内容");
+              }
+            } else {
+              this.$message.error("获取替换内容失败");
+            }
+          } catch (error) {
+            console.error("替换失败:", error);
+            this.$message.error(error.response?.data?.message || "替换失败");
+          }
+        })
+        .catch(() => {
+          // 取消操作
+        });
+    },
+    /*更新  */
+    onUpload() {
+      let _this = this;
+      if (_this.TemList.length <= 0) {
+        _this.$alert("增加组件");
+        return;
+      }
+      if (_this.docAttr.title == "") {
+        _this.$alert("请填写模版标题");
+        return;
+      }
+      _this.docAttr.links = JSON.stringify(_this.docAttr.linkProduct);
+      _this.docAttr.projects = JSON.stringify(_this.docAttr.linkProject);
+      _this.docAttr.data = JSON.stringify(_this.TemList);
+      _this.docAttr.is_template = 1;
+      _this.docAttr.id = _this.docAttr.id;
+      _this.docAttr.user_name = _this.docAttr.name;
+      _this.docAttr.user_id = _this.docAttr.id;
+      updateDocument(_this.docAttr).then((res) => {
+        if (res.status != 200) return; //更新文档
+        _this.docAttr.id = res.data;
+        _this.$alert("模版更新成功");
+        _this.searchArticle();
+      });
+    },
+    /*批量替换 */
+    replaceAll() {
+      this.loadingAll = true;
+
+      this.$confirm(
+        `确认批量替换 ${this.matchResults.length} 项内容?`,
+        "提示",
+        {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning",
+        }
+      )
+        .then(async () => {
+          try {
+            // 清理所有项的方括号
+            const cleanItems = this.matchResults.map((item) =>
+              item.replace(/[\[\]]/g, "")
+            );
+            const params = this.formatRequestParams(cleanItems);
+            const response = await axios.post(
+              `${process.env.VUE_APP_BASE_API}/project-raw-data/batch-content`,
+              params,
+              {
+                headers: {
+                  "Content-Type": "application/json",
+                },
+              }
+            );
+
+            if (response.status === 200 && response.data) {
+              // 修改这里,使用与单个替换相同的数据结构
+              const replacementValues =
+                response.data.data.input_item_contents?.map(
+                  (item) => item.content
+                );
+              // 添加验证:检查是否所有值都为 null
+              if (replacementValues.every((value) => value === null)) {
+                this.$message.warning("所有替换内容均无效,请检查数据");
+                return;
+              }
+
+              // 添加验证:检查是否存在 null 值
+              const nullCount = replacementValues.filter(
+                (value) => value === null
+              ).length;
+              if (nullCount > 0) {
+                this.$message.warning(
+                  `${nullCount} 项内容无法替换,请检查数据`
+                );
+                return;
+              }
+              if (replacementValues.length > 0) {
+                this.TemList.forEach((el, index) => {
+                  let updatedContent = el.content;
+                  this.matchResults.forEach((item, idx) => {
+                    const replacementValue = replacementValues[idx];
+                    if (replacementValue) {
+                      updatedContent = updatedContent.replace(
+                        new RegExp(this.escapeRegExp(item), "g"),
+                        replacementValue
+                      );
+                    }
+                  });
+                  this.$set(this.TemList[index], "content", updatedContent);
+                });
+                this.onUpload();
+                this.matchResults = [];
+                this.$message.success("批量替换成功");
+                // this.$emit("close"); // 成功后触发关闭事件
+              } else {
+                this.$message.warning("未获取到替换内容");
+              }
+            } else {
+              this.$message.error("获取替换内容失败");
+            }
+          } catch (error) {
+            console.error("批量替换失败:", error);
+            this.$message.error(
+              error.response?.data?.message || "批量替换失败"
+            );
+          }
+        })
+        .catch(() => {
+          // 取消操作
+        })
+        .finally(() => {
+          this.loadingAll = false;
+        });
+    },
+
+    // 更新 escapeRegExp 方法以支持两种括号类型
+    escapeRegExp(string) {
+      const isChinese = string.startsWith("【");
+      const content = isChinese
+        ? string.replace(/^【|】$/g, "")
+        : string.replace(/^\[|\]$/g, "");
+      const escaped = content.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&");
+      return isChinese ? `\\【${escaped}\\】` : `\\[${escaped}\\]`;
+    },
     /* 选择模版 */
-    selTemplate() {},
+    selTemplate(row) {
+      this.currentData = row;
+      this.selectedTemplates = []; // 重置选择
+      this.templateQueryForm.page = 1; // 重置页码
+      this.loadTemplateList();
+    },
+    // 处理模版分页变化
+    handleTemplatePageChange(page) {
+      this.templateQueryForm.page = page;
+      this.loadTemplateList();
+    },
+    // 加载模版列表
+    // 加载模版列表
+    loadTemplateList() {
+      this.templateLoading = true;
+      searchList(this.templateQueryForm)
+        .then((res) => {
+          if (res.status === 200) {
+            this.templateList = res.data.dataList;
+            this.templateTotal = res.data.totalRecord;
+            this.templateDialogVisible = true;
+          }
+        })
+        .finally(() => {
+          this.templateLoading = false;
+        });
+    },
+
+    handleTemplateSelect(selection) {
+      this.selectedTemplates = selection;
+    },
+
+    closeTemplateDialog() {
+      this.templateDialogVisible = false;
+      this.selectedTemplates = [];
+      // 清除所有选择
+      this.$refs.templateTable.clearSelection();
+    },
+
+    confirmTemplateSelection() {
+      if (this.selectedTemplates.length === 0) {
+        this.$message.warning("请至少选择一个模版");
+        return;
+      }
+
+      // 将选中的模版对象数组转换为 ID 数组
+      const docIds = this.selectedTemplates.map((template) => template.id);
+
+      relate({
+        project_id: this.currentData.id,
+        doc_ids: JSON.stringify(docIds), // 使用转换后的 ID 数组
+      }).then((res) => {
+        if (res.status !== 200) return;
+        this.$message.success("模版关联成功");
+        this.closeTemplateDialog();
+        this.search();
+      });
+
+      /*   // 这里添加保存模版选择的逻辑
+      console.log("Selected templates:", this.selectedTemplates);
+      console.log("Current project:", this.currentData); */
+    },
+
+    // 如果需要手动选中/取消选中某一行
+    toggleRowSelection(row, selected) {
+      this.$refs.templateTable.toggleRowSelection(row, selected);
+    },
+
+    // 如果需要全选功能
+    toggleAllSelection() {
+      this.$refs.templateTable.toggleAllSelection();
+    },
     /* 输入项目 */
     enterPro(row) {
       this.$router.push({
@@ -257,4 +767,25 @@ export default {
 
 <style lang="scss">
 @import "./dataList.scss";
+.match-results {
+  margin-top: 20px;
+
+  .match-item {
+    padding: 10px;
+    border-bottom: 1px solid #eee;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+
+    .match-content {
+      flex: 1;
+      display: flex;
+      gap: 20px;
+    }
+
+    .actions {
+      margin-left: 10px;
+    }
+  }
+}
 </style>

Някои файлове не бяха показани, защото твърде много файлове са промени