ソースを参照

修改替换下载问题

yangg 7 ヶ月 前
コミット
27326de9d2

+ 1 - 1
.env.development

@@ -3,7 +3,7 @@ ENV = 'development'
 port = 8080
 # base api
 VUE_APP_BASE_AI_API='https://ylaiapi.raycos.com.cn'
-VUE_APP_BASE_API = 'http://192.168.66.163:8084'
+VUE_APP_BASE_API = 'http://192.168.66.187:8084'
 #192.168.66.187
 #朱:192.168.66.163
 #测试服务器:121.36.251.245

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


+ 0 - 1
dist/static/css/chunk-199c449c.0157d636.css

@@ -1 +0,0 @@
-.gpu-list[data-v-78a78f32]{padding:20px}.filter-section[data-v-78a78f32]{margin-bottom:20px}.ml-15[data-v-78a78f32]{margin-left:15px}.pagination[data-v-78a78f32]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end;margin-top:15px}.progress-wrapper[data-v-78a78f32],.status-wrapper[data-v-78a78f32]{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-transition:none!important;transition:none!important}.progress-wrapper[data-v-78a78f32]{padding:2px}.progress-wrapper[data-v-78a78f32] .el-progress{line-height:1;-webkit-transition:none!important;transition:none!important}.progress-wrapper[data-v-78a78f32] .el-progress .el-progress__text{font-size:12px!important;color:inherit;-webkit-transition:none!important;transition:none!important}.progress-wrapper[data-v-78a78f32] .el-progress .el-progress-circle{margin:0;-webkit-transition:none!important;transition:none!important}.progress-wrapper[data-v-78a78f32] .el-progress .el-progress-circle__path,.progress-wrapper[data-v-78a78f32] .el-progress .el-progress-circle__track,.progress-wrapper[data-v-78a78f32] .el-progress svg{-webkit-transition:none!important;transition:none!important}

+ 1 - 0
dist/static/css/chunk-1cf15dbe.7120a946.css

@@ -0,0 +1 @@
+.gpu-list[data-v-62483171]{padding:20px}.filter-section[data-v-62483171]{margin-bottom:20px}.ml-15[data-v-62483171]{margin-left:15px}.pagination[data-v-62483171]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end;margin-top:15px}.progress-wrapper[data-v-62483171],.status-wrapper[data-v-62483171]{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;-webkit-transition:none!important;transition:none!important}.progress-wrapper[data-v-62483171]{padding:2px}.progress-wrapper[data-v-62483171] .el-progress{line-height:1;-webkit-transition:none!important;transition:none!important}.progress-wrapper[data-v-62483171] .el-progress .el-progress__text{font-size:12px!important;color:inherit;-webkit-transition:none!important;transition:none!important}.progress-wrapper[data-v-62483171] .el-progress .el-progress-circle{margin:0;-webkit-transition:none!important;transition:none!important}.progress-wrapper[data-v-62483171] .el-progress .el-progress-circle__path,.progress-wrapper[data-v-62483171] .el-progress .el-progress-circle__track,.progress-wrapper[data-v-62483171] .el-progress svg{-webkit-transition:none!important;transition:none!important}

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


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


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


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


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


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


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


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


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


+ 1 - 1
src/views/login/aiIndex.vue

@@ -449,7 +449,7 @@ export default {
               user_id: this.phone,
               session_id: this.session_id || "",
               source: 'pc',
-              image_url: imageUrls,
+              image_urls: imageUrls,
               history_limit: 10
             }
           });

+ 68 - 46
src/views/middPage/list.vue

@@ -243,12 +243,19 @@
                 <template #content>
                   <div style="padding: 5px">
                     <div style="margin-top: 5px; font-size: 12px">
-                      {{ row.progress_msg || (row.processing_status === 3 ? "处理失败" : "正在处理中...") }}
+                      {{
+                        row.progress_msg ||
+                        (row.processing_status === 3
+                          ? "处理失败"
+                          : "正在处理中...")
+                      }}
                     </div>
                   </div>
                 </template>
                 <div class="status-wrapper">
-                  <template v-if="[1, 5].includes(Number(row.processing_status))">
+                  <template
+                    v-if="[1, 5].includes(Number(row.processing_status))"
+                  >
                     <el-tag type="warning">处理中</el-tag>
                   </template>
                   <template v-else>
@@ -481,7 +488,7 @@ export default {
   },
 
   created() {
-    // 使用抖处理数据加载
+    // 使用抖处理数据加载
     this.debouncedFetchGpuList = debounce(this.fetchGpuList, 300);
   },
 
@@ -511,7 +518,7 @@ export default {
         this.$message.error("获取分类列表失败");
       }
     },
-    // 添加导出处理方法
+    // 修改导出处理方法
     async handleExport() {
       try {
         const ids = this.selectedRows.map((row) => row.id);
@@ -523,36 +530,43 @@ export default {
           type: "warning",
         });
 
-        // 调用导出接口
-        const response = await axios({
-          url: `${process.env.VUE_APP_BASE_API}/literature-data/export/`,
-          method: "POST",
-          data: { ids },
-          responseType: "blob", // 指定响应类型为 blob
-        });
+        // 调用导出接口获取下载链接
+        const response = await axios.post(
+          `${process.env.VUE_APP_BASE_API}/literature-data/export/`,
+          { ids }
+        );
 
-        // 创建下载链接
-        const blob = new Blob([response.data], {
-          type: response.headers["content-type"],
-        });
-        const url = window.URL.createObjectURL(blob);
-        const link = document.createElement("a");
-        link.href = url;
-
-        // 从响应头获取文件名,如果没有则使用默认名称
-        const filename = response.headers["content-disposition"]
-          ? decodeURIComponent(
-              response.headers["content-disposition"].split("filename=")[1]
-            )
-          : "文献导出.xlsx";
-
-        link.setAttribute("download", filename);
-        document.body.appendChild(link);
-        link.click();
-        document.body.removeChild(link);
-        window.URL.revokeObjectURL(url);
-
-        this.$message.success("导出成功");
+        if (response.status === 200) {
+          const downloadUrl = response.data.data.data.download_url;
+
+          // 显示下载链接对话框
+          await this.$alert(
+            `<div>
+              <p>导出成功!请点击下方链接下载文件:</p>
+              <p style="word-break: break-all;">
+                <a href="${downloadUrl}" target="_blank">${downloadUrl}</a>
+              </p>
+              <p>您也可以复制链接后在新窗口打开</p>
+            </div>`,
+            "下载链接",
+            {
+              dangerouslyUseHTMLString: true,
+              confirmButtonText: "复制链接",
+              callback: (action) => {
+                if (action === "confirm") {
+                  // 复制链接到剪贴板
+                  const textarea = document.createElement("textarea");
+                  textarea.value = downloadUrl;
+                  document.body.appendChild(textarea);
+                  textarea.select();
+                  document.execCommand("copy");
+                  document.body.removeChild(textarea);
+                  this.$message.success("链接已复制到剪贴板");
+                }
+              },
+            }
+          );
+        }
       } catch (error) {
         if (error === "cancel") return;
         this.$message.error(error.message || "导出失败");
@@ -1067,22 +1081,22 @@ export default {
 
         // 更新数据和分页信息
         const { list, total, current_page, page_size } = response.data.data;
-        
+
         // 处理列表数据
-        this.gpuList = list.map(item => {
+        this.gpuList = list.map((item) => {
           const nameParts = item.name.split(".");
           return {
             ...item,
             name: nameParts[0],
             type: nameParts.length > 1 ? nameParts.pop() : "未知",
-            processing_status: Number(item.processing_status)
+            processing_status: Number(item.processing_status),
           };
         });
 
         // 获取需要查询详细状态的文献ID(处理中和处理失败的)
         const needStatusCheckIds = this.gpuList
-          .filter(item => [1, 3, 5].includes(Number(item.processing_status)))
-          .map(item => item.id);
+          .filter((item) => [1, 3, 5].includes(Number(item.processing_status)))
+          .map((item) => item.id);
 
         if (needStatusCheckIds.length > 0) {
           await this.fetchBatchStatus(needStatusCheckIds);
@@ -1103,21 +1117,28 @@ export default {
     // 修改批量获取状态的方法
     async fetchBatchStatus(ids) {
       try {
-        const statusPromises = ids.map(id => literResult(id));
+        const statusPromises = ids.map((id) => literResult(id));
         const responses = await Promise.allSettled(statusPromises);
-        
+
         responses.forEach((response, index) => {
-          if (response.status === 'fulfilled' && response.value.status === 200) {
+          if (
+            response.status === "fulfilled" &&
+            response.value.status === 200
+          ) {
             const id = ids[index];
-            const item = this.gpuList.find(item => item.id === id);
+            const item = this.gpuList.find((item) => item.id === id);
             if (item) {
               const literatureInfo = response.value.data.data.literature_info;
               const status = Number(literatureInfo.processing_status);
-              
+
               // 只更新处理中和处理失败的状态信息
               if ([1, 3, 5].includes(status)) {
                 this.$set(item, "processing_status", status);
-                this.$set(item, "progress_msg", literatureInfo.status_description);
+                this.$set(
+                  item,
+                  "progress_msg",
+                  literatureInfo.status_description
+                );
 
                 // 如果状态是处理中,启动轮询
                 if (status === 1 || status === 5) {
@@ -1159,7 +1180,8 @@ export default {
                 await this.fetchGpuList();
               } else if (newStatus === 3) {
                 // 处理失败 - 保存失败原因
-                const failureReason = literatureInfo.status_description || "处理失败";
+                const failureReason =
+                  literatureInfo.status_description || "处理失败";
                 this.$set(item, "progress_msg", failureReason);
                 this.$message.error(failureReason);
                 this.stopPolling(id);
@@ -1223,7 +1245,7 @@ export default {
             name: row.name,
             tag: JSON.stringify(row.tags),
             identifier: row.identifier,
-            type:row.type
+            type: row.type,
           },
         });
         // 这里可以添加详情处理逻辑

+ 378 - 101
src/views/project/components/dataList.vue

@@ -192,9 +192,17 @@
         <el-table-column prop="createTime" label="绑定时间" align="center" />
         <el-table-column label="操作" align="center" width="320">
           <template #default="scope">
-            <el-button type="primary" @click="previewTemplate(scope.row)">预览</el-button>
-            <el-button type="primary" @click="replaceTemplate(scope.row)">替换</el-button>
-            <el-dropdown style="margin-left: 10px;" trigger="click" @command="(command) => handleExport(command, scope.row)">
+            <el-button type="primary" @click="previewTemplate(scope.row)"
+              >预览</el-button
+            >
+            <el-button type="primary" @click="replaceTemplate(scope.row)"
+              >替换</el-button
+            >
+            <el-dropdown
+              style="margin-left: 10px"
+              trigger="click"
+              @command="(command) => handleExport(command, scope.row)"
+            >
               <el-button type="success">
                 导出文档<i class="el-icon-arrow-down el-icon--right"></i>
               </el-button>
@@ -202,6 +210,9 @@
                 <el-dropdown-menu>
                   <el-dropdown-item command="word">Word格式</el-dropdown-item>
                   <el-dropdown-item command="pdf">PDF格式</el-dropdown-item>
+                  <!-- <el-dropdown-item command="pdf-to-word"
+                    >PDF转Word</el-dropdown-item
+                  > -->
                 </el-dropdown-menu>
               </template>
             </el-dropdown>
@@ -259,7 +270,12 @@ import {
   calculate,
   getphases,
 } from "@/api/project";
-import { searchList, updateDocument, exportDocument,exportToPDF } from "@/api/document";
+import {
+  searchList,
+  updateDocument,
+  exportDocument,
+  exportToPDF,
+} from "@/api/document";
 import { searchCustomer } from "@/api/customer";
 import { searchProduct } from "@/api/product";
 /* import {searchTemplate} from "@/api/template" */
@@ -343,6 +359,57 @@ export default {
     this.init();
   },
   methods: {
+    // 添加 PDF 转 Word 的方法
+    async convertPDFToWord(pdfPath) {
+      try {
+        // 创建FormData对象
+        const formData = new FormData();
+        formData.append("oss_url", pdfPath);
+
+        // 调用后端转换接口
+        const res = await axios.post(
+          `${process.env.VUE_APP_BASE_API}/convert/pdftoword`,
+          formData,
+          {
+            headers: {
+              "Content-Type": "multipart/form-data", // 设置请求头为form-data
+            },
+          }
+        );
+
+        if (res.status === 200) {
+          // 将base64转换为Blob
+          const byteCharacters = atob(res.data.data.file_content);
+          const byteNumbers = new Array(byteCharacters.length);
+          for (let i = 0; i < byteCharacters.length; i++) {
+            byteNumbers[i] = byteCharacters.charCodeAt(i);
+          }
+          const byteArray = new Uint8Array(byteNumbers);
+          const blob = new Blob([byteArray], {
+            type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
+          });
+
+          // 创建下载链接
+          const link = document.createElement("a");
+          link.href = URL.createObjectURL(blob);
+          link.download = "converted.docx"; // 或使用服务器返回的文件名
+          document.body.appendChild(link);
+          link.click();
+          document.body.removeChild(link);
+
+          // 清理URL对象
+          URL.revokeObjectURL(link.href);
+
+          this.$message.success("转换成功");
+        } else {
+          throw new Error(res.data.message || "转换失败");
+        }
+      } catch (error) {
+        console.error("PDF转Word失败:", error);
+        this.$message.error("PDF转Word失败,请稍后重试");
+      }
+    },
+
     // 修改后的导出文档方法
     async exportDocument(template) {
       try {
@@ -357,64 +424,168 @@ export default {
         templateData.forEach((item) => {
           const templateSection = document.createElement("div");
           templateSection.className = "template-textarea";
-          templateSection.innerHTML = item.content;
-          contentContainer.appendChild(templateSection);
-        });
 
-        // 处理所有输入框
-        contentContainer
-          .querySelectorAll('input[type="text"]')
-          .forEach((input) => {
-            const span = document.createElement("span");
-            span.textContent = input.value;
-            input.parentNode.replaceChild(span, input);
+          // 创建临时div来解析HTML
+          const tempDiv = document.createElement("div");
+          tempDiv.innerHTML = item.content;
+
+          // 处理标题格式
+          tempDiv
+            .querySelectorAll("h1, h2, h3, h4, h5, h6")
+            .forEach((heading) => {
+              heading.style.fontWeight = "bold";
+              heading.style.margin = "1em 0";
+
+              switch (heading.tagName.toLowerCase()) {
+                case "h1":
+                  heading.style.fontSize = "24pt";
+                  break;
+                case "h2":
+                  heading.style.fontSize = "18pt";
+                  break;
+                case "h3":
+                  heading.style.fontSize = "14pt";
+                  break;
+                case "h4":
+                case "h5":
+                case "h6":
+                  heading.style.fontSize = "12pt";
+                  break;
+              }
+            });
+
+          // 处理表格
+          tempDiv.querySelectorAll("table").forEach((table) => {
+            // 设置表格属性
+            table.setAttribute("border", "1");
+            table.setAttribute("cellspacing", "0");
+            table.setAttribute("cellpadding", "5");
+            table.style.borderCollapse = "collapse";
+            table.style.width = "100%";
+            table.style.border = "1px solid black";
+
+            // 处理所有单元格
+            const cells = table.querySelectorAll("td, th");
+            cells.forEach((cell) => {
+              cell.style.border = "1px solid black";
+              cell.style.padding = "5px";
+              if (cell.tagName === "TH") {
+                cell.style.backgroundColor = "#f0f0f0";
+                cell.style.fontWeight = "bold";
+              }
+            });
           });
 
+          templateSection.innerHTML = tempDiv.innerHTML;
+          contentContainer.appendChild(templateSection);
+        });
+
         // 构建HTML内容
-        let contentData = `<!DOCTYPE html>
-        <html>
-          <head>
-            <meta charset="UTF-8">
-            <style>
-              @page {
-                size: A4;
-                margin: 2cm;
-              }
-              body {
-                font-family: Arial, sans-serif;
-                font-size: 12pt;
-                line-height: 1.5;
-              }
-              table {
-                width: 100%;
-                border-collapse: collapse;
-              }
-              td, th {
-                border: 1px solid black;
-                padding: 5px;
-              }
-              .template-textarea {
-                page-break-after: always;
-              }
-              .TOC {
-                display: none !important;
-              }
-            </style>
-          </head>
-          <body>
-            ${contentContainer.innerHTML}
-          </body>
+        let contentData = `
+        <!DOCTYPE html>
+        <html xmlns:o='urn:schemas-microsoft-com:office:office' 
+              xmlns:w='urn:schemas-microsoft-com:office:word'
+              xmlns='http://www.w3.org/TR/REC-html40'>
+        <head>
+          <meta charset="UTF-8">
+          <title>${template.title}</title>
+          <xml>
+            <w:WordDocument>
+              <w:View>Print</w:View>
+              <w:Zoom>100</w:Zoom>
+              <w:DoNotOptimizeForBrowser/>
+            </w:WordDocument>
+          </xml>
+          <style>
+            /* 重置样式 */
+            * {
+              margin: 0;
+              padding: 0;
+              box-sizing: border-box;
+            }
+            
+            /* 页面设置 */
+            @page {
+              size: A4;
+              margin: 2cm;
+            }
+            
+            /* 基本样式 */
+            body {
+              font-family: "Times New Roman", SimSun, serif;
+              font-size: 12pt;
+              line-height: 1.5;
+            }
+            
+            /* 标题样式 */
+            h1 { font-size: 24pt; margin: 1em 0; }
+            h2 { font-size: 18pt; margin: 1em 0; }
+            h3 { font-size: 14pt; margin: 1em 0; }
+            h4, h5, h6 { font-size: 12pt; margin: 1em 0; }
+            
+            /* 表格样式 */
+            table {
+              width: 100%;
+              border-collapse: collapse;
+              border: 1px solid black;
+              margin: 1em 0;
+              mso-table-lspace: 0pt;
+              mso-table-rspace: 0pt;
+              mso-border-alt: solid windowtext .5pt;
+            }
+            
+            td, th {
+              border: 1px solid black;
+              padding: 5px;
+              mso-border-alt: solid windowtext .5pt;
+            }
+            
+            th {
+              background-color: #f0f0f0;
+              font-weight: bold;
+            }
+            
+            /* Word特定样式 */
+            .MsoNormal {
+              margin-bottom: .0001pt;
+              text-align: justify;
+              text-justify: inter-ideograph;
+            }
+            
+            /* 分页控制 */
+            .template-textarea {
+              page-break-after: always;
+            }
+            
+            /* 隐藏目录 */
+            .TOC {
+              display: none !important;
+            }
+          </style>
+          <!--[if gte mso 9]>
+          <xml>
+            <w:WordDocument>
+              <w:View>Print</w:View>
+              <w:Zoom>100</w:Zoom>
+              <w:DoNotOptimizeForBrowser/>
+            </w:WordDocument>
+          </xml>
+          <![endif]-->
+        </head>
+        <body>
+          ${contentContainer.innerHTML}
+        </body>
         </html>`;
 
-       /*  // 处理表格样式
-        contentData = contentData
-          .replaceAll("<table ", `<table style="border-collapse: collapse;" `)
-          .replaceAll("<td>", `<td style="border: 1px solid black;">`); */
-
         // 调用导出接口
         const res = await exportDocument({
           content: contentData,
           title: template.title,
+          options: {
+            format: "docx",
+            encoding: "utf-8",
+            preserveStyles: true,
+          },
         });
 
         if (res.status !== 200) {
@@ -425,7 +596,7 @@ export default {
         // 处理文件下载
         const link = document.createElement("a");
         link.href = res.data.file_path;
-        link.download = res.data.file_name;
+        link.download = `${template.title}.docx`;
         document.body.appendChild(link);
         link.click();
         document.body.removeChild(link);
@@ -1025,7 +1196,8 @@ export default {
     selTemplate(row) {
       this.currentData = row;
       this.selectedTemplates = []; // 重置选择
-      this.templateQueryForm.page = 1; // 重置页码
+      this.selectedTemplateIds = new Set(row.documentIds || []); // 初始化已绑定的模版ID
+      this.templateQueryForm.page = 1; // 重置���码
       this.loadTemplateList();
     },
     // 处理模版分页变化
@@ -1058,10 +1230,12 @@ export default {
               const currentPageSelected = this.selectedPageIds.get(currentPage);
 
               this.templateList.forEach((row) => {
-                const shouldSelect = 
-                  this.isAllSelected || 
+                const shouldSelect =
+                  this.isAllSelected ||
                   (currentPageSelected && currentPageSelected.has(row.id)) ||
-                  this.selectedTemplateIds.has(row.id);
+                  this.selectedTemplateIds.has(row.id) ||
+                  (this.currentData.documentIds &&
+                    this.currentData.documentIds.includes(row.id));
 
                 if (shouldSelect) {
                   this.$refs.templateTable.toggleRowSelection(row, true);
@@ -1089,8 +1263,8 @@ export default {
       // 如果之前是全选状态,现在取消了某些选择
       if (this.isAllSelected && selection.length < this.templateList.length) {
         this.isAllSelected = false;
-        // 保留其他页面的选择,仅更新当前页
-        const allSelectedIds = new Set();
+        // 保留其他页面的选择和已绑定的模版
+        const allSelectedIds = new Set(this.currentData.documentIds || []);
         this.selectedPageIds.forEach((pageIds, page) => {
           if (page !== currentPage) {
             pageIds.forEach((id) => allSelectedIds.add(id));
@@ -1100,11 +1274,13 @@ export default {
         currentPageIds.forEach((id) => allSelectedIds.add(id));
         this.selectedTemplateIds = allSelectedIds;
       } else {
-        // 正常选择模式,直接更新选中状态
+        // 正常选择模式,保留已绑定的模版
+        const existingIds = new Set(this.currentData.documentIds || []);
         this.templateList.forEach((item) => {
           if (currentPageIds.includes(item.id)) {
             this.selectedTemplateIds.add(item.id);
-          } else {
+          } else if (!existingIds.has(item.id)) {
+            // 只删除非已绑定的模版
             this.selectedTemplateIds.delete(item.id);
           }
         });
@@ -1116,22 +1292,24 @@ export default {
       const currentPage = this.templateQueryForm.page;
 
       if (selection.length > 0) {
-        this.$confirm('是否选择所有页的模版?', '提示', {
-          confirmButtonText: '全部选择',
-          cancelButtonText: '仅选择当前页',
-          type: 'info'
-        }).then(() => {
-          // 选择所有页
-          this.selectAllPages();
-        }).catch(() => {
-          // 仅选择当前页
-          this.selectCurrentPage(selection);
-          // 更新当前页的选择记录
-          this.selectedPageIds.set(
-            currentPage,
-            new Set(selection.map((item) => item.id))
-          );
-        });
+        this.$confirm("是否选择所有页的模版?", "提示", {
+          confirmButtonText: "全部选择",
+          cancelButtonText: "仅选择当前页",
+          type: "info",
+        })
+          .then(() => {
+            // 选择所有页
+            this.selectAllPages();
+          })
+          .catch(() => {
+            // 仅选择当前页
+            this.selectCurrentPage(selection);
+            // 更新当前页的选择记录
+            this.selectedPageIds.set(
+              currentPage,
+              new Set(selection.map((item) => item.id))
+            );
+          });
       } else {
         // 取消全选
         this.isAllSelected = false;
@@ -1160,8 +1338,8 @@ export default {
 
         this.$message.success(`已选择全部 ${this.templateTotal} 条数据`);
       } catch (error) {
-        console.error('获取所有模版失败:', error);
-        this.$message.error('获取所有模版失败');
+        console.error("获取所有模版失败:", error);
+        this.$message.error("获取所有模版失败");
       }
     },
 
@@ -1182,7 +1360,7 @@ export default {
       const params = {
         ...this.templateQueryForm,
         page: 1,
-        pageSize
+        pageSize,
       };
 
       try {
@@ -1192,7 +1370,7 @@ export default {
         }
         return [];
       } catch (error) {
-        console.error('获取所有模版失败:', error);
+        console.error("获取所有模版失败:", error);
         return [];
       }
     },
@@ -1200,16 +1378,20 @@ export default {
     // 确认选择
     confirmTemplateSelection() {
       if (this.selectedTemplateIds.size === 0) {
-        this.$message.warning('请至少选择一个模版');
+        this.$message.warning("请至少选择一个模版");
         return;
       }
-
+      // 合并已有的文档ID和新选择的ID
+      const allDocIds = new Set([
+        ...(this.currentData.documentIds || []),
+        ...this.selectedTemplateIds,
+      ]);
       relate({
         project_id: this.currentData.id,
-        doc_ids: JSON.stringify([...this.selectedTemplateIds])
+        doc_ids: JSON.stringify([...allDocIds]),
       }).then((res) => {
         if (res.status !== 200) return;
-        this.$message.success('模版关联成功');
+        this.$message.success("模版关联成功");
         this.closeTemplateDialog();
         this.search();
       });
@@ -1441,7 +1623,7 @@ export default {
         this.pageTotal = res.data.totalPage;
       } catch (err) {
         console.error("搜索失败:", err);
-        this.$message.error("获取项目列表失��");
+        this.$message.error("获取项目列表失");
       }
     },
     //修改分页
@@ -1473,17 +1655,108 @@ export default {
       });
     },
     // 新增:处理导出命令
-    handleExport(command, row) {
+    async handleExport(command, row) {
       switch (command) {
-        case 'word':
-          this.exportDocument(row);
+        case "word":
+          await this.exportDocument(row);
           break;
-        case 'pdf':
-          this.exportToPDF(row);
+        case "pdf":
+          await this.exportToPDF(row);
+          break;
+        case "pdf-to-word": // 添加新的命令类型
+          const pdfResult = await this.exportToPDF_url(row);
+          if (pdfResult && pdfResult.file_path) {
+            await this.convertPDFToWord(pdfResult.file_path);
+          }
           break;
       }
     },
+    /* 过去pdf链接 */
+    async exportToPDF_url(template) {
+      try {
+        // 解析模版内容
+        const templateData = JSON.parse(template.data || "[]");
+
+        // 创建一个临时的容器来存放内容
+        const contentContainer = document.createElement("div");
+        contentContainer.className = "content";
+
+        // 处理每个模版段落
+        templateData.forEach((item) => {
+          const templateSection = document.createElement("div");
+          templateSection.className = "template-textarea";
+          templateSection.innerHTML = item.content;
+          contentContainer.appendChild(templateSection);
+        });
+
+        // 处理所有输入框
+        contentContainer
+          .querySelectorAll('input[type="text"]')
+          .forEach((input) => {
+            const span = document.createElement("span");
+            span.textContent = input.value;
+            input.parentNode.replaceChild(span, input);
+          });
+
+        // 构建HTML内容
+        let contentData = `<!DOCTYPE html>
+        <html>
+          <head>
+            <meta charset="UTF-8">
+            <style>
+              @page {
+                size: A4;
+                margin: 2cm;
+              }
+              body {
+                font-family: Arial, sans-serif;
+                font-size: 12pt;
+                line-height: 1.5;
+              }
+              table {
+                width: 100%;
+                border-collapse: collapse;
+              }
+              td, th {
+                border: 1px solid black;
+                padding: 5px;
+              }
+              .template-textarea {
+                page-break-after: always;
+              }
+              .TOC {
+                display: none !important;
+              }
+            </style>
+          </head>
+          <body>
+            ${contentContainer.innerHTML}
+          </body>
+        </html>`;
+
+        /*    // 处理表格样式
+        contentData = contentData
+          .replaceAll("<table ", `<table style="border-collapse: collapse;" `)
+          .replaceAll("<td>", `<td style="border: 1px solid black;">`); */
 
+        // 调用导出接口,指定导出类型为 PDF
+        const res = await exportToPDF({
+          content: contentData,
+          title: template.title,
+        });
+
+        if (res.status !== 200) {
+          this.$alert(res.errMsg);
+          return;
+        }
+
+        // 处理文件下载
+        return res.data; // 返回结果,包含 file_path
+      } catch (error) {
+        console.error("导出PDF失败:", error);
+        this.$alert("导出PDF时发生错误,请稍后重试。");
+      }
+    },
     // 添加新的 exportToPDF 方法
     async exportToPDF(template) {
       try {
@@ -1503,11 +1776,13 @@ export default {
         });
 
         // 处理所有输入框
-        contentContainer.querySelectorAll('input[type="text"]').forEach((input) => {
-          const span = document.createElement("span");
-          span.textContent = input.value;
-          input.parentNode.replaceChild(span, input);
-        });
+        contentContainer
+          .querySelectorAll('input[type="text"]')
+          .forEach((input) => {
+            const span = document.createElement("span");
+            span.textContent = input.value;
+            input.parentNode.replaceChild(span, input);
+          });
 
         // 构建HTML内容
         let contentData = `<!DOCTYPE html>
@@ -1545,7 +1820,7 @@ export default {
           </body>
         </html>`;
 
-     /*    // 处理表格样式
+        /*    // 处理表格样式
         contentData = contentData
           .replaceAll("<table ", `<table style="border-collapse: collapse;" `)
           .replaceAll("<td>", `<td style="border: 1px solid black;">`); */
@@ -1570,6 +1845,7 @@ export default {
         document.body.removeChild(link);
 
         this.$message.success("导出成功");
+        return res.data; // 返回结果,包含 file_path
       } catch (error) {
         console.error("导出PDF失败:", error);
         this.$alert("导出PDF时发生错误,请稍后重试。");
@@ -1621,15 +1897,16 @@ export default {
 // 添加PDF导出相关样式
 .content {
   background: white;
-  
+
   .template-textarea {
     page-break-after: always;
-    
+
     table {
       width: 100%;
       border-collapse: collapse;
-      
-      td, th {
+
+      td,
+      th {
         border: 1px solid black;
         padding: 5px;
       }

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