Prechádzať zdrojové kódy

feat: 公文范文增加盖章功能

liujt 1 týždeň pred
rodič
commit
eba2e071ce

+ 3 - 1
src/views/bpm/documents/documentTemplate/components/file-add.vue

@@ -84,10 +84,11 @@ export default {
         // code: [{ required: true, message: '请输入范文编号', trigger: 'blur' }],
         name: [{ required: true, message: '请输入范文名称', trigger: 'blur' }],
         status: [{ required: true, message: '请选择状态', trigger: 'blur' }],
-      }
+      },
     }
   },
   created() {
+    console.log('created~~~~');
     this.routerQuery = this.$route.query || {};
     this.formData.directoryId = this.routerQuery.treeId || '';
     if(this.routerQuery.type != 'add') {
@@ -101,6 +102,7 @@ export default {
   },
   methods: {
     async getDetail() {
+      console.log('getDetail~~~~');
       let res = await docTplTemplateById(this.routerQuery.id);
       res.status = res.status == 1 ? true : false;
       this.formData = res;

+ 274 - 3
src/views/bpm/documents/noticeIssuance/components/addOrEdit.vue

@@ -6,9 +6,47 @@
                     <MainBodyTemplate ref="mainBodyTemplate" :type="routerQuery.type" menu="notice" :disabled="disabled" @sendFiles="getFiles"></MainBodyTemplate>
                 </el-tab-pane>
                 <el-tab-pane label="正文" name="2">
-                     <div v-show="tabValue === '2'" style="min-height: 525px;">
-                        <tinymce-editor v-if="tinymceConfig && tabValue === '2'" :disabled="disabled" v-model="formData.content" :init="tinymceConfig" />
-                     </div>
+                    <div class="editor-wrapper">
+                        <div class="editor-main">
+                            <div v-show="tabValue === '2'" style="min-height: 525px;">
+                                <tinymce-editor v-if="tinymceConfig && tabValue === '2'" :disabled="disabled" v-model="formData.content" :init="tinymceConfig" />
+                            </div>
+                        </div>
+                        <div class="seal-panel" v-if="isApprove">
+                            <div class="seal-section">
+                                <div class="panel-title">公用章</div>
+                                <div class="seal-grid">
+                                    <div
+                                        v-for="(img, index) in publicImages"
+                                        :key="'pub-' + index"
+                                        class="seal-item"
+                                        draggable="true"
+                                        @dragstart="handleDragStart($event, img, 'public')"
+                                    >
+                                        <img :src="img.imgUrl" :alt="img.name" />
+                                        <span class="seal-name">{{ img.name }}</span>
+                                    </div>
+                                    <div v-if="!publicImages.length" class="no-seal">暂无公用章</div>
+                                </div>
+                            </div>
+                            <div class="seal-section">
+                                <div class="panel-title">个人章</div>
+                                <div class="seal-grid">
+                                    <div
+                                        v-for="(img, index) in privateImages"
+                                        :key="'pri-' + index"
+                                        class="seal-item"
+                                        draggable="true"
+                                        @dragstart="handleDragStart($event, img, 'private')"
+                                    >
+                                        <img :src="img.imgUrl" :alt="img.name" />
+                                        <span class="seal-name">{{ img.name }}</span>
+                                    </div>
+                                    <div v-if="!privateImages.length" class="no-seal">暂无个人章</div>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
                 </el-tab-pane>
                 <el-tab-pane label="附件查看" name="3">
                   <div class="file-container">
@@ -59,6 +97,8 @@ import { getCode } from '@/api/codeManagement';
 import { uploadFile } from '@/api/system/file/index.js';
 import processSubmitDialog  from '@/BIZComponents/enventSubmitDialog/processSubmitDialog'
 import { setFileUrl } from '@/components/addDoc/util.js'
+import { getSealPage } from '@/api/bpm/components/sealManagement';
+import { getUserDetail } from '@/api/system/organization/index';
 export default {
   components: {
     MainBodyTemplate,
@@ -94,6 +134,9 @@ export default {
         fields: [],
       },
       fileList: [],
+      publicImages: [],
+      privateImages: [],
+      sealsFetched: false,
       tinymceConfig: {
         height: 525,
         resize: false,
@@ -123,6 +166,106 @@ export default {
             }).catch(error => {
                 this.$message.error('文件上传失败');
             });
+        },
+        setup: (editor) => {
+          editor.on('init', () => {
+            // 查看详情/只读模式下不注册任何拖拽事件
+            // if (this.disabled) return;
+
+            const body = editor.getBody();
+            const win = editor.getWin();
+            let dragState = null;
+            let pendingImg = null;
+            let pendingSx = 0, pendingSy = 0;
+            const THRESHOLD = 5;
+
+            /* ===== 1. 从外部签章面板拖入编辑器 ===== */
+            body.addEventListener('dragover', (e) => {
+              e.preventDefault();
+              e.dataTransfer.dropEffect = 'copy';
+            }, true);
+
+            body.addEventListener('drop', (e) => {
+              e.preventDefault();
+              e.stopPropagation();
+              const imageData = e.dataTransfer.getData('imageData');
+              if (!imageData) return;
+              try {
+                const data = JSON.parse(imageData);
+                const rect = body.getBoundingClientRect();
+                const x = e.clientX - rect.left + body.scrollLeft;
+                const y = e.clientY - rect.top + body.scrollTop;
+
+                let w = 200, h = 200;
+                if (data.sealType === 'private') { w = 210; h = 70; }
+
+                const imgHtml = `<img src="${data.url}" class="seal-drag-img"
+                  style="position:absolute;left:${Math.max(0, x)}px;top:${Math.max(0, y)}px;width:${w}px;height:${h}px;cursor:move;z-index:10;"
+                  data-seal-type="${data.sealType}" data-seal-name="${data.name}" />`;
+
+                editor.undoManager.transact(() => {
+                  editor.execCommand('mceInsertContent', false, imgHtml);
+                });
+                this.$message({ showClose: true, message: '已插入签章', type: 'success' });
+              } catch (err) {
+                console.error('签章插入失败:', err);
+              }
+            }, true);
+
+            /* ===== 2. 编辑器内拖动签章图片 ===== */
+            body.addEventListener('mousedown', (e) => {
+              const target = e.target;
+              if (!target || !target.closest) return;
+              const img = target.closest('img.seal-drag-img');
+              if (!img) return;
+              e.preventDefault();
+              e.stopPropagation();
+              pendingImg = img;
+              pendingSx = e.clientX;
+              pendingSy = e.clientY;
+            }, true);
+
+            win.addEventListener('mousemove', (e) => {
+              if (pendingImg && !dragState) {
+                const dx = e.clientX - pendingSx;
+                const dy = e.clientY - pendingSy;
+                if (Math.abs(dx) > THRESHOLD || Math.abs(dy) > THRESHOLD) {
+                  dragState = {
+                    img: pendingImg,
+                    sx: pendingSx,
+                    sy: pendingSy,
+                    sLeft: parseFloat(pendingImg.style.left) || 0,
+                    sTop: parseFloat(pendingImg.style.top) || 0
+                  };
+                  pendingImg = null;
+                  dragState.img.style.cursor = 'grabbing';
+                  body.style.userSelect = 'none';
+                }
+              }
+              if (!dragState) return;
+              e.preventDefault();
+              dragState.img.style.left = (dragState.sLeft + e.clientX - dragState.sx) + 'px';
+              dragState.img.style.top  = (dragState.sTop  + e.clientY - dragState.sy) + 'px';
+            });
+
+            win.addEventListener('mouseup', () => {
+              if (dragState) {
+                dragState.img.style.cursor = 'move';
+                body.style.userSelect = '';
+                dragState = null;
+              }
+              pendingImg = null;
+            });
+
+            body.addEventListener('mouseleave', () => {
+              if (dragState) {
+                dragState.img.style.cursor = 'move';
+                body.style.userSelect = '';
+                dragState = null;
+              }
+              pendingImg = null;
+            });
+          });
         }
       },
     }
@@ -201,6 +344,51 @@ export default {
     },
     handleTabClick(tab) {
       this.tabValue = tab.name;
+      // 切换到「正文」Tab 时加载签章
+      if (tab.name === '2' && !this.sealsFetched) {
+        this.sealsFetched = true;
+        this.fetchSeals();
+      }
+    },
+    /* 获取签章列表 */
+    async fetchSeals() {
+      try {
+        const userId = this.$store?.state?.user?.info?.userId;
+        // 公用章
+        getSealPage({
+          size: 999,
+          pageNum: 1,
+          status: 1,
+          approvalStatus: 2,
+          sealHolderId: userId
+        }).then((res) => {
+          this.publicImages = res.list || [];
+        }).catch(() => {});
+        // 个人章(签名)
+        if (userId) {
+          getUserDetail(userId).then((res) => {
+            if (res.signature?.[0]) {
+              this.privateImages = [{
+                imgUrl: res.signature[0].url,
+                name: res.name
+              }];
+            }
+          }).catch(() => {});
+        }
+      } catch (e) {
+        console.error('获取签章失败:', e);
+      }
+    },
+    /* 签章拖拽开始,写入 imageData */
+    handleDragStart(e, img, type) {
+      const data = {
+        url: img.imgUrl,
+        name: img.name,
+        imgId: type === 'public' ? img.id : '',
+        sealType: type
+      };
+      e.dataTransfer.setData('imageData', JSON.stringify(data));
+      e.dataTransfer.effectAllowed = 'copy';
     },
     async save(type) {
       try {
@@ -303,4 +491,87 @@ export default {
   cursor: pointer;
   margin-left: 20px;
 }
+/* 编辑器 + 签章面板布局 */
+.editor-wrapper {
+  display: flex;
+  gap: 16px;
+  align-items: flex-start;
+}
+.editor-main {
+  flex: 1;
+  min-width: 0;
+}
+.seal-panel {
+  flex: 0 0 200px;
+  background: #f8fafc;
+  border: 1px solid #e2e8f0;
+  border-radius: 8px;
+  padding: 12px;
+  max-height: 525px;
+  overflow-y: auto;
+}
+.seal-section {
+  margin-bottom: 12px;
+}
+.seal-section:last-child {
+  margin-bottom: 0;
+}
+.panel-title {
+  font-weight: 600;
+  font-size: 13px;
+  color: #0f172a;
+  margin-bottom: 8px;
+  padding-bottom: 6px;
+  border-bottom: 1px solid #e2e8f0;
+}
+.seal-grid {
+  display: grid;
+  grid-template-columns: repeat(2, 1fr);
+  gap: 8px;
+}
+.seal-item {
+  background: #fff;
+  border: 2px solid #e2e8f0;
+  border-radius: 8px;
+  padding: 6px;
+  cursor: grab;
+  transition: all 0.2s;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 4px;
+}
+.seal-item:hover {
+  border-color: #3b82f6;
+  background: #eff6ff;
+  transform: translateY(-1px);
+  box-shadow: 0 2px 8px rgba(59,130,246,0.15);
+}
+.seal-item:active {
+  cursor: grabbing;
+}
+.seal-item img {
+  width: 100%;
+  height: 60px;
+  object-fit: contain;
+  pointer-events: none;
+  border-radius: 4px;
+  background: #f1f5f9;
+}
+.seal-name {
+  font-size: 11px;
+  color: #64748b;
+  text-align: center;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  width: 100%;
+}
+.no-seal {
+  grid-column: 1 / -1;
+  text-align: center;
+  color: #94a3b8;
+  font-size: 12px;
+  padding: 12px 0;
+}
 </style>

+ 13 - 2
src/views/bpm/handleTask/components/noticeIssuance/detailDialog.vue

@@ -1,6 +1,6 @@
 <template>
   <div>
-    <noticeDocument :query="query" :isApprove="true"></noticeDocument>
+    <noticeDocument ref="noticeDocument" :query="query" :isApprove="true"></noticeDocument>
   </div>
 </template>
 <script>
@@ -37,7 +37,18 @@
       // this.getInfo(this.businessId);
     },
     methods: {
-     
+     async getTableValue() {
+        try {
+          let formData = await this.$refs.noticeDocument.formData;
+          let customFields = await this.$refs.noticeDocument.$refs.mainBodyTemplate.generateCustomFields();
+          console.log('customFields~~~', customFields, formData);
+          formData.fields = customFields;
+          formData.status = formData.status ? 1 : 0;
+          return formData;
+        } catch (e) {
+          this.$message.error('获取公文信息失败,请重试');
+        }
+      }
     }
   };
 </script>

+ 15 - 0
src/views/bpm/handleTask/components/noticeIssuance/submit.vue

@@ -52,6 +52,7 @@
   import { UpdateInformation, cancel } from '@/api/bpm/components/contractManage/contractBook';
   import {approveTaskWithVariables, rejectTask,cancelTask} from '@/api/bpm/task';
   import { listAllUserBind } from '@/api/system/organization';
+  import { noticeDocumentUpdateAPI } from '@/api/documents/noticeIssuance/index.js';
 
   // 流程实例的详情页,可用于审批
   export default {
@@ -100,6 +101,20 @@
 
 
       async handleAudit(status) {
+        // 通过前先更新公文信息
+        if (status == 1) {
+          try {
+            const loading = this.$loading({ lock: true, text: '正在更新公文信息...' });
+              let formData = await this.getTableValue();
+              console.log('formData~~~', formData);
+              await noticeDocumentUpdateAPI(formData);
+            
+            loading.close();
+          } catch (e) {
+            this.$message.error('更新公文信息失败,请重试');
+            return;
+          }
+        }
         let variables = {
           pass: !!status
         };