ソースを参照

完善项目中的操作逻辑

yangg 7 ヶ月 前
コミット
df341b7102

+ 1 - 1
.env.development

@@ -5,4 +5,4 @@ port = 8080
 VUE_APP_BASE_API = 'http://192.168.1.199:8084'
 #192.168.1.199
 
-#183.195.216.54
+#183.195.216.54

ファイルの差分が大きいため隠しています
+ 0 - 0
dist/index.html


ファイルの差分が大きいため隠しています
+ 0 - 0
dist/static/css/chunk-016e5c0e.210d0353.css


ファイルの差分が大きいため隠しています
+ 0 - 0
dist/static/css/chunk-3ab88ac2.f7015c45.css


ファイルの差分が大きいため隠しています
+ 0 - 0
dist/static/css/chunk-ec3f0ab6.7672182a.css


ファイルの差分が大きいため隠しています
+ 0 - 0
dist/static/css/chunk-fa78c2d4.f7015c45.css


ファイルの差分が大きいため隠しています
+ 0 - 0
dist/static/js/app.fb10f0f7.js


ファイルの差分が大きいため隠しています
+ 0 - 0
dist/static/js/chunk-016e5c0e.ca18d88f.js


ファイルの差分が大きいため隠しています
+ 0 - 0
dist/static/js/chunk-01f26467.2aacedbb.js


ファイルの差分が大きいため隠しています
+ 0 - 0
dist/static/js/chunk-01f26467.e90a9006.js


ファイルの差分が大きいため隠しています
+ 0 - 0
dist/static/js/chunk-04ba33be.d32915c6.js


ファイルの差分が大きいため隠しています
+ 0 - 0
dist/static/js/chunk-04ba33be.ed4f0e8d.js


ファイルの差分が大きいため隠しています
+ 0 - 0
dist/static/js/chunk-3ab88ac2.e1447fa0.js


ファイルの差分が大きいため隠しています
+ 0 - 0
dist/static/js/chunk-5c6eee35.50ee6563.js


ファイルの差分が大きいため隠しています
+ 0 - 0
dist/static/js/chunk-5c6eee35.76a71000.js


ファイルの差分が大きいため隠しています
+ 0 - 0
dist/static/js/chunk-9f092188.8c495226.js


ファイルの差分が大きいため隠しています
+ 0 - 0
dist/static/js/chunk-def78608.adafde64.js


ファイルの差分が大きいため隠しています
+ 0 - 0
dist/static/js/chunk-e2fd4352.620a965c.js


ファイルの差分が大きいため隠しています
+ 0 - 0
dist/static/js/chunk-e90c2844.71e1b202.js


ファイルの差分が大きいため隠しています
+ 0 - 0
dist/static/js/chunk-e90c2844.f7efa576.js


ファイルの差分が大きいため隠しています
+ 0 - 0
dist/static/js/chunk-ec3f0ab6.95ae04ed.js


ファイルの差分が大きいため隠しています
+ 0 - 0
dist/static/js/chunk-fa78c2d4.b75ee132.js


+ 17 - 0
src/api/project.js

@@ -96,7 +96,24 @@ 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>
+  

+ 471 - 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
@@ -120,6 +125,17 @@
         <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"
@@ -127,14 +143,70 @@
         >
       </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 { searchList } from "@/api/document";
+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,
@@ -165,6 +237,11 @@ export default {
   },
   data() {
     return {
+      templateQueryForm: {
+        page: 1,
+        pageSize: 10,
+      },
+      templateTotal: 0, // 模版总数
       dialogVisible: false, //控制产品审核
       currentDataId: 0,
       recordCount: 0,
@@ -175,6 +252,19 @@ export default {
       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() {
@@ -196,24 +286,357 @@ export default {
         case "delete":
           this.btnDelete(command.row.id);
           break;
+        case "viewTemplates":
+          this.viewBoundTemplates(command.row);
+          break;
       }
     },
-    /* 选择模版 */
-    selTemplate(row) {
+    // Add new methods
+    // 查看已绑定模版
+    viewBoundTemplates(row) {
       this.currentData = row;
-      this.selectedTemplates = []; // 重置选择
-      searchList().then((res) => {
+      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.templateList = res.data.dataList;
-          this.templateDialogVisible = true;
-          // 如果需要默认选中某些行,可以在nextTick中处理
-          this.$nextTick(() => {
-            // 例如:this.$refs.templateTable.toggleRowSelection(this.templateList[0], true);
-          });
+          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(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;
     },
@@ -231,12 +654,22 @@ export default {
         return;
       }
 
-      // 这里添加保存模版选择的逻辑
-      console.log("Selected templates:", this.selectedTemplates);
-      console.log("Current project:", this.currentData);
+      // 将选中的模版对象数组转换为 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();
+      });
 
-      this.$message.success("模版选择成功");
-      this.closeTemplateDialog();
+      /*   // 这里添加保存模版选择的逻辑
+      console.log("Selected templates:", this.selectedTemplates);
+      console.log("Current project:", this.currentData); */
     },
 
     // 如果需要手动选中/取消选中某一行
@@ -334,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>

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません