Sfoglia il codice sorgente

修改部分页面

yangg 7 mesi fa
parent
commit
a1f0d544a2

File diff suppressed because it is too large
+ 0 - 0
dist/index.html


File diff suppressed because it is too large
+ 0 - 0
dist/static/css/app.49dde997.css


+ 0 - 1
dist/static/css/chunk-048e3da4.680f8f1c.css

@@ -1 +0,0 @@
-.header[data-v-67e46fa8]{margin-left:20px}.header h2[data-v-67e46fa8]{margin-bottom:0}.center[data-v-67e46fa8]{margin:10px}.center[data-v-67e46fa8] .el-form--inline .el-form-item{display:block}.center[data-v-67e46fa8] .el-input-number--medium{width:150px}.footer[data-v-67e46fa8]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end;margin:0 100px 20px}[data-v-67e46fa8] .avatar-uploader .el-upload{border:1px dashed #d9d9d9;border-radius:6px;cursor:pointer;position:relative;overflow:hidden}.avatar-uploader .el-upload[data-v-67e46fa8]:hover{border-color:#409eff}.avatar-uploader-icon[data-v-67e46fa8]{font-size:28px;color:#8c939d;width:100px;height:100px;line-height:100px;text-align:center}.avatar[data-v-67e46fa8]{width:100px;height:100px;display:block}.hint[data-v-67e46fa8]{font-size:12px;color:#c0c4cc}

+ 1 - 0
dist/static/css/chunk-7f7cce74.65cd4a82.css

@@ -0,0 +1 @@
+.header[data-v-37483169]{margin-left:20px}.header h2[data-v-37483169]{margin-bottom:0}.center[data-v-37483169]{margin:10px}.center[data-v-37483169] .el-form--inline .el-form-item{display:block}.center[data-v-37483169] .el-input-number--medium{width:150px}.footer[data-v-37483169]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end;margin:0 100px 20px}[data-v-37483169] .avatar-uploader .el-upload{border:1px dashed #d9d9d9;border-radius:6px;cursor:pointer;position:relative;overflow:hidden}.avatar-uploader .el-upload[data-v-37483169]:hover{border-color:#409eff}.avatar-uploader-icon[data-v-37483169]{font-size:28px;color:#8c939d;width:100px;height:100px;line-height:100px;text-align:center}.avatar[data-v-37483169]{width:100px;height:100px;display:block}.hint[data-v-37483169]{font-size:12px;color:#c0c4cc}

File diff suppressed because it is too large
+ 0 - 0
dist/static/js/app.350932ac.js


File diff suppressed because it is too large
+ 0 - 0
dist/static/js/app.c6a197ac.js


File diff suppressed because it is too large
+ 0 - 0
dist/static/js/chunk-048e3da4.0a9afdb4.js


File diff suppressed because it is too large
+ 0 - 0
dist/static/js/chunk-2d22d9b8.3f135adb.js


File diff suppressed because it is too large
+ 0 - 0
dist/static/js/chunk-7f7cce74.ad93f6ec.js


File diff suppressed because it is too large
+ 0 - 0
dist/static/js/chunk-libs.5d84fd37.js


+ 80 - 3
src/components/FilePreview/index.vue

@@ -26,7 +26,11 @@
       <div v-else-if="isWord && !wordLoaded" class="loading-word">
         {{ wordLoadingError ? "加载失败,请重试" : "加载中..." }}
       </div>
-
+      <div
+        v-if="isExcel"
+        id="luckysheet"
+        style="width: 100%; height: 822px"
+      ></div>
       <!-- 其他 Office 文档预览 -->
       <iframe
         v-else-if="isOffice"
@@ -51,6 +55,8 @@
   <script>
 import { searchTaskInfo } from "@/api/knowledge";
 import mammoth from "mammoth";
+// 添加 LuckyExcel 导入
+import LuckyExcel from "luckyexcel";
 export default {
   props: {
     fileUrl: {
@@ -110,8 +116,11 @@ export default {
     isWord() {
       return ["doc", "docx"].includes(this.fileType);
     },
+    isExcel() {
+      return ["xls", "xlsx"].includes(this.fileType);
+    },
     isOffice() {
-      return ["xls", "xlsx", "ppt", "pptx"].includes(this.fileType);
+      return [ "ppt", "pptx"].includes(this.fileType);
     },
   },
   mounted() {
@@ -119,6 +128,8 @@ export default {
       this.loadTextContent();
     } else if (this.isWord) {
       this.loadWordContent();
+    } else if (this.isExcel) {
+      this.loadExcelContent();
     }
   },
   methods: {
@@ -172,6 +183,72 @@ export default {
         reader.readAsArrayBuffer(blob);
       });
     },
+    async loadTextContent() {
+      try {
+        // 获取文档 URL
+        if (this.type !== "ai") {
+          const res = await searchTaskInfo({
+            page: 1,
+            page_size: 10,
+            document_id: this.$route.query.id,
+          });
+          this.internalFileUrl = res.data.documentUrl;
+        } else {
+          this.internalFileUrl = localStorage.getItem("href");
+        }
+
+        // 获取文本内容
+        const response = await fetch(this.internalFileUrl);
+        if (!response.ok) {
+          throw new Error(`HTTP error! status: ${response.status}`);
+        }
+
+        this.textContent = await response.text();
+      } catch (error) {
+        console.error("加载文本内容时出错:", error);
+        this.textContent = `无法加载文本内容: ${error.message}`;
+        this.$message.error(`文本加载失败: ${error.message}`);
+      }
+    },
+    async loadExcelContent() {
+      try {
+        // 获取文档 URL
+        if (this.type !== "ai") {
+          const res = await searchTaskInfo({
+            page: 1,
+            page_size: 10,
+            document_id: this.$route.query.id,
+          });
+          this.internalFileUrl = res.data.documentUrl;
+        } else {
+          this.internalFileUrl = localStorage.getItem("href");
+        }
+
+        const response = await fetch(this.internalFileUrl);
+        const blob = await response.blob();
+
+        // 使用 LuckyExcel 加载文件
+        LuckyExcel.transformExcelToLucky(blob, function (exportJson) {
+          if (exportJson.sheets == null || exportJson.sheets.length === 0) {
+            alert("Failed to load Excel file");
+            return;
+          }
+
+          // 初始化 luckysheet
+          luckysheet.create({
+            container: "luckysheet",
+            data: exportJson.sheets,
+            // 设置只读模式
+            showinfobar: false,
+            allowEdit: false,
+            showtoolbar: false,
+          });
+        });
+      } catch (error) {
+        console.error("加载Excel文档内容时出错:", error);
+        this.$message.error(`Excel文档加载失败: ${error.message}`);
+      }
+    },
   },
 };
 </script>
@@ -179,7 +256,7 @@ export default {
   <style scoped>
 .file-preview {
   width: 100%;
-  height: 830px;
+  height: 850px;
   overflow: auto;
 }
 .unsupported-file {

+ 49 - 34
src/components/webAi/js/ChatBox.js

@@ -822,19 +822,18 @@ handleLinkClick(event) {
       const wsUrl =`${process.env.VUE_APP_BASE_API}/chatbot/chat`//this.wsUrl//'http://58.246.234.210:7860/api/v1/run/3ef7369e-b617-40d2-9e2e-54f3ee76ed2e?stream=false'
       let message = this.userInput.trim();
       if (message) {
-        // Markdown换行:在每个换行符之前添加两个空格
-        message = message.replace(/(\r\n|\r|\n)/g, "  \n");
+        // 添加用户消息
+      this.messages.push({
+        time: Date.now(),
+        message: message.replace(/(\r\n|\r|\n)/g, "  \n"),
+        messageType: "TEXT",
+        user: "user",
+        done: true,
+      });
+      this.userInput = "";
 
-        this.messages.push({
-          time: Date.now(),
-          message: message,
-          messageType: "TEXT",
-          user: "user",
-          done: true,
-        });
-        this.userInput = "";
-       // 添加机器人的响应消息(初始为空)
-       const botMessage = {
+      // 添加机器人的响应消息
+      const botMessage = {
         user: "bot",
         messageType: "TEXT",
         message: "",
@@ -844,8 +843,12 @@ handleLinkClick(event) {
       };
       this.messages.push(botMessage);
 
-      // 开始模拟思考
-      const thinkingPromise = this.simulateThinking(botMessage);
+      // 开始思考动画
+      const thinkingController = new AbortController();
+      const thinkingPromise = this.simulateThinking(
+        botMessage,
+        thinkingController.signal
+      );
         // 通过 HTTP 发送消息
         try {
           ///send-message
@@ -878,6 +881,9 @@ handleLinkClick(event) {
               } */,
             }),
           });
+           // 停止思考动画
+        thinkingController.abort();
+        await thinkingPromise;
           const result = await response.json();
           this.session_id=result.data.session_id
           console.log(result.status);
@@ -950,29 +956,38 @@ handleLinkClick(event) {
         }
       }
     },
-    async simulateThinking(message) {
+    async simulateThinking(message, signal) {
       this.isThinking = true;
-      const thinkingTime = Math.random() * 1000 + 500; // 思考时间为 0.5-1.5 秒
       const dotInterval = 200; // 每 200ms 更新一次点
-      
-      const updateDots = () => {
-        this.thinkingDots += '.';
-        if (this.thinkingDots.length > 3) {
-          this.thinkingDots = '';
-        }
-        message.message =  this.thinkingDots;
-      };
-  
-      const intervalId = setInterval(updateDots, dotInterval);
-  
-      await new Promise(resolve => setTimeout(resolve, thinkingTime));
-      
-      clearInterval(intervalId);
-      this.isThinking = false;
-      this.thinkingDots = '';
-      message.message = '';
+
+      return new Promise((resolve) => {
+        const updateDots = () => {
+          if (signal.aborted) {
+            clearInterval(intervalId);
+            this.isThinking = false;
+            this.thinkingDots = "";
+            message.message = "";
+            resolve();
+            return;
+          }
+
+          this.thinkingDots += ".";
+          if (this.thinkingDots.length > 3) {
+            this.thinkingDots = "";
+          }
+          message.message = "思考中" + this.thinkingDots;
+        };
+
+        const intervalId = setInterval(updateDots, dotInterval);
+        signal.addEventListener("abort", () => {
+          clearInterval(intervalId);
+          this.isThinking = false;
+          this.thinkingDots = "";
+          message.message = "";
+          resolve();
+        });
+      });
     },
-  
     async handleResponse(value, existingMessage) {
       const data = value.data.answer//outputs[0].outputs[0].results.message;
       

+ 12 - 21
src/views/appConfig/index.vue

@@ -131,12 +131,13 @@
               >
                 <el-option label="全部" value="all"></el-option>
                 <el-option
-                  v-for="(doc, index) in documentList"
+                  v-for="(doc) in documentList"
                   :label="doc.name"
                   :value="doc.id"
-                  :key="index"
+                  :key="`doc-${doc.id}`"
                 ></el-option>
               </el-select>
+              <div class="hint">请输入文档名称搜索</div>
             </el-form-item>
           </el-col>
         </el-row>
@@ -581,12 +582,13 @@
               >
                 <el-option label="全部" value="all"></el-option>
                 <el-option
-                  v-for="(doc, index) in editDocumentList"
+                  v-for="(doc) in editDocumentList"
                   :label="doc.name"
                   :value="doc.id"
-                  :key="index"
+                  :key="`edit-doc-${doc.id}`"
                 ></el-option>
               </el-select>
+              <div class="hint">请输入文档名称搜索</div>
             </el-form-item>
           </el-col>
         </el-row>
@@ -1711,7 +1713,7 @@ export default {
         } else {
           // 文档模式:加载所有文档
           if (this.editForm.knowledge_base_names.length > 0) {
-            this.loadAllEditDocuments(this.editForm.knowledge_base_names[0]);
+            //this.loadAllEditDocuments(this.editForm.knowledge_base_names[0]);
           }
         }
       }
@@ -1772,7 +1774,7 @@ export default {
         } else {
           // 文档模式:加载所有文档
           if (this.AIform.knowledge_base_names.length > 0) {
-            this.loadAllDocuments(this.AIform.knowledge_base_names[0]);
+            //this.loadAllDocuments(this.AIform.knowledge_base_names[0]);
           }
         }
       }
@@ -1807,23 +1809,12 @@ export default {
         // 如果选择了"全部",则清空其他选项
         this.editForm.documents = ["all"];
       } else {
-        // 获取当前选中的文档ID列表
-        const currentSelectedIds = new Set(selectedValues);
-
-        // 保留原有未删除的文档
-        const originalDocs = this.editForm.tempDocuments.filter(
-          (doc) => !doc.startsWith("temp_") && !currentSelectedIds.has(doc)
-        );
-
-        // 合并原有文档和新选择的文档
-        const allDocs = [...originalDocs, ...selectedValues];
-
-        // 如果总数超过10个,保留最新的文档
-        if (allDocs.length > 10) {
-          this.editForm.documents = allDocs.slice(-10);
+        // 直接使用新的选择值,不再保留原有文档
+        if (selectedValues.length > 10) {
+          this.editForm.documents = selectedValues.slice(0, 10);
           this.$message.warning("最多只能选择10个文档");
         } else {
-          this.editForm.documents = allDocs;
+          this.editForm.documents = selectedValues;
         }
       }
     },

+ 54 - 0
src/views/middPage/list.vue

@@ -118,6 +118,12 @@
             :disabled="selectedRows.length == 0"
             >批量绑定标签</el-button
           >
+          <el-button
+            type="primary"
+            @click="handleExport"
+            :disabled="selectedRows.length == 0"
+            >文件导出</el-button
+          >
         </div>
         <!-- 数据表格区域 -->
         <el-table
@@ -353,6 +359,54 @@ export default {
   },
 
   methods: {
+    // 添加导出处理方法
+    async handleExport() {
+      try {
+        const ids = this.selectedRows.map((row) => row.id);
+
+        // 添加确认对话框
+        await this.$confirm("确认要导出选中的文献吗?", "提示", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning",
+        });
+
+        // 调用导出接口
+        const response = await axios({
+          url: `${process.env.VUE_APP_BASE_API}/literature-data/export/`,
+          method: "POST",
+          data: { ids },
+          responseType: "blob", // 指定响应类型为 blob
+        });
+
+        // 创建下载链接
+        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("导出成功");
+      } catch (error) {
+        if (error === "cancel") return;
+        this.$message.error(error.message || "导出失败");
+        console.error("导出错误:", error);
+      }
+    },
     /* 重复处理 */
     async handleReprocess(row) {
       try {

+ 680 - 0
src/views/project/ProjectInput.vue

@@ -0,0 +1,680 @@
+<!-- 项目输入页面 -->
+<template>
+    <div class="project-input-container">
+      <!-- 从原代码中移动第一个 tab-pane 的内容 -->
+      <div class="project-selector">
+        <el-form :inline="true">
+          <el-form-item label="当前项目">
+            <el-select
+              disabled
+              v-model="selectedProjectId"
+              placeholder="请选择项目"
+              style="width: 300px"
+              @change="handleProjectConfirm"
+            >
+              <el-option
+                v-for="project in projectOptions"
+                :key="project.id"
+                :label="project.name"
+                :value="project.id"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item>
+            <el-button
+              type="primary"
+              @click="handleAddGspr"
+              v-if="!hasExistingData"
+              >创建输入项目</el-button
+            >
+            <el-button type="primary" @click="handleUpdateGspr" v-else
+              >更新输入项目</el-button
+            >
+            <el-button type="danger" @click="handleDeleteItems">删除</el-button>
+          </el-form-item>
+        </el-form>
+      </div>
+      <!-- 使用 v-if 控制其余内容的显示 -->
+      <template v-if="selectedProjectId">
+        <div class="operation-bar">
+          <el-form :inline="true" :model="gsprSearchForm" class="search-form">
+            <el-form-item label="内容">
+              <el-input
+                v-model="gsprSearchForm.keyword"
+                placeholder="请输入项目名称/技术报告位置"
+                style="width: 300px"
+                clearable
+              />
+            </el-form-item>
+            <el-form-item label="部门">
+              <el-select
+                v-model="gsprSearchForm.department"
+                placeholder="请选择部门"
+                clearable
+              >
+                <el-option
+                  v-for="(item, index) in departList"
+                  :key="index"
+                  :label="item.label"
+                  :value="item.label"
+                />
+              </el-select>
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" @click="handleGsprSearch">搜索</el-button>
+              <el-button @click="handleGsprReset">重置</el-button>
+            </el-form-item>
+          </el-form>
+        </div>
+  
+        <el-table
+          :data="gsprTableData"
+          style="width: 100%"
+          :default-sort="{ prop: sort_field, order: sort_order }"
+          @sort-change="handleSortChange"
+        >
+          <el-table-column prop="name" label="项目名称" sortable="custom" />
+          <el-table-column prop="tech_report_location" label="技术报告位置" />
+          <el-table-column prop="department" label="部门" sortable="custom" />
+          <el-table-column prop="content" label="内容">
+            <template slot-scope="scope">
+              <div style="display: flex; align-items: center">
+                <span style="margin-right: 10px">{{ scope.row.content }}</span>
+                <i
+                  class="el-icon-edit"
+                  @click="handleEditContent(scope.row)"
+                  style="cursor: pointer; margin-left: 5px"
+                ></i>
+              </div>
+            </template>
+          </el-table-column>
+          <el-table-column label="操作" width="200">
+            <template slot-scope="scope">
+              <el-button type="primary" @click="handleEditGspr(scope.row)"
+                >编辑</el-button
+              >
+              <el-button type="danger" @click="handleDeleteGspr(scope.row)"
+                >删除</el-button
+              >
+            </template>
+          </el-table-column>
+        </el-table>
+  
+        <div class="pagination-container">
+          <el-pagination
+            @size-change="handleGsprSizeChange"
+            @current-change="handleGsprCurrentChange"
+            :current-page="gsprPageForm.page"
+            :page-sizes="[10, 20, 30, 50]"
+            :page-size="gsprPageForm.page_size"
+            layout="total, sizes, prev, pager, next, jumper"
+            :total="gsprTotal"
+          />
+        </div>
+  
+        <!-- GSPR表单弹窗 -->
+        <el-dialog
+          :title="gsprDialogTitle"
+          :visible.sync="gsprDialogVisible"
+          width="700px"
+        >
+          <el-form
+            :model="gsprForm"
+            ref="gsprForm"
+            :rules="gsprRules"
+            label-width="120px"
+          >
+            <el-form-item label="名称" prop="name">
+              <el-input v-model="gsprForm.name" />
+            </el-form-item>
+            <el-form-item label="技术报告位置" prop="tech_report_location">
+              <el-input v-model="gsprForm.tech_report_location" />
+            </el-form-item>
+  
+            <el-form-item label="部门" prop="department">
+              <el-select v-model="gsprForm.department" placeholder="请选择状态">
+                <el-option
+                  v-for="(item, index) in departList"
+                  :key="index"
+                  :label="item.label"
+                  :value="item.label"
+                />
+              </el-select>
+            </el-form-item>
+            <el-form-item label="内容" prop="content">
+              <el-input
+                type="textarea"
+                v-model="gsprForm.content"
+                :rows="4"
+                placeholder="请输入内容"
+              />
+            </el-form-item>
+          </el-form>
+          <div slot="footer">
+            <el-button @click="gsprDialogVisible = false">取消</el-button>
+            <el-button type="primary" @click="handleGsprSubmit">确定</el-button>
+          </div>
+        </el-dialog>
+        <!-- 添加内容编辑弹窗 -->
+        <el-dialog
+          title="编辑内容"
+          :visible.sync="contentDialogVisible"
+          width="600px"
+        >
+          <el-form :model="contentForm" ref="contentForm">
+            <el-form-item label="内容" prop="content">
+              <el-input
+                type="textarea"
+                v-model="contentForm.content"
+                :rows="4"
+                placeholder="请输入内容"
+              />
+            </el-form-item>
+          </el-form>
+          <div slot="footer">
+            <el-button @click="contentDialogVisible = false">取消</el-button>
+            <el-button type="primary" @click="handleContentSubmit"
+              >确定</el-button
+            >
+          </div>
+        </el-dialog>
+      </template>
+  
+      <!-- 未选择项目时显示提示 -->
+      <el-empty
+        v-else
+        description="请先选择一个项目"
+        :image-size="200"
+      ></el-empty>
+  
+      <!-- 添加删除确认对话框 -->
+      <el-dialog
+        title="删除确认"
+        :visible.sync="deleteDialogVisible"
+        width="400px"
+      >
+        <!-- 添加提示信息区域 -->
+        <div class="delete-warning" style="margin-bottom: 20px">
+          <i
+            class="el-icon-warning"
+            style="color: #e6a23c; margin-right: 8px"
+          ></i>
+          <span
+            >此操作将永久删除该项目下的所有输入项目,请输入'DELETE'确认删除</span
+          >
+        </div>
+  
+        <el-form
+          :model="deleteForm"
+          :rules="deleteRules"
+          ref="deleteForm"
+          label-width="100px"
+        >
+          <el-form-item label="确认文本" prop="confirmText">
+            <el-input
+              v-model="deleteForm.confirmText"
+              placeholder="请输入'DELETE'"
+            ></el-input>
+          </el-form-item>
+        </el-form>
+        <span slot="footer" class="dialog-footer">
+          <el-button @click="deleteDialogVisible = false">取消</el-button>
+          <el-button type="danger" @click="confirmDelete">确认删除</el-button>
+        </span>
+      </el-dialog>
+      <!-- 其余第一个tab的内容... -->
+    </div>
+  </template>
+    
+  <script>
+  import {
+    projectList,
+    projectAdd,
+    departments,
+    projectUpdate,
+    projectDelete,
+    create_items,
+    delete_items,
+    update_items,
+  } from "@/api/knowledge";
+  import { searchProject } from "@/api/project";
+  import { mapState } from "vuex";
+  
+  export default {
+    data() {
+      // 自定义验证规则
+      const validateDeleteConfirm = (rule, value, callback) => {
+        if (value !== "DELETE") {
+          callback(new Error("请输入正确的确认文本"));
+        } else {
+          callback();
+        }
+      };
+      return {
+        selectedProjectId: null,
+        projectOptions: [],
+        gsprTableData: [],
+        gsprSearchForm: {
+          keyword: "",
+          department: "",
+          project_id: null,
+        },
+        gsprPageForm: {
+          page: 1,
+          page_size: 30,
+        },
+        gsprTotal: 0,
+        departList: [],
+        sort_field: "name",
+        sort_order: "desc",
+        hasExistingData: false,
+        gsprDialogVisible: false,
+        gsprDialogTitle: "",
+        gsprForm: {
+          id: "",
+          name: "",
+          tech_report_location: "",
+          department: "",
+          content: "",
+          status: 5,
+        },
+        gsprPageForm: {
+          page: 1,
+          page_size: 30,
+        },
+        gsprTotal: 0,
+        gsprRules: {
+          name: [{ required: true, message: "请输入名称", trigger: "blur" }],
+          tech_report_location: [
+            { required: true, message: "请输入技术报告位置", trigger: "blur" },
+          ],
+          department: [
+            { required: true, message: "请输入部门", trigger: "blur" },
+          ],
+          status: [{ required: true, message: "请选择状态", trigger: "change" }],
+        },
+  
+        contentDialogVisible: false,
+        contentForm: {
+          id: "",
+          content: "",
+          name: "",
+          department: "",
+        },
+        /* 删除逻辑 */
+        deleteDialogVisible: false,
+        deleteForm: {
+          confirmText: "",
+        },
+        deleteRules: {
+          confirmText: [
+            { required: true, message: "请输入确认文本", trigger: "blur" },
+            { validator: validateDeleteConfirm, trigger: "blur" },
+          ],
+        },
+      };
+    },
+    mounted() {
+      this.fetchProjects().then(() => {
+        // Get project ID from URL query parameter
+        const urlProjectId = this.$route.query.id;
+        if (urlProjectId) {
+          this.selectedProjectId = parseInt(urlProjectId);
+          this.handleProjectConfirm();
+        }
+      });
+    },
+  
+    methods: {
+      /* 删除 */
+      async handleDeleteGspr(row) {
+        try {
+          await this.$confirm("确认删除该记录?", "提示", {
+            type: "warning",
+          });
+          // 替换为实际的删除API调用
+          await projectDelete({ id: row.id });
+          this.$message.success("删除成功");
+          this.fetchGsprData();
+        } catch (error) {
+          if (error !== "cancel") {
+            this.$message.error("删除失败");
+          }
+        }
+      },
+      handleEditGspr(row) {
+        this.gsprDialogTitle = "编辑文献项目";
+        this.gsprForm = { ...row };
+        console.log(this.gsprForm);
+        this.gsprDialogVisible = true;
+      },
+      /* 编辑内容 */
+      handleEditContent(row) {
+        const selectedProject = this.projectOptions.find(
+          (p) => p.id === this.selectedProjectId
+        );
+        this.contentForm = {
+          project: selectedProject.id,
+          project_name: selectedProject.name,
+          id: row.id,
+          tech_report_location: row.tech_report_location,
+          content: row.content,
+          name: row.name,
+          department: row.department,
+        };
+        this.contentDialogVisible = true;
+      },
+      /*  */
+      async handleContentSubmit() {
+        try {
+          await projectUpdate(this.contentForm);
+          this.$message.success("内容更新成功");
+          this.contentDialogVisible = false;
+          this.fetchGsprData();
+        } catch (error) {
+          this.$message.error("内容更新失败");
+        }
+      },
+      /*  */
+      async handleGsprSubmit() {
+        try {
+          await this.$refs.gsprForm.validate();
+          // 替换为实际的保存API调用
+          if (this.gsprForm.id) {
+            await projectUpdate(this.gsprForm);
+          } else {
+            await projectAdd(this.gsprForm);
+          }
+          this.$message.success(this.gsprForm.id ? "更新成功" : "创建成功");
+          this.gsprDialogVisible = false;
+          this.fetchGsprData();
+        } catch (error) {
+          if (error !== false) {
+            this.$message.error("保存失败");
+          }
+        }
+      },
+      /* 初始化项目列表 */
+      async fetchProjects() {
+        try {
+          const response = await searchProject({ page: 1, pageSize: 999 });
+          if (response.status === 200) {
+            this.projectOptions = response.data.dataList;
+  
+            // 如果有当前项目ID但没有选中项目,自动选中
+            // 如果有当前项目ID但没有选中项目,自动选中
+            if (!this.selectedProjectId && this.$route.query.id) {
+              this.selectedProjectId = parseInt(this.$route.query.id);
+              this.handleProjectConfirm();
+            }
+          }
+        } catch (error) {
+          this.$message.error("获取项目列表失败");
+        }
+      },
+      async handleProjectConfirm() {
+        if (!this.selectedProjectId) {
+          this.$message.warning("请先选择一个项目");
+          return;
+        }
+        console.log(this.selectedProjectId);
+        const selectedProject = this.projectOptions.find(
+          (p) => p.id === this.selectedProjectId
+        );
+        if (!selectedProject) {
+          this.$message.error("未找到选中的项目");
+          return;
+        }
+  
+        this.$store.dispatch("project/setCurrentProject", {
+          id: selectedProject.id,
+          name: selectedProject.name,
+        });
+  
+        localStorage.setItem(
+          "currentProject",
+          JSON.stringify({
+            id: selectedProject.id,
+            name: selectedProject.name,
+          })
+        );
+  
+        this.gsprSearchForm.project_id = this.selectedProjectId;
+        this.fetchGsprData();
+      },
+      // 创建项目
+      async handleAddGspr() {
+        if (!this.selectedProjectId) {
+          this.$message.warning("请先选择一个项目");
+          return;
+        }
+  
+        const loading = this.$loading({
+          lock: true,
+          text: "正在创建项目...",
+          spinner: "el-icon-loading",
+          background: "rgba(0, 0, 0, 0.7)",
+        });
+  
+        try {
+          const res = await create_items({
+            project_id: this.selectedProjectId,
+            project_name: this.projectOptions.find(
+              (p) => p.id === this.selectedProjectId
+            ).name,
+          });
+  
+          if (res.status === 200) {
+            this.$message.success(
+              `成功创建 ${res.data.data.created_count} 个输入项目!`
+            );
+            await this.fetchGsprData();
+          }
+        } catch (error) {
+          this.$message.error("创建失败: " + (error.message || "未知错误"));
+        } finally {
+          loading.close();
+        }
+      },
+  
+      // 更新项目
+      async handleUpdateGspr() {
+        if (!this.selectedProjectId) {
+          this.$message.warning("请先选择一个项目");
+          return;
+        }
+        const selectedProject = this.projectOptions.find(
+          (p) => p.id === this.selectedProjectId
+        );
+  
+        try {
+          await this.$confirm(
+            "此操作将更新当前项目的所有输入项目,是否继续?",
+            "提示",
+            {
+              confirmButtonText: "确定",
+              cancelButtonText: "取消",
+              type: "warning",
+            }
+          );
+  
+          const loading = this.$loading({
+            lock: true,
+            text: "正在更新项目...",
+            spinner: "el-icon-loading",
+            background: "rgba(0, 0, 0, 0.7)",
+          });
+  
+          try {
+            const res = await update_items({
+              // 假设有一个update_items的API
+              project_id: selectedProject.id,
+              project_name: selectedProject.name,
+            });
+  
+            if (res.status !== 200) {
+              throw new Error("更新失败");
+            }
+  
+            this.$message.success(
+              `成功更新 ${res.data.data.updated_count} 个输入项目!`
+            );
+            await this.fetchGsprData();
+          } finally {
+            loading.close();
+          }
+        } catch (error) {
+          if (error !== "cancel") {
+            this.$message.error("更新失败: " + (error.message || "未知错误"));
+          }
+        }
+      },
+  
+      // 删除项目
+      async handleDeleteItems() {
+        if (!this.selectedProjectId) {
+          this.$message.warning("请先选择一个项目");
+          return;
+        }
+  
+        const selectedProject = this.projectOptions.find(
+          (p) => p.id === this.selectedProjectId
+        );
+  
+        this.$confirm(
+          `您确定要删除项目 "${selectedProject.name}" 的所有输入项目吗?此操作不可恢复。`,
+          "警告",
+          {
+            confirmButtonText: "继续删除",
+            cancelButtonText: "取消",
+            type: "warning",
+          }
+        )
+          .then(() => {
+            this.deleteForm.confirmText = "";
+            this.deleteDialogVisible = true;
+          })
+          .catch(() => {
+            this.$message({
+              type: "info",
+              message: "已取消删除",
+            });
+          });
+      },
+        // 确认删除
+        confirmDelete() {
+        this.$refs.deleteForm.validate(async (valid) => {
+          if (!valid) {
+            return false;
+          }
+  
+          try {
+            const loading = this.$loading({
+              lock: true,
+              text: "正在删除...",
+              spinner: "el-icon-loading",
+              background: "rgba(0, 0, 0, 0.7)",
+            });
+  
+            const response = await delete_items({
+              project_id: this.selectedProjectId,
+            });
+  
+            loading.close();
+  
+            if (response.status === 200) {
+              this.$message.success("删除成功");
+              this.deleteDialogVisible = false;
+              // 重新加载数据
+              this.fetchGsprData();
+            } else {
+              this.$message.error(response.message || "删除失败");
+            }
+          } catch (error) {
+            this.$message.error("删除失败:" + (error.message || "未知错误"));
+          }
+        });
+      },
+  
+      // 数据获取与分页
+      async fetchGsprData() {
+        if (!this.selectedProjectId) {
+          return; // 如果没有选择项目,直接返回
+        }
+  
+        const loading = this.$loading({
+          lock: true,
+          text: "加载中...",
+          spinner: "el-icon-loading",
+          background: "rgba(0, 0, 0, 0.7)",
+        });
+  
+        try {
+          const params = {
+            sort_field: this.sort_field,
+            sort_order: this.sort_order,
+            project_id: this.selectedProjectId,
+            ...this.gsprPageForm,
+            ...this.gsprSearchForm,
+          };
+          const response = await projectList(params);
+          if (response.status === 200) {
+            this.gsprTableData = response.data.list;
+            this.gsprTotal = response.data.total;
+            this.hasExistingData = this.gsprTotal > 0;
+          }
+        } catch (error) {
+          console.error("获取数据失败:", error);
+          this.$message.error("获取数据失败");
+        } finally {
+          loading.close();
+        }
+      },
+  
+      handleGsprSizeChange(val) {
+        this.gsprPageForm.page_size = val;
+        this.fetchGsprData();
+      },
+  
+      handleGsprCurrentChange(val) {
+        this.gsprPageForm.page = val;
+        this.fetchGsprData();
+      },
+  
+      // 搜索相关
+      handleGsprSearch() {
+        this.gsprPageForm.page = 1;
+        this.fetchGsprData();
+      },
+  
+      handleGsprReset() {
+        this.gsprSearchForm = {
+          keyword: "",
+          department: "",
+          project_id: this.selectedProjectId,
+        };
+        this.handleGsprSearch();
+      },
+  
+      // 排序处理
+      handleSortChange({ prop, order }) {
+        this.sort_field = prop;
+        this.sort_order = order === "ascending" ? "asc" : "desc";
+        if (!order) {
+          this.sort_field = "name";
+          this.sort_order = "desc";
+        }
+        this.fetchGsprData();
+      },
+    },
+  };
+  </script>
+  <style >
+  .project-input-container {
+    padding: 20px;
+  }
+  .pagination-container {
+    display: flex;
+    justify-content: flex-end;
+  }
+  </style>

+ 67 - 20
src/views/project/components/dataList.vue

@@ -7,29 +7,14 @@
       empty-text="没有项目信息"
     >
       <el-table-column prop="id" label="ID" align="center" width="80" />
-      <el-table-column
-        prop="categoryId"
-        label="所属分类"
-        align="left"
-        min-width="180"
-      >
+      <el-table-column prop="categoryId" label="所属分类" align="left">
         <template #default="scope">
           <span>{{ getCategoryName(scope.row.categoryId) }}</span>
         </template>
       </el-table-column>
       <!-- <el-table-column prop="customer.name" label="所属客户" align="left" min-width="180"/> -->
-      <el-table-column
-        prop="name"
-        label="项目名称"
-        align="left"
-        min-width="180"
-      />
-      <el-table-column
-        prop="createTime"
-        label="创建时间"
-        align="center"
-        width="180"
-      />
+      <el-table-column prop="name" label="项目名称" align="left" />
+      <el-table-column prop="createTime" label="创建时间" align="center" />
       <el-table-column
         prop="statusName"
         label="项目状态"
@@ -41,9 +26,46 @@
           <div v-if="scope.row.status == 6">停用</div>
         </template>
       </el-table-column>
-      <el-table-column label="操作" align="center" fixed="right" width="200">
+      <el-table-column label="操作" align="center" fixed="right">
         <template #default="scope">
+          <el-dropdown @command="handleCommand" :data="scope.row">
+            <span class="el-dropdown-link">
+              <el-button
+                type="text"
+                size="small"
+                circle
+                class="operation-button"
+                style="font-size: 15px; margin-left: 10px"
+                icon="el-icon-more"
+              ></el-button>
+            </span>
+            <template #dropdown>
+              <el-dropdown-menu>
+                <el-dropdown-item
+                  :command="{ type: 'template', row: scope.row }"
+                  >选择模版</el-dropdown-item
+                >
+                <el-dropdown-item :command="{ type: 'enter', row: scope.row }"
+                  >输入项目</el-dropdown-item
+                >
+                <el-dropdown-item :command="{ type: 'edit', row: scope.row }">
+                  <svg-icon icon-class="edit" />编辑
+                </el-dropdown-item>
+                <el-dropdown-item :command="{ type: 'delete', row: scope.row }">
+                  <svg-icon icon-class="delete" />删除
+                </el-dropdown-item>
+              </el-dropdown-menu>
+            </template>
+          </el-dropdown>
+        </template>
+        <!-- <template #default="scope">
           <div class="btns">
+            <el-button type="primary" size="small" @click="selTemplate(scope.row)"
+              >选择模版</el-button
+            >
+            <el-button type="primary" size="small" @click="enterPro(scope.row)"
+              >输入项目</el-button
+            >
             <el-button type="primary" size="small" @click="btnEdit(scope.row)"
               ><svg-icon icon-class="edit" />编辑</el-button
             >
@@ -54,7 +76,7 @@
               ><svg-icon icon-class="delete" />删除</el-button
             >
           </div>
-        </template>
+        </template> -->
       </el-table-column>
     </el-table>
 
@@ -131,6 +153,31 @@ export default {
     this.init();
   },
   methods: {
+    handleCommand(command) {
+    switch (command.type) {
+      case 'template':
+        this.selTemplate(command.row);
+        break;
+      case 'enter':
+        this.enterPro(command.row);
+        break;
+      case 'edit':
+        this.btnEdit(command.row);
+        break;
+      case 'delete':
+        this.btnDelete(command.row.id);
+        break;
+    }
+  },
+    /* 选择模版 */
+    selTemplate() {},
+    /* 输入项目 */
+    enterPro(row) {
+      this.$router.push({
+        path: "/project/ProjectInput",
+        query: { id: row.id },
+      });
+    },
     getCategoryName(categoryId) {
       const category = this.categoryList.find((item) => item.id === categoryId);
       return category ? category.name : categoryId;

+ 130 - 109
src/views/templateLoca/index.vue

@@ -1,9 +1,8 @@
 <template>
-  <div class="template-location-container">
-    <el-tabs v-model="activeTab" @tab-click="handleTabClick"
-      ><!-- 新增的第二个标签页 -->
-      <el-tab-pane label="文献项目输入表" name="first">
-        <!-- 项目选择器始终显示 -->
+  <div class="template-location-container" style="padding: 20px 10px">
+    <!--  <el-tabs v-model="activeTab" @tab-click="handleTabClick"
+      > --><!-- 新增的第二个标签页 -->
+    <!--  <el-tab-pane label="文献项目输入表" name="first">
         <div class="project-selector">
           <el-form :inline="true">
             <el-form-item label="当前项目">
@@ -22,9 +21,6 @@
               </el-select>
             </el-form-item>
             <el-form-item>
-              <!--  <el-button type="primary" @click="handleProjectConfirm"
-                >确认选择</el-button
-              > -->
               <el-button
                 type="primary"
                 @click="handleAddGspr"
@@ -40,7 +36,7 @@
             </el-form-item>
           </el-form>
         </div>
-        <!-- 使用 v-if 控制其余内容的显示 -->
+       
         <template v-if="currentProjectId">
           <div class="operation-bar">
             <el-form :inline="true" :model="gsprSearchForm" class="search-form">
@@ -122,7 +118,7 @@
             />
           </div>
 
-          <!-- GSPR表单弹窗 -->
+      
           <el-dialog
             :title="gsprDialogTitle"
             :visible.sync="gsprDialogVisible"
@@ -170,7 +166,7 @@
               >
             </div>
           </el-dialog>
-          <!-- 添加内容编辑弹窗 -->
+          
           <el-dialog
             title="编辑内容"
             :visible.sync="contentDialogVisible"
@@ -195,100 +191,95 @@
           </el-dialog>
         </template>
 
-        <!-- 未选择项目时显示提示 -->
         <el-empty
           v-else
           description="请先选择一个项目"
           :image-size="200"
         ></el-empty>
-      </el-tab-pane>
-      <!-- 第一个标签页 - 原有的模板位置表 -->
-      <el-tab-pane label="模板位置表" name="second">
-        <!-- 顶部操作栏 -->
-        <div class="operation-bar">
-          <el-form
-            :inline="true"
-            :model="searchForm"
-            class="search-form"
-            label-width="100px"
-          >
-            <el-form-item label="动作类型">
-              <!-- <el-input
+      </el-tab-pane> -->
+    <!-- 第一个标签页 - 原有的模板位置表 -->
+    <!--  <el-tab-pane label="模板位置表" name="first"> -->
+    <!-- 顶部操作栏 -->
+    <div class="operation-bar">
+      <el-form
+        :inline="true"
+        :model="searchForm"
+        class="search-form"
+        label-width="100px"
+      >
+        <el-form-item label="动作类型">
+          <!-- <el-input
                 v-model="searchForm.action_type"
                 placeholder="请输入动作类型"
                 clearable
               /> -->
-              <el-select
-                v-model="searchForm.action_type"
-                placeholder="请选择动作类型"
-                clearable
-              >
-                <el-option
-                  v-for="(item, index) in typeList"
-                  :key="index"
-                  :label="item.value"
-                  :value="item.value"
-                />
-                <!-- <el-option label="WE" value="WE" /> -->
-              </el-select>
-            </el-form-item>
-            <el-form-item label="模板位置">
-              <el-input
-                v-model="searchForm.template_location"
-                placeholder="请输入模板位置"
-                clearable
-              />
-            </el-form-item>
-            <el-form-item label="技术报告位置">
-              <el-input
-                v-model="searchForm.tech_report_location"
-                placeholder="请输入技术报告位置"
-                clearable
-              />
-            </el-form-item>
-            <el-form-item label="中间层位置">
-              <el-input
-                v-model="searchForm.middle_layer_location"
-                placeholder="请输入中间层位置"
-                clearable
-              />
-            </el-form-item>
-            <el-form-item>
-              <el-button type="primary" @click="handleSearch">搜索</el-button>
-              <el-button @click="handleReset">重置</el-button>
-            </el-form-item>
-          </el-form>
-          <el-button type="primary" @click="handleImport"
-            >导入模版位置表</el-button
+          <el-select
+            v-model="searchForm.action_type"
+            placeholder="请选择动作类型"
+            clearable
           >
-          <el-button type="primary" @click="handleExport"
-            >导出模版位置表</el-button
-          >
-        </div>
-
-        <!-- 数据列表 -->
-        <el-table :data="tableData" style="width: 100%">
-          <el-table-column prop="id" label="ID" width="80" />
-          <el-table-column prop="action_type" label="动作类型" width="100" />
-          <el-table-column prop="template_location" label="模板位置" />
-          <el-table-column prop="middle_layer_location" label="中间层位置" />
-          <el-table-column prop="tech_report_location" label="技术报告位置" />
-          <el-table-column prop="other_location" label="其他位置" />
-          <el-table-column prop="create_time" label="创建时间" width="160" />
-        </el-table>
-        <div class="pagination-container">
-          <el-pagination
-            @size-change="handleSizeChange"
-            @current-change="handleCurrentChange"
-            :current-page="pageForm.page"
-            :page-sizes="[10, 20, 30, 50]"
-            :page-size="pageForm.page_size"
-            layout="total, sizes, prev, pager, next, jumper"
-            :total="total"
+            <el-option
+              v-for="(item, index) in typeList"
+              :key="index"
+              :label="item.value"
+              :value="item.value"
+            />
+            <!-- <el-option label="WE" value="WE" /> -->
+          </el-select>
+        </el-form-item>
+        <el-form-item label="模板位置">
+          <el-input
+            v-model="searchForm.template_location"
+            placeholder="请输入模板位置"
+            clearable
           />
-        </div>
-      </el-tab-pane>
-    </el-tabs>
+        </el-form-item>
+        <el-form-item label="技术报告位置">
+          <el-input
+            v-model="searchForm.tech_report_location"
+            placeholder="请输入技术报告位置"
+            clearable
+          />
+        </el-form-item>
+        <el-form-item label="中间层位置">
+          <el-input
+            v-model="searchForm.middle_layer_location"
+            placeholder="请输入中间层位置"
+            clearable
+          />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="handleSearch">搜索</el-button>
+          <el-button @click="handleReset">重置</el-button>
+        </el-form-item>
+      </el-form>
+      <el-button type="primary" @click="handleImport">导入模版位置表</el-button>
+      <el-button type="primary" @click="handleExport">导出模版位置表</el-button>
+    </div>
+
+    <!-- 数据列表 -->
+    <el-table :data="tableData" style="width: 100%">
+      <el-table-column prop="id" label="ID" width="80" />
+      <el-table-column prop="action_type" label="动作类型" width="100" />
+      <el-table-column prop="template_location" label="模板位置" />
+      <el-table-column prop="middle_layer_location" label="中间层位置" />
+      <el-table-column prop="tech_report_location" label="技术报告位置" />
+      <el-table-column prop="other_location" label="其他位置" />
+      <el-table-column prop="create_time" label="创建时间" width="160" />
+    </el-table>
+    <div class="pagination-container">
+      <el-pagination
+        @size-change="handleSizeChange"
+        @current-change="handleCurrentChange"
+        :current-page="pageForm.page"
+        :page-sizes="[10, 20, 30, 50]"
+        :page-size="pageForm.page_size"
+        layout="total, sizes, prev, pager, next, jumper"
+        :total="total"
+      />
+    </div>
+    <!-- </el-tab-pane> -->
+    <!--  </el-tabs> -->
     <!-- 添加删除确认对话框 -->
     <el-dialog
       title="删除确认"
@@ -489,7 +480,8 @@ export default {
     },
   },
   mounted() {
-    this.fetchProjects();
+    /* this.fetchProjects(); */
+    this.fetchData();
     /*  this.fetchGsprData(); */
     this.init();
   },
@@ -867,20 +859,49 @@ export default {
       try {
         const response = await export_template();
 
-        // 创建一个blob链接并触发下载
-        const blob = new Blob([response.data], {
-          type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
-        });
-        const url = window.URL.createObjectURL(blob);
-        const link = document.createElement("a");
-        link.href = url;
-        link.download = "模板位置表.xlsx";
-        document.body.appendChild(link);
-        link.click();
-        document.body.removeChild(link);
-        window.URL.revokeObjectURL(url);
-
-        this.$message.success("导出成功");
+        // 检查并清理 base64 字符串
+        let base64String = response.data.file_content;
+        // 移除可能存在的换行符和空格
+        base64String = base64String.replace(/[\n\r\s]/g, "");
+
+        // 如果字符串包含引号,需要去除
+        if (base64String.startsWith('"') && base64String.endsWith('"')) {
+          base64String = base64String.slice(1, -1);
+        }
+
+        try {
+          // 将 base64 字符串解码为二进制数据
+          const binaryString = window.atob(base64String);
+          const len = binaryString.length;
+          const bytes = new Uint8Array(len);
+
+          for (let i = 0; i < len; i++) {
+            bytes[i] = binaryString.charCodeAt(i);
+          }
+
+          // 创建 blob 对象
+          const blob = new Blob([bytes], {
+            type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
+          });
+
+          // 创建下载链接并触发下载
+          const url = window.URL.createObjectURL(blob);
+          const link = document.createElement("a");
+          link.href = url;
+          link.download = "模板位置表.xlsx";
+          document.body.appendChild(link);
+          link.click();
+
+          // 清理
+          document.body.removeChild(link);
+          window.URL.revokeObjectURL(url);
+
+          this.$message.success("导出成功");
+        } catch (decodeError) {
+          console.error("Base64解码失败:", decodeError);
+          console.log("问题字符串:", base64String);
+          this.$message.error("文件格式错误");
+        }
       } catch (error) {
         console.error("导出失败:", error);
         this.$message.error("导出失败");

Some files were not shown because too many files changed in this diff