|
|
@@ -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>
|