Browse Source

merge: 将 test 分支合并到 master

xieyong 2 days ago
parent
commit
bfa3167d1c

+ 156 - 112
src/components/addDoc/doc_template.vue

@@ -1,5 +1,32 @@
 <template>
-  <div class="ele-body" style="height:60vh;overflow: auto;">
+  <div class="ele-body" style="height: 60vh; overflow: auto">
+    <!-- 搜索栏 -->
+    <div style="margin-bottom: 10px">
+      <el-input
+        v-model="keyword"
+        placeholder="匹配编码与文档名称"
+        clearable
+        size="small"
+        style="width: 220px"
+        @keyup.enter.native="handleSearch"
+      ></el-input>
+      <el-button
+        type="primary"
+        size="small"
+        icon="el-icon-search"
+        style="margin-left: 8px"
+        @click="handleSearch"
+        >搜索</el-button
+      >
+      <el-button
+        size="small"
+        icon="el-icon-refresh-left"
+        style="margin-left: 4px"
+        @click="handleReset"
+        >重置</el-button
+      >
+    </div>
+
     <el-card shadow="never" v-loading="loading">
       <ele-split-layout
         width="210px"
@@ -7,12 +34,10 @@
         :right-style="{ overflow: 'hidden' }"
       >
         <div>
-          <!-- 操作按钮 -->
-          <ele-toolbar class="ele-toolbar-actions">
-            <div style="margin: 5px 0"> </div>
-          </ele-toolbar>
-             
-          <div class="ele-border-lighter sys-organization-list"  style="height: 65vh">
+          <div
+            class="ele-border-lighter sys-organization-list"
+            style="height: 65vh"
+          >
             <el-tree
               ref="tree"
               :data="data"
@@ -24,7 +49,7 @@
               :default-expand-all="true"
               @node-click="onNodeClick"
             >
-              <span class="custom-tree-node" slot-scope="{ node, data }">
+              <span class="custom-tree-node" slot-scope="{ node }">
                 <ElementTreeLine
                   :node="node"
                   :showLabelLine="true"
@@ -53,125 +78,144 @@
 </template>
 
 <script>
-//
-import FileTableList from './file-table-listTemplate.vue';
-import { getDocTreeListAPI } from './api';
-import { mapGetters } from 'vuex';
+  //
+  import FileTableList from './file-table-listTemplate.vue';
+  import { getDocTreeListAPI } from './api';
+  import { mapGetters } from 'vuex';
 
-import ElementTreeLine from 'element-tree-line';
-// css
-import 'element-tree-line/dist/style.scss';
-export default {
-  components: { FileTableList, ElementTreeLine },
+  import ElementTreeLine from 'element-tree-line';
+  // css
+  import 'element-tree-line/dist/style.scss';
+  export default {
+    components: { FileTableList, ElementTreeLine },
 
-  data() {
-    return {
-      loading: true,
-      // 列表数据
-      data: [],
-      fileType:0,
-      // 选中数据
-      current: {}
-    };
-  },
-  props: {
-    lcyStatus: '', //1:文档工作区 2:文档归档区 3:文档发布区 4:文档废止区
+    data() {
+      return {
+        loading: true,
+        // 列表数据
+        data: [],
+        fileType: 0,
+        // 选中数据
+        current: {},
+        // 搜索关键词
+        keyword: ''
+      };
+    },
+    props: {
+      lcyStatus: {
+        type: String,
+        default: ''
+      }, //1:文档工作区 2:文档归档区 3:文档发布区 4:文档废止区
 
-    disabledTableList: {
-      //已选择列表
-      default: () => []
-    }
-  },
-  computed: {
-    ...mapGetters(['user'])
-  },
-  created() {
-    // this.query();
-  },
+      disabledTableList: {
+        //已选择列表
+        default: () => []
+      }
+    },
+    computed: {
+      ...mapGetters(['user'])
+    },
+    created() {
+      // this.query();
+    },
 
-  methods: {
-    /* 查询 */
-    // 0:公司 1:个人
-    async query(type) {
-      this.loading = true;
-      let query = {
-        type,
-        currentUserId: this.user.info.userId
-      };
-      this.data = await getDocTreeListAPI(query);
+    methods: {
+      /* 查询 */
+      // 0:公司 1:个人
+      async query(type) {
+        this.loading = true;
+        let query = {
+          type,
+          currentUserId: this.user.info.userId
+        };
+        this.data = await getDocTreeListAPI(query);
 
-      this.current = null;
-      this.$nextTick(() => {
-        this.$refs.tree.setCurrentKey(this.data[0].id);
+        this.current = null;
+        this.$nextTick(() => {
+          this.$refs.tree.setCurrentKey(this.data[0].id);
 
-        this.onNodeClick(this.data[0]);
-      });
-      this.loading = false;
-    },
-    /* 选择数据 */
-    onNodeClick(row) {
-      if (row) {
-        this.current = row;
+          this.onNodeClick(this.data[0]);
+        });
+        this.loading = false;
+      },
+      /* 选择数据 */
+      onNodeClick(row) {
+        if (row) {
+          this.current = row;
+          this.keyword = '';
+          this.$nextTick(() => {
+            this.$refs.tableRef.reload();
+          });
+        } else {
+          this.current = null;
+        }
+      },
+
+      getTableList() {
+        return this.$refs.tableRef.getTableList();
+      },
+      /* 搜索 */
+      handleSearch() {
         this.$nextTick(() => {
-          this.$refs.tableRef.reload();
+          this.$refs.tableRef.reload({ keyword: this.keyword });
+        });
+      },
+      /* 重置 */
+      handleReset() {
+        this.keyword = '';
+        this.$nextTick(() => {
+          this.$refs.tableRef.reload({ keyword: '' });
         });
-      } else {
-        this.current = null;
       }
-    },
-
-    getTableList() {
-      return this.$refs.tableRef.getTableList();
     }
-  }
-};
+  };
 </script>
 
 <style lang="scss" scoped>
-.sys-organization-list {
-  height: calc(100vh - 180px);
-  box-sizing: border-box;
-  border-width: 1px;
-  border-style: solid;
-  overflow: auto;
-}
+  .sys-organization-list {
+    height: calc(100vh - 180px);
+    box-sizing: border-box;
+    border-width: 1px;
+    border-style: solid;
+    overflow: auto;
+  }
 
-.sys-organization-list :deep(.el-tree-node__content) {
-  height: 30px;
+  .sys-organization-list :deep(.el-tree-node__content) {
+    height: 30px;
 
-  & > .el-tree-node__expand-icon {
-    margin-left: 10px;
+    & > .el-tree-node__expand-icon {
+      margin-left: 10px;
+    }
+  }
+  .custom-tree-node {
+    display: flex;
+    align-items: center;
+  }
+  :deep(.el-popover) {
+    min-width: 50px;
+    position: fixed;
+  }
+  :deep(.el-link--inner) {
+    padding: 3px 0;
   }
-}
-.custom-tree-node {
-  display: flex;
-  align-items: center;
-}
-:deep(.el-popover) {
-  min-width: 50px;
-  position: fixed;
-}
-:deep(.el-link--inner) {
-  padding: 3px 0;
-}
-// :deep(.element-tree-node-line-hor) {
-//   border-bottom: 1px solid #dcdfe6;
-// }
-// :deep(.element-tree-node-line-ver) {
-//   border-left: 1px solid #dcdfe6;
-// }
+  // :deep(.element-tree-node-line-hor) {
+  //   border-bottom: 1px solid #dcdfe6;
+  // }
+  // :deep(.element-tree-node-line-ver) {
+  //   border-left: 1px solid #dcdfe6;
+  // }
 </style>
 <style lang="scss">
-.el-tree
-  > .el-tree-node
-  > .el-tree-node__content:nth-of-type(1)
-  .element-tree-node-line-hor {
-  border: none;
-}
-.el-tree
-  > .el-tree-node
-  > .el-tree-node__content:nth-of-type(1)
-  .element-tree-node-line-ver {
-  border: none;
-}
-</style>
+  .el-tree
+    > .el-tree-node
+    > .el-tree-node__content:nth-of-type(1)
+    .element-tree-node-line-hor {
+    border: none;
+  }
+  .el-tree
+    > .el-tree-node
+    > .el-tree-node__content:nth-of-type(1)
+    .element-tree-node-line-ver {
+    border: none;
+  }
+</style>

+ 2 - 1
src/components/addDoc/file-table-listTemplate.vue

@@ -185,7 +185,8 @@
           pageNum: page,
           size: limit,
           directoryId: this.parentData?.id,
-          isQueryAll: this.isQueryAll
+          isQueryAll: this.isQueryAll,
+          keyword: where?.keyword || ''
         });
       },
       /* 刷新表格 */

+ 32 - 5
src/components/addDoc/main.vue

@@ -34,11 +34,20 @@
         <el-link
           type="primary"
           :underline="false"
-          icon="el-icon-edit"
+          icon="el-icon-view"
           @click="browseOpen(row)"
         >
           浏览
         </el-link>
+        <el-link
+          type="primary"
+          :underline="false"
+          icon="el-icon-download"
+          class="ele-action"
+          @click="downloadOpen(row)"
+        >
+          下载
+        </el-link>
 
         <el-popconfirm
           v-if="type != 'view'"
@@ -88,7 +97,7 @@
   import fileEdit from './file-edit.vue';
   import browse from './browse.vue';
   import { queryIds } from './api';
-  import { setFileUrl } from './util.js';
+  import { setFileUrl, setDownloadUrl } from './util.js';
   import tableColumnsMixin from '@/mixins/tableColumnsMixin.js';
 
   export default {
@@ -161,10 +170,18 @@
             minWidth: 100
           },
 
+          {
+            prop: 'remark',
+            label: '备注',
+            align: 'center',
+            showOverflowTooltip: true,
+            minWidth: 100
+          },
+
           {
             columnKey: 'action',
             label: '操作',
-            width: 200,
+            width: 240,
             align: 'center',
             resizable: false,
             slot: 'action',
@@ -188,8 +205,8 @@
       openFileList(type) {
         this.fileShow = true;
         this.$nextTick(() => {
-          this.$refs.doc_templateRef.query(type)
-        })
+          this.$refs.doc_templateRef.query(type);
+        });
       },
       fileEditOpen() {
         this.$refs.fileEditRef.open();
@@ -212,6 +229,16 @@
       browseOpen(row) {
         window.open(setFileUrl(row));
       },
+      downloadOpen(row) {
+        const url = setDownloadUrl(row);
+        const link = document.createElement('a');
+        link.href = url;
+        link.target = '_blank';
+        link.download = row.storagePath[0]?.name || '';
+        document.body.appendChild(link);
+        link.click();
+        document.body.removeChild(link);
+      },
       addFile() {
         this.$emit(
           'success',

+ 87 - 12
src/components/addDoc/seal.vue

@@ -122,6 +122,12 @@
       };
     },
     created() {
+      // 竞态控制字段(非响应式,挂在实例上避免响应式开销)
+      this._renderSeq = 0; // 渲染序号,用于竞态控制
+      this._lastRenderPromise = null; // 最近一次渲染完成的 Promise
+      this._renderResolve = null; // 最近一次渲染的 resolve 回调
+      this._renderReject = null; // 最近一次渲染的 reject 回调
+
       // 获取公用章列表
       getSealPage({
         size: 999,
@@ -812,9 +818,17 @@
 
       // 渲染Canvas核心函数
       async renderToCanvas(type) {
+        // 竞态保护:记录本次渲染序号与 Promise,供 exportToPDF 等待最新一次渲染完成
+        const mySeq = ++this._renderSeq;
+        this._lastRenderPromise = new Promise((resolve, reject) => {
+          this._renderResolve = resolve;
+          this._renderReject = reject;
+        });
+
         const targetElement = document.getElementById('captureTarget');
         if (!targetElement) {
           console.warn('未找到目标元素');
+          this._renderReject && this._renderReject(new Error('未找到目标元素'));
           return;
         }
 
@@ -838,6 +852,13 @@
         try {
           // 使用 html2canvas 进行转换,scale: 2 提高清晰度
           const scale = 2;
+          // captureTarget 是隐藏元素(left: -100000px),scrollWidth/Height 偶现为 0,
+          // 用 getBoundingClientRect 兜底获取真实宽高,避免渲染出 0 尺寸 canvas
+          const rect = targetElement.getBoundingClientRect();
+          const targetWidth =
+            targetElement.scrollWidth || Math.ceil(rect.width) || 1;
+          const targetHeight =
+            targetElement.scrollHeight || Math.ceil(rect.height) || 1;
           const canvas = await html2canvas(targetElement, {
             scale: scale, // 2倍分辨率,提高清晰度
             backgroundColor: '#ffffff',
@@ -845,8 +866,8 @@
             logging: false,
             allowTaint: false,
             imageTimeout: 5000,
-            windowWidth: targetElement.scrollWidth,
-            windowHeight: targetElement.scrollHeight
+            windowWidth: targetWidth,
+            windowHeight: targetHeight
           });
 
           // 移除加载提示
@@ -876,6 +897,11 @@
             this.redrawCanvas();
           }
 
+          // 仅当这是最新一次渲染时才 resolve,避免旧渲染覆盖新状态
+          if (mySeq === this._renderSeq && this._renderResolve) {
+            this._renderResolve();
+          }
+
           // 显示成功提示
           // this.showMessage('✅ 渲染成功!可下载图片', 'success');
         } catch (error) {
@@ -883,34 +909,83 @@
           const temp = document.getElementById('tempLoadingMsg');
           if (temp) temp.remove();
           this.canvasGenerated = false;
+          // 仅当这是最新一次渲染时才 reject
+          if (mySeq === this._renderSeq && this._renderReject) {
+            this._renderReject(error);
+          }
           // this.showMessage('⚠️ 渲染失败,请检查HTML结构或外部资源', 'error');
         }
       },
 
       // 导出为 PDF(使用 jsPDF)
-      exportToPDF() {
+      async exportToPDF() {
+        // 竞态保护:如果正在渲染,等待最新一次渲染完成(最多等 15s),
+        // 避免 html2canvas 尚未绘制完成就导出导致偶现失败
+        if (this._lastRenderPromise) {
+          try {
+            await Promise.race([
+              this._lastRenderPromise,
+              new Promise((_, reject) =>
+                setTimeout(() => reject(new Error('等待渲染完成超时')), 15000)
+              )
+            ]);
+          } catch (err) {
+            console.error('等待渲染完成失败,跳过导出:', err);
+            this.$message && this.$message.error('渲染尚未完成,请稍后重试');
+            return;
+          }
+        }
+
         const outputCanvas = document.getElementById('outputCanvas');
         if (
           !this.canvasGenerated ||
           !outputCanvas ||
           outputCanvas.width === 0
         ) {
-          // this.showMessage(
-          //   '请先点击"渲染至Canvas"生成图像后再导出PDF',
-          //   'error'
-          // );
+          this.$message && this.$message.error('内容尚未渲染,无法导出PDF');
           return;
         }
 
         try {
           // 使用 Canvas 的 CSS 尺寸(显示尺寸)
-          const cssWidth =
+          let cssWidth =
             parseFloat(outputCanvas.style.width) || outputCanvas.width;
-          const cssHeight =
+          let cssHeight =
             parseFloat(outputCanvas.style.height) || outputCanvas.height;
 
-          // 获取高清图片数据,使用最高质量
-          const imgData = outputCanvas.toDataURL('image/png', 1.0);
+          // 防止 css 尺寸异常(比如渲染竞态时 style 未设置),兜底用 canvas 实际像素
+          if (!cssWidth || !cssHeight || isNaN(cssWidth) || isNaN(cssHeight)) {
+            cssWidth = outputCanvas.width;
+            cssHeight = outputCanvas.height;
+          }
+
+          // 计算需要的降采样比例:
+          // jsPDF 最大页面尺寸约 5080mm,按 96dpi 换算约 19200px,留 10% 余量用 17000px
+          const MAX_SIDE = 17000;
+          let scale = 1;
+          const maxSide = Math.max(outputCanvas.width, outputCanvas.height);
+          if (maxSide > MAX_SIDE) {
+            scale = MAX_SIDE / maxSide;
+          }
+
+          // 降采样后从临时 canvas 取图,避免超大图 toDataURL OOM / 超出 jsPDF 限制
+          let imgData;
+          if (scale < 1) {
+            const tempCanvas = document.createElement('canvas');
+            tempCanvas.width = Math.round(outputCanvas.width * scale);
+            tempCanvas.height = Math.round(outputCanvas.height * scale);
+            const tempCtx = tempCanvas.getContext('2d');
+            tempCtx.drawImage(
+              outputCanvas,
+              0,
+              0,
+              tempCanvas.width,
+              tempCanvas.height
+            );
+            imgData = tempCanvas.toDataURL('image/jpeg', 0.95);
+          } else {
+            imgData = outputCanvas.toDataURL('image/jpeg', 0.95);
+          }
 
           // 创建 PDF 实例,横向或纵向根据 Canvas 尺寸决定
           const orientation = cssWidth > cssHeight ? 'l' : 'p';
@@ -924,7 +999,7 @@
           const pdf = new jsPDF(orientation, 'mm', [pdfWidth, pdfHeight]);
 
           // 将 Canvas 图片添加到 PDF
-          pdf.addImage(imgData, 'PNG', 0, 0, pdfWidth, pdfHeight);
+          pdf.addImage(imgData, 'JPEG', 0, 0, pdfWidth, pdfHeight);
 
           // 获取 PDF 文件流并触发事件
           const pdfBlob = pdf.output('blob');

+ 11 - 0
src/components/addDoc/util.js

@@ -64,6 +64,17 @@ export function setFileUrl(row) {
     fileNames[fileNames.length - 1];
   return '/kkfile/onlinePreview?url=' + btoa(url);
 }
+export function setDownloadUrl(row) {
+  let file = row.storagePath[0];
+  let fileNames = file.storePath.split('/');
+  return (
+    window.location.origin +
+    '/api/main/file/getFile?objectName=' +
+    file.storePath +
+    '&fullfilename=' +
+    fileNames[fileNames.length - 1]
+  );
+}
 export function setFolderList(data) {
   //递归过滤文件夹权限
   data.forEach((item) => {

+ 26 - 7
src/views/bpm/collaborative/index.vue

@@ -70,19 +70,24 @@
                 @keyup.enter.native="handleSearch"
                 @submit.native.prevent
               >
-                <el-row :gutter="15" style="display: flex; align-items: center">
-                  <el-col v-bind="{ span: 4 }">
+                <el-row :gutter="[20, 20]" style="row-gap: 20px">
+                  <el-col v-bind="{ span: 5 }">
                     <el-form-item
                       label="关键字:"
                       prop="keyword"
                       label-width="70px"
                     >
-                      <el-input placeholder="请输入" v-model="params.keyword"></el-input>
+                      <el-input
+                        style="width: 100%"
+                        placeholder="请输入"
+                        v-model="params.keyword"
+                      ></el-input>
                     </el-form-item>
                   </el-col>
-                  <el-col v-bind="{ span: 4 }">
+                  <el-col v-bind="{ span: 5 }">
                     <el-form-item label="分类:" label-width="60px">
                       <el-select
+                        style="width: 100%"
                         v-model="params.dictType"
                         @change="changeDictTypeChange"
                         clearable
@@ -98,9 +103,10 @@
                       </el-select>
                     </el-form-item>
                   </el-col>
-                  <el-col v-bind="{ span: 4 }">
+                  <el-col v-bind="{ span: 5 }">
                     <el-form-item label="单据:" label-width="60px">
                       <el-select
+                        style="width: 100%"
                         v-model="params.formId"
                         @change="changeFormIdChange"
                         clearable
@@ -121,6 +127,7 @@
                       label-width="60px"
                     >
                       <DictSelection
+                        style="width: 100%; display: block"
                         dictName="流程实例的结果"
                         clearable
                         v-model="params.result"
@@ -128,19 +135,20 @@
                       </DictSelection>
                     </el-form-item>
                   </el-col>
-                  <el-col v-bind="{ span: 4 }">
+                  <el-col v-bind="{ span: 5 }">
                     <el-form-item
                       label="发起人:"
                       prop="startUserName"
                       label-width="70px"
                     >
                       <el-input
+                        style="width: 100%"
                         v-model="params.startUserName"
                         clearable
                       ></el-input>
                     </el-form-item>
                   </el-col>
-                  <el-col v-bind="{ span: 6 }">
+                  <el-col v-bind="{ span: 7 }">
                     <el-form-item
                       label="创建时间:"
                       prop="createTime1"
@@ -819,6 +827,17 @@
   .filter-container {
     margin-bottom: 10px;
     margin-top: 10px;
+
+    ::v-deep .el-form-item {
+      margin-bottom: 15px;
+    }
+
+    ::v-deep .el-form-item .el-input,
+    ::v-deep .el-form-item .el-select,
+    ::v-deep .el-form-item .el-date-editor,
+    ::v-deep .el-form-item .ele-select {
+      width: 100%;
+    }
   }
 
   .content-box {

+ 25 - 7
src/views/bpm/handleTask/components/saleOrder/accountstatement/receivableInfo.vue

@@ -171,7 +171,7 @@
           },
           {
             minWidth: 140,
-            prop: 'advanceCode',
+            prop: 'advanceCodes',
             label: '预收编码',
             align: 'center',
             showOverflowTooltip: true
@@ -199,15 +199,27 @@
           },
           {
             minWidth: 140,
-            prop: 'unreconciledAdvanceConfirmAmount',
-            label: '未对账收款确收金额',
+            prop: 'advanceTotalAmt',
+            label: '预收金额',
             align: 'center',
             showOverflowTooltip: true
           },
           {
             minWidth: 160,
-            prop: 'statementAdvanceConfirmAmount',
-            label: '本次对账预收款确收金额',
+            prop: 'confirmedTotalAmt',
+            label: '预收款确收金额',
+            align: 'center'
+          },
+          {
+            minWidth: 160,
+            prop: 'uncollectedAdvanceAmt',
+            label: '预收款未收金额',
+            align: 'center'
+          },
+          {
+            minWidth: 160,
+            prop: 'lastReceivedAmt',
+            label: '上期累计应收金额',
             align: 'center'
           },
           {
@@ -217,10 +229,16 @@
             align: 'center',
             showOverflowTooltip: true
           },
+          {
+            minWidth: 160,
+            prop: 'statementAmt',
+            label: '本次对账应收总金额',
+            align: 'center'
+          },
           {
             minWidth: 120,
-            prop: 'receivedPrice',
-            label: '本次应收金额',
+            prop: 'receivedAmt',
+            label: '累计应收金额',
             align: 'center',
             showOverflowTooltip: true
           }