TinyMCE-----word表格图片进阶版

1、文件大小 1M 前置检查:在解析 Hex 阶段就进行大小判断,防止超大字符串导致浏览器卡死。

2、UI 体验:增加 400px 滚动高度,并确保任务"全部完事"后才提示手动关闭。

3、我是国际化 可以自己修改代码

javascript 复制代码
data:{
	uploadNotifications: {},
	totalUploadImages: 0,
	uploadedImageCount: 0,
	uploadNotificationInstance: null, // 全局通知实例
}

mounted 添加样式

javascript 复制代码
       const style = document.createElement('style');
        style.innerHTML = `
    @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
    @keyframes slideIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } }
    .status-spinning { display: inline-block; animation: spin 1s linear infinite; }
    .upload-item-animate { animation: slideIn 0.3s ease-out forwards; }
    .el-progress-bar__inner { transition: width 0.4s ease-in-out !important; }
`;
        document.head.appendChild(style);

tinymce paste 方法改造

javascript 复制代码
 ed.on('paste', async (event) => {
                        const rtf = event.clipboardData.getData('text/rtf');
                        if (rtf && rtf.includes('\\pict')) {
                            const extracted = extractHexImagesFromRTF(rtf);
                            _this.totalUploadImages = extracted.length; // 设置总数
                            _this.uploadedImageCount = 0; // 重置已上传数

                            if (_this.totalUploadImages === 0) return;

                            // 初始化主通知框
                            _this.updateUploadProgressNotification(0, 'init'); // 这里的参数只是为了触发通知框的创建

                            const uploadPromises = extracted.map(async (img, i) => {
                                const isTooLarge = img.hex.length > 2 * 1024 * 1024; // 1MB 限制

                                _this.updateUploadProgressNotification(i, 'processing'); // 更新单张图片状态

                                if (isTooLarge) {
                                    _this.updateUploadProgressNotification(i, 'tooLarge');
                                    return; // 跳过此图片
                                }

                                try {
                                    const base64 = _this.hexToBase64Sync(img.hex, img.mimeType);
                                    _this.updateUploadProgressNotification(i, 'uploading'); // 更新为上传中
                                    await _this.uploadSingleImage(base64, i);
                                    _this.updateUploadProgressNotification(i, 'success'); // 上传成功
                                } catch (err) {
                                    console.error('Upload failed:', err);
                                    _this.updateUploadProgressNotification(i, 'fail', err.message || 'Network error'); // 上传失败
                                }
                            });

                            await Promise.all(uploadPromises);
                            // Promise.all 完成后, updateUploadProgressNotification 会自动计算 totalCount === uploadedCount 并关闭
                        }
                    });
javascript 复制代码
updateUploadProgressNotification(imgIndex, status = 'processing', message = '') {
            // 快捷调用 $t
            const t = (key, params) => this.$t(`imageTask.${key}`, params);

            if (!this.uploadNotificationInstance) {
                this.uploadNotificationInstance = this.$notify({
                    title: t('title'),
                    dangerouslyUseHTMLString: true,
                    message: `
                <div id="image-upload-container" style="width: 300px;">
                    <p id="total-status-text" style="margin: 0 0 12px 0; font-size: 14px; color: #606266; font-weight: 500;">
                        ${t('preparing', { total: this.totalUploadImages })}
                    </p>
                    
                    <div class="el-progress el-progress--line" style="margin-bottom: 15px;">
                        <div class="el-progress-bar">
                            <div class="el-progress-bar__outer" style="height: 10px; background: #ebeef5; border-radius: 5px;">
                                <div class="el-progress-bar__inner" style="width: 0%; background: #409EFF; transition: width 0.4s ease-out;"></div>
                            </div>
                        </div>
                        <div class="el-progress__text" style="font-size: 12px; font-weight: bold; margin-top: 2px;">0%</div>
                    </div>

                    <ul id="image-individual-status" style="max-height: 400px; overflow-y: auto; padding: 0; margin: 0; list-style: none; border-top: 1px solid #f0f0f0; padding-top: 10px;"></ul>

                    <p id="manual-close-tip" style="display:none; margin-top:15px; font-size:12px; color:#909399; text-align:right; border-top: 1px dashed #eee; padding-top: 8px;">
                        ${t('manualClose')}
                    </p>
                </div>`,
                    duration: 0,
                    position: 'bottom-left',
                    onClose: () => {
                        this.uploadNotificationInstance = null;
                        this.uploadedImageCount = 0;
                        this.totalUploadImages = 0;
                    }
                });
            }

            const listContainer = document.getElementById('image-individual-status');
            if (!listContainer) return;

            let itemEl = document.getElementById(`img-status-${imgIndex}`);
            if (!itemEl) {
                itemEl = document.createElement('li');
                itemEl.id = `img-status-${imgIndex}`;
                itemEl.className = 'upload-item-animate';
                itemEl.style.cssText =
                    'font-size: 13px; padding: 8px 5px; display: flex; align-items: center; border-bottom: 1px solid #fafafa;';
                itemEl.innerHTML = `
            <span style="width: 55px; color: #909399; font-weight: bold;">${t('imgLabel')} ${imgIndex + 1}:</span>
            <span class="status-icon" style="margin: 0 10px;"></span>
            <span class="status-text" style="flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: #666;">...</span>
        `;
                listContainer.appendChild(itemEl);
                listContainer.scrollTop = listContainer.scrollHeight;
            }

            const statusIcon = itemEl.querySelector('.status-icon');
            const statusText = itemEl.querySelector('.status-text');

            let isFinished = false;
            if (statusIcon && statusText) {
                switch (status) {
                    case 'processing':
                        statusIcon.innerHTML = '<span class="status-spinning">🔄</span>';
                        statusText.innerText = t('parsing');
                        break;
                    case 'uploading':
                        statusIcon.innerHTML = '<span class="status-spinning">⬆️</span>';
                        statusText.innerText = t('uploading');
                        break;
                    case 'tooLarge':
                        statusIcon.innerHTML = '⚠️';
                        statusText.innerText = t('tooLarge');
                        itemEl.style.color = '#E6A23C';
                        isFinished = true;
                        break;
                    case 'success':
                        statusIcon.innerHTML = '✅';
                        statusText.innerText = t('success');
                        itemEl.style.color = '#67C23A';
                        isFinished = true;
                        break;
                    case 'fail':
                        statusIcon.innerHTML = '❌';
                        statusText.innerText = t('error', { msg: message });
                        itemEl.style.color = '#F56C6C';
                        isFinished = true;
                        break;
                }
            }

            if (isFinished) {
                this.uploadedImageCount++;

                const progressPercent = Math.min(100, Math.round((this.uploadedImageCount / this.totalUploadImages) * 100));
                const barInner = this.uploadNotificationInstance.$el.querySelector('.el-progress-bar__inner');
                const barText = this.uploadNotificationInstance.$el.querySelector('.el-progress__text');
                const statusTotalText = document.getElementById('total-status-text');

                if (barInner) barInner.style.width = `${progressPercent}%`;
                if (barText) barText.innerText = `${progressPercent}%`;
                if (statusTotalText) {
                    statusTotalText.innerText = t('progress', { current: this.uploadedImageCount, total: this.totalUploadImages });
                }

                if (this.uploadedImageCount >= this.totalUploadImages) {
                    // 更新标题为完成状态
                    const titleEl = this.uploadNotificationInstance.$el.querySelector('.el-notification__title');
                    if (titleEl) titleEl.innerText = t('completed');

                    if (barInner) barInner.style.background = '#67C23A';

                    const tipEl = document.getElementById('manual-close-tip');
                    if (tipEl) tipEl.style.display = 'block';

                    if (statusTotalText) {
                        statusTotalText.innerText = t('allDone', { total: this.totalUploadImages });
                    }
                }
            }
        },
相关推荐
灵感__idea14 分钟前
Hello 算法:众里寻她千“百度”
前端·javascript·算法
袋鼠云数栈UED团队1 小时前
基于 Lexical 实现变量输入编辑器
前端·javascript·架构
亦妤2 小时前
JS执行机制、作用域及作用域链
javascript
SuperEugene3 小时前
表单最佳实践:从 v-model 到自定义表单组件(含校验)
前端·javascript·vue.js
不会敲代码14 小时前
React性能优化:深入理解useMemo和useCallback
前端·javascript·react.js
YukiMori236 小时前
一个有趣的原型继承实验:为什么“男人也会生孩子”?从对象赋值到构造函数继承的完整推演
前端·javascript
摸鱼的春哥6 小时前
惊!黑客靠AI把墨西哥政府打穿了,海量数据被黑
前端·javascript·后端
小兵张健6 小时前
Playwright MCP 截图标注方案调研(推荐方案1)
前端·javascript·github
我叫黑大帅9 小时前
Vue3和Uniapp的爱恨情仇:小白也能懂的跨端秘籍
前端·javascript·vue.js
None3219 小时前
【NestJs】使用Winston+ELK分布式链路追踪日志采集
javascript·node.js